Skip to content

Commit

Permalink
feat: rule check
Browse files Browse the repository at this point in the history
  • Loading branch information
lisonge committed Aug 28, 2024
1 parent ffe1282 commit d230b52
Show file tree
Hide file tree
Showing 11 changed files with 272 additions and 25 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
],
"dependencies": {
"@antv/g6": "4.8.24",
"@gkd-kit/api": "0.3.3",
"@gkd-kit/selector": "0.4.2",
"@gkd-kit/wasm_matches": "0.0.1",
"@rushstack/eslint-patch": "1.10.3",
Expand Down
1 change: 1 addition & 0 deletions plugins/unAutoImport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const unAutoImport = (): Plugin[] => {
'useLoadingBar',
...naiveComponents,
],
json5: [['default', 'JSON5']],
},
],
eslintrc: {
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 18 additions & 18 deletions src/utils/types.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
export type PrimitiveType = boolean | string | number | null | undefined;

export type RpcError = {
export interface RpcError {
message: string;
code: number;
__error: true;
};
}

export type Device = {
export interface Device {
device: string;
model: string;
manufacturer: string;
Expand All @@ -22,9 +22,9 @@ export type Device = {
* @deprecated use gkdAppInfo instead
*/
gkdVersionName?: string;
};
}

export type RawNode = {
export interface RawNode {
id: number;
pid: number;
quickFind?: boolean;
Expand All @@ -35,9 +35,9 @@ export type RawNode = {
// list to tree
parent?: RawNode;
children: RawNode[];
};
}

export type RawAttr = {
export interface RawAttr {
id?: string;
vid?: string;
name: string;
Expand All @@ -56,9 +56,9 @@ export type RawAttr = {
bottom: number;
_id?: number;
_pid?: number;
};
}

export type Overview = {
export interface Overview {
id: number;

appId: string;
Expand All @@ -83,31 +83,31 @@ export type Overview = {
* @deprecated use appInfo instead
*/
appVersionCode?: number;
};
}

export type Snapshot = Overview & {
export interface Snapshot extends Overview {
device: Device;
nodes: RawNode[];
};
}

export type AppInfo = {
export interface AppInfo {
id: string;
name: string;
versionCode: number;
versionName?: string;
isSystem: boolean;
mtime: number;
hidden: boolean;
};
}

export type RectX = {
export interface RectX {
bottom: number;
left: number;
right: number;
top: number;
};
}

export type SizeExt = {
export interface SizeExt {
height: number;
width: number;
};
}
1 change: 0 additions & 1 deletion src/views/DevicePage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { screenshotStorage, snapshotStorage } from '@/utils/storage';
import { useSnapshotColumns } from '@/utils/table';
import { useBatchTask, useTask } from '@/utils/task';
import type { Device, Snapshot } from '@/utils/types';
import JSON5 from 'json5';
import type { DataTableColumns, PaginationProps } from 'naive-ui';
import type { SortState } from 'naive-ui/es/data-table/src/interface';
import pLimit from 'p-limit';
Expand Down
1 change: 0 additions & 1 deletion src/views/PreviewSharePage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { toValidURL } from '@/utils/check';
import { loadingBar } from '@/utils/discrete';
import { enhanceFetch } from '@/utils/fetch';
import { copy, timeAgo, useAdaptMobile } from '@/utils/others';
import JSON5 from 'json5';
const route = useRoute();
useAdaptMobile();
Expand Down
1 change: 0 additions & 1 deletion src/views/home/BuildShareDlg.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import { uploadAsset } from '@/utils/github';
import { copy } from '@/utils/others';
import { useTask } from '@/utils/task';
import JSON5 from 'json5';
import QRCode from 'qrcode';
const router = useRouter();
Expand Down
225 changes: 225 additions & 0 deletions src/views/snapshot/RuleCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
<script setup lang="ts">
import DraggableCard from '@/components/DraggableCard.vue';
import { getNodeLabel } from '@/utils/node';
import { parseSelector, type GkdSelector } from '@/utils/selector';
import { gkdWidth, vw } from '@/utils/size';
import type { RawNode } from '@/utils/types';
const props = withDefaults(
defineProps<{
root: RawNode;
focusNode?: RawNode;
onUpdateFocusNode?: (data: RawNode) => void;
}>(),
{},
);
const tabShow = shallowRef(false);
const text = shallowRef('');
const lazyText = refDebounced(text, 500);
interface ResolvedData {
matches: string[];
anyMatches: string[];
excludeMatches: string[];
}
const toArray = (v: any): string[] | undefined => {
if (v === undefined || v === null) return [];
if (typeof v === 'string') return [v];
if (Array.isArray(v) && v.every((s) => typeof s === 'string')) return v;
};
const dataRef = computed<RawNode | string>(() => {
if (!lazyText.value) return '';
const obj = (() => {
try {
return JSON5.parse<ResolvedData>(lazyText.value);
} catch (e) {
return e as Error;
}
})();
if (obj instanceof Error) {
return `非法格式: ${obj.message}`;
}
if (typeof obj !== 'object' && obj !== null) {
return '非法格式: 请使用对象格式';
}
const matches = toArray(obj.matches);
if (!matches) {
return '非法格式: matches';
}
const anyMatches = toArray(obj.anyMatches);
if (!anyMatches) {
return '非法格式: anyMatches';
}
const excludeMatches = toArray(obj.excludeMatches);
if (!excludeMatches) {
return '非法格式: excludeMatches';
}
const resolvedMatches: GkdSelector[] = [];
for (let i = 0; i < matches.length; i++) {
const v = matches[i];
try {
resolvedMatches.push(parseSelector(v));
} catch (e) {
const message = e instanceof Error ? e.message : String(e);
return `非法选择器: matches[${i}]\n错误: ${message}`;
}
}
const matchesResult = resolvedMatches.map((s) =>
s.querySelectorAll(props.root),
);
if (resolvedMatches.length) {
const notIndex = matchesResult.findIndex((s) => s.length === 0);
if (notIndex >= 0) {
return `无法匹配: matches[${notIndex}] 查找结果为空`;
}
}
const resolvedAnyMatches: GkdSelector[] = [];
for (let i = 0; i < anyMatches.length; i++) {
const v = anyMatches[i];
try {
resolvedAnyMatches.push(parseSelector(v));
} catch (e) {
const message = e instanceof Error ? e.message : String(e);
return `非法选择器: anyMatches[${i}]\n错误: ${message}`;
}
}
const anyMatchesResult = resolvedAnyMatches.map((s) =>
s.querySelectorAll(props.root),
);
if (resolvedAnyMatches.length) {
if (anyMatchesResult.every((s) => s.length === 0)) {
return `无法匹配: anyMatches 查找结果为空`;
}
}
if (!matches.length && !anyMatches.length) {
return '非法规则: matches 和 anyMatches 至少存在一个';
}
const resolvedExcludeMatches: GkdSelector[] = [];
for (let i = 0; i < excludeMatches.length; i++) {
const v = excludeMatches[i];
try {
resolvedExcludeMatches.push(parseSelector(v));
} catch (e) {
const message = e instanceof Error ? e.message : String(e);
return `非法选择器: excludeMatches[${i}]\n错误: ${message}`;
}
}
const excludeMatchesResult = resolvedExcludeMatches.map((s) =>
s.querySelectorAll(props.root),
);
if (resolvedExcludeMatches.length) {
const index = excludeMatchesResult.findIndex((s) => s.length !== 0);
if (index >= 0) {
return `无法匹配: excludeMatches[${index}] 查找结果不为空`;
}
}
if (!matchesResult.length) {
return anyMatchesResult[0][0];
}
return matchesResult.at(-1)![0];
});
const errorText = computed(() => {
if (text.value && lazyText.value && typeof dataRef.value === 'string') {
return dataRef.value;
}
return '';
});
const targetNode = computed(() => {
if (typeof dataRef.value === 'string') return null;
return dataRef.value;
});
</script>
<template>
<DraggableCard
:initialValue="{
top: 40,
right: Math.max(315, 12 * vw + 135),
width: Math.max(480, gkdWidth * 0.3),
}"
:minWidth="300"
sizeDraggable
v-slot="{ onRef }"
class="z-2 box-shadow-dim"
:show="tabShow"
>
<div bg-white b-1px b-solid b-gray-200 rounded-4px p-8px>
<div flex m-b-4px pr-4px>
<div>测试规则</div>
<div flex-1 cursor-move :ref="onRef"></div>
<NButton @click="tabShow = !tabShow" text title="最小化">
<template #icon>
<NIcon>
<svg viewBox="0 0 24 24">
<path fill="currentColor" d="M6 13v-2h12v2z" />
</svg>
</NIcon>
</template>
</NButton>
</div>
<NInput
v-model:value="text"
type="textarea"
placeholder="请输入单个规则测试"
size="small"
class="gkd_code m-b-4px"
:autosize="{
minRows: 10,
maxRows: 20,
}"
/>
<div min-h-24px>
<div color-red whitespace-pre v-if="errorText">{{ errorText }}</div>

<NButton
v-else-if="targetNode"
@click="onUpdateFocusNode?.(targetNode)"
size="small"
:class="{
'color-[#00F]': targetNode === focusNode,
}"
>
{{ getNodeLabel(targetNode) }}
</NButton>
</div>
</div>
</DraggableCard>
<DraggableCard
:initialValue="{
bottom: 56,
right: 16,
}"
:minWidth="300"
v-slot="{ onRef, moved }"
class="z-1 box-shadow-dim rounded-1/2 bg-white"
:show="!tabShow"
>
<div :ref="onRef">
<NButton
@click="
if (!moved) {
tabShow = !tabShow;
}
"
circle
size="large"
title="测试规则"
>
<template #icon>
<NIcon>
<svg viewBox="0 0 24 24">
<path
fill="currentColor"
d="M14.4 20L13 18.6l2.6-2.6l-2.6-2.6l1.4-1.4l2.6 2.6l2.6-2.6l1.4 1.4l-2.6 2.6l2.6 2.6l-1.4 1.4l-2.6-2.6zm1.975-9l-3.55-3.55l1.4-1.4l2.125 2.125l4.25-4.25L22 5.35zM2 17v-2h9v2zm0-8V7h9v2z"
/>
</svg>
</NIcon>
</template>
</NButton>
</div>
</DraggableCard>
</template>
Loading

0 comments on commit d230b52

Please sign in to comment.