-
Notifications
You must be signed in to change notification settings - Fork 29
/
feedback-action-dispatcher.ts
157 lines (140 loc) · 6.54 KB
/
feedback-action-dispatcher.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/********************************************************************************
* Copyright (c) 2019-2024 EclipseSource and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import {
Action,
ActionHandlerRegistry,
Command,
CommandActionHandler,
CommandExecutionContext,
Disposable,
IActionDispatcher,
ICommand,
ILogger,
TYPES,
toTypeGuard
} from '@eclipse-glsp/sprotty';
import { inject, injectable } from 'inversify';
import { getFeedbackRank } from './feedback-command';
export interface IFeedbackEmitter {}
/**
* Dispatcher for actions that are meant to show visual feedback on
* the diagram that is not part of the diagram sent from the server
* after a model update.
*
* The purpose of this dispatcher is to re-establish the feedback
* after the model has been updated or reset by the server, as this would
* overwrite the already established feedback, in case it is drawn by
* extending the `GModelRoot`. Therefore, tools can register themselves
* as feedback emitters with actions they want to place for showing
* feedback. This dispatcher will then re-establish all feedback actions
* of the registered emitters, whenever the `GModelRoot` has been set or updated.
*/
export interface IFeedbackActionDispatcher {
/**
* Registers `actions` to be sent out by a `feedbackEmitter`.
* @param feedbackEmitter the emitter sending out feedback actions.
* @param feedbackActions the actions to be sent out.
* @param cleanupActions the actions to be sent out when the feedback is de-registered through the returned Disposable.
* @returns A 'Disposable' that de-registers the feedback and cleans up any pending feedback with the given `cleanupActions`.
*/
registerFeedback(feedbackEmitter: IFeedbackEmitter, feedbackActions: Action[], cleanupActions?: Action[]): Disposable;
/**
* Deregisters a `feedbackEmitter` from this dispatcher and thereafter
* dispatches the provided `actions`.
* @param feedbackEmitter the emitter to be deregistered.
* @param cleanupActions the actions to be dispatched right after the deregistration.
* These actions do not have to be related to the actions sent out by the
* deregistered `feedbackEmitter`. The purpose of these actions typically is
* to reset the normal state of the diagram without the feedback (e.g., reset a
* CSS class that was set by a feedbackEmitter).
*/
deregisterFeedback(feedbackEmitter: IFeedbackEmitter, cleanupActions?: Action[]): void;
/**
* Retrieve all `actions` sent out by currently registered `feedbackEmitter`.
*/
getRegisteredFeedback(): Action[];
/**
* Retrieves all commands based on the registered feedback actions, ordered by their rank (lowest rank first).
*/
getFeedbackCommands(): Command[];
/**
* Applies all current feedback commands to the given command execution context.
*/
applyFeedbackCommands(context: CommandExecutionContext): Promise<void>;
}
@injectable()
export class FeedbackActionDispatcher implements IFeedbackActionDispatcher {
protected registeredFeedback: Map<IFeedbackEmitter, Action[]> = new Map();
@inject(TYPES.IActionDispatcherProvider) protected actionDispatcher: () => Promise<IActionDispatcher>;
@inject(TYPES.ILogger) protected logger: ILogger;
@inject(ActionHandlerRegistry) protected actionHandlerRegistry: ActionHandlerRegistry;
registerFeedback(feedbackEmitter: IFeedbackEmitter, feedbackActions: Action[], cleanupActions?: Action[] | undefined): Disposable {
if (feedbackActions.length > 0) {
this.registeredFeedback.set(feedbackEmitter, feedbackActions);
this.dispatchFeedback(feedbackActions, feedbackEmitter);
}
return Disposable.create(() => this.deregisterFeedback(feedbackEmitter, cleanupActions));
}
deregisterFeedback(feedbackEmitter: IFeedbackEmitter, cleanupActions?: Action[] | undefined): void {
this.registeredFeedback.delete(feedbackEmitter);
if (cleanupActions && cleanupActions.length > 0) {
this.dispatchFeedback(cleanupActions, feedbackEmitter);
}
}
getRegisteredFeedback(): Action[] {
const result: Action[] = [];
this.registeredFeedback.forEach(actions => result.push(...actions));
return result;
}
getRegisteredFeedbackEmitters(action: Action): IFeedbackEmitter[] {
const result: IFeedbackEmitter[] = [];
this.registeredFeedback.forEach((actions, emitter) => {
if (actions.includes(action)) {
result.push(emitter);
}
});
return result;
}
getFeedbackCommands(): Command[] {
return this.getRegisteredFeedback()
.flatMap(action => this.actionToCommands(action))
.sort((left, right) => getFeedbackRank(left) - getFeedbackRank(right));
}
async applyFeedbackCommands(context: CommandExecutionContext): Promise<void> {
const feedbackCommands = this.getFeedbackCommands() ?? [];
if (feedbackCommands?.length > 0) {
const results = feedbackCommands.map(command => command.execute(context));
await Promise.all(results);
}
}
protected actionToCommands(action: Action): ICommand[] {
return (
this.actionHandlerRegistry
?.get(action.kind)
.filter(toTypeGuard(CommandActionHandler))
.map(handler => handler.handle(action)) ?? []
);
}
protected async dispatchFeedback(actions: Action[], feedbackEmitter: IFeedbackEmitter): Promise<void> {
try {
const actionDispatcher = await this.actionDispatcher();
await actionDispatcher.dispatchAll(actions);
this.logger.info(this, `Dispatched feedback actions for ${feedbackEmitter}`);
} catch (reason) {
this.logger.error(this, 'Failed to dispatch feedback actions', reason);
}
}
}