2020-04-20 16:54:19 +08:00

295 lines
7.6 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.

import { EventEmitter } from 'events';
import store from 'store';
import { IocContext, RegisterOptions } from '@ali/lowcode-globals';
import {
EditorConfig,
HooksConfig,
LocaleType,
PluginStatusSet,
Utils,
PluginClassSet,
PluginSet,
} from './definitions';
import pluginFactory from './pluginFactory';
import * as editorUtils from './utils';
const { registShortCuts, transformToPromise, unRegistShortCuts } = editorUtils;
let instance: Editor;
EventEmitter.defaultMaxListeners = 100;
export interface HooksFuncs {
[idx: number]: (msg: string, handler: (...args: []) => void) => void;
}
export type KeyType = Function | symbol | string;
export type ClassType = Function | (new (...args: any[]) => any);
export interface GetOptions {
forceNew?: boolean;
sourceCls?: ClassType;
}
export type GetReturnType<T, ClsType> = T extends undefined
? ClsType extends {
prototype: infer R;
}
? R
: any
: T;
const NOT_FOUND = Symbol.for('not_found');
export default class Editor extends EventEmitter {
static getInstance = (config: EditorConfig, components: PluginClassSet, utils?: Utils): Editor => {
if (!instance) {
instance = new Editor(config, components, utils);
}
return instance;
};
private _components?: PluginClassSet;
get components(): PluginClassSet {
if (!this._components) {
this._components = {};
Object.keys(this.componentsMap).forEach((key) => {
(this._components as any)[key] = pluginFactory(this.componentsMap[key]);
});
}
return this._components;
}
readonly utils: Utils;
/**
* Ioc Container
*/
private context = new IocContext({
notFoundHandler: (type: KeyType) => NOT_FOUND,
});
pluginStatus?: PluginStatusSet;
plugins?: PluginSet;
locale?: LocaleType;
hooksFuncs?: HooksFuncs;
constructor(readonly config: EditorConfig = {}, readonly componentsMap: PluginClassSet = {}, utils?: Utils) {
super();
this.utils = { ...editorUtils, ...utils } as any;
instance = this;
}
get<T = undefined, KeyOrType = any>(keyOrType: KeyOrType, opt?: GetOptions): GetReturnType<T, KeyOrType> | undefined {
const x = this.context.get<T, KeyOrType>(keyOrType, opt);
if (x === NOT_FOUND) {
return undefined;
}
return x;
}
has(keyOrType: KeyType): boolean {
return this.context.has(keyOrType);
}
set(key: KeyType, data: any): void {
if (this.context.has(key)) {
this.context.replace(key, data, undefined, true);
} else {
this.context.register(data, key);
}
this.notifyGot(key);
}
onceGot<T = undefined, KeyOrType extends KeyType = any>(keyOrType: KeyOrType): Promise<GetReturnType<T, KeyOrType>> {
const x = this.context.get<T, KeyOrType>(keyOrType);
if (x !== NOT_FOUND) {
return Promise.resolve(x);
}
return new Promise((resolve) => {
this.setWait(keyOrType, resolve, true);
});
}
onGot<T = undefined, KeyOrType extends KeyType = any>(
keyOrType: KeyOrType,
fn: (data: GetReturnType<T, KeyOrType>) => void,
): () => void {
const x = this.context.get<T, KeyOrType>(keyOrType);
if (x !== NOT_FOUND) {
fn(x);
return () => {};
} else {
this.setWait(keyOrType, fn);
return () => {
this.delWait(keyOrType, fn);
};
}
}
register(data: any, key?: KeyType, options?: RegisterOptions): void {
this.context.register(data, key, options);
this.notifyGot(key || data);
}
async init(): Promise<any> {
const { hooks, shortCuts = [], lifeCycles } = this.config || {};
this.locale = store.get('lowcode-editor-locale') || 'zh-CN';
this.pluginStatus = this.initPluginStatus();
this.initHooks(hooks || []);
this.emit('editor.beforeInit');
const init = (lifeCycles && lifeCycles.init) || ((): void => {});
// 用户可以通过设置extensions.init自定义初始化流程
try {
await transformToPromise(init(this));
// 注册快捷键
registShortCuts(shortCuts, this);
this.emit('editor.afterInit');
return true;
} catch (err) {
console.error(err);
}
}
destroy(): void {
try {
const { hooks = [], shortCuts = [], lifeCycles = {} } = this.config;
unRegistShortCuts(shortCuts);
this.destroyHooks(hooks);
if (lifeCycles.destroy) {
lifeCycles.destroy(this);
}
} catch (err) {
console.warn(err);
}
}
batchOn(events: string[], lisenter: (...args: any[]) => void): void {
if (!Array.isArray(events)) {
return;
}
events.forEach((event): void => {
this.on(event, lisenter);
});
}
batchOnce(events: string[], lisenter: (...args: any[]) => void): void {
if (!Array.isArray(events)) {
return;
}
events.forEach((event): void => {
this.once(event, lisenter);
});
}
batchOff(events: string[], lisenter: (...args: any[]) => void): void {
if (!Array.isArray(events)) {
return;
}
events.forEach((event): void => {
this.off(event, lisenter);
});
}
private waits = new Map<
KeyType,
Array<{
once?: boolean;
resolve: (data: any) => void;
}>
>();
private notifyGot(key: KeyType) {
let waits = this.waits.get(key);
if (!waits) {
return;
}
waits = waits.slice().reverse();
let i = waits.length;
while (i--) {
waits[i].resolve(this.get(key));
if (waits[i].once) {
waits.splice(i, 1);
}
}
if (waits.length > 0) {
this.waits.set(key, waits);
} else {
this.waits.delete(key);
}
}
private setWait(key: KeyType, resolve: (data: any) => void, once?: boolean) {
const waits = this.waits.get(key);
if (waits) {
waits.push({ resolve, once });
} else {
this.waits.set(key, [{ resolve, once }]);
}
}
private delWait(key: KeyType, fn: any) {
const waits = this.waits.get(key);
if (!waits) {
return;
}
let i = waits.length;
while (i--) {
if (waits[i].resolve === fn) {
waits.splice(i, 1);
}
}
if (waits.length < 1) {
this.waits.delete(key);
}
}
// 销毁hooks中的消息监听
private destroyHooks(hooks: HooksConfig = []): void {
hooks.forEach((item, idx): void => {
if (typeof this.hooksFuncs?.[idx] === 'function') {
this.off(item.message, this.hooksFuncs[idx]);
}
});
delete this.hooksFuncs;
}
// 初始化hooks中的消息监听
private initHooks(hooks: HooksConfig = []): void {
this.hooksFuncs = hooks.map((item): ((...arg: any[]) => void) => {
const func = (...args: any[]): void => {
item.handler(this, ...args);
};
this[item.type](item.message, func);
return func;
});
}
private initPluginStatus(): PluginStatusSet {
const { plugins = {} } = this.config;
const pluginAreas = Object.keys(plugins);
const res: PluginStatusSet = {};
pluginAreas.forEach((area): void => {
(plugins[area] || []).forEach((plugin): void => {
if (plugin.type === 'Divider') {
return;
}
const { visible, disabled, marked } = plugin.props || {};
res[plugin.pluginKey] = {
visible: typeof visible === 'boolean' ? visible : true,
disabled: typeof disabled === 'boolean' ? disabled : false,
marked: typeof marked === 'boolean' ? marked : false,
};
const pluginClass = this.components[plugin.pluginKey];
// 判断如果编辑器插件有init静态方法则在此执行init方法
if (pluginClass && pluginClass.init) {
pluginClass.init(this);
}
});
});
return res;
}
}