mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2025-06-17 18:51:33 +08:00
refactor: 依赖收集相关代码抽出独立成库
This commit is contained in:
parent
2b10e7eda9
commit
94641bad76
@ -54,6 +54,7 @@
|
||||
"chalk": "^4.1.0",
|
||||
"commitizen": "^4.3.0",
|
||||
"conventional-changelog-cli": "^4.1.0",
|
||||
"cosmiconfig": "^8.3.6",
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
"element-plus": "^2.2.32",
|
||||
"enquirer": "^2.3.6",
|
||||
@ -79,7 +80,7 @@
|
||||
"vite": "^4.4.4",
|
||||
"vitepress": "1.0.0-rc.24",
|
||||
"vitest": "^0.31.1",
|
||||
"vue": "^3.3.4"
|
||||
"vue": "^3.3.8"
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
|
42
packages/dep/package.json
Normal file
42
packages/dep/package.json
Normal file
@ -0,0 +1,42 @@
|
||||
{
|
||||
"version": "1.3.1",
|
||||
"name": "@tmagic/dep",
|
||||
"type": "module",
|
||||
"sideEffects": [
|
||||
"dist/*"
|
||||
],
|
||||
"main": "dist/tmagic-dep.umd.cjs",
|
||||
"module": "dist/tmagic-dep.js",
|
||||
"types": "types/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./types/index.d.ts",
|
||||
"import": "./dist/tmagic-dep.js",
|
||||
"require": "./dist/tmagic-dep.umd.cjs"
|
||||
},
|
||||
"./*": "./*"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"build": "npm run build:type && vite build",
|
||||
"build:type": "npm run clear:type && tsc --declaration --emitDeclarationOnly --project tsconfig.build.json",
|
||||
"clear:type": "rimraf ./types"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Tencent/tmagic-editor.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tmagic/schema": "1.3.1",
|
||||
"@tmagic/utils": "1.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^15.12.4",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^5.0.4",
|
||||
"vite": "^4.4.4"
|
||||
}
|
||||
}
|
112
packages/dep/src/Target.ts
Normal file
112
packages/dep/src/Target.ts
Normal file
@ -0,0 +1,112 @@
|
||||
import type { DepData } from '@tmagic/schema';
|
||||
|
||||
import { DepTargetType, type IsTarget, type TargetOptions } from './types';
|
||||
|
||||
/**
|
||||
* 需要收集依赖的目标
|
||||
* 例如:一个代码块可以为一个目标
|
||||
*/
|
||||
export default class Target {
|
||||
/**
|
||||
* 如何识别目标
|
||||
*/
|
||||
public isTarget: IsTarget;
|
||||
/**
|
||||
* 目标id,不可重复
|
||||
* 例如目标是代码块,则为代码块id
|
||||
*/
|
||||
public id: string | number;
|
||||
/**
|
||||
* 目标名称,用于显示在依赖列表中
|
||||
*/
|
||||
public name?: string;
|
||||
/**
|
||||
* 不同的目标可以进行分类,例如代码块,数据源可以为两个不同的type
|
||||
*/
|
||||
public type: string = DepTargetType.DEFAULT;
|
||||
/**
|
||||
* 依赖详情
|
||||
* 实例:{ 'node_id': { name: 'node_name', keys: [ created, mounted ] } }
|
||||
*/
|
||||
public deps: DepData = {};
|
||||
|
||||
constructor(options: TargetOptions) {
|
||||
this.isTarget = options.isTarget;
|
||||
this.id = options.id;
|
||||
this.name = options.name;
|
||||
if (options.type) {
|
||||
this.type = options.type;
|
||||
}
|
||||
if (options.initialDeps) {
|
||||
this.deps = options.initialDeps;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新依赖
|
||||
* @param node 节点配置
|
||||
* @param key 哪个key配置了这个目标的id
|
||||
*/
|
||||
public updateDep(node: Record<string | number, any>, key: string | number) {
|
||||
const dep = this.deps[node.id] || {
|
||||
name: node.name,
|
||||
keys: [],
|
||||
};
|
||||
|
||||
if (node.name) {
|
||||
dep.name = node.name;
|
||||
}
|
||||
|
||||
this.deps[node.id] = dep;
|
||||
|
||||
if (dep.keys.indexOf(key) === -1) {
|
||||
dep.keys.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除依赖
|
||||
* @param node 哪个节点的依赖需要移除,如果为空,则移除所有依赖
|
||||
* @param key 节点下哪个key需要移除,如果为空,则移除改节点下的所有依赖key
|
||||
* @returns void
|
||||
*/
|
||||
public removeDep(node?: Record<string | number, any>, key?: string | number) {
|
||||
if (!node) {
|
||||
Object.keys(this.deps).forEach((depKey) => {
|
||||
delete this.deps[depKey];
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const dep = this.deps[node.id];
|
||||
|
||||
if (!dep) return;
|
||||
|
||||
if (key) {
|
||||
const index = dep.keys.indexOf(key);
|
||||
dep.keys.splice(index, 1);
|
||||
|
||||
if (dep.keys.length === 0) {
|
||||
delete this.deps[node.id];
|
||||
}
|
||||
} else {
|
||||
delete this.deps[node.id];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断指定节点下的指定key是否存在在依赖列表中
|
||||
* @param node 哪个节点
|
||||
* @param key 哪个key
|
||||
* @returns boolean
|
||||
*/
|
||||
public hasDep(node: Record<string | number, any>, key: string | number) {
|
||||
const dep = this.deps[node.id];
|
||||
|
||||
return Boolean(dep?.keys.find((d) => d === key));
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.deps = {};
|
||||
}
|
||||
}
|
175
packages/dep/src/Watcher.ts
Normal file
175
packages/dep/src/Watcher.ts
Normal file
@ -0,0 +1,175 @@
|
||||
import { isObject } from '@tmagic/utils';
|
||||
|
||||
import type Target from './Target';
|
||||
import { DepTargetType, type TargetList } from './types';
|
||||
|
||||
export default class Watcher {
|
||||
private targetsList: TargetList = {};
|
||||
|
||||
constructor(options?: { initialTargets?: TargetList }) {
|
||||
if (options?.initialTargets) {
|
||||
this.targetsList = options.initialTargets;
|
||||
}
|
||||
}
|
||||
|
||||
public getTargetsList() {
|
||||
return this.targetsList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定类型中的所有target
|
||||
* @param type 分类
|
||||
* @returns Target[]
|
||||
*/
|
||||
public getTargets(type: string = DepTargetType.DEFAULT) {
|
||||
return this.targetsList[type] || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加新的目标
|
||||
* @param target Target
|
||||
*/
|
||||
public addTarget(target: Target) {
|
||||
const targets = this.getTargets(target.type) || {};
|
||||
this.targetsList[target.type] = targets;
|
||||
targets[target.id] = target;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定id的target
|
||||
* @param id target id
|
||||
* @returns Target
|
||||
*/
|
||||
public getTarget(id: string | number) {
|
||||
const allTargets = Object.values(this.targetsList);
|
||||
for (const targets of allTargets) {
|
||||
if (targets[id]) {
|
||||
return targets[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否存在指定id的target
|
||||
* @param id target id
|
||||
* @returns boolean
|
||||
*/
|
||||
public hasTarget(id: string | number) {
|
||||
return Boolean(this.getTarget(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定id的target
|
||||
* @param id target id
|
||||
*/
|
||||
public removeTarget(id: string | number) {
|
||||
const allTargets = Object.values(this.targetsList);
|
||||
for (const targets of allTargets) {
|
||||
if (targets[id]) {
|
||||
targets[id].destroy();
|
||||
delete targets[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定分类的所有target
|
||||
* @param type 分类
|
||||
* @returns void
|
||||
*/
|
||||
public removeTargets(type: string = DepTargetType.DEFAULT) {
|
||||
const targets = this.targetsList[type];
|
||||
|
||||
if (!targets) return;
|
||||
|
||||
for (const target of Object.values(targets)) {
|
||||
target.destroy();
|
||||
}
|
||||
|
||||
delete this.targetsList[type];
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除所有target
|
||||
*/
|
||||
public clearTargets() {
|
||||
Object.keys(this.targetsList).forEach((key) => {
|
||||
delete this.targetsList[key];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集依赖
|
||||
* @param nodes 需要收集的节点
|
||||
* @param deep 是否需要收集子节点
|
||||
*/
|
||||
public collect(nodes: Record<string | number, any>[], deep = false) {
|
||||
Object.values(this.targetsList).forEach((targets) => {
|
||||
Object.values(targets).forEach((target) => {
|
||||
nodes.forEach((node) => {
|
||||
// 先删除原有依赖,重新收集
|
||||
target.removeDep(node);
|
||||
this.collectItem(node, target, deep);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除依赖
|
||||
* @param nodes 需要清除依赖的节点
|
||||
*/
|
||||
public clear(nodes?: Record<string | number, any>[]) {
|
||||
const clearedItemsNodeIds: (string | number)[] = [];
|
||||
Object.values(this.targetsList).forEach((targets) => {
|
||||
Object.values(targets).forEach((target) => {
|
||||
if (nodes) {
|
||||
nodes.forEach((node) => {
|
||||
target.removeDep(node);
|
||||
|
||||
if (Array.isArray(node.items) && node.items.length && !clearedItemsNodeIds.includes(node.id)) {
|
||||
clearedItemsNodeIds.push(node.id);
|
||||
this.clear(node.items);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
target.removeDep();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private collectItem(node: Record<string | number, any>, target: Target, deep = false) {
|
||||
const collectTarget = (config: Record<string | number, any>, prop = '') => {
|
||||
const doCollect = (key: string, value: any) => {
|
||||
const keyIsItems = key === 'items';
|
||||
const fullKey = prop ? `${prop}.${key}` : key;
|
||||
|
||||
if (target.isTarget(fullKey, value)) {
|
||||
target.updateDep(node, fullKey);
|
||||
} else if (!keyIsItems && Array.isArray(value)) {
|
||||
value.forEach((item, index) => {
|
||||
if (isObject(item)) {
|
||||
collectTarget(item, `${fullKey}.${index}`);
|
||||
}
|
||||
});
|
||||
} else if (isObject(value)) {
|
||||
collectTarget(value, fullKey);
|
||||
}
|
||||
|
||||
if (keyIsItems && deep && Array.isArray(value)) {
|
||||
value.forEach((child) => {
|
||||
this.collectItem(child, target, deep);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Object.entries(config).forEach(([key, value]) => {
|
||||
if (typeof value === 'undefined' || value === '') return;
|
||||
doCollect(key, value);
|
||||
});
|
||||
};
|
||||
|
||||
collectTarget(node);
|
||||
}
|
||||
}
|
4
packages/dep/src/index.ts
Normal file
4
packages/dep/src/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export { default as Target } from './Target';
|
||||
export { default as Watcher } from './Watcher';
|
||||
export * from './utils';
|
||||
export * from './types';
|
33
packages/dep/src/types.ts
Normal file
33
packages/dep/src/types.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { DepData } from '@tmagic/schema';
|
||||
|
||||
import type Target from './Target';
|
||||
|
||||
/** 依赖收集的目标类型 */
|
||||
export enum DepTargetType {
|
||||
DEFAULT = 'default',
|
||||
/** 代码块 */
|
||||
CODE_BLOCK = 'code-block',
|
||||
/** 数据源 */
|
||||
DATA_SOURCE = 'data-source',
|
||||
/** 数据源方法 */
|
||||
DATA_SOURCE_METHOD = 'data-source-method',
|
||||
/** 数据源条件 */
|
||||
DATA_SOURCE_COND = 'data-source-cond',
|
||||
}
|
||||
|
||||
export type IsTarget = (key: string | number, value: any) => boolean;
|
||||
|
||||
export interface TargetOptions {
|
||||
isTarget: IsTarget;
|
||||
id: string | number;
|
||||
/** 类型,数据源、代码块或其他 */
|
||||
type?: string;
|
||||
name?: string;
|
||||
initialDeps?: DepData;
|
||||
}
|
||||
|
||||
export interface TargetList {
|
||||
[type: string]: {
|
||||
[targetId: string | number]: Target;
|
||||
};
|
||||
}
|
@ -1,23 +1,28 @@
|
||||
import { isEmpty } from 'lodash-es';
|
||||
|
||||
import { type CodeBlockContent, HookType, type Id } from '@tmagic/schema';
|
||||
import {
|
||||
type CodeBlockContent,
|
||||
type DataSourceSchema,
|
||||
type DepData,
|
||||
type HookData,
|
||||
HookType,
|
||||
type Id,
|
||||
} from '@tmagic/schema';
|
||||
import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX } from '@tmagic/utils';
|
||||
|
||||
import dataSourceService from '@editor/services/dataSource';
|
||||
import { Target } from '@editor/services/dep';
|
||||
import { DepTargetType, type HookData } from '@editor/type';
|
||||
import Target from './Target';
|
||||
import { DepTargetType } from './types';
|
||||
|
||||
export const createCodeBlockTarget = (id: Id, codeBlock: CodeBlockContent) =>
|
||||
export const createCodeBlockTarget = (id: Id, codeBlock: CodeBlockContent, initialDeps: DepData = {}) =>
|
||||
new Target({
|
||||
type: DepTargetType.CODE_BLOCK,
|
||||
id,
|
||||
initialDeps,
|
||||
name: codeBlock.name,
|
||||
isTarget: (key: string | number, value: any) => {
|
||||
if (id === value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value?.hookType === HookType.CODE && !isEmpty(value.hookData)) {
|
||||
if (value?.hookType === HookType.CODE && Array.isArray(value.hookData)) {
|
||||
const index = value.hookData.findIndex((item: HookData) => item.codeId === id);
|
||||
return Boolean(index > -1);
|
||||
}
|
||||
@ -26,13 +31,23 @@ export const createCodeBlockTarget = (id: Id, codeBlock: CodeBlockContent) =>
|
||||
},
|
||||
});
|
||||
|
||||
export const createDataSourceTarget = (id: Id) =>
|
||||
export const createDataSourceTarget = (ds: DataSourceSchema, initialDeps: DepData = {}) =>
|
||||
new Target({
|
||||
type: DepTargetType.DATA_SOURCE,
|
||||
id,
|
||||
id: ds.id,
|
||||
initialDeps,
|
||||
isTarget: (key: string | number, value: any) => {
|
||||
// 关联数据源对象或者在模板在使用数据源
|
||||
if ((value?.isBindDataSource && value.dataSourceId) || (typeof value === 'string' && value.includes(`${id}`))) {
|
||||
// 关联数据源对象,如:{ isBindDataSource: true, dataSourceId: 'xxx'}
|
||||
// 或者在模板在使用数据源,如:`xxx${id.field}xxx`
|
||||
if (
|
||||
(value?.isBindDataSource && value.dataSourceId) ||
|
||||
(typeof value === 'string' && value.includes(`${ds.id}`))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 指定数据源的字符串模板,如:{ isBindDataSourceField: true, dataSourceId: 'id', template: `xxx${field}xxx`}
|
||||
if (value?.isBindDataSourceField && value.dataSourceId && typeof value.template === 'string') {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -50,31 +65,28 @@ export const createDataSourceTarget = (id: Id) =>
|
||||
|
||||
const dsId = prefixId.substring(prefixIndex + DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX.length);
|
||||
|
||||
return dsId === id && Boolean(dataSourceService.getDataSourceById(id));
|
||||
return dsId === ds.id;
|
||||
},
|
||||
});
|
||||
|
||||
export const createDataSourceCondTarget = (id: string) =>
|
||||
export const createDataSourceCondTarget = (ds: DataSourceSchema, initialDeps: DepData = {}) =>
|
||||
new Target({
|
||||
type: DepTargetType.DATA_SOURCE_COND,
|
||||
id,
|
||||
id: ds.id,
|
||||
initialDeps,
|
||||
isTarget: (key: string | number, value: any) => {
|
||||
if (!Array.isArray(value) || value[0] !== id || !`${key}`.startsWith('displayConds')) return false;
|
||||
|
||||
const ds = dataSourceService.getDataSourceById(id);
|
||||
|
||||
if (!Array.isArray(value) || value[0] !== ds.id || !`${key}`.startsWith('displayConds')) return false;
|
||||
return Boolean(ds?.fields?.find((field) => field.name === value[1]));
|
||||
},
|
||||
});
|
||||
|
||||
export const createDataSourceMethodTarget = (id: string) =>
|
||||
export const createDataSourceMethodTarget = (ds: DataSourceSchema, initialDeps: DepData = {}) =>
|
||||
new Target({
|
||||
type: DepTargetType.DATA_SOURCE_METHOD,
|
||||
id,
|
||||
id: ds.id,
|
||||
initialDeps,
|
||||
isTarget: (key: string | number, value: any) => {
|
||||
if (!Array.isArray(value) || value[0] !== id) return false;
|
||||
|
||||
const ds = dataSourceService.getDataSourceById(id);
|
||||
if (!Array.isArray(value) || value[0] !== ds.id) return false;
|
||||
|
||||
return Boolean(ds?.methods?.find((method) => method.name === value[1]));
|
||||
},
|
28
packages/dep/tests/Target.spec.ts
Normal file
28
packages/dep/tests/Target.spec.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import Target from '../src/Target';
|
||||
|
||||
describe('Target', () => {
|
||||
test('instance', () => {
|
||||
const target = new Target({
|
||||
isTarget: () => true,
|
||||
id: 'target',
|
||||
});
|
||||
expect(target).toBeInstanceOf(Target);
|
||||
});
|
||||
|
||||
test('default target type', () => {
|
||||
const defaultTarget = new Target({
|
||||
isTarget: () => true,
|
||||
id: 'default',
|
||||
type: 'default',
|
||||
});
|
||||
const target = new Target({
|
||||
isTarget: () => true,
|
||||
id: 'target',
|
||||
type: 'target',
|
||||
});
|
||||
expect(defaultTarget.type).toBe('default');
|
||||
expect(target.type).toBe('target');
|
||||
});
|
||||
});
|
@ -1,88 +1,110 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import depService, { Target, Watcher } from '@editor/services/dep';
|
||||
import Target from '../src/Target';
|
||||
import Watcher from '../src/Watcher';
|
||||
|
||||
describe('Watcher', () => {
|
||||
test('instance', () => {
|
||||
const watcher = new Watcher();
|
||||
expect(watcher).toBeInstanceOf(Watcher);
|
||||
});
|
||||
});
|
||||
|
||||
describe('depService', () => {
|
||||
const defaultTarget = new Target({
|
||||
id: 1,
|
||||
name: 'test',
|
||||
isTarget: () => true,
|
||||
});
|
||||
|
||||
const target = new Target({
|
||||
type: 'target',
|
||||
id: 2,
|
||||
name: 'test',
|
||||
isTarget: () => true,
|
||||
});
|
||||
|
||||
test('default target type', () => {
|
||||
expect(defaultTarget.type).toBe('default');
|
||||
expect(target.type).toBe('target');
|
||||
});
|
||||
|
||||
test('addTarget', () => {
|
||||
depService.addTarget(target);
|
||||
const watcher = new Watcher();
|
||||
const target = new Target({
|
||||
isTarget: () => true,
|
||||
id: 'target',
|
||||
type: 'target',
|
||||
});
|
||||
|
||||
expect(depService.getTarget(1)).toBeUndefined();
|
||||
expect(depService.getTarget(2)?.id).toBe(2);
|
||||
expect(Object.keys(depService.getTargets())).toHaveLength(0);
|
||||
expect(Object.keys(depService.getTargets('target'))).toHaveLength(1);
|
||||
watcher.addTarget(target);
|
||||
|
||||
expect(watcher.getTarget(1)).toBeUndefined();
|
||||
expect(watcher.getTarget('target')?.id).toBe('target');
|
||||
expect(Object.keys(watcher.getTargets())).toHaveLength(0);
|
||||
expect(Object.keys(watcher.getTargets('target'))).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('clearTargets', () => {
|
||||
depService.clearTargets();
|
||||
const watcher = new Watcher();
|
||||
watcher.clearTargets();
|
||||
|
||||
depService.addTarget(target);
|
||||
const target = new Target({
|
||||
isTarget: () => true,
|
||||
id: 'target',
|
||||
});
|
||||
|
||||
expect(depService.hasTarget(2)).toBeTruthy();
|
||||
watcher.addTarget(target);
|
||||
|
||||
depService.clearTargets();
|
||||
expect(watcher.hasTarget('target')).toBeTruthy();
|
||||
|
||||
expect(depService.hasTarget(2)).toBeFalsy();
|
||||
watcher.clearTargets();
|
||||
|
||||
expect(watcher.hasTarget('target')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('hasTarget', () => {
|
||||
depService.clearTargets();
|
||||
const watcher = new Watcher();
|
||||
|
||||
depService.addTarget(target);
|
||||
const target = new Target({
|
||||
isTarget: () => true,
|
||||
id: 'target2',
|
||||
});
|
||||
|
||||
expect(depService.hasTarget(1)).toBeFalsy();
|
||||
expect(depService.hasTarget(2)).toBeTruthy();
|
||||
watcher.clearTargets();
|
||||
|
||||
watcher.addTarget(target);
|
||||
|
||||
expect(watcher.hasTarget('target')).toBeFalsy();
|
||||
expect(watcher.hasTarget('target2')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('removeTarget', () => {
|
||||
depService.clearTargets();
|
||||
depService.addTarget(target);
|
||||
expect(depService.hasTarget(2)).toBeTruthy();
|
||||
depService.removeTarget(2);
|
||||
expect(depService.hasTarget(2)).toBeFalsy();
|
||||
const watcher = new Watcher();
|
||||
|
||||
const target = new Target({
|
||||
isTarget: () => true,
|
||||
id: 'target',
|
||||
});
|
||||
|
||||
watcher.clearTargets();
|
||||
watcher.addTarget(target);
|
||||
expect(watcher.hasTarget('target')).toBeTruthy();
|
||||
watcher.removeTarget('target');
|
||||
expect(watcher.hasTarget('target')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('removeTargets', () => {
|
||||
depService.clearTargets();
|
||||
depService.addTarget(defaultTarget);
|
||||
depService.addTarget(target);
|
||||
expect(depService.hasTarget(1)).toBeTruthy();
|
||||
expect(depService.hasTarget(2)).toBeTruthy();
|
||||
depService.removeTargets('target');
|
||||
expect(depService.hasTarget(1)).toBeTruthy();
|
||||
expect(depService.hasTarget(2)).toBeFalsy();
|
||||
const watcher = new Watcher();
|
||||
|
||||
depService.removeTargets('target1');
|
||||
const defaultTarget = new Target({
|
||||
isTarget: () => true,
|
||||
id: 'defaultTarget',
|
||||
});
|
||||
|
||||
const target = new Target({
|
||||
isTarget: () => true,
|
||||
type: 'targetType',
|
||||
id: 'target',
|
||||
});
|
||||
|
||||
watcher.clearTargets();
|
||||
watcher.addTarget(defaultTarget);
|
||||
watcher.addTarget(target);
|
||||
expect(watcher.hasTarget('defaultTarget')).toBeTruthy();
|
||||
expect(watcher.hasTarget('target')).toBeTruthy();
|
||||
|
||||
watcher.removeTargets('targetType');
|
||||
expect(watcher.hasTarget('defaultTarget')).toBeTruthy();
|
||||
expect(watcher.hasTarget('target')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('collect', () => {
|
||||
depService.clearTargets();
|
||||
const watcher = new Watcher();
|
||||
|
||||
depService.addTarget(
|
||||
watcher.clearTargets();
|
||||
|
||||
watcher.addTarget(
|
||||
new Target({
|
||||
type: 'target',
|
||||
id: 'collect_1',
|
||||
@ -91,7 +113,7 @@ describe('depService', () => {
|
||||
}),
|
||||
);
|
||||
|
||||
depService.addTarget(
|
||||
watcher.addTarget(
|
||||
new Target({
|
||||
type: 'target',
|
||||
id: 'collect_2',
|
||||
@ -100,7 +122,7 @@ describe('depService', () => {
|
||||
}),
|
||||
);
|
||||
|
||||
depService.collect([
|
||||
watcher.collect([
|
||||
{
|
||||
id: 'node_1',
|
||||
name: 'node',
|
||||
@ -119,15 +141,15 @@ describe('depService', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
const target1 = depService.getTarget('collect_1');
|
||||
const target2 = depService.getTarget('collect_2');
|
||||
const target1 = watcher.getTarget('collect_1');
|
||||
const target2 = watcher.getTarget('collect_2');
|
||||
|
||||
expect(target1?.deps?.node_1.name).toBe('node');
|
||||
expect(target2?.deps?.node_1.name).toBe('node');
|
||||
expect(target1?.deps?.node_1.keys).toHaveLength(1);
|
||||
expect(target2?.deps?.node_1.keys).toHaveLength(3);
|
||||
|
||||
depService.collect([
|
||||
watcher.collect([
|
||||
{
|
||||
id: 'node_1',
|
||||
name: 'node',
|
||||
@ -149,7 +171,7 @@ describe('depService', () => {
|
||||
expect(target1?.deps?.node_1).toBeUndefined();
|
||||
expect(target2?.deps?.node_1.keys).toHaveLength(1);
|
||||
|
||||
depService.collect([
|
||||
watcher.collect([
|
||||
{
|
||||
id: 'node_1',
|
||||
name: 'node',
|
||||
@ -161,7 +183,7 @@ describe('depService', () => {
|
||||
expect(target1?.deps?.node_1).toBeUndefined();
|
||||
expect(target2?.deps?.node_1.keys[0]).toBe('text1');
|
||||
|
||||
depService.clear([
|
||||
watcher.clear([
|
||||
{
|
||||
id: 'node_1',
|
||||
name: 'node',
|
||||
@ -173,9 +195,11 @@ describe('depService', () => {
|
||||
});
|
||||
|
||||
test('collect deep', () => {
|
||||
depService.clearTargets();
|
||||
const watcher = new Watcher();
|
||||
|
||||
depService.addTarget(
|
||||
watcher.clearTargets();
|
||||
|
||||
watcher.addTarget(
|
||||
new Target({
|
||||
type: 'target',
|
||||
id: 'collect_1',
|
||||
@ -184,7 +208,7 @@ describe('depService', () => {
|
||||
}),
|
||||
);
|
||||
|
||||
depService.collect(
|
||||
watcher.collect(
|
||||
[
|
||||
{
|
||||
id: 'node_1',
|
||||
@ -202,12 +226,12 @@ describe('depService', () => {
|
||||
true,
|
||||
);
|
||||
|
||||
const target1 = depService.getTarget('collect_1');
|
||||
const target1 = watcher.getTarget('collect_1');
|
||||
|
||||
expect(target1?.deps?.node_1.name).toBe('node');
|
||||
expect(target1?.deps?.node_2.name).toBe('node2');
|
||||
|
||||
depService.clear([
|
||||
watcher.clear([
|
||||
{
|
||||
id: 'node_1',
|
||||
name: 'node',
|
@ -1,10 +1,10 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import * as dep from '@editor/utils/dep';
|
||||
import * as utils from '../src/utils';
|
||||
|
||||
describe('dep', () => {
|
||||
describe('utils', () => {
|
||||
test('createCodeBlockTarget', () => {
|
||||
const target = dep.createCodeBlockTarget('code_5316', {
|
||||
const target = utils.createCodeBlockTarget('code_5316', {
|
||||
name: 'code',
|
||||
content: () => false,
|
||||
params: [],
|
||||
@ -33,7 +33,7 @@ describe('dep', () => {
|
||||
|
||||
expect(isTarget).toBeTruthy();
|
||||
|
||||
const target1 = dep.createCodeBlockTarget('1', {
|
||||
const target1 = utils.createCodeBlockTarget('1', {
|
||||
name: 'code',
|
||||
content: () => false,
|
||||
params: [],
|
13
packages/dep/tsconfig.build.json
Normal file
13
packages/dep/tsconfig.build.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"declaration": true,
|
||||
"declarationDir": "types",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"paths": {},
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
}
|
6
packages/dep/tsconfig.json
Normal file
6
packages/dep/tsconfig.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "../..",
|
||||
},
|
||||
}
|
51
packages/dep/vite.config.ts
Normal file
51
packages/dep/vite.config.ts
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
||||
*
|
||||
* Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
import pkg from './package.json';
|
||||
|
||||
export default defineConfig({
|
||||
resolve: {
|
||||
alias:
|
||||
process.env.NODE_ENV === 'production'
|
||||
? []
|
||||
: [{ find: /^@tmagic\/schema/, replacement: path.join(__dirname, '../schema/src/index.ts') }],
|
||||
},
|
||||
|
||||
build: {
|
||||
sourcemap: true,
|
||||
minify: false,
|
||||
target: 'esnext',
|
||||
|
||||
lib: {
|
||||
entry: 'src/index.ts',
|
||||
name: 'TMagicDep',
|
||||
fileName: 'tmagic-dep',
|
||||
},
|
||||
|
||||
rollupOptions: {
|
||||
// 确保外部化处理那些你不想打包进库的依赖
|
||||
external(id: string) {
|
||||
return Object.keys(pkg.dependencies).some((k) => new RegExp(`^${k}`).test(id));
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
@ -37,15 +37,15 @@
|
||||
"typescript"
|
||||
],
|
||||
"dependencies": {
|
||||
"vue": "^3.3.4"
|
||||
"vue": "^3.3.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.3.4"
|
||||
"vue": "^3.3.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^15.12.4",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vue/compiler-sfc": "^3.3.4",
|
||||
"@vue/compiler-sfc": "^3.3.8",
|
||||
"@vue/test-utils": "^2.3.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^5.0.4",
|
||||
|
@ -48,6 +48,7 @@
|
||||
"@babel/core": "^7.18.0",
|
||||
"@element-plus/icons-vue": "^2.0.9",
|
||||
"@tmagic/core": "1.3.1",
|
||||
"@tmagic/dep": "1.3.1",
|
||||
"@tmagic/design": "1.3.1",
|
||||
"@tmagic/form": "1.3.1",
|
||||
"@tmagic/schema": "1.3.1",
|
||||
@ -63,13 +64,13 @@
|
||||
"monaco-editor": "^0.41.0",
|
||||
"moveable": "^0.51.1",
|
||||
"serialize-javascript": "^6.0.0",
|
||||
"vue": "^3.3.4"
|
||||
"vue": "^3.3.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tmagic/design": "1.3.1",
|
||||
"@tmagic/form": "1.3.1",
|
||||
"monaco-editor": "^0.41.0",
|
||||
"vue": "^3.3.4"
|
||||
"vue": "^3.3.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/events": "^3.0.0",
|
||||
@ -77,7 +78,7 @@
|
||||
"@types/node": "^15.12.4",
|
||||
"@types/serialize-javascript": "^5.0.1",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vue/compiler-sfc": "^3.3.4",
|
||||
"@vue/compiler-sfc": "^3.3.8",
|
||||
"@vue/test-utils": "^2.3.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"sass": "^1.35.1",
|
||||
|
@ -40,6 +40,7 @@ import './theme/index.scss';
|
||||
|
||||
export type { OnDrag } from 'gesto';
|
||||
|
||||
export type { DepTargetType } from '@tmagic/dep';
|
||||
export type { MoveableOptions } from '@tmagic/stage';
|
||||
export * from './type';
|
||||
export * from './hooks';
|
||||
|
@ -1,20 +1,21 @@
|
||||
import { onUnmounted, toRaw, watch } from 'vue';
|
||||
import { onUnmounted, reactive, toRaw, watch } from 'vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import type { EventOption } from '@tmagic/core';
|
||||
import type { CodeBlockContent, DataSourceSchema, Id, MApp, MNode, MPage } from '@tmagic/schema';
|
||||
import { getNodes } from '@tmagic/utils';
|
||||
|
||||
import PropsPanel from './layouts/PropsPanel.vue';
|
||||
import type { Target } from './services/dep';
|
||||
import type { Target } from '@tmagic/dep';
|
||||
import {
|
||||
createCodeBlockTarget,
|
||||
createDataSourceCondTarget,
|
||||
createDataSourceMethodTarget,
|
||||
createDataSourceTarget,
|
||||
} from './utils/dep';
|
||||
DepTargetType,
|
||||
} from '@tmagic/dep';
|
||||
import type { CodeBlockContent, DataSourceSchema, Id, MApp, MNode, MPage } from '@tmagic/schema';
|
||||
import { getNodes } from '@tmagic/utils';
|
||||
|
||||
import PropsPanel from './layouts/PropsPanel.vue';
|
||||
import { EditorProps } from './editorProps';
|
||||
import { DepTargetType, Services } from './type';
|
||||
import { Services } from './type';
|
||||
|
||||
export declare type LooseRequired<T> = {
|
||||
[P in string & keyof T]: T[P];
|
||||
@ -270,9 +271,9 @@ export const initServiceEvents = (
|
||||
depService.on('collected', collectedHandler);
|
||||
|
||||
const initDataSourceDepTarget = (ds: DataSourceSchema) => {
|
||||
depService.addTarget(createDataSourceTarget(ds.id));
|
||||
depService.addTarget(createDataSourceMethodTarget(ds.id));
|
||||
depService.addTarget(createDataSourceCondTarget(ds.id));
|
||||
depService.addTarget(createDataSourceTarget(ds, reactive({})));
|
||||
depService.addTarget(createDataSourceMethodTarget(ds, reactive({})));
|
||||
depService.addTarget(createDataSourceCondTarget(ds, reactive({})));
|
||||
};
|
||||
|
||||
const rootChangeHandler = async (value: MApp | null, preValue?: MApp | null) => {
|
||||
|
@ -44,13 +44,14 @@
|
||||
import { computed, inject, ref } from 'vue';
|
||||
import { Close, Edit, View } from '@element-plus/icons-vue';
|
||||
|
||||
import { DepTargetType } from '@tmagic/dep';
|
||||
import { tMagicMessage, tMagicMessageBox, TMagicTooltip, TMagicTree } from '@tmagic/design';
|
||||
import type { Id } from '@tmagic/schema';
|
||||
|
||||
import Icon from '@editor/components/Icon.vue';
|
||||
import AppManageIcon from '@editor/icons/AppManageIcon.vue';
|
||||
import CodeIcon from '@editor/icons/CodeIcon.vue';
|
||||
import { CodeBlockListSlots, CodeDeleteErrorType, CodeDslItem, DepTargetType, Services } from '@editor/type';
|
||||
import { type CodeBlockListSlots, CodeDeleteErrorType, type CodeDslItem, type Services } from '@editor/type';
|
||||
|
||||
defineSlots<CodeBlockListSlots>();
|
||||
|
||||
|
@ -38,11 +38,12 @@
|
||||
import { computed, inject, ref } from 'vue';
|
||||
import { Aim, Close, Coin, Edit, View } from '@element-plus/icons-vue';
|
||||
|
||||
import { DepTargetType } from '@tmagic/dep';
|
||||
import { tMagicMessageBox, TMagicTooltip, TMagicTree } from '@tmagic/design';
|
||||
import { Dep, Id } from '@tmagic/schema';
|
||||
import { DepData, Id } from '@tmagic/schema';
|
||||
|
||||
import Icon from '@editor/components/Icon.vue';
|
||||
import { type DataSourceListSlots, DepTargetType, type Services } from '@editor/type';
|
||||
import type { DataSourceListSlots, Services } from '@editor/type';
|
||||
|
||||
defineSlots<DataSourceListSlots>();
|
||||
|
||||
@ -65,10 +66,10 @@ const dsDep = computed(() => depService?.getTargets(DepTargetType.DATA_SOURCE) |
|
||||
const dsMethodDep = computed(() => depService?.getTargets(DepTargetType.DATA_SOURCE_METHOD) || {});
|
||||
const dsCondDep = computed(() => depService?.getTargets(DepTargetType.DATA_SOURCE_COND) || {});
|
||||
|
||||
const getKeyTreeConfig = (dep: Dep[string], type?: string) =>
|
||||
const getKeyTreeConfig = (dep: DepData[string], type?: string) =>
|
||||
dep.keys.map((key) => ({ name: key, id: key, type: 'key', isMethod: type === 'method', isCond: type === 'cond' }));
|
||||
|
||||
const getNodeTreeConfig = (id: string, dep: Dep[string], type?: string) => ({
|
||||
const getNodeTreeConfig = (id: string, dep: DepData[string], type?: string) => ({
|
||||
name: dep.name,
|
||||
type: 'node',
|
||||
id,
|
||||
@ -81,7 +82,7 @@ const getNodeTreeConfig = (id: string, dep: Dep[string], type?: string) => ({
|
||||
* @param deps 依赖
|
||||
* @param type 依赖类型
|
||||
*/
|
||||
const mergeChildren = (children: any[], deps: Dep, type?: string) => {
|
||||
const mergeChildren = (children: any[], deps: DepData, type?: string) => {
|
||||
Object.entries(deps).forEach(([id, dep]) => {
|
||||
// 已经生成过的节点
|
||||
const nodeItem = children.find((item) => item.id === id);
|
||||
@ -97,9 +98,9 @@ const mergeChildren = (children: any[], deps: Dep, type?: string) => {
|
||||
|
||||
const list = computed(() =>
|
||||
dataSources.value.map((ds) => {
|
||||
const dsDeps = dsDep.value[ds.id].deps;
|
||||
const dsMethodDeps = dsMethodDep.value[ds.id].deps;
|
||||
const dsCondDeps = dsCondDep.value[ds.id].deps;
|
||||
const dsDeps = dsDep.value[ds.id]?.deps || {};
|
||||
const dsMethodDeps = dsMethodDep.value[ds.id]?.deps || {};
|
||||
const dsCondDeps = dsCondDep.value[ds.id]?.deps || {};
|
||||
|
||||
const children: any[] = [];
|
||||
// 数据源依赖分为三种类型:key/node、method、cond,是分开存储,这里将其合并展示
|
||||
|
@ -15,320 +15,58 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
import { reactive } from 'vue';
|
||||
|
||||
import type { Dep, Id, MNode } from '@tmagic/schema';
|
||||
import { isObject } from '@tmagic/utils';
|
||||
import { DepTargetType, type Target, Watcher } from '@tmagic/dep';
|
||||
import type { Id, MNode } from '@tmagic/schema';
|
||||
|
||||
import { DepTargetType } from '@editor/type';
|
||||
import BaseService from './BaseService';
|
||||
|
||||
type IsTarget = (key: string | number, value: any) => boolean;
|
||||
class Dep extends BaseService {
|
||||
private watcher = new Watcher({ initialTargets: reactive({}) });
|
||||
|
||||
interface TargetOptions {
|
||||
isTarget: IsTarget;
|
||||
id: string | number;
|
||||
/** 类型,数据源、代码块或其他 */
|
||||
type?: DepTargetType | string;
|
||||
name?: string;
|
||||
}
|
||||
public removeTargets(type: string = DepTargetType.DEFAULT) {
|
||||
this.watcher.removeTargets(type);
|
||||
|
||||
interface TargetList {
|
||||
[type: DepTargetType | string]: {
|
||||
[targetId: string | number]: Target;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 需要收集依赖的目标
|
||||
* 例如:一个代码块可以为一个目标
|
||||
*/
|
||||
export class Target extends EventEmitter {
|
||||
/**
|
||||
* 如何识别目标
|
||||
*/
|
||||
public isTarget: IsTarget;
|
||||
/**
|
||||
* 目标id,不可重复
|
||||
* 例如目标是代码块,则为代码块id
|
||||
*/
|
||||
public id: string | number;
|
||||
/**
|
||||
* 目标名称,用于显示在依赖列表中
|
||||
*/
|
||||
public name?: string;
|
||||
/**
|
||||
* 不同的目标可以进行分类,例如代码块,数据源可以为两个不同的type
|
||||
*/
|
||||
public type: DepTargetType | string = DepTargetType.DEFAULT;
|
||||
/**
|
||||
* 依赖详情
|
||||
* 实例:{ 'node_id': { name: 'node_name', keys: [ created, mounted ] } }
|
||||
*/
|
||||
public deps = reactive<Dep>({});
|
||||
|
||||
constructor(options: TargetOptions) {
|
||||
super();
|
||||
this.isTarget = options.isTarget;
|
||||
this.id = options.id;
|
||||
this.name = options.name;
|
||||
if (options.type) {
|
||||
this.type = options.type;
|
||||
}
|
||||
this.emit('remove-target');
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新依赖
|
||||
* @param node 节点配置
|
||||
* @param key 哪个key配置了这个目标的id
|
||||
*/
|
||||
public updateDep(node: MNode, key: string | number) {
|
||||
const dep = this.deps[node.id] || {
|
||||
name: node.name,
|
||||
keys: [],
|
||||
};
|
||||
|
||||
if (node.name) {
|
||||
dep.name = node.name;
|
||||
}
|
||||
|
||||
this.deps[node.id] = dep;
|
||||
|
||||
if (dep.keys.indexOf(key) === -1) {
|
||||
dep.keys.push(key);
|
||||
}
|
||||
|
||||
this.emit('change');
|
||||
public getTargets(type: string = DepTargetType.DEFAULT) {
|
||||
return this.watcher.getTargets(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除依赖
|
||||
* @param node 哪个节点的依赖需要移除,如果为空,则移除所有依赖
|
||||
* @param key 节点下哪个key需要移除,如果为空,则移除改节点下的所有依赖key
|
||||
* @returns void
|
||||
*/
|
||||
public removeDep(node?: MNode, key?: string | number) {
|
||||
if (!node) {
|
||||
Object.keys(this.deps).forEach((depKey) => {
|
||||
delete this.deps[depKey];
|
||||
});
|
||||
this.emit('change');
|
||||
return;
|
||||
}
|
||||
|
||||
const dep = this.deps[node.id];
|
||||
|
||||
if (!dep) return;
|
||||
|
||||
if (key) {
|
||||
const index = dep.keys.indexOf(key);
|
||||
dep.keys.splice(index, 1);
|
||||
|
||||
if (dep.keys.length === 0) {
|
||||
delete this.deps[node.id];
|
||||
}
|
||||
} else {
|
||||
delete this.deps[node.id];
|
||||
}
|
||||
|
||||
this.emit('change');
|
||||
public getTarget(id: Id) {
|
||||
return this.watcher.getTarget(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断指定节点下的指定key是否存在在依赖列表中
|
||||
* @param node 哪个节点
|
||||
* @param key 哪个key
|
||||
* @returns boolean
|
||||
*/
|
||||
public hasDep(node: MNode, key: string | number) {
|
||||
const dep = this.deps[node.id];
|
||||
|
||||
return Boolean(dep?.keys.find((d) => d === key));
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.removeAllListeners();
|
||||
}
|
||||
}
|
||||
|
||||
export class Watcher extends EventEmitter {
|
||||
private targets = reactive<TargetList>({});
|
||||
|
||||
/**
|
||||
* 获取指定类型中的所有target
|
||||
* @param type 分类
|
||||
* @returns Target[]
|
||||
*/
|
||||
public getTargets(type: DepTargetType | string = DepTargetType.DEFAULT) {
|
||||
return this.targets[type] || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加新的目标
|
||||
* @param target Target
|
||||
*/
|
||||
public addTarget(target: Target) {
|
||||
const targets = this.getTargets(target.type) || {};
|
||||
this.targets[target.type] = targets;
|
||||
targets[target.id] = target;
|
||||
|
||||
this.watcher.addTarget(target);
|
||||
this.emit('add-target', target);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定id的target
|
||||
* @param id target id
|
||||
* @returns Target
|
||||
*/
|
||||
public getTarget(id: string | number) {
|
||||
const allTargets = Object.values(this.targets);
|
||||
for (const targets of allTargets) {
|
||||
if (targets[id]) {
|
||||
return targets[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否存在指定id的target
|
||||
* @param id target id
|
||||
* @returns boolean
|
||||
*/
|
||||
public hasTarget(id: string | number) {
|
||||
const allTargets = Object.values(this.targets);
|
||||
for (const targets of allTargets) {
|
||||
if (targets[id]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定id的target
|
||||
* @param id target id
|
||||
*/
|
||||
public removeTarget(id: string | number) {
|
||||
const allTargets = Object.values(this.targets);
|
||||
for (const targets of allTargets) {
|
||||
if (targets[id]) {
|
||||
targets[id].destroy();
|
||||
delete targets[id];
|
||||
}
|
||||
}
|
||||
|
||||
public removeTarget(id: Id) {
|
||||
this.watcher.removeTarget(id);
|
||||
this.emit('remove-target');
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定分类的所有target
|
||||
* @param type 分类
|
||||
* @returns void
|
||||
*/
|
||||
public removeTargets(type: DepTargetType | string = DepTargetType.DEFAULT) {
|
||||
const targets = this.targets[type];
|
||||
|
||||
if (!targets) return;
|
||||
|
||||
for (const target of Object.values(targets)) {
|
||||
target.destroy();
|
||||
}
|
||||
|
||||
delete this.targets[type];
|
||||
|
||||
this.emit('remove-target');
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除所有target
|
||||
*/
|
||||
public clearTargets() {
|
||||
Object.keys(this.targets).forEach((key) => {
|
||||
delete this.targets[key];
|
||||
});
|
||||
this.watcher.clearTargets();
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集依赖
|
||||
* @param nodes 需要收集的节点
|
||||
* @param deep 是否需要收集子节点
|
||||
*/
|
||||
public collect(nodes: MNode[], deep = false) {
|
||||
Object.values(this.targets).forEach((targets) => {
|
||||
Object.values(targets).forEach((target) => {
|
||||
nodes.forEach((node) => {
|
||||
// 先删除原有依赖,重新收集
|
||||
target.removeDep(node);
|
||||
this.collectItem(node, target, deep);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
this.watcher.collect(nodes, deep);
|
||||
this.emit('collected', nodes, deep);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除依赖
|
||||
* @param nodes 需要清除依赖的节点
|
||||
*/
|
||||
public clear(nodes?: MNode[]) {
|
||||
const clearedItemsNodeIds: Id[] = [];
|
||||
Object.values(this.targets).forEach((targets) => {
|
||||
Object.values(targets).forEach((target) => {
|
||||
if (nodes) {
|
||||
nodes.forEach((node) => {
|
||||
target.removeDep(node);
|
||||
|
||||
if (Array.isArray(node.items) && node.items.length && !clearedItemsNodeIds.includes(node.id)) {
|
||||
clearedItemsNodeIds.push(node.id);
|
||||
this.clear(node.items);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
target.removeDep();
|
||||
}
|
||||
});
|
||||
});
|
||||
return this.watcher.clear(nodes);
|
||||
}
|
||||
|
||||
private collectItem(node: MNode, target: Target, deep = false) {
|
||||
const collectTarget = (config: Record<string | number, any>, prop = '') => {
|
||||
const doCollect = (key: string, value: any) => {
|
||||
const keyIsItems = key === 'items';
|
||||
const fullKey = prop ? `${prop}.${key}` : key;
|
||||
|
||||
if (target.isTarget(fullKey, value)) {
|
||||
target.updateDep(node, fullKey);
|
||||
this.emit('update-dep', node, fullKey);
|
||||
} else if (!keyIsItems && Array.isArray(value)) {
|
||||
value.forEach((item, index) => {
|
||||
if (isObject(item)) {
|
||||
collectTarget(item, `${fullKey}.${index}`);
|
||||
}
|
||||
});
|
||||
} else if (isObject(value)) {
|
||||
collectTarget(value, fullKey);
|
||||
}
|
||||
|
||||
if (keyIsItems && deep && Array.isArray(value)) {
|
||||
value.forEach((child) => {
|
||||
this.collectItem(child, target, deep);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Object.entries(config).forEach(([key, value]) => {
|
||||
if (typeof value === 'undefined' || value === '') return;
|
||||
doCollect(key, value);
|
||||
});
|
||||
};
|
||||
|
||||
collectTarget(node);
|
||||
public hasTarget(id: Id) {
|
||||
return this.watcher.hasTarget(id);
|
||||
}
|
||||
}
|
||||
|
||||
export type DepService = Watcher;
|
||||
export type DepService = Dep;
|
||||
|
||||
export default new Watcher();
|
||||
export default new Dep();
|
||||
|
@ -425,13 +425,6 @@ export type CodeState = {
|
||||
paramsColConfig?: ColumnConfig;
|
||||
};
|
||||
|
||||
export type HookData = {
|
||||
/** 代码块id */
|
||||
codeId: Id;
|
||||
/** 参数 */
|
||||
params?: object;
|
||||
};
|
||||
|
||||
export type CodeRelation = {
|
||||
/** 组件id:[代码id1,代码id2] */
|
||||
[compId: Id]: Id[];
|
||||
@ -594,19 +587,6 @@ export interface DataSourceFieldSelectConfig {
|
||||
display?: boolean | FilterFunction;
|
||||
}
|
||||
|
||||
/** 依赖收集的目标类型 */
|
||||
export enum DepTargetType {
|
||||
DEFAULT = 'default',
|
||||
/** 代码块 */
|
||||
CODE_BLOCK = 'code-block',
|
||||
/** 数据源 */
|
||||
DATA_SOURCE = 'data-source',
|
||||
/** 数据源方法 */
|
||||
DATA_SOURCE_METHOD = 'data-source-method',
|
||||
/** 数据源条件 */
|
||||
DATA_SOURCE_COND = 'data-source-cond',
|
||||
}
|
||||
|
||||
/** 可新增的数据源类型选项 */
|
||||
export interface DatasourceTypeOption {
|
||||
/** 数据源类型 */
|
||||
|
@ -39,12 +39,12 @@
|
||||
"dependencies": {
|
||||
"@tmagic/design": "1.3.1",
|
||||
"element-plus": "^2.2.32",
|
||||
"vue": "^3.3.4"
|
||||
"vue": "^3.3.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tmagic/design": "1.3.1",
|
||||
"element-plus": "^2.2.32",
|
||||
"vue": "^3.3.4"
|
||||
"vue": "^3.3.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^15.12.4",
|
||||
|
@ -41,10 +41,10 @@
|
||||
"@tmagic/utils": "1.3.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"sortablejs": "^1.14.0",
|
||||
"vue": "^3.3.4"
|
||||
"vue": "^3.3.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.3.4"
|
||||
"vue": "^3.3.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.18.0",
|
||||
@ -52,7 +52,7 @@
|
||||
"@types/node": "^15.12.4",
|
||||
"@types/sortablejs": "^1.10.7",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vue/compiler-sfc": "^3.3.4",
|
||||
"@vue/compiler-sfc": "^3.3.8",
|
||||
"@vue/test-utils": "^2.3.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"sass": "^1.35.1",
|
||||
|
@ -64,7 +64,7 @@ export enum ActionType {
|
||||
}
|
||||
|
||||
export interface DataSourceDeps {
|
||||
[dataSourceId: string | number]: Dep;
|
||||
[dataSourceId: string | number]: DepData;
|
||||
}
|
||||
|
||||
/** 事件类型(已废弃,后续不建议继续使用) */
|
||||
@ -241,10 +241,17 @@ export interface DataSourceSchema {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface Dep {
|
||||
export interface DepData {
|
||||
[nodeId: Id]: {
|
||||
/** 组件名称 */
|
||||
name: string;
|
||||
keys: (string | number)[];
|
||||
};
|
||||
}
|
||||
|
||||
export type HookData = {
|
||||
/** 代码块id */
|
||||
codeId: Id;
|
||||
/** 参数 */
|
||||
params?: object;
|
||||
};
|
||||
|
@ -38,18 +38,18 @@
|
||||
"@tmagic/form": "1.3.1",
|
||||
"@tmagic/utils": "1.3.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"vue": "^3.3.4"
|
||||
"vue": "^3.3.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tmagic/form": "1.3.1",
|
||||
"vue": "^3.3.4"
|
||||
"vue": "^3.3.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/color": "^3.0.1",
|
||||
"@types/lodash-es": "^4.17.4",
|
||||
"@types/node": "^15.12.4",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vue/compiler-sfc": "^3.3.4",
|
||||
"@vue/compiler-sfc": "^3.3.8",
|
||||
"@vue/test-utils": "^2.3.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"sass": "^1.35.1",
|
||||
|
@ -39,16 +39,16 @@
|
||||
"dependencies": {
|
||||
"@tmagic/design": "1.3.1",
|
||||
"tdesign-vue-next": "^1.3.4",
|
||||
"vue": "^3.3.4"
|
||||
"vue": "^3.3.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tmagic/design": "1.3.1",
|
||||
"tdesign-vue-next": "^1.3.4",
|
||||
"vue": "^3.3.4"
|
||||
"vue": "^3.3.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vue/compiler-sfc": "^3.3.4",
|
||||
"@vue/compiler-sfc": "^3.3.8",
|
||||
"@types/node": "^15.12.4",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^5.0.4",
|
||||
|
@ -18,16 +18,16 @@
|
||||
"delegate": "^3.2.0",
|
||||
"qrcode": "^1.5.0",
|
||||
"tiny-emitter": "^2.1.0",
|
||||
"vue": "^3.3.4"
|
||||
"vue": "^3.3.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"qrcode": "^1.5.0",
|
||||
"vue": "^3.3.4"
|
||||
"vue": "^3.3.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/vue": "^6.4.2",
|
||||
"@types/qrcode": "^1.4.2",
|
||||
"@vue/compiler-sfc": "^3.3.4",
|
||||
"@vue/compiler-sfc": "^3.3.8",
|
||||
"@vue/test-utils": "^2.3.2"
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@
|
||||
"element-plus": "^2.2.32",
|
||||
"monaco-editor": "^0.41.0",
|
||||
"serialize-javascript": "^6.0.0",
|
||||
"vue": "^3.3.4",
|
||||
"vue": "^3.3.8",
|
||||
"vue-router": "^4.0.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -34,7 +34,7 @@
|
||||
"@vitejs/plugin-legacy": "^4.1.0",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vitejs/plugin-vue-jsx": "^3.0.1",
|
||||
"@vue/compiler-sfc": "^3.3.4",
|
||||
"@vue/compiler-sfc": "^3.3.8",
|
||||
"sass": "^1.35.1",
|
||||
"terser": "^5.14.2",
|
||||
"typescript": "^5.0.4",
|
||||
|
@ -59,6 +59,7 @@ export default defineConfig({
|
||||
{ find: /^@tmagic\/utils/, replacement: path.join(__dirname, '../packages/utils/src/index.ts') },
|
||||
{ find: /^@tmagic\/design/, replacement: path.join(__dirname, '../packages/design/src/index.ts') },
|
||||
{ find: /^@tmagic\/data-source/, replacement: path.join(__dirname, '../packages/data-source/src/index.ts') },
|
||||
{ find: /^@tmagic\/dep/, replacement: path.join(__dirname, '../packages/dep/src/index.ts') },
|
||||
{ find: /^@data-source/, replacement: path.join(__dirname, '../packages/data-source/src') },
|
||||
{
|
||||
find: /^@tmagic\/element-plus-adapter/,
|
||||
|
540
pnpm-lock.yaml
generated
540
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -26,7 +26,7 @@
|
||||
"@tmagic/stage": "1.3.1",
|
||||
"@tmagic/utils": "1.3.1",
|
||||
"axios": "^0.25.0",
|
||||
"vue": "^3.3.4"
|
||||
"vue": "^3.3.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.21.4",
|
||||
@ -34,7 +34,7 @@
|
||||
"@vitejs/plugin-legacy": "^4.1.0",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vitejs/plugin-vue-jsx": "^3.0.1",
|
||||
"@vue/compiler-sfc": "^3.3.4",
|
||||
"@vue/compiler-sfc": "^3.3.8",
|
||||
"@vue/test-utils": "^2.3.2",
|
||||
"recast": "^0.20.4",
|
||||
"rollup": "^2.25.0",
|
||||
|
@ -22,6 +22,7 @@ export default defineConfig({
|
||||
'./packages/stage/tests/**',
|
||||
'./packages/utils/tests/**',
|
||||
'./packages/data-source/tests/**',
|
||||
'./packages/dep/tests/**',
|
||||
],
|
||||
environment: 'jsdom',
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user