@finos/legend-application 10.0.15 → 10.1.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.
Files changed (45) hide show
  1. package/lib/application/LegendApplication.d.ts.map +1 -1
  2. package/lib/application/LegendApplication.js +7 -4
  3. package/lib/application/LegendApplication.js.map +1 -1
  4. package/lib/components/LegendApplicationComponentFrameworkProvider.d.ts.map +1 -1
  5. package/lib/components/LegendApplicationComponentFrameworkProvider.js +18 -7
  6. package/lib/components/LegendApplicationComponentFrameworkProvider.js.map +1 -1
  7. package/lib/components/shared/TabManager.d.ts +24 -0
  8. package/lib/components/shared/TabManager.d.ts.map +1 -0
  9. package/lib/components/shared/TabManager.js +115 -0
  10. package/lib/components/shared/TabManager.js.map +1 -0
  11. package/lib/index.css +2 -2
  12. package/lib/index.css.map +1 -1
  13. package/lib/index.d.ts +2 -1
  14. package/lib/index.d.ts.map +1 -1
  15. package/lib/index.js +2 -3
  16. package/lib/index.js.map +1 -1
  17. package/lib/stores/CommandCenter.d.ts +10 -2
  18. package/lib/stores/CommandCenter.d.ts.map +1 -1
  19. package/lib/stores/CommandCenter.js +2 -2
  20. package/lib/stores/CommandCenter.js.map +1 -1
  21. package/lib/stores/DocumentationService.d.ts +5 -0
  22. package/lib/stores/DocumentationService.d.ts.map +1 -1
  23. package/lib/stores/DocumentationService.js.map +1 -1
  24. package/lib/stores/KeyboardShortcutsService.d.ts +5 -0
  25. package/lib/stores/KeyboardShortcutsService.d.ts.map +1 -1
  26. package/lib/stores/KeyboardShortcutsService.js +5 -0
  27. package/lib/stores/KeyboardShortcutsService.js.map +1 -1
  28. package/lib/stores/PureLanguageSupport.d.ts.map +1 -1
  29. package/lib/stores/PureLanguageSupport.js +5 -0
  30. package/lib/stores/PureLanguageSupport.js.map +1 -1
  31. package/lib/stores/shared/TabManagerState.d.ts +37 -0
  32. package/lib/stores/shared/TabManagerState.d.ts.map +1 -0
  33. package/lib/stores/shared/TabManagerState.js +55 -0
  34. package/lib/stores/shared/TabManagerState.js.map +1 -0
  35. package/package.json +5 -5
  36. package/src/application/LegendApplication.tsx +10 -3
  37. package/src/components/LegendApplicationComponentFrameworkProvider.tsx +18 -6
  38. package/src/components/shared/TabManager.tsx +285 -0
  39. package/src/index.ts +2 -3
  40. package/src/stores/CommandCenter.ts +11 -3
  41. package/src/stores/DocumentationService.ts +5 -0
  42. package/src/stores/KeyboardShortcutsService.ts +5 -0
  43. package/src/stores/PureLanguageSupport.ts +5 -0
  44. package/src/stores/shared/TabManagerState.ts +77 -0
  45. package/tsconfig.json +2 -0
