mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2025-06-22 09:49:28 +08:00
feat(editor): service扩展支持设置成同步的
This commit is contained in:
parent
75dd89f2fe
commit
5c6a3455b0
@ -127,9 +127,9 @@ watch(
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const show = async (e: MouseEvent) => {
|
||||
const show = (e: MouseEvent) => {
|
||||
menu.value?.show(e);
|
||||
const data = await storageService.getItem(COPY_STORAGE_KEY);
|
||||
const data = storageService.getItem(COPY_STORAGE_KEY);
|
||||
canPaste.value = data !== 'undefined' && !!data;
|
||||
};
|
||||
|
||||
|
@ -24,13 +24,48 @@ const methodName = (prefix: string, name: string) => `${prefix}${name[0].toUpper
|
||||
|
||||
const isError = (error: any): boolean => Object.prototype.toString.call(error) === '[object Error]';
|
||||
|
||||
const doAction = async (
|
||||
const doAction = (
|
||||
args: any[],
|
||||
scope: any,
|
||||
sourceMethod: any,
|
||||
beforeMethodName: string,
|
||||
afterMethodName: string,
|
||||
fn: (args: any[], next?: Function | undefined) => Promise<void>,
|
||||
fn: (args: any[], next?: Function | undefined) => void,
|
||||
) => {
|
||||
try {
|
||||
let beforeArgs = args;
|
||||
|
||||
for (const beforeMethod of scope.pluginOptionsList[beforeMethodName]) {
|
||||
beforeArgs = beforeMethod(...beforeArgs) || [];
|
||||
|
||||
if (isError(beforeArgs)) throw beforeArgs;
|
||||
|
||||
if (!Array.isArray(beforeArgs)) {
|
||||
beforeArgs = [beforeArgs];
|
||||
}
|
||||
}
|
||||
|
||||
let returnValue: any = fn(beforeArgs, sourceMethod.bind(scope));
|
||||
|
||||
for (const afterMethod of scope.pluginOptionsList[afterMethodName]) {
|
||||
returnValue = afterMethod(returnValue, ...beforeArgs);
|
||||
|
||||
if (isError(returnValue)) throw returnValue;
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const doAsyncAction = async (
|
||||
args: any[],
|
||||
scope: any,
|
||||
sourceMethod: any,
|
||||
beforeMethodName: string,
|
||||
afterMethodName: string,
|
||||
fn: (args: any[], next?: Function | undefined) => Promise<void> | void,
|
||||
) => {
|
||||
try {
|
||||
let beforeArgs = args;
|
||||
@ -112,10 +147,10 @@ export default class extends EventEmitter {
|
||||
private taskList: (() => Promise<void>)[] = [];
|
||||
private doingTask = false;
|
||||
|
||||
constructor(methods: string[] = [], serialMethods: string[] = []) {
|
||||
constructor(methods: { name: string; isAsync: boolean }[] = [], serialMethods: string[] = []) {
|
||||
super();
|
||||
|
||||
methods.forEach((propertyName: string) => {
|
||||
methods.forEach(({ name: propertyName, isAsync }) => {
|
||||
const scope = this as any;
|
||||
|
||||
const sourceMethod = scope[propertyName];
|
||||
@ -127,32 +162,34 @@ export default class extends EventEmitter {
|
||||
this.pluginOptionsList[afterMethodName] = [];
|
||||
this.middleware[propertyName] = [];
|
||||
|
||||
const fn = compose(this.middleware[propertyName]);
|
||||
const fn = compose(this.middleware[propertyName], isAsync);
|
||||
Object.defineProperty(scope, propertyName, {
|
||||
value: async (...args: any[]) => {
|
||||
if (!serialMethods.includes(propertyName)) {
|
||||
return doAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn);
|
||||
}
|
||||
|
||||
// 由于async await,所以会出现函数执行到await时让出线程,导致执行顺序出错,例如调用了select(1) -> update -> select(2),这个时候就有可能出现update了2;
|
||||
// 这里保证函数调用严格按顺序执行;
|
||||
const promise = new Promise<any>((resolve, reject) => {
|
||||
this.taskList.push(async () => {
|
||||
try {
|
||||
const value = await doAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn);
|
||||
resolve(value);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
value: isAsync
|
||||
? async (...args: any[]) => {
|
||||
if (!serialMethods.includes(propertyName)) {
|
||||
return doAsyncAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (!this.doingTask) {
|
||||
this.doTask();
|
||||
}
|
||||
// 由于async await,所以会出现函数执行到await时让出线程,导致执行顺序出错,例如调用了select(1) -> update -> select(2),这个时候就有可能出现update了2;
|
||||
// 这里保证函数调用严格按顺序执行;
|
||||
const promise = new Promise<any>((resolve, reject) => {
|
||||
this.taskList.push(async () => {
|
||||
try {
|
||||
const value = await doAsyncAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn);
|
||||
resolve(value);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return promise;
|
||||
},
|
||||
if (!this.doingTask) {
|
||||
this.doTask();
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
: (...args: any[]) => doAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -38,7 +38,13 @@ class CodeBlock extends BaseService {
|
||||
});
|
||||
|
||||
constructor() {
|
||||
super(['setCodeDslById', 'setEditStatus', 'setCombineIds', 'setUndeleteableList', 'deleteCodeDslByIds']);
|
||||
super([
|
||||
{ name: 'setCodeDslById', isAsync: true },
|
||||
{ name: 'setEditStatus', isAsync: true },
|
||||
{ name: 'setCombineIds', isAsync: true },
|
||||
{ name: 'setUndeleteableList', isAsync: true },
|
||||
{ name: 'deleteCodeDslByIds', isAsync: true },
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -61,27 +61,27 @@ class Editor extends BaseService {
|
||||
constructor() {
|
||||
super(
|
||||
[
|
||||
'getLayout',
|
||||
'select',
|
||||
'doAdd',
|
||||
'add',
|
||||
'doRemove',
|
||||
'remove',
|
||||
'doUpdate',
|
||||
'update',
|
||||
'sort',
|
||||
'copy',
|
||||
'paste',
|
||||
'doPaste',
|
||||
'doAlignCenter',
|
||||
'alignCenter',
|
||||
'moveLayer',
|
||||
'moveToContainer',
|
||||
'move',
|
||||
'undo',
|
||||
'redo',
|
||||
'highlight',
|
||||
'dragTo',
|
||||
{ name: 'getLayout', isAsync: true },
|
||||
{ name: 'select', isAsync: true },
|
||||
{ name: 'doAdd', isAsync: true },
|
||||
{ name: 'add', isAsync: true },
|
||||
{ name: 'doRemove', isAsync: true },
|
||||
{ name: 'remove', isAsync: true },
|
||||
{ name: 'doUpdate', isAsync: true },
|
||||
{ name: 'update', isAsync: true },
|
||||
{ name: 'sort', isAsync: true },
|
||||
{ name: 'copy', isAsync: true },
|
||||
{ name: 'paste', isAsync: true },
|
||||
{ name: 'doPaste', isAsync: true },
|
||||
{ name: 'doAlignCenter', isAsync: true },
|
||||
{ name: 'alignCenter', isAsync: true },
|
||||
{ name: 'moveLayer', isAsync: true },
|
||||
{ name: 'moveToContainer', isAsync: true },
|
||||
{ name: 'move', isAsync: true },
|
||||
{ name: 'undo', isAsync: true },
|
||||
{ name: 'redo', isAsync: true },
|
||||
{ name: 'highlight', isAsync: true },
|
||||
{ name: 'dragTo', isAsync: true },
|
||||
],
|
||||
// 需要注意循环依赖问题,如果函数间有相互调用的话,不能设置为串行调用
|
||||
['select', 'update', 'moveLayer'],
|
||||
@ -597,8 +597,8 @@ class Editor extends BaseService {
|
||||
* @param config 组件节点配置
|
||||
* @returns 组件节点配置
|
||||
*/
|
||||
public async copy(config: MNode | MNode[]): Promise<void> {
|
||||
await storageService.setItem(COPY_STORAGE_KEY, Array.isArray(config) ? config : [config], {
|
||||
public copy(config: MNode | MNode[]): void {
|
||||
storageService.setItem(COPY_STORAGE_KEY, Array.isArray(config) ? config : [config], {
|
||||
protocol: Protocol.OBJECT,
|
||||
});
|
||||
}
|
||||
@ -609,7 +609,7 @@ class Editor extends BaseService {
|
||||
* @returns 添加后的组件节点配置
|
||||
*/
|
||||
public async paste(position: PastePosition = {}): Promise<MNode | MNode[] | void> {
|
||||
const config: MNode[] = await storageService.getItem(COPY_STORAGE_KEY);
|
||||
const config: MNode[] = storageService.getItem(COPY_STORAGE_KEY);
|
||||
|
||||
if (!Array.isArray(config)) return;
|
||||
|
||||
|
@ -36,14 +36,14 @@ class Props extends BaseService {
|
||||
|
||||
constructor() {
|
||||
super([
|
||||
'setPropsConfig',
|
||||
'getPropsConfig',
|
||||
'setPropsValue',
|
||||
'getPropsValue',
|
||||
'createId',
|
||||
'setNewItemId',
|
||||
'fillConfig',
|
||||
'getDefaultPropsValue',
|
||||
{ name: 'setPropsConfig', isAsync: true },
|
||||
{ name: 'getPropsConfig', isAsync: true },
|
||||
{ name: 'setPropsValue', isAsync: true },
|
||||
{ name: 'getPropsValue', isAsync: true },
|
||||
{ name: 'createId', isAsync: false },
|
||||
{ name: 'setNewItemId', isAsync: true },
|
||||
{ name: 'fillConfig', isAsync: true },
|
||||
{ name: 'getDefaultPropsValue', isAsync: true },
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,14 @@ export class WebStorage extends BaseService {
|
||||
private namespace = 'tmagic';
|
||||
|
||||
constructor() {
|
||||
super(['getStorage', 'getNamespace', 'clear', 'getItem', 'removeItem', 'setItem']);
|
||||
super([
|
||||
{ name: 'getStorage', isAsync: false },
|
||||
{ name: 'getNamespace', isAsync: false },
|
||||
{ name: 'clear', isAsync: false },
|
||||
{ name: 'getItem', isAsync: false },
|
||||
{ name: 'removeItem', isAsync: false },
|
||||
{ name: 'setItem', isAsync: false },
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -38,26 +45,27 @@ export class WebStorage extends BaseService {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
public async getStorage(): Promise<Storage> {
|
||||
public getStorage(): Storage {
|
||||
return this.storage;
|
||||
}
|
||||
|
||||
public async getNamespace(): Promise<string> {
|
||||
public getNamespace(): string {
|
||||
return this.namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理,支持storageService.usePlugin
|
||||
*/
|
||||
public async clear(): Promise<void> {
|
||||
const storage = await this.getStorage();
|
||||
public clear(): void {
|
||||
const storage = this.getStorage();
|
||||
storage.clear();
|
||||
}
|
||||
/**
|
||||
* 获取存储项,支持storageService.usePlugin
|
||||
*/
|
||||
public async getItem(key: string, options: Options = {}): Promise<any> {
|
||||
const [storage, namespace] = await Promise.all([this.getStorage(), this.getNamespace()]);
|
||||
public getItem(key: string, options: Options = {}): any {
|
||||
const storage = this.getStorage();
|
||||
const namespace = this.getNamespace();
|
||||
const { protocol = options.protocol, item } = this.getValueAndProtocol(
|
||||
storage.getItem(`${options.namespace || namespace}:${key}`),
|
||||
);
|
||||
@ -80,24 +88,26 @@ export class WebStorage extends BaseService {
|
||||
/**
|
||||
* 获取指定索引位置的key
|
||||
*/
|
||||
public async key(index: number): Promise<string | null> {
|
||||
const storage = await this.getStorage();
|
||||
public key(index: number): string | null {
|
||||
const storage = this.getStorage();
|
||||
return storage.key(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除存储项,支持storageService.usePlugin
|
||||
*/
|
||||
public async removeItem(key: string, options: Options = {}): Promise<void> {
|
||||
const [storage, namespace] = await Promise.all([this.getStorage(), this.getNamespace()]);
|
||||
public removeItem(key: string, options: Options = {}): void {
|
||||
const storage = this.getStorage();
|
||||
const namespace = this.getNamespace();
|
||||
storage.removeItem(`${options.namespace || namespace}:${key}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置存储项,支持storageService.usePlugin
|
||||
*/
|
||||
public async setItem(key: string, value: any, options: Options = {}): Promise<void> {
|
||||
const [storage, namespace] = await Promise.all([this.getStorage(), this.getNamespace()]);
|
||||
public setItem(key: string, value: any, options: Options = {}): void {
|
||||
const storage = this.getStorage();
|
||||
const namespace = this.getNamespace();
|
||||
let item = value;
|
||||
const protocol = options.protocol ? `${options.protocol}:` : '';
|
||||
if (typeof value === Protocol.STRING || typeof value === Protocol.NUMBER) {
|
||||
|
@ -52,7 +52,10 @@ const state = reactive<UiState>({
|
||||
|
||||
class Ui extends BaseService {
|
||||
constructor() {
|
||||
super(['zoom', 'calcZoom']);
|
||||
super([
|
||||
{ name: 'zoom', isAsync: true },
|
||||
{ name: 'calcZoom', isAsync: true },
|
||||
]);
|
||||
}
|
||||
|
||||
public set<K extends keyof UiState, T extends UiState[K]>(name: K, value: T) {
|
||||
|
@ -2,7 +2,7 @@
|
||||
* @param {Array} middleware
|
||||
* @return {Function}
|
||||
*/
|
||||
export const compose = (middleware: Function[]) => {
|
||||
export const compose = (middleware: Function[], isAsync: boolean) => {
|
||||
if (!Array.isArray(middleware)) throw new TypeError('Middleware 必须是一个数组!');
|
||||
for (const fn of middleware) {
|
||||
if (typeof fn !== 'function') throw new TypeError('Middleware 必须由函数组成!');
|
||||
@ -17,16 +17,35 @@ export const compose = (middleware: Function[]) => {
|
||||
// last called middleware #
|
||||
let index = -1;
|
||||
return dispatch(0);
|
||||
function dispatch(i: number): Promise<void> {
|
||||
if (i <= index) return Promise.reject(new Error('next() 被多次调用'));
|
||||
function dispatch(i: number): Promise<void> | void {
|
||||
if (i <= index) {
|
||||
const error = new Error('next() 被多次调用');
|
||||
if (isAsync) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
index = i;
|
||||
let fn = middleware[i];
|
||||
if (i === middleware.length && next) fn = next;
|
||||
if (!fn) return Promise.resolve();
|
||||
if (!fn) {
|
||||
if (isAsync) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAsync) {
|
||||
try {
|
||||
return Promise.resolve(fn(...args, dispatch.bind(null, i + 1)));
|
||||
} catch (err) {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
try {
|
||||
return Promise.resolve(fn(...args, dispatch.bind(null, i + 1)));
|
||||
return fn(...args, dispatch.bind(null, i + 1));
|
||||
} catch (err) {
|
||||
return Promise.reject(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -365,7 +365,7 @@ describe('copy', () => {
|
||||
test('正常', async () => {
|
||||
const node = editorService.getNodeById(NodeId.NODE_ID2);
|
||||
await editorService.copy(node!);
|
||||
const str = await storageService.getItem(COPY_STORAGE_KEY);
|
||||
const str = storageService.getItem(COPY_STORAGE_KEY);
|
||||
expect(str).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user