335 lines
8.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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();