@flowgram-vue/command 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/dist/index.cjs +278 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +217 -0
- package/dist/index.js +273 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
- package/src/command-container-module.ts +17 -0
- package/src/command-service.ts +29 -0
- package/src/command.ts +387 -0
- package/src/index.ts +14 -0
package/src/command.ts
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { injectable, multiInject, optional } from 'inversify';
|
|
7
|
+
import { Disposable, DisposableCollection, Emitter } from '@flowgram-vue/utils';
|
|
8
|
+
|
|
9
|
+
import { type CommandService } from './command-service';
|
|
10
|
+
|
|
11
|
+
export interface Command {
|
|
12
|
+
/**
|
|
13
|
+
* id,唯一 key
|
|
14
|
+
*/
|
|
15
|
+
id: string;
|
|
16
|
+
/**
|
|
17
|
+
* 展示用 label
|
|
18
|
+
*/
|
|
19
|
+
label?: string;
|
|
20
|
+
/**
|
|
21
|
+
* 在一些明确的场景下,部分只展示简短的 label 即可
|
|
22
|
+
*/
|
|
23
|
+
shortLabel?: string;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 展示用 command icon
|
|
27
|
+
*/
|
|
28
|
+
icon?: string | object | ((props: any) => any);
|
|
29
|
+
/**
|
|
30
|
+
* 暂不使用
|
|
31
|
+
*/
|
|
32
|
+
category?: string;
|
|
33
|
+
}
|
|
34
|
+
export namespace Command {
|
|
35
|
+
export enum Default {
|
|
36
|
+
ZOOM_IN = 'ZOOM_IN',
|
|
37
|
+
ZOOM_OUT = 'ZOOM_OUT',
|
|
38
|
+
DELETE = 'DELETE',
|
|
39
|
+
COPY = 'COPY',
|
|
40
|
+
PASTE = 'PASTE',
|
|
41
|
+
UNDO = 'UNDO',
|
|
42
|
+
REDO = 'REDO',
|
|
43
|
+
VIEW_CLOSE_ALL_WIDGET = 'view.closeAllWidget',
|
|
44
|
+
VIEW_CLOSE_CURRENT_WIDGET = 'view.closeCurrentWidget',
|
|
45
|
+
VIEW_REOPEN_LAST_WIDGET = 'view.reopenLastWidget',
|
|
46
|
+
VIEW_CLOSE_OTHER_WIDGET = 'view.closeOtherWidget',
|
|
47
|
+
VIEW_CLOSE_BOTTOM_PANEL = 'view.closeBottomPannel',
|
|
48
|
+
VIEW_OPEN_NEXT_TAB = 'view.openNextTab',
|
|
49
|
+
VIEW_OEPN_LAST_TAB = 'view.openLastTab',
|
|
50
|
+
VIEW_FULL_SCREEN = 'view.fullScreen',
|
|
51
|
+
VIEW_SAVING_WIDGET_CLOSE_CONFIRM = 'view.savingWidgetCloseConfirm',
|
|
52
|
+
VIEW_SHORTCUTS = 'view.shortcuts',
|
|
53
|
+
VIEW_PREFERENCES = 'view.preferences',
|
|
54
|
+
VIEW_LOG = 'view.log',
|
|
55
|
+
VIEW_PROBLEMS = 'view.problems',
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 判断是否是 command
|
|
60
|
+
*/
|
|
61
|
+
export function is(arg: Command | any): arg is Command {
|
|
62
|
+
return !!arg && arg === Object(arg) && 'id' in arg;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface CommandHandler {
|
|
67
|
+
/**
|
|
68
|
+
* handler 执行函数
|
|
69
|
+
*/
|
|
70
|
+
execute(...args: any[]): any;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 该 handler 是否可以执行
|
|
74
|
+
*/
|
|
75
|
+
isEnabled?(...args: any[]): boolean;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 预留 contextMenu 用,该 handler 是否可见
|
|
79
|
+
*/
|
|
80
|
+
isVisible?(...args: any[]): boolean;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 预留 contextMenu 用,该 handler 是否可以触发
|
|
84
|
+
*/
|
|
85
|
+
isToggled?(...args: any[]): boolean;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface CommandEvent {
|
|
89
|
+
/**
|
|
90
|
+
* commandId
|
|
91
|
+
*/
|
|
92
|
+
commandId: string;
|
|
93
|
+
/**
|
|
94
|
+
* 参数
|
|
95
|
+
*/
|
|
96
|
+
args: any[];
|
|
97
|
+
}
|
|
98
|
+
export const CommandContribution = Symbol('CommandContribution');
|
|
99
|
+
|
|
100
|
+
export interface CommandContribution {
|
|
101
|
+
/**
|
|
102
|
+
* 注册 command
|
|
103
|
+
*/
|
|
104
|
+
registerCommands(commands: CommandService): void;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 当前正在运行的 command
|
|
109
|
+
*/
|
|
110
|
+
interface CommandExecuting {
|
|
111
|
+
/**
|
|
112
|
+
* commandid
|
|
113
|
+
*/
|
|
114
|
+
id: string;
|
|
115
|
+
/**
|
|
116
|
+
* 参数
|
|
117
|
+
*/
|
|
118
|
+
args: any[];
|
|
119
|
+
/**
|
|
120
|
+
* 正在进行的 promise
|
|
121
|
+
*/
|
|
122
|
+
promise?: Promise<any>;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
namespace CommandExecuting {
|
|
126
|
+
/**
|
|
127
|
+
* 获取正在运行的 command 单个实例
|
|
128
|
+
*/
|
|
129
|
+
export function findSimple(
|
|
130
|
+
arrs: Set<CommandExecuting>,
|
|
131
|
+
newCmd: CommandExecuting,
|
|
132
|
+
): CommandExecuting | undefined {
|
|
133
|
+
for (const item of arrs.values()) {
|
|
134
|
+
if (
|
|
135
|
+
item.id === newCmd.id &&
|
|
136
|
+
item.args.length === newCmd.args.length &&
|
|
137
|
+
item.args.every((arg, index) => (newCmd as any)[index] === arg)
|
|
138
|
+
) {
|
|
139
|
+
return item;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export const CommandRegistryFactory = 'CommandRegistryFactory';
|
|
146
|
+
|
|
147
|
+
@injectable()
|
|
148
|
+
export class CommandRegistry implements CommandService {
|
|
149
|
+
protected readonly _handlers: { [id: string]: CommandHandler[] } = {};
|
|
150
|
+
|
|
151
|
+
protected readonly _commands: { [id: string]: Command } = {};
|
|
152
|
+
|
|
153
|
+
protected readonly _commandExecutings = new Set<CommandExecuting>();
|
|
154
|
+
|
|
155
|
+
protected readonly toUnregisterCommands = new Map<string, Disposable>();
|
|
156
|
+
|
|
157
|
+
protected readonly onDidExecuteCommandEmitter = new Emitter<CommandEvent>();
|
|
158
|
+
|
|
159
|
+
readonly onDidExecuteCommand = this.onDidExecuteCommandEmitter.event;
|
|
160
|
+
|
|
161
|
+
protected readonly onWillExecuteCommandEmitter = new Emitter<CommandEvent>();
|
|
162
|
+
|
|
163
|
+
readonly onWillExecuteCommand = this.onWillExecuteCommandEmitter.event;
|
|
164
|
+
|
|
165
|
+
@multiInject(CommandContribution)
|
|
166
|
+
@optional()
|
|
167
|
+
protected readonly contributions: CommandContribution[];
|
|
168
|
+
|
|
169
|
+
init() {
|
|
170
|
+
for (const contrib of this.contributions) {
|
|
171
|
+
contrib.registerCommands(this);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* 当前所有 command
|
|
177
|
+
*/
|
|
178
|
+
get commands(): Command[] {
|
|
179
|
+
const commands: Command[] = [];
|
|
180
|
+
for (const id of this.commandIds) {
|
|
181
|
+
const cmd = this.getCommand(id);
|
|
182
|
+
if (cmd) {
|
|
183
|
+
commands.push(cmd);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return commands;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 当前所有 commandid
|
|
191
|
+
*/
|
|
192
|
+
get commandIds(): string[] {
|
|
193
|
+
return Object.keys(this._commands);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* registerCommand
|
|
198
|
+
*/
|
|
199
|
+
registerCommand(id: string, handler?: CommandHandler): Disposable;
|
|
200
|
+
|
|
201
|
+
registerCommand(command: Command, handler?: CommandHandler): Disposable;
|
|
202
|
+
|
|
203
|
+
registerCommand(commandOrId: string | Command, handler?: CommandHandler): Disposable {
|
|
204
|
+
const command: Command = typeof commandOrId === 'string' ? { id: commandOrId } : commandOrId;
|
|
205
|
+
|
|
206
|
+
if (this._commands[command.id]) {
|
|
207
|
+
console.warn(`A command ${command.id} is already registered.`);
|
|
208
|
+
return Disposable.NULL;
|
|
209
|
+
}
|
|
210
|
+
const toDispose = new DisposableCollection(this.doRegisterCommand(command));
|
|
211
|
+
if (handler) {
|
|
212
|
+
toDispose.push(this.registerHandler(command.id, handler));
|
|
213
|
+
}
|
|
214
|
+
this.toUnregisterCommands.set(command.id, toDispose);
|
|
215
|
+
toDispose.push(Disposable.create(() => this.toUnregisterCommands.delete(command.id)));
|
|
216
|
+
return toDispose;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* unregisterCommand
|
|
221
|
+
*/
|
|
222
|
+
unregisterCommand(command: Command): void;
|
|
223
|
+
|
|
224
|
+
unregisterCommand(id: string): void;
|
|
225
|
+
|
|
226
|
+
unregisterCommand(commandOrId: Command | string): void {
|
|
227
|
+
const id = Command.is(commandOrId) ? commandOrId.id : commandOrId;
|
|
228
|
+
const toUnregister = this.toUnregisterCommands.get(id);
|
|
229
|
+
if (toUnregister) {
|
|
230
|
+
toUnregister.dispose();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* 注册 handler
|
|
236
|
+
*/
|
|
237
|
+
registerHandler(commandId: string, handler: CommandHandler): Disposable {
|
|
238
|
+
let handlers = this._handlers[commandId];
|
|
239
|
+
if (!handlers) {
|
|
240
|
+
this._handlers[commandId] = handlers = [];
|
|
241
|
+
}
|
|
242
|
+
handlers.unshift(handler);
|
|
243
|
+
return {
|
|
244
|
+
dispose: () => {
|
|
245
|
+
const idx = handlers.indexOf(handler);
|
|
246
|
+
if (idx >= 0) {
|
|
247
|
+
handlers.splice(idx, 1);
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* 预留 contextMenu 用,该 handler 是否可见
|
|
255
|
+
*/
|
|
256
|
+
isVisible(command: string, ...args: any[]): boolean {
|
|
257
|
+
return typeof this.getVisibleHandler(command, ...args) !== 'undefined';
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* command 是否可用
|
|
262
|
+
*/
|
|
263
|
+
isEnabled(command: string, ...args: any[]): boolean {
|
|
264
|
+
return typeof this.getActiveHandler(command, ...args) !== 'undefined';
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* 预留 contextMenu 用,该 handler 是否可以触发
|
|
269
|
+
*/
|
|
270
|
+
isToggled(command: string, ...args: any[]): boolean {
|
|
271
|
+
return typeof this.getToggledHandler(command, ...args) !== 'undefined';
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* 执行 command,会先判断是否可以执行,不会重复执行
|
|
276
|
+
*/
|
|
277
|
+
async executeCommand<T>(commandId: string, ...args: any[]): Promise<T | undefined> {
|
|
278
|
+
const handler = this.getActiveHandler(commandId, ...args);
|
|
279
|
+
const execInfo: CommandExecuting = { id: commandId, args };
|
|
280
|
+
const simpleExecInfo = CommandExecuting.findSimple(this._commandExecutings, execInfo);
|
|
281
|
+
if (simpleExecInfo) {
|
|
282
|
+
return execInfo.promise;
|
|
283
|
+
}
|
|
284
|
+
if (handler) {
|
|
285
|
+
try {
|
|
286
|
+
this._commandExecutings.add(execInfo);
|
|
287
|
+
this.onWillExecuteCommandEmitter.fire({ commandId, args });
|
|
288
|
+
const promise = handler.execute(...args);
|
|
289
|
+
execInfo.promise = promise;
|
|
290
|
+
const result = await promise;
|
|
291
|
+
this.onDidExecuteCommandEmitter.fire({ commandId, args });
|
|
292
|
+
return result;
|
|
293
|
+
} finally {
|
|
294
|
+
this._commandExecutings.delete(execInfo);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
getVisibleHandler(commandId: string, ...args: any[]): CommandHandler | undefined {
|
|
300
|
+
const handlers = this._handlers[commandId];
|
|
301
|
+
if (handlers) {
|
|
302
|
+
for (const handler of handlers) {
|
|
303
|
+
try {
|
|
304
|
+
if (!handler.isVisible || handler.isVisible(...args)) {
|
|
305
|
+
return handler;
|
|
306
|
+
}
|
|
307
|
+
} catch (error) {
|
|
308
|
+
console.error(error);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return undefined;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
getActiveHandler(commandId: string, ...args: any[]): CommandHandler | undefined {
|
|
316
|
+
const handlers = this._handlers[commandId];
|
|
317
|
+
if (handlers) {
|
|
318
|
+
for (const handler of handlers) {
|
|
319
|
+
try {
|
|
320
|
+
if (!handler.isEnabled || handler.isEnabled(...args)) {
|
|
321
|
+
return handler;
|
|
322
|
+
}
|
|
323
|
+
} catch (error) {
|
|
324
|
+
console.error(error);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return undefined;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* 获取 command 对应的所有 handler
|
|
333
|
+
*/
|
|
334
|
+
getAllHandlers(commandId: string): CommandHandler[] {
|
|
335
|
+
const handlers = this._handlers[commandId];
|
|
336
|
+
return handlers ? handlers.slice() : [];
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
getToggledHandler(commandId: string, ...args: any[]): CommandHandler | undefined {
|
|
340
|
+
const handlers = this._handlers[commandId];
|
|
341
|
+
if (handlers) {
|
|
342
|
+
for (const handler of handlers) {
|
|
343
|
+
try {
|
|
344
|
+
if (handler.isToggled && handler.isToggled(...args)) {
|
|
345
|
+
return handler;
|
|
346
|
+
}
|
|
347
|
+
} catch (error) {
|
|
348
|
+
console.error(error);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return undefined;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* 获取 command
|
|
357
|
+
*/
|
|
358
|
+
getCommand(id: string): Command | undefined {
|
|
359
|
+
return this._commands[id];
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
protected doRegisterCommand(command: Command): Disposable {
|
|
363
|
+
this._commands[command.id] = command;
|
|
364
|
+
return {
|
|
365
|
+
dispose: () => {
|
|
366
|
+
delete this._commands[command.id];
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* 更新 command
|
|
373
|
+
*/
|
|
374
|
+
public updateCommand(id: string, command: Partial<Omit<Command, 'id'>>) {
|
|
375
|
+
if (this._commands[id]) {
|
|
376
|
+
this._commands[id] = {
|
|
377
|
+
...this._commands[id],
|
|
378
|
+
...command,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
dispose() {
|
|
384
|
+
this.onWillExecuteCommandEmitter.dispose();
|
|
385
|
+
this.onDidExecuteCommandEmitter.dispose();
|
|
386
|
+
}
|
|
387
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
CommandContribution,
|
|
8
|
+
type CommandHandler,
|
|
9
|
+
CommandRegistry,
|
|
10
|
+
Command,
|
|
11
|
+
CommandRegistryFactory,
|
|
12
|
+
} from './command';
|
|
13
|
+
export { CommandService } from './command-service';
|
|
14
|
+
export { CommandContainerModule } from './command-container-module';
|