Skip to content

Commit

Permalink
feat(editor): service扩展支持设置成同步的
Browse files Browse the repository at this point in the history
  • Loading branch information
roymondchen committed Dec 6, 2023
1 parent 75dd89f commit 5c6a345
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 82 deletions.
4 changes: 2 additions & 2 deletions packages/editor/src/layouts/workspace/viewer/ViewerMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down
89 changes: 63 additions & 26 deletions packages/editor/src/services/BaseService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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];
Expand All @@ -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);
}
});
});

if (!this.doingTask) {
this.doTask();
}

return promise;
},
return promise;
}
: (...args: any[]) => doAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn),
});
});
}
Expand Down
8 changes: 7 additions & 1 deletion packages/editor/src/services/codeBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
]);
}

/**
Expand Down
48 changes: 24 additions & 24 deletions packages/editor/src/services/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand Down Expand Up @@ -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,
});
}
Expand All @@ -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;

Expand Down
16 changes: 8 additions & 8 deletions packages/editor/src/services/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
]);
}

Expand Down
36 changes: 23 additions & 13 deletions packages/editor/src/services/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
]);
}

/**
Expand All @@ -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}`),
);
Expand All @@ -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) {
Expand Down
5 changes: 4 additions & 1 deletion packages/editor/src/services/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
31 changes: 25 additions & 6 deletions packages/editor/src/utils/compose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 必须由函数组成!');
Expand All @@ -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;
}
}
};
Expand Down
2 changes: 1 addition & 1 deletion packages/editor/tests/unit/services/editor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
Expand Down

0 comments on commit 5c6a345

Please sign in to comment.