@@ -0,0 +1,285 @@
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 {
18
+ ChevronDownIcon,
19
+ clsx,
20
+ ContextMenu,
21
+ DragPreviewLayer,
22
+ DropdownMenu,
23
+ MenuContent,
24
+ MenuContentItem,
25
+ PanelEntryDropZonePlaceholder,
26
+ TimesIcon,
27
+ useDragPreviewLayer,
28
+ } from '@finos/legend-art';
29
+ import { observer } from 'mobx-react-lite';
30
+ import { useRef, useCallback } from 'react';
31
+ import { type DropTargetMonitor, useDrop, useDrag } from 'react-dnd';
32
+ import type {
33
+ TabManagerState,
34
+ TabState,
35
+ } from '../../stores/shared/TabManagerState.js';
36
+
37
+ type TabDragSource = {
38
+ tab: TabState;
39
+ };
40
+
41
+ const horizontalToVerticalScroll: React.WheelEventHandler = (event) => {
42
+ // if scrolling is more horizontal than vertical, there's nothing much to do, the OS should handle it just fine
43
+ // else, intercept
44
+ if (Math.abs(event.deltaY) <= Math.abs(event.deltaX)) {
45
+ return;
46
+ }
47
+ event.stopPropagation();
48
+ let deltaX = event.deltaX;
49
+ // NOTE: only convert horizontal to vertical scroll when the scroll causes more horizontal than vertical displacement
50
+ // let the direction of `deltaY` be the direction of the scroll, i.e.
51
+ // - if we scroll upward, that translate to a left scroll
52
+ // - if we scroll downward, that translates to a right scroll
53
+ if (event.deltaY === 0) {
54
+ deltaX = event.deltaY;
55
+ } else if (event.deltaY < 0) {
56
+ deltaX = -Math.abs(event.deltaY);
57
+ } else {
58
+ deltaX = Math.abs(event.deltaY);
59
+ }
60
+ event.currentTarget.scrollBy(deltaX, 0);
61
+ };
62
+
63
+ const TabContextMenu = observer(
64
+ (props: { tabState: TabState; managerTabState: TabManagerState }) => {
65
+ const { tabState, managerTabState } = props;
66
+ const close = (): void => managerTabState.closeTab(tabState);
67
+ const closeOthers = (): void => managerTabState.closeAllOtherTabs(tabState);
68
+ const closeAll = (): void => managerTabState.closeAllTabs();
69
+
70
+ return (
71
+ <MenuContent>
72
+ <MenuContentItem onClick={close}>Close</MenuContentItem>
73
+ <MenuContentItem
74
+ disabled={managerTabState.tabs.length < 2}
75
+ onClick={closeOthers}
76
+ >
77
+ Close Others
78
+ </MenuContentItem>
79
+ <MenuContentItem onClick={closeAll}>Close All</MenuContentItem>
80
+ </MenuContent>
81
+ );
82
+ },
83
+ );
84
+
85
+ const Tab = observer(
86
+ (props: {
87
+ tabState: TabState;
88
+ tabManagerState: TabManagerState;
89
+ tabRenderer?: ((editorState: TabState) => React.ReactNode) | undefined;
90
+ }) => {
91
+ const ref = useRef<HTMLDivElement>(null);
92
+ const { tabManagerState, tabState, tabRenderer } = props;
93
+
94
+ // Drag and Drop
95
+ const handleHover = useCallback(
96
+ (item: TabDragSource, monitor: DropTargetMonitor): void => {
97
+ const draggingTab = item.tab;
98
+ const hoveredTab = tabState;
99
+
100
+ const dragIndex = tabManagerState.tabs.findIndex(
101
+ (e) => e === draggingTab,
102
+ );
103
+ const hoverIndex = tabManagerState.tabs.findIndex(
104
+ (e) => e === hoveredTab,
105
+ );
106
+
107
+ const hoverBoundingReact = ref.current?.getBoundingClientRect();
108
+ const distanceThreshold =
109
+ ((hoverBoundingReact?.left ?? 0) - (hoverBoundingReact?.right ?? 0)) /
110
+ 2;
111
+ const dragDistance =
112
+ (monitor.getClientOffset()?.x ?? 0) -
113
+ (hoverBoundingReact?.right ?? 0);
114
+ if (dragIndex < hoverIndex && dragDistance < distanceThreshold) {
115
+ return;
116
+ }
117
+ if (dragIndex > hoverIndex && dragDistance > distanceThreshold) {
118
+ return;
119
+ }
120
+
121
+ tabManagerState.swapTabs(draggingTab, hoveredTab);
122
+ },
123
+ [tabManagerState, tabState],
124
+ );
125
+
126
+ const closeTabOnMiddleClick =
127
+ (currTab: TabState): React.MouseEventHandler =>
128
+ (event): void => {
129
+ if (event.nativeEvent.button === 1) {
130
+ tabManagerState.closeTab(currTab);
131
+ }
132
+ };
133
+
134
+ const [{ isBeingDraggedEditorPanel }, dropConnector] = useDrop<
135
+ TabDragSource,
136
+ void,
137
+ { isBeingDraggedEditorPanel: TabState | undefined }
138
+ >(
139
+ () => ({
140
+ accept: [tabManagerState.dndType],
141
+ hover: (item, monitor) => handleHover(item, monitor),
142
+ collect: (
143
+ monitor,
144
+ ): {
145
+ isBeingDraggedEditorPanel: TabState | undefined;
146
+ } => ({
147
+ isBeingDraggedEditorPanel: monitor.getItem<TabDragSource | null>()
148
+ ?.tab,
149
+ }),
150
+ }),
151
+ [handleHover],
152
+ );
153
+ const isBeingDragged = tabState === isBeingDraggedEditorPanel;
154
+
155
+ const [, dragConnector, dragPreviewConnector] = useDrag<TabDragSource>(
156
+ () => ({
157
+ type: tabManagerState.dndType,
158
+ item: () => ({
159
+ tab: tabState,
160
+ }),
161
+ }),
162
+ [tabState, tabManagerState],
163
+ );
164
+ dragConnector(dropConnector(ref));
165
+ useDragPreviewLayer(dragPreviewConnector);
166
+
167
+ return (
168
+ <div
169
+ ref={ref}
170
+ className={clsx('tab-manager__tab', {
171
+ 'tab-manager__tab--active': tabState === tabManagerState.currentTab,
172
+ 'tab-manager__tab--dragged': isBeingDragged,
173
+ })}
174
+ onMouseUp={closeTabOnMiddleClick(tabState)}
175
+ >
176
+ <PanelEntryDropZonePlaceholder
177
+ showPlaceholder={false}
178
+ className="tab-manager__tab__dnd__placeholder"
179
+ >
180
+ <ContextMenu
181
+ content={
182
+ <TabContextMenu
183
+ tabState={tabState}
184
+ managerTabState={tabManagerState}
185
+ />
186
+ }
187
+ className="tab-manager__tab__content"
188
+ >
189
+ <button
190
+ className="tab-manager__tab__label"
191
+ tabIndex={-1}
192
+ onClick={() => tabManagerState.openTab(tabState)}
193
+ title={tabState.description}
194
+ >
195
+ {tabRenderer?.(tabState) ?? tabState.label}
196
+ </button>
197
+ <button
198
+ className="tab-manager__tab__close-btn"
199
+ onClick={() => tabManagerState.closeTab(tabState)}
200
+ tabIndex={-1}
201
+ title="Close"
202
+ >
203
+ <TimesIcon />
204
+ </button>
205
+ </ContextMenu>
206
+ </PanelEntryDropZonePlaceholder>
207
+ </div>
208
+ );
209
+ },
210
+ );
211
+
212
+ const TabMenu = observer((props: { managerTabState: TabManagerState }) => {
213
+ const { managerTabState } = props;
214
+ return (
215
+ <DropdownMenu
216
+ className="tab-manager__menu__toggler"
217
+ title="Show All Tabs"
218
+ content={
219
+ <MenuContent className="tab-manager__menu">
220
+ {managerTabState.tabs.map((tabState) => (
221
+ <MenuContentItem
222
+ key={tabState.uuid}
223
+ className="tab-manager__menu__item"
224
+ onClick={() => managerTabState.openTab(tabState)}
225
+ >
226
+ <div className="tab-manager__menu__item__label">
227
+ {tabState.label}
228
+ </div>
229
+ <div
230
+ className="tab-manager__menu__item__close-btn"
231
+ onClick={(event) => {
232
+ // NOTE: prevent default action of dropdown menu
233
+ event.stopPropagation();
234
+ managerTabState.closeTab(tabState);
235
+ }}
236
+ tabIndex={-1}
237
+ title="Close"
238
+ >
239
+ <TimesIcon />
240
+ </div>
241
+ </MenuContentItem>
242
+ ))}
243
+ </MenuContent>
244
+ }
245
+ menuProps={{
246
+ anchorOrigin: { vertical: 'bottom', horizontal: 'right' },
247
+ transformOrigin: { vertical: 'top', horizontal: 'right' },
248
+ }}
249
+ >
250
+ <ChevronDownIcon />
251
+ </DropdownMenu>
252
+ );
253
+ });
254
+
255
+ export const TabManager = observer(
256
+ (props: {
257
+ tabManagerState: TabManagerState;
258
+ tabRenderer?: ((editorState: TabState) => React.ReactNode) | undefined;
259
+ }) => {
260
+ const { tabManagerState, tabRenderer } = props;
261
+
262
+ return (
263
+ <div className="tab-manager">
264
+ <div
265
+ className="tab-manager__content"
266
+ onWheel={horizontalToVerticalScroll}
267
+ >
268
+ {tabManagerState.tabs.map((tab) => (
269
+ <Tab
270
+ key={tab.uuid}
271
+ tabState={tab}
272
+ tabManagerState={tabManagerState}
273
+ tabRenderer={tabRenderer}
274
+ />
275
+ ))}
276
+ <DragPreviewLayer
277
+ labelGetter={(item: TabDragSource): string => item.tab.label}
278
+ types={[tabManagerState.dndType]}
279
+ />
280
+ </div>
281
+ <TabMenu managerTabState={tabManagerState} />
282
+ </div>
283
+ );
284
+ },
285
+ );
package/src/index.ts CHANGED
@@ -25,9 +25,6 @@ export * from './components/useApplicationNavigationContext.js';
25
25
  export * from './components/useCommands.js';
