feat(editor): service扩展支持设置成同步的

This commit is contained in:
roymondchen 2023-12-06 15:28:18 +08:00
parent 75dd89f2fe
commit 5c6a3455b0
9 changed files with 157 additions and 82 deletions

View File

@ -127,9 +127,9 @@ watch(
{ immediate: true }, { immediate: true },
); );
const show = async (e: MouseEvent) => { const show = (e: MouseEvent) => {
menu.value?.show(e); menu.value?.show(e);
const data = await storageService.getItem(COPY_STORAGE_KEY); const data = storageService.getItem(COPY_STORAGE_KEY);
canPaste.value = data !== 'undefined' && !!data; canPaste.value = data !== 'undefined' && !!data;
}; };

View File

@ -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 isError = (error: any): boolean => Object.prototype.toString.call(error) === '[object Error]';
const doAction = async ( const doAction = (
args: any[], args: any[],
scope: any, scope: any,
sourceMethod: any, sourceMethod: any,
beforeMethodName: string, beforeMethodName: string,
afterMethodName: 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 { try {
let beforeArgs = args; let beforeArgs = args;
@ -112,10 +147,10 @@ export default class extends EventEmitter {
private taskList: (() => Promise<void>)[] = []; private taskList: (() => Promise<void>)[] = [];
private doingTask = false; private doingTask = false;
constructor(methods: string[] = [], serialMethods: string[] = []) { constructor(methods: { name: string; isAsync: boolean }[] = [], serialMethods: string[] = []) {
super(); super();
methods.forEach((propertyName: string) => { methods.forEach(({ name: propertyName, isAsync }) => {
const scope = this as any; const scope = this as any;
const sourceMethod = scope[propertyName]; const sourceMethod = scope[propertyName];
@ -127,32 +162,34 @@ export default class extends EventEmitter {
this.pluginOptionsList[afterMethodName] = []; this.pluginOptionsList[afterMethodName] = [];
this.middleware[propertyName] = []; this.middleware[propertyName] = [];
const fn = compose(this.middleware[propertyName]); const fn = compose(this.middleware[propertyName], isAsync);
Object.defineProperty(scope, propertyName, { Object.defineProperty(scope, propertyName, {
value: async (...args: any[]) => { value: isAsync
if (!serialMethods.includes(propertyName)) { ? async (...args: any[]) => {
return doAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn); if (!serialMethods.includes(propertyName)) {
} return doAsyncAction(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);
} }
});
});
if (!this.doingTask) { // 由于async await所以会出现函数执行到await时让出线程导致执行顺序出错例如调用了select(1) -> update -> select(2)这个时候就有可能出现update了2
this.doTask(); // 这里保证函数调用严格按顺序执行;
} 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),
}); });
}); });
} }

View File

@ -38,7 +38,13 @@ class CodeBlock extends BaseService {
}); });
constructor() { 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 },
]);
} }
/** /**

View File

@ -61,27 +61,27 @@ class Editor extends BaseService {
constructor() { constructor() {
super( super(
[ [
'getLayout', { name: 'getLayout', isAsync: true },
'select', { name: 'select', isAsync: true },
'doAdd', { name: 'doAdd', isAsync: true },
'add', { name: 'add', isAsync: true },
'doRemove', { name: 'doRemove', isAsync: true },
'remove', { name: 'remove', isAsync: true },
'doUpdate', { name: 'doUpdate', isAsync: true },
'update', { name: 'update', isAsync: true },
'sort', { name: 'sort', isAsync: true },
'copy', { name: 'copy', isAsync: true },
'paste', { name: 'paste', isAsync: true },
'doPaste', { name: 'doPaste', isAsync: true },
'doAlignCenter', { name: 'doAlignCenter', isAsync: true },
'alignCenter', { name: 'alignCenter', isAsync: true },
'moveLayer', { name: 'moveLayer', isAsync: true },
'moveToContainer', { name: 'moveToContainer', isAsync: true },
'move', { name: 'move', isAsync: true },
'undo', { name: 'undo', isAsync: true },
'redo', { name: 'redo', isAsync: true },
'highlight', { name: 'highlight', isAsync: true },
'dragTo', { name: 'dragTo', isAsync: true },
], ],
// 需要注意循环依赖问题,如果函数间有相互调用的话,不能设置为串行调用 // 需要注意循环依赖问题,如果函数间有相互调用的话,不能设置为串行调用
['select', 'update', 'moveLayer'], ['select', 'update', 'moveLayer'],
@ -597,8 +597,8 @@ class Editor extends BaseService {
* @param config * @param config
* @returns * @returns
*/ */
public async copy(config: MNode | MNode[]): Promise<void> { public copy(config: MNode | MNode[]): void {
await storageService.setItem(COPY_STORAGE_KEY, Array.isArray(config) ? config : [config], { storageService.setItem(COPY_STORAGE_KEY, Array.isArray(config) ? config : [config], {
protocol: Protocol.OBJECT, protocol: Protocol.OBJECT,
}); });
} }
@ -609,7 +609,7 @@ class Editor extends BaseService {
* @returns * @returns
*/ */
public async paste(position: PastePosition = {}): Promise<MNode | MNode[] | void> { 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; if (!Array.isArray(config)) return;

View File

@ -36,14 +36,14 @@ class Props extends BaseService {
constructor() { constructor() {
super([ super([
'setPropsConfig', { name: 'setPropsConfig', isAsync: true },
'getPropsConfig', { name: 'getPropsConfig', isAsync: true },
'setPropsValue', { name: 'setPropsValue', isAsync: true },
'getPropsValue', { name: 'getPropsValue', isAsync: true },
'createId', { name: 'createId', isAsync: false },
'setNewItemId', { name: 'setNewItemId', isAsync: true },
'fillConfig', { name: 'fillConfig', isAsync: true },
'getDefaultPropsValue', { name: 'getDefaultPropsValue', isAsync: true },
]); ]);
} }

View File

@ -25,7 +25,14 @@ export class WebStorage extends BaseService {
private namespace = 'tmagic'; private namespace = 'tmagic';
constructor() { 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; return this.storage;
} }
public async getNamespace(): Promise<string> { public getNamespace(): string {
return this.namespace; return this.namespace;
} }
/** /**
* storageService.usePlugin * storageService.usePlugin
*/ */
public async clear(): Promise<void> { public clear(): void {
const storage = await this.getStorage(); const storage = this.getStorage();
storage.clear(); storage.clear();
} }
/** /**
* storageService.usePlugin * storageService.usePlugin
*/ */
public async getItem(key: string, options: Options = {}): Promise<any> { public getItem(key: string, options: Options = {}): any {
const [storage, namespace] = await Promise.all([this.getStorage(), this.getNamespace()]); const storage = this.getStorage();
const namespace = this.getNamespace();
const { protocol = options.protocol, item } = this.getValueAndProtocol( const { protocol = options.protocol, item } = this.getValueAndProtocol(
storage.getItem(`${options.namespace || namespace}:${key}`), storage.getItem(`${options.namespace || namespace}:${key}`),
); );
@ -80,24 +88,26 @@ export class WebStorage extends BaseService {
/** /**
* key * key
*/ */
public async key(index: number): Promise<string | null> { public key(index: number): string | null {
const storage = await this.getStorage(); const storage = this.getStorage();
return storage.key(index); return storage.key(index);
} }
/** /**
* storageService.usePlugin * storageService.usePlugin
*/ */
public async removeItem(key: string, options: Options = {}): Promise<void> { public removeItem(key: string, options: Options = {}): void {
const [storage, namespace] = await Promise.all([this.getStorage(), this.getNamespace()]); const storage = this.getStorage();
const namespace = this.getNamespace();
storage.removeItem(`${options.namespace || namespace}:${key}`); storage.removeItem(`${options.namespace || namespace}:${key}`);
} }
/** /**
* storageService.usePlugin * storageService.usePlugin
*/ */
public async setItem(key: string, value: any, options: Options = {}): Promise<void> { public setItem(key: string, value: any, options: Options = {}): void {
const [storage, namespace] = await Promise.all([this.getStorage(), this.getNamespace()]); const storage = this.getStorage();
const namespace = this.getNamespace();
let item = value; let item = value;
const protocol = options.protocol ? `${options.protocol}:` : ''; const protocol = options.protocol ? `${options.protocol}:` : '';
if (typeof value === Protocol.STRING || typeof value === Protocol.NUMBER) { if (typeof value === Protocol.STRING || typeof value === Protocol.NUMBER) {

View File

@ -52,7 +52,10 @@ const state = reactive<UiState>({
class Ui extends BaseService { class Ui extends BaseService {
constructor() { 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) { public set<K extends keyof UiState, T extends UiState[K]>(name: K, value: T) {

View File

@ -2,7 +2,7 @@
* @param {Array} middleware * @param {Array} middleware
* @return {Function} * @return {Function}
*/ */
export const compose = (middleware: Function[]) => { export const compose = (middleware: Function[], isAsync: boolean) => {
if (!Array.isArray(middleware)) throw new TypeError('Middleware 必须是一个数组!'); if (!Array.isArray(middleware)) throw new TypeError('Middleware 必须是一个数组!');
for (const fn of middleware) { for (const fn of middleware) {
if (typeof fn !== 'function') throw new TypeError('Middleware 必须由函数组成!'); if (typeof fn !== 'function') throw new TypeError('Middleware 必须由函数组成!');
@ -17,16 +17,35 @@ export const compose = (middleware: Function[]) => {
// last called middleware # // last called middleware #
let index = -1; let index = -1;
return dispatch(0); return dispatch(0);
function dispatch(i: number): Promise<void> { function dispatch(i: number): Promise<void> | void {
if (i <= index) return Promise.reject(new Error('next() 被多次调用')); if (i <= index) {
const error = new Error('next() 被多次调用');
if (isAsync) {
return Promise.reject(error);
}
throw error;
}
index = i; index = i;
let fn = middleware[i]; let fn = middleware[i];
if (i === middleware.length && next) fn = next; 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 { try {
return Promise.resolve(fn(...args, dispatch.bind(null, i + 1))); return fn(...args, dispatch.bind(null, i + 1));
} catch (err) { } catch (err) {
return Promise.reject(err); throw err;
} }
} }
}; };

View File

@ -365,7 +365,7 @@ describe('copy', () => {
test('正常', async () => { test('正常', async () => {
const node = editorService.getNodeById(NodeId.NODE_ID2); const node = editorService.getNodeById(NodeId.NODE_ID2);
await editorService.copy(node!); await editorService.copy(node!);
const str = await storageService.getItem(COPY_STORAGE_KEY); const str = storageService.getItem(COPY_STORAGE_KEY);
expect(str).toHaveLength(1); expect(str).toHaveLength(1);
}); });
}); });