mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-01-05 08:58:12 +00:00
335 lines
8.1 KiB
TypeScript
335 lines
8.1 KiB
TypeScript
/*
|
||
* 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 { EventEmitter } from 'events';
|
||
|
||
import { reactive } from 'vue';
|
||
|
||
import type { Dep, Id, MNode } from '@tmagic/schema';
|
||
import { isObject } from '@tmagic/utils';
|
||
|
||
import { DepTargetType } from '@editor/type';
|
||
|
||
type IsTarget = (key: string | number, value: any) => boolean;
|
||
|
||
interface TargetOptions {
|
||
isTarget: IsTarget;
|
||
id: string | number;
|
||
/** 类型,数据源、代码块或其他 */
|
||
type?: DepTargetType | string;
|
||
name?: string;
|
||
}
|
||
|
||
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;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新依赖
|
||
* @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');
|
||
}
|
||
|
||
/**
|
||
* 删除依赖
|
||
* @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');
|
||
}
|
||
|
||
/**
|
||
* 判断指定节点下的指定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.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];
|
||
}
|
||
}
|
||
|
||
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];
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 收集依赖
|
||
* @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.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();
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
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);
|
||
}
|
||
}
|
||
|
||
export type DepService = Watcher;
|
||
|
||
export default new Watcher();
|