@finos/legend-application 9.0.1 → 10.0.1
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/lib/application/LegendApplication.d.ts.map +1 -1
- package/lib/application/LegendApplication.js +7 -0
- package/lib/application/LegendApplication.js.map +1 -1
- package/lib/components/ActionAlert.d.ts.map +1 -1
- package/lib/components/ActionAlert.js +4 -5
- package/lib/components/ActionAlert.js.map +1 -1
- package/lib/components/LegendApplicationComponentFrameworkProvider.d.ts +10 -0
- package/lib/components/LegendApplicationComponentFrameworkProvider.d.ts.map +1 -1
- package/lib/components/LegendApplicationComponentFrameworkProvider.js +53 -2
- package/lib/components/LegendApplicationComponentFrameworkProvider.js.map +1 -1
- package/lib/components/VirtualAssistant.js +3 -4
- package/lib/components/VirtualAssistant.js.map +1 -1
- package/lib/components/execution-plan-viewer/ExecutionPlanViewer.d.ts.map +1 -1
- package/lib/components/execution-plan-viewer/ExecutionPlanViewer.js +2 -2
- package/lib/components/execution-plan-viewer/ExecutionPlanViewer.js.map +1 -1
- package/lib/components/shared/TextInputEditor.d.ts +5 -5
- package/lib/components/shared/TextInputEditor.d.ts.map +1 -1
- package/lib/components/shared/TextInputEditor.js +17 -16
- package/lib/components/shared/TextInputEditor.js.map +1 -1
- package/lib/components/{ApplicationNavigationContextServiceUtils.d.ts → useApplicationNavigationContext.d.ts} +1 -1
- package/lib/components/useApplicationNavigationContext.d.ts.map +1 -0
- package/lib/components/{ApplicationNavigationContextServiceUtils.js → useApplicationNavigationContext.js} +1 -1
- package/lib/components/useApplicationNavigationContext.js.map +1 -0
- package/lib/components/useCommands.d.ts +18 -0
- package/lib/components/useCommands.d.ts.map +1 -0
- package/lib/components/useCommands.js +23 -0
- package/lib/components/useCommands.js.map +1 -0
- package/lib/index.css +2 -2
- package/lib/index.css.map +1 -1
- package/lib/index.d.ts +3 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -1
- package/lib/index.js.map +1 -1
- package/lib/stores/ApplicationEvent.d.ts +2 -0
- package/lib/stores/ApplicationEvent.d.ts.map +1 -1
- package/lib/stores/ApplicationEvent.js +2 -0
- package/lib/stores/ApplicationEvent.js.map +1 -1
- package/lib/stores/ApplicationStore.d.ts +11 -0
- package/lib/stores/ApplicationStore.d.ts.map +1 -1
- package/lib/stores/ApplicationStore.js +33 -1
- package/lib/stores/ApplicationStore.js.map +1 -1
- package/lib/stores/CommandCenter.d.ts +45 -0
- package/lib/stores/CommandCenter.d.ts.map +1 -0
- package/lib/stores/CommandCenter.js +54 -0
- package/lib/stores/CommandCenter.js.map +1 -0
- package/lib/stores/DocumentationService.d.ts +3 -3
- package/lib/stores/DocumentationService.d.ts.map +1 -1
- package/lib/stores/DocumentationService.js.map +1 -1
- package/lib/stores/KeyboardShortcutsService.d.ts +55 -0
- package/lib/stores/KeyboardShortcutsService.d.ts.map +1 -0
- package/lib/stores/KeyboardShortcutsService.js +113 -0
- package/lib/stores/KeyboardShortcutsService.js.map +1 -0
- package/lib/stores/LegendApplicationPlugin.d.ts +5 -0
- package/lib/stores/LegendApplicationPlugin.d.ts.map +1 -1
- package/lib/stores/LegendApplicationPlugin.js.map +1 -1
- package/lib/stores/PureLanguageSupport.d.ts.map +1 -1
- package/lib/stores/PureLanguageSupport.js +10 -1
- package/lib/stores/PureLanguageSupport.js.map +1 -1
- package/lib/stores/WebApplicationNavigator.d.ts +37 -26
- package/lib/stores/WebApplicationNavigator.d.ts.map +1 -1
- package/lib/stores/WebApplicationNavigator.js +58 -32
- package/lib/stores/WebApplicationNavigator.js.map +1 -1
- package/lib/stores/WebApplicationRouter.d.ts +1 -1
- package/lib/stores/WebApplicationRouter.d.ts.map +1 -1
- package/lib/stores/WebApplicationRouter.js +1 -1
- package/lib/stores/WebApplicationRouter.js.map +1 -1
- package/package.json +10 -10
- package/src/application/LegendApplication.tsx +8 -0
- package/src/components/ActionAlert.tsx +4 -5
- package/src/components/LegendApplicationComponentFrameworkProvider.tsx +88 -3
- package/src/components/VirtualAssistant.tsx +3 -3
- package/src/components/execution-plan-viewer/ExecutionPlanViewer.tsx +5 -9
- package/src/components/shared/TextInputEditor.tsx +14 -21
- package/src/components/{ApplicationNavigationContextServiceUtils.tsx → useApplicationNavigationContext.tsx} +0 -0
- package/src/components/useCommands.tsx +25 -0
- package/src/index.ts +3 -1
- package/src/stores/ApplicationEvent.ts +3 -0
- package/src/stores/ApplicationStore.ts +34 -1
- package/src/stores/CommandCenter.ts +89 -0
- package/src/stores/DocumentationService.ts +3 -3
- package/src/stores/KeyboardShortcutsService.ts +142 -0
- package/src/stores/LegendApplicationPlugin.ts +6 -0
- package/src/stores/PureLanguageSupport.ts +10 -0
- package/src/stores/WebApplicationNavigator.ts +102 -62
- package/src/stores/WebApplicationRouter.ts +1 -0
- package/tsconfig.json +4 -1
- package/lib/components/ApplicationNavigationContextServiceUtils.d.ts.map +0 -1
- package/lib/components/ApplicationNavigationContextServiceUtils.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@finos/legend-application",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.0.1",
|
|
4
4
|
"description": "Legend application core",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"legend",
|
|
@@ -43,34 +43,34 @@
|
|
|
43
43
|
"test:watch": "jest --watch"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@finos/legend-art": "4.0
|
|
47
|
-
"@finos/legend-graph": "
|
|
48
|
-
"@finos/legend-shared": "6.1.
|
|
46
|
+
"@finos/legend-art": "4.1.0",
|
|
47
|
+
"@finos/legend-graph": "19.1.0",
|
|
48
|
+
"@finos/legend-shared": "6.1.5",
|
|
49
49
|
"@types/css-font-loading-module": "0.0.7",
|
|
50
50
|
"@types/react": "18.0.21",
|
|
51
51
|
"@types/react-dom": "18.0.6",
|
|
52
52
|
"@types/react-router-dom": "5.3.3",
|
|
53
|
-
"date-fns": "2.29.3",
|
|
54
53
|
"history": "5.3.0",
|
|
55
54
|
"mobx": "6.6.2",
|
|
56
55
|
"mobx-react-lite": "3.4.0",
|
|
57
|
-
"monaco-editor": "0.34.
|
|
56
|
+
"monaco-editor": "0.34.1",
|
|
58
57
|
"react": "18.2.0",
|
|
59
58
|
"react-dnd": "16.0.1",
|
|
60
59
|
"react-dnd-html5-backend": "16.0.1",
|
|
61
60
|
"react-dom": "18.2.0",
|
|
62
61
|
"react-draggable": "4.4.5",
|
|
62
|
+
"react-hotkeys": "2.0.0",
|
|
63
63
|
"react-router": "5.3.4",
|
|
64
64
|
"react-router-dom": "5.3.4",
|
|
65
|
-
"serializr": "
|
|
65
|
+
"serializr": "3.0.1",
|
|
66
66
|
"sql-formatter": "11.0.2"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
|
-
"@finos/legend-dev-utils": "2.0.
|
|
70
|
-
"@jest/globals": "29.1
|
|
69
|
+
"@finos/legend-dev-utils": "2.0.21",
|
|
70
|
+
"@jest/globals": "29.2.1",
|
|
71
71
|
"cross-env": "7.0.3",
|
|
72
72
|
"eslint": "8.25.0",
|
|
73
|
-
"jest": "29.1
|
|
73
|
+
"jest": "29.2.1",
|
|
74
74
|
"npm-run-all": "4.1.5",
|
|
75
75
|
"rimraf": "3.0.2",
|
|
76
76
|
"sass": "1.55.0",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import { configure as configureMobx } from 'mobx';
|
|
18
18
|
import { editor as monacoEditorAPI } from 'monaco-editor';
|
|
19
|
+
import { configure as configureReactHotkeys } from 'react-hotkeys';
|
|
19
20
|
import { MONOSPACED_FONT_FAMILY } from '../const.js';
|
|
20
21
|
import type {
|
|
21
22
|
LegendApplicationConfig,
|
|
@@ -131,6 +132,13 @@ export const setupLegendApplicationUILibrary = async (
|
|
|
131
132
|
enforceActions: 'observed',
|
|
132
133
|
});
|
|
133
134
|
|
|
135
|
+
configureReactHotkeys({
|
|
136
|
+
// By default, `react-hotkeys` will avoid capturing keys from input tags like <input>, <textarea>, <select>
|
|
137
|
+
// We want to listen to hotkey from every where in the app so we disable that
|
|
138
|
+
// See https://github.com/greena13/react-hotkeys#ignoring-events
|
|
139
|
+
ignoreTags: [],
|
|
140
|
+
});
|
|
141
|
+
|
|
134
142
|
configureComponents();
|
|
135
143
|
};
|
|
136
144
|
|
|
@@ -48,10 +48,6 @@ const ActionAlertContent = observer((props: { info: ActionAlertInfo }) => {
|
|
|
48
48
|
actions.find((action) => action.default)?.handler?.();
|
|
49
49
|
handleClose();
|
|
50
50
|
};
|
|
51
|
-
const onSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
|
|
52
|
-
event.preventDefault();
|
|
53
|
-
handleSubmit();
|
|
54
|
-
};
|
|
55
51
|
|
|
56
52
|
return (
|
|
57
53
|
<Dialog
|
|
@@ -62,7 +58,10 @@ const ActionAlertContent = observer((props: { info: ActionAlertInfo }) => {
|
|
|
62
58
|
}}
|
|
63
59
|
>
|
|
64
60
|
<form
|
|
65
|
-
onSubmit={
|
|
61
|
+
onSubmit={(event) => {
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
handleSubmit();
|
|
64
|
+
}}
|
|
66
65
|
className={`modal search-modal modal--dark blocking-alert blocking-alert--${(
|
|
67
66
|
type ?? ActionAlertType.STANDARD
|
|
68
67
|
).toLowerCase()}`}
|
|
@@ -14,27 +14,112 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { Backdrop, LegendStyleProvider } from '@finos/legend-art';
|
|
17
|
+
import { Backdrop, LegendStyleProvider, Portal } from '@finos/legend-art';
|
|
18
18
|
import { observer } from 'mobx-react-lite';
|
|
19
19
|
import { DndProvider } from 'react-dnd';
|
|
20
20
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
|
21
|
+
import { type KeyMap, GlobalHotKeys } from 'react-hotkeys';
|
|
21
22
|
import { ActionAlert } from './ActionAlert.js';
|
|
22
23
|
import { useApplicationStore } from './ApplicationStoreProvider.js';
|
|
23
24
|
import { BlockingAlert } from './BlockingAlert.js';
|
|
24
25
|
import { NotificationManager } from './NotificationManager.js';
|
|
25
26
|
|
|
27
|
+
const APP_CONTAINER_ID = 'app.container';
|
|
28
|
+
const APP_BACKDROP_CONTAINER_ID = 'app.backdrop-container';
|
|
29
|
+
|
|
30
|
+
const buildReactHotkeysConfiguration = (
|
|
31
|
+
commandKeyMap: Map<string, string | undefined>,
|
|
32
|
+
handlerCreator: (
|
|
33
|
+
keyCombination: string,
|
|
34
|
+
) => (keyEvent?: KeyboardEvent) => void,
|
|
35
|
+
): [KeyMap, { [key: string]: (keyEvent?: KeyboardEvent) => void }] => {
|
|
36
|
+
const keyMap: Record<PropertyKey, string[]> = {};
|
|
37
|
+
commandKeyMap.forEach((keyCombination, commandKey) => {
|
|
38
|
+
if (keyCombination) {
|
|
39
|
+
keyMap[commandKey] = [keyCombination];
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
const handlers: Record<PropertyKey, (keyEvent?: KeyboardEvent) => void> = {};
|
|
43
|
+
commandKeyMap.forEach((keyCombination, commandKey) => {
|
|
44
|
+
if (keyCombination) {
|
|
45
|
+
handlers[commandKey] = handlerCreator(keyCombination);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
return [keyMap, handlers];
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const forceDispatchKeyboardEvent = (event: KeyboardEvent): void => {
|
|
52
|
+
document
|
|
53
|
+
.getElementById(APP_CONTAINER_ID)
|
|
54
|
+
?.dispatchEvent(new KeyboardEvent(event.type, event));
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Potential location to mount backdrop on
|
|
59
|
+
*
|
|
60
|
+
* NOTE: we usually want the backdrop container to be the first child of its immediate parent
|
|
61
|
+
* so that it properly lies under the content that we pick to show on top of the backdrop
|
|
62
|
+
*/
|
|
63
|
+
export const BackdropContainer: React.FC<{ elementID: string }> = (props) => (
|
|
64
|
+
<div className="backdrop__container" id={props.elementID} />
|
|
65
|
+
);
|
|
66
|
+
|
|
26
67
|
export const LegendApplicationComponentFrameworkProvider = observer(
|
|
27
68
|
(props: { children: React.ReactNode }) => {
|
|
28
69
|
const { children } = props;
|
|
29
70
|
const applicationStore = useApplicationStore();
|
|
71
|
+
const backdropContainer = applicationStore.backdropContainerElementID
|
|
72
|
+
? document.getElementById(applicationStore.backdropContainerElementID) ??
|
|
73
|
+
document.getElementById(APP_BACKDROP_CONTAINER_ID)
|
|
74
|
+
: document.getElementById(APP_BACKDROP_CONTAINER_ID);
|
|
75
|
+
|
|
76
|
+
const [keyMap, hotkeyHandlerMap] = buildReactHotkeysConfiguration(
|
|
77
|
+
applicationStore.keyboardShortcutsService.commandKeyMap,
|
|
78
|
+
(keyCombination: string) => (event?: KeyboardEvent) => {
|
|
79
|
+
// NOTE: Though tempting since it's a good way to simplify and potentially avoid conflicts,
|
|
80
|
+
// we should not call `preventDefault()` because if we have any hotkey which is too short, such as `r`, `a`
|
|
81
|
+
// we risk blocking some very common interaction, i.e. user typing, or even constructing longer
|
|
82
|
+
// key combinations
|
|
83
|
+
applicationStore.keyboardShortcutsService.dispatch(keyCombination);
|
|
84
|
+
},
|
|
85
|
+
);
|
|
30
86
|
|
|
31
87
|
return (
|
|
32
88
|
<LegendStyleProvider>
|
|
33
89
|
<BlockingAlert />
|
|
34
90
|
<ActionAlert />
|
|
35
91
|
<NotificationManager />
|
|
36
|
-
|
|
37
|
-
|
|
92
|
+
{applicationStore.showBackdrop && (
|
|
93
|
+
// We use <Portal> here to insert backdrop into different parts of the app
|
|
94
|
+
// as backdrop relies heavily on z-index mechanism so its location in the DOM
|
|
95
|
+
// really matters.
|
|
96
|
+
// For example, the default location of the backdrop works fine for most cases
|
|
97
|
+
// but if we want to use the backdrop for elements within modal dialogs, we would
|
|
98
|
+
// need to mount the backdrop at a different location
|
|
99
|
+
<Portal container={backdropContainer}>
|
|
100
|
+
<Backdrop
|
|
101
|
+
className="backdrop"
|
|
102
|
+
open={applicationStore.showBackdrop}
|
|
103
|
+
/>
|
|
104
|
+
</Portal>
|
|
105
|
+
)}
|
|
106
|
+
<DndProvider backend={HTML5Backend}>
|
|
107
|
+
<GlobalHotKeys
|
|
108
|
+
keyMap={keyMap}
|
|
109
|
+
handlers={hotkeyHandlerMap}
|
|
110
|
+
allowChanges={true}
|
|
111
|
+
>
|
|
112
|
+
<div
|
|
113
|
+
className="app__container"
|
|
114
|
+
// NOTE: this `id` is used to quickly identify this DOM node so we could manually
|
|
115
|
+
// dispatch keyboard event here in order to be captured by our global hotkeys matchers
|
|
116
|
+
id={APP_CONTAINER_ID}
|
|
117
|
+
>
|
|
118
|
+
<BackdropContainer elementID={APP_BACKDROP_CONTAINER_ID} />
|
|
119
|
+
{children}
|
|
120
|
+
</div>
|
|
121
|
+
</GlobalHotKeys>
|
|
122
|
+
</DndProvider>
|
|
38
123
|
</LegendStyleProvider>
|
|
39
124
|
);
|
|
40
125
|
},
|
|
@@ -42,10 +42,10 @@ import {
|
|
|
42
42
|
ContentType,
|
|
43
43
|
debounce,
|
|
44
44
|
downloadFileUsingDataURI,
|
|
45
|
+
formatDate,
|
|
45
46
|
isString,
|
|
46
47
|
uuid,
|
|
47
48
|
} from '@finos/legend-shared';
|
|
48
|
-
import { format } from 'date-fns';
|
|
49
49
|
import { observer } from 'mobx-react-lite';
|
|
50
50
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
51
51
|
import { TAB_SIZE } from '../const.js';
|
|
@@ -284,7 +284,7 @@ const VirtualAssistantSearchPanel = observer(() => {
|
|
|
284
284
|
|
|
285
285
|
const downloadDocRegistry = (): void => {
|
|
286
286
|
downloadFileUsingDataURI(
|
|
287
|
-
`documentation-registry_${
|
|
287
|
+
`documentation-registry_${formatDate(
|
|
288
288
|
new Date(Date.now()),
|
|
289
289
|
DATE_TIME_FORMAT,
|
|
290
290
|
)}.json`,
|
|
@@ -298,7 +298,7 @@ const VirtualAssistantSearchPanel = observer(() => {
|
|
|
298
298
|
};
|
|
299
299
|
const downloadContextualDocIndex = (): void => {
|
|
300
300
|
downloadFileUsingDataURI(
|
|
301
|
-
`documentation-registry_${
|
|
301
|
+
`documentation-registry_${formatDate(
|
|
302
302
|
new Date(Date.now()),
|
|
303
303
|
DATE_TIME_FORMAT,
|
|
304
304
|
)}.json`,
|
|
@@ -330,7 +330,8 @@ const ExecutionPlanViewPanel = observer(
|
|
|
330
330
|
</button>
|
|
331
331
|
</div>
|
|
332
332
|
<DropdownMenu
|
|
333
|
-
className="execution-plan-viewer__panel__view-
|
|
333
|
+
className="execution-plan-viewer__panel__view-mode__type"
|
|
334
|
+
title="View as..."
|
|
334
335
|
content={
|
|
335
336
|
<MenuContent className="execution-plan-viewer__panel__view-mode__options execution-plan-viewer__panel__view-mode__options--with-group">
|
|
336
337
|
<div className="execution-plan-viewer__panel__view-mode__option__group execution-plan-viewer__panel__view-mode__option__group--native">
|
|
@@ -358,14 +359,9 @@ const ExecutionPlanViewPanel = observer(
|
|
|
358
359
|
transformOrigin: { vertical: 'top', horizontal: 'right' },
|
|
359
360
|
}}
|
|
360
361
|
>
|
|
361
|
-
<
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
>
|
|
365
|
-
<div className="execution-plan-viewer__panel__view-mode__type__label">
|
|
366
|
-
{executionPlanState.viewMode}
|
|
367
|
-
</div>
|
|
368
|
-
</button>
|
|
362
|
+
<div className="execution-plan-viewer__panel__view-mode__type__label">
|
|
363
|
+
{executionPlanState.viewMode}
|
|
364
|
+
</div>
|
|
369
365
|
</DropdownMenu>
|
|
370
366
|
</div>
|
|
371
367
|
<div className="panel__content execution-plan-viewer__panel__content">
|
|
@@ -22,7 +22,6 @@ import {
|
|
|
22
22
|
} from 'monaco-editor';
|
|
23
23
|
import {
|
|
24
24
|
disposeEditor,
|
|
25
|
-
disableEditorHotKeys,
|
|
26
25
|
baseTextEditorSettings,
|
|
27
26
|
resetLineNumberGutterWidth,
|
|
28
27
|
getEditorValue,
|
|
@@ -31,10 +30,14 @@ import {
|
|
|
31
30
|
} from '@finos/legend-art';
|
|
32
31
|
import { type EDITOR_LANGUAGE, EDITOR_THEME, TAB_SIZE } from '../../const.js';
|
|
33
32
|
import { useApplicationStore } from '../ApplicationStoreProvider.js';
|
|
33
|
+
import { forceDispatchKeyboardEvent } from '../LegendApplicationComponentFrameworkProvider.js';
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
/**
|
|
36
|
+
* NOTE: `monaco-editor` does not bubble `keydown` event in natural order, we will
|
|
37
|
+
* have to manually do this in order to take advantage of application keyboard shortcuts service
|
|
38
|
+
*/
|
|
39
|
+
export const createPassThroughOnKeyHandler = () => (event: IKeyboardEvent) => {
|
|
40
|
+
forceDispatchKeyboardEvent(event.browserEvent);
|
|
38
41
|
};
|
|
39
42
|
|
|
40
43
|
export const TextInputEditor: React.FC<{
|
|
@@ -47,7 +50,6 @@ export const TextInputEditor: React.FC<{
|
|
|
47
50
|
| (monacoEditorAPI.IEditorOptions & monacoEditorAPI.IGlobalEditorOptions)
|
|
48
51
|
| undefined;
|
|
49
52
|
updateInput?: ((val: string) => void) | undefined;
|
|
50
|
-
onKeyDownEventHandlers?: TextInputEditorOnKeyDownEventHandler[] | undefined;
|
|
51
53
|
}> = (props) => {
|
|
52
54
|
const {
|
|
53
55
|
inputValue,
|
|
@@ -57,13 +59,11 @@ export const TextInputEditor: React.FC<{
|
|
|
57
59
|
showMiniMap,
|
|
58
60
|
hideGutter,
|
|
59
61
|
extraEditorOptions,
|
|
60
|
-
onKeyDownEventHandlers,
|
|
61
62
|
} = props;
|
|
62
63
|
const applicationStore = useApplicationStore();
|
|
63
64
|
const [editor, setEditor] = useState<
|
|
64
65
|
monacoEditorAPI.IStandaloneCodeEditor | undefined
|
|
65
66
|
>();
|
|
66
|
-
const onKeyDownEventDisposer = useRef<IDisposable | undefined>(undefined);
|
|
67
67
|
const onDidChangeModelContentEventDisposer = useRef<IDisposable | undefined>(
|
|
68
68
|
undefined,
|
|
69
69
|
);
|
|
@@ -97,7 +97,13 @@ export const TextInputEditor: React.FC<{
|
|
|
97
97
|
formatOnType: true,
|
|
98
98
|
formatOnPaste: true,
|
|
99
99
|
});
|
|
100
|
-
|
|
100
|
+
// NOTE: if we ever set any hotkey explicitly, we would like to use the disposer partern instead
|
|
101
|
+
// else, we could risk triggering these hotkeys command multiple times
|
|
102
|
+
// e.g.
|
|
103
|
+
// const onKeyDownEventDisposer = useRef<IDisposable | undefined>(undefined);
|
|
104
|
+
// onKeyDownEventDisposer.current?.dispose();
|
|
105
|
+
// onKeyDownEventDisposer.current = editor.onKeyDown(() => ...)
|
|
106
|
+
_editor.onKeyDown(() => createPassThroughOnKeyHandler());
|
|
101
107
|
setEditor(_editor);
|
|
102
108
|
}
|
|
103
109
|
}, [applicationStore, editor]);
|
|
@@ -124,19 +130,6 @@ export const TextInputEditor: React.FC<{
|
|
|
124
130
|
}
|
|
125
131
|
});
|
|
126
132
|
|
|
127
|
-
// dispose to avoid trigger hotkeys multiple times
|
|
128
|
-
// for a more extensive note on this, see `LambdaEditor`
|
|
129
|
-
onKeyDownEventDisposer.current?.dispose();
|
|
130
|
-
onKeyDownEventDisposer.current = editor.onKeyDown((event) => {
|
|
131
|
-
onKeyDownEventHandlers?.forEach((handler) => {
|
|
132
|
-
if (handler.matcher(event)) {
|
|
133
|
-
event.preventDefault();
|
|
134
|
-
event.stopPropagation();
|
|
135
|
-
handler.action(event);
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
|
|
140
133
|
// Set the text value and editor options
|
|
141
134
|
const currentValue = getEditorValue(editor);
|
|
142
135
|
if (currentValue !== value) {
|
|
File without changes
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2020-present, Goldman Sachs
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { useEffect } from 'react';
|
|
18
|
+
import type { CommandRegistrar } from '../stores/CommandCenter.js';
|
|
19
|
+
|
|
20
|
+
export const useCommands = (registrar: CommandRegistrar): void => {
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
registrar.registerCommands();
|
|
23
|
+
return () => registrar.deregisterCommands();
|
|
24
|
+
}, [registrar]);
|
|
25
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -21,7 +21,8 @@ export * from './application/LegendApplication.js';
|
|
|
21
21
|
export * from './components/ApplicationStoreProvider.js';
|
|
22
22
|
export * from './components/WebApplicationNavigatorProvider.js';
|
|
23
23
|
export * from './components/LegendApplicationComponentFrameworkProvider.js';
|
|
24
|
-
export * from './components/
|
|
24
|
+
export * from './components/useApplicationNavigationContext.js';
|
|
25
|
+
export * from './components/useCommands.js';
|
|
25
26
|
export * from './components/ApplicationStoreProviderTestUtils.js';
|
|
26
27
|
export * from './components/WebApplicationNavigatorProviderTestUtils.js';
|
|
27
28
|
// TODO: consider moving this to `LegendApplicationComponentFrameworkProvider`
|
|
@@ -34,6 +35,7 @@ export * from './stores/ApplicationEvent.js';
|
|
|
34
35
|
export * from './application/LegendApplicationConfig.js';
|
|
35
36
|
export { WebApplicationNavigator } from './stores/WebApplicationNavigator.js';
|
|
36
37
|
export * from './stores/DocumentationService.js';
|
|
38
|
+
export * from './stores/CommandCenter.js';
|
|
37
39
|
export * from './stores/EventService.js';
|
|
38
40
|
export * from './stores/AssistantService.js';
|
|
39
41
|
export * from './stores/ApplicationNavigationContextService.js';
|
|
@@ -24,8 +24,11 @@ export enum APPLICATION_EVENT {
|
|
|
24
24
|
APPLICATION_DOCUMENTATION_FETCH_FAILURE = 'application.fetch.documentation.failure',
|
|
25
25
|
APPLICATION_DOCUMENTATION_LOAD_SKIPPED = 'application.load.documentation.skipped',
|
|
26
26
|
APPLICATION_DOCUMENTATION_REQUIREMENT_CHECK_FAILURE = 'application.load.documentation.requirement-check.failure',
|
|
27
|
+
APPLICATION_KEYBOARD_SHORTCUTS_CONFIGURATION_CHECK_FAILURE = 'application.load.keyboard-shortcuts.configuration-check.failure',
|
|
27
28
|
APPLICATION_CONTEXTUAL_DOCUMENTATION_LOAD_SKIPPED = 'application.load.contextual-documentation.skipped',
|
|
28
29
|
|
|
30
|
+
APPLICATION_COMMAND_CENTER_REGISTRATION_FAILURE = 'application.command-center.registration.failure',
|
|
31
|
+
|
|
29
32
|
APPLICATION_LOADED = 'application.load.success',
|
|
30
33
|
APPLICATION_LOAD_FAILURE = 'application.load.failure',
|
|
31
34
|
|
|
@@ -35,6 +35,8 @@ import { AssistantService } from './AssistantService.js';
|
|
|
35
35
|
import { EventService } from './EventService.js';
|
|
36
36
|
import { ApplicationNavigationContextService } from './ApplicationNavigationContextService.js';
|
|
37
37
|
import type { LegendApplicationPlugin } from './LegendApplicationPlugin.js';
|
|
38
|
+
import { CommandCenter } from './CommandCenter.js';
|
|
39
|
+
import { KeyboardShortcutsService } from './KeyboardShortcutsService.js';
|
|
38
40
|
|
|
39
41
|
export enum ActionAlertType {
|
|
40
42
|
STANDARD = 'STANDARD',
|
|
@@ -136,7 +138,15 @@ export class ApplicationStore<
|
|
|
136
138
|
telemetryService = new TelemetryService();
|
|
137
139
|
tracerService = new TracerService();
|
|
138
140
|
|
|
139
|
-
//
|
|
141
|
+
// control and interactions
|
|
142
|
+
commandCenter: CommandCenter;
|
|
143
|
+
keyboardShortcutsService: KeyboardShortcutsService;
|
|
144
|
+
|
|
145
|
+
// TODO: config
|
|
146
|
+
// See https://github.com/finos/legend-studio/issues/407
|
|
147
|
+
|
|
148
|
+
// backdrop
|
|
149
|
+
backdropContainerElementID?: string | undefined;
|
|
140
150
|
showBackdrop = false;
|
|
141
151
|
|
|
142
152
|
// theme
|
|
@@ -153,7 +163,9 @@ export class ApplicationStore<
|
|
|
153
163
|
blockingAlertInfo: observable,
|
|
154
164
|
actionAlertInfo: observable,
|
|
155
165
|
TEMPORARY__isLightThemeEnabled: observable,
|
|
166
|
+
backdropContainerElementID: observable,
|
|
156
167
|
showBackdrop: observable,
|
|
168
|
+
setBackdropContainerElementID: action,
|
|
157
169
|
setShowBackdrop: action,
|
|
158
170
|
setBlockingAlert: action,
|
|
159
171
|
setActionAlertInfo: action,
|
|
@@ -180,6 +192,8 @@ export class ApplicationStore<
|
|
|
180
192
|
this.telemetryService.registerPlugins(
|
|
181
193
|
pluginManager.getTelemetryServicePlugins(),
|
|
182
194
|
);
|
|
195
|
+
this.commandCenter = new CommandCenter(this);
|
|
196
|
+
this.keyboardShortcutsService = new KeyboardShortcutsService(this);
|
|
183
197
|
this.tracerService.registerPlugins(pluginManager.getTracerServicePlugins());
|
|
184
198
|
this.eventService.registerEventNotifierPlugins(
|
|
185
199
|
pluginManager.getEventNotifierPlugins(),
|
|
@@ -190,11 +204,25 @@ export class ApplicationStore<
|
|
|
190
204
|
this.TEMPORARY__isLightThemeEnabled = val;
|
|
191
205
|
}
|
|
192
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Change the ID used to find the base element to mount the backdrop on.
|
|
209
|
+
* This is useful when we want to use backdrop with embedded application which
|
|
210
|
+
* requires its own backdrop usage.
|
|
211
|
+
*/
|
|
212
|
+
setBackdropContainerElementID(val: string | undefined): void {
|
|
213
|
+
this.backdropContainerElementID = val;
|
|
214
|
+
}
|
|
215
|
+
|
|
193
216
|
setShowBackdrop(val: boolean): void {
|
|
194
217
|
this.showBackdrop = val;
|
|
195
218
|
}
|
|
196
219
|
|
|
197
220
|
setBlockingAlert(alertInfo: BlockingAlertInfo | undefined): void {
|
|
221
|
+
if (alertInfo) {
|
|
222
|
+
this.keyboardShortcutsService.blockGlobalHotkeys();
|
|
223
|
+
} else {
|
|
224
|
+
this.keyboardShortcutsService.unblockGlobalHotkeys();
|
|
225
|
+
}
|
|
198
226
|
this.blockingAlertInfo = alertInfo;
|
|
199
227
|
}
|
|
200
228
|
|
|
@@ -204,6 +232,11 @@ export class ApplicationStore<
|
|
|
204
232
|
'Action alert is stacked: new alert is invoked while another one is being displayed',
|
|
205
233
|
);
|
|
206
234
|
}
|
|
235
|
+
if (alertInfo) {
|
|
236
|
+
this.keyboardShortcutsService.blockGlobalHotkeys();
|
|
237
|
+
} else {
|
|
238
|
+
this.keyboardShortcutsService.unblockGlobalHotkeys();
|
|
239
|
+
}
|
|
207
240
|
this.actionAlertInfo = alertInfo;
|
|
208
241
|
}
|
|
209
242
|
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2020-present, Goldman Sachs
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { LogEvent } from '@finos/legend-shared';
|
|
18
|
+
import { action, makeObservable, observable } from 'mobx';
|
|
19
|
+
import { APPLICATION_EVENT } from './ApplicationEvent.js';
|
|
20
|
+
import type { GenericLegendApplicationStore } from './ApplicationStore.js';
|
|
21
|
+
|
|
22
|
+
export interface CommandRegistrar {
|
|
23
|
+
registerCommands(): void;
|
|
24
|
+
deregisterCommands(): void;
|
|
25
|
+
}
|
|
26
|
+
export type CommandConfigEntry = {
|
|
27
|
+
title?: string;
|
|
28
|
+
defaultKeyboardShortcut?: string;
|
|
29
|
+
when?: string;
|
|
30
|
+
};
|
|
31
|
+
export type KeyedCommandConfigEntry = {
|
|
32
|
+
key: string;
|
|
33
|
+
content: CommandConfigEntry;
|
|
34
|
+
};
|
|
35
|
+
export type CommandConfigData = Record<string, CommandConfigEntry>;
|
|
36
|
+
export const collectKeyedCommandConfigEntriesFromConfig = (
|
|
37
|
+
rawEntries: Record<string, CommandConfigEntry>,
|
|
38
|
+
): KeyedCommandConfigEntry[] =>
|
|
39
|
+
Object.entries(rawEntries).map((entry) => ({
|
|
40
|
+
key: entry[0],
|
|
41
|
+
content: entry[1],
|
|
42
|
+
}));
|
|
43
|
+
export type Command = {
|
|
44
|
+
key: string;
|
|
45
|
+
trigger?: () => boolean;
|
|
46
|
+
action?: () => void;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export class CommandCenter {
|
|
50
|
+
readonly applicationStore: GenericLegendApplicationStore;
|
|
51
|
+
readonly commandRegistry = new Map<string, Command>();
|
|
52
|
+
|
|
53
|
+
constructor(applicationStore: GenericLegendApplicationStore) {
|
|
54
|
+
makeObservable(this, {
|
|
55
|
+
commandRegistry: observable,
|
|
56
|
+
registerCommand: action,
|
|
57
|
+
deregisterCommand: action,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
this.applicationStore = applicationStore;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
registerCommand(command: Command): void {
|
|
64
|
+
const commandKey = command.key;
|
|
65
|
+
if (this.commandRegistry.has(commandKey)) {
|
|
66
|
+
this.applicationStore.log.warn(
|
|
67
|
+
LogEvent.create(
|
|
68
|
+
APPLICATION_EVENT.APPLICATION_COMMAND_CENTER_REGISTRATION_FAILURE,
|
|
69
|
+
),
|
|
70
|
+
`Can't register command: command is already registered`,
|
|
71
|
+
);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
this.commandRegistry.set(commandKey, command);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
deregisterCommand(commandKey: string): void {
|
|
78
|
+
this.commandRegistry.delete(commandKey);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
runCommand(commandKey: string): boolean {
|
|
82
|
+
const command = this.commandRegistry.get(commandKey);
|
|
83
|
+
if (command && (!command.trigger || command.trigger())) {
|
|
84
|
+
command.action?.();
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -122,10 +122,10 @@ export const collectContextualDocumnetationEntries = (
|
|
|
122
122
|
}));
|
|
123
123
|
|
|
124
124
|
export class DocumentationService {
|
|
125
|
-
url?: string | undefined;
|
|
125
|
+
readonly url?: string | undefined;
|
|
126
126
|
|
|
127
|
-
private docRegistry = new Map<string, DocumentationEntry>();
|
|
128
|
-
private contextualDocIndex = new Map<string, DocumentationEntry>();
|
|
127
|
+
private readonly docRegistry = new Map<string, DocumentationEntry>();
|
|
128
|
+
private readonly contextualDocIndex = new Map<string, DocumentationEntry>();
|
|
129
129
|
|
|
130
130
|
constructor(applicationStore: GenericLegendApplicationStore) {
|
|
131
131
|
// set the main documenation site url
|