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 },
);
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;
};

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 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),
});
});
}

View File

@ -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 },
]);
}
/**

View File

@ -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;

View File

@ -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 },
]);
}

View File

@ -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) {

View File

@ -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) {

View File

@ -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;
}
}
};

View File

@ -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);
});
});