26
26
  export * from './components/ApplicationStoreProviderTestUtils.js';
27
27
  export * from './components/WebApplicationNavigatorProviderTestUtils.js';
28
- // TODO: consider moving this to `LegendApplicationComponentFrameworkProvider`
29
- // once we think we can add virtual assistant support for all apps
30
- export * from './components/VirtualAssistant.js';
31
28
 
32
29
  export * from './stores/ApplicationStore.js';
33
30
  export * from './stores/ApplicationTelemetry.js';
@@ -53,8 +50,10 @@ export * from './stores/WebApplicationRouter.js';
53
50
  export * from './components/shared/DocumentationLink.js';
54
51
  export * from './components/shared/TextInputEditor.js';
55
52
  export * from './components/shared/PackageableElementOptionLabel.js';
53
+ export * from './components/shared/TabManager.js';
56
54
 
57
55
  export * from './stores/shared/PackageableElementOption.js';
56
+ export * from './stores/shared/TabManagerState.js';
58
57
 
59
58
  /**
60
59
  * @modularize
@@ -25,6 +25,11 @@ export interface CommandRegistrar {
25
25
  }
26
26
  export type CommandConfigEntry = {
27
27
  title?: string;
28
+ /**
29
+ * NOTE: only support keyboard code instead of key
30
+ * See https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
31
+ * See https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code
32
+ */
28
33
  defaultKeyboardShortcut?: string;
29
34
  when?: string;
30
35
  };
@@ -40,10 +45,13 @@ export const collectKeyedCommandConfigEntriesFromConfig = (
40
45
  key: entry[0],
41
46
  content: entry[1],
42
47
  }));
48
+ export type CommandArguments = {
49
+ event?: Event;
50
+ };
43
51
  export type Command = {
44
52
  key: string;
45
53
  trigger?: () => boolean;
46
- action?: () => void;
54
+ action?: (args?: CommandArguments) => void;
47
55
  };
48
56
 
49
57
  export class CommandCenter {
@@ -78,10 +86,10 @@ export class CommandCenter {
78
86
  this.commandRegistry.delete(commandKey);
79
87
  }
80
88
 
81
- runCommand(commandKey: string): boolean {
89
+ runCommand(commandKey: string, args?: CommandArguments): boolean {
82
90
  const command = this.commandRegistry.get(commandKey);
83
91
  if (command && (!command.trigger || command.trigger())) {
84
- command.action?.();
92
+ command.action?.(args);
85
93
  return true;
86
94
  }
87
95
  return false;
@@ -48,6 +48,11 @@ export type DocumentationRegistryEntry = {
48
48
  * See https://cors-anywhere.herokuapp.com/
49
49
  */
50
50
  simple?: boolean | undefined;
51
+ /**
52
+ * Optional list of wildcard patterns to be matched against documentation entries' keys to
53
+ * narrow the scope of inclusion
54
+ */
55
+ includes?: string[];
51
56
  };
52
57
 
53
58
  export type DocumentationRegistryData = {
@@ -21,6 +21,11 @@ import type { GenericLegendApplicationStore } from './ApplicationStore.js';
21
21
 
22
22
  export class KeyboardShortcutsService {
23
23
  readonly applicationStore: GenericLegendApplicationStore;
24
+ /**
25
+ * NOTE: with this design, the relationship between command and key is many-to-one
26
+ * We can have multiple commands being mapped to the same key combination, not the other
27
+ * way around
28
+ */
24
29
  readonly keyMap = new Map<string, string[]>();
25
30
  /**
26
31
  * NOTE: we want to leave the value of the map as optional because we want
@@ -362,6 +362,11 @@ export const setupPureLanguageService = (
362
362
  // Override `monaco-editor` native hotkeys
363
363
  // See https://github.com/microsoft/monaco-editor/issues/102#issuecomment-1282897640
364
364
  monacoEditorAPI.addKeybindingRules([
365
+ {
366
+ // disable cursor move (core command)
367
+ keybinding: KeyMod.WinCtrl | KeyCode.KeyB,
368
+ command: null,
369
+ },
365
370
  {
366
371
  // disable cursor move (core command)
367
372
  keybinding: KeyMod.WinCtrl | KeyCode.KeyO,
@@ -0,0 +1,77 @@
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 { assertNonNullable, swapEntry, uuid } from '@finos/legend-shared';
18
+ import { action, makeObservable, observable } from 'mobx';
19
+
20
+ export abstract class TabState {
21
+ readonly uuid = uuid();
22
+
23
+ abstract get label(): string;
24
+
25
+ get description(): string | undefined {
26
+ return undefined;
27
+ }
28
+ }
29
+
30
+ export abstract class TabManagerState {
31
+ currentTab?: TabState | undefined;
32
+ tabs: TabState[] = [];
33
+
34
+ constructor() {
35
+ makeObservable(this, {
36
+ currentTab: observable,
37
+ tabs: observable,
38
+ setCurrentTab: action,
39
+ closeTab: action,
40
+ closeAllTabs: action,
41
+ closeAllOtherTabs: action,
42
+ openTab: action,
43
+ swapTabs: action,
44
+ });
45
+ }
46
+
47
+ setCurrentTab(val: TabState | undefined): void {
48
+ this.currentTab = val;
49
+ }
50
+
51
+ closeAllOtherTabs(tab: TabState): void {
52
+ assertNonNullable(
53
+ this.tabs.find((e) => e === tab),
54
+ 'Specified tab should be currently opened',
55
+ );
56
+ this.setCurrentTab(tab);
57
+ this.tabs = [tab];
58
+ }
59
+
60
+ closeAllTabs(): void {
61
+ this.setCurrentTab(undefined);
62
+ this.tabs = [];
63
+ }
64
+
65
+ swapTabs(tab1: TabState, tab2: TabState): void {
66
+ swapEntry(this.tabs, tab1, tab2);
67
+ }
68
+
69
+ /**
70
+ * The unique drag and drop type
71
+ * See https://react-dnd.github.io/react-dnd/docs/overview#items-and-types
72
+ */
73
+ abstract get dndType(): string;
74
+
75
+ abstract openTab(tab: TabState): void;
76
+ abstract closeTab(tab: TabState): void;
77
+ }
package/tsconfig.json CHANGED
@@ -48,6 +48,7 @@
48
48
  "./src/stores/WebApplicationNavigator.ts",
49
49
  "./src/stores/WebApplicationRouter.ts",
50
50
  "./src/stores/shared/PackageableElementOption.ts",
51
+ "./src/stores/shared/TabManagerState.ts",
51
52
  "./src/stores/shared/TextSearchAdvancedConfigState.ts",
52
53
  "./src/application/LegendApplication.tsx",
53
54
  "./src/application/LegendApplicationPluginManager.tsx",
@@ -66,6 +67,7 @@
66
67
  "./src/components/execution-plan-viewer/SQLExecutionNodeViewer.tsx",
67
68
  "./src/components/shared/DocumentationLink.tsx",
68
69
  "./src/components/shared/PackageableElementOptionLabel.tsx",
70
+ "./src/components/shared/TabManager.tsx",
69
71
  "./src/components/shared/TextInputEditor.tsx",
70
72
  "./src/components/shared/TextSearchAdvancedConfigMenu.tsx"
71
73
  ],