@finos/legend-application 8.0.2 → 9.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.
Files changed (123) hide show
  1. package/lib/components/LegendApplicationComponentFrameworkProvider.d.ts +4 -2
  2. package/lib/components/LegendApplicationComponentFrameworkProvider.d.ts.map +1 -1
  3. package/lib/components/LegendApplicationComponentFrameworkProvider.js +7 -4
  4. package/lib/components/LegendApplicationComponentFrameworkProvider.js.map +1 -1
  5. package/lib/components/NotificationManager.js +1 -1
  6. package/lib/components/NotificationManager.js.map +1 -1
  7. package/lib/components/WebApplicationNavigatorProvider.d.ts.map +1 -1
  8. package/lib/components/WebApplicationNavigatorProvider.js +1 -1
  9. package/lib/components/WebApplicationNavigatorProvider.js.map +1 -1
  10. package/lib/components/{shared/execution-plan-viewer → execution-plan-viewer}/ExecutionPlanViewer.d.ts +1 -1
  11. package/lib/components/execution-plan-viewer/ExecutionPlanViewer.d.ts.map +1 -0
  12. package/lib/components/{shared/execution-plan-viewer → execution-plan-viewer}/ExecutionPlanViewer.js +3 -3
  13. package/lib/components/execution-plan-viewer/ExecutionPlanViewer.js.map +1 -0
  14. package/lib/components/{shared/execution-plan-viewer → execution-plan-viewer}/SQLExecutionNodeViewer.d.ts +1 -1
  15. package/lib/components/execution-plan-viewer/SQLExecutionNodeViewer.d.ts.map +1 -0
  16. package/lib/components/{shared/execution-plan-viewer → execution-plan-viewer}/SQLExecutionNodeViewer.js +2 -2
  17. package/lib/components/execution-plan-viewer/SQLExecutionNodeViewer.js.map +1 -0
  18. package/lib/components/shared/DocumentationLink.d.ts +5 -0
  19. package/lib/components/shared/DocumentationLink.d.ts.map +1 -1
  20. package/lib/components/shared/DocumentationLink.js +12 -2
  21. package/lib/components/shared/DocumentationLink.js.map +1 -1
  22. package/lib/components/shared/{PackageableElementOptionRenderer.d.ts → PackageableElementOptionLabel.d.ts} +1 -1
  23. package/lib/components/shared/PackageableElementOptionLabel.d.ts.map +1 -0
  24. package/lib/components/shared/{PackageableElementOptionRenderer.js → PackageableElementOptionLabel.js} +5 -5
  25. package/lib/components/shared/PackageableElementOptionLabel.js.map +1 -0
  26. package/lib/components/shared/TextInputEditor.d.ts.map +1 -1
  27. package/lib/components/shared/TextInputEditor.js +1 -2
  28. package/lib/components/shared/TextInputEditor.js.map +1 -1
  29. package/lib/index.css +2 -2
  30. package/lib/index.css.map +1 -1
  31. package/lib/index.d.ts +8 -9
  32. package/lib/index.d.ts.map +1 -1
  33. package/lib/index.js +8 -9
  34. package/lib/index.js.map +1 -1
  35. package/lib/stores/ApplicationStore.d.ts +2 -0
  36. package/lib/stores/ApplicationStore.d.ts.map +1 -1
  37. package/lib/stores/ApplicationStore.js +13 -10
  38. package/lib/stores/ApplicationStore.js.map +1 -1
  39. package/lib/stores/AssistantService.js +1 -1
  40. package/lib/stores/AssistantService.js.map +1 -1
  41. package/lib/stores/{shared/ExecutionPlanState.d.ts → ExecutionPlanState.d.ts} +1 -1
  42. package/lib/stores/ExecutionPlanState.d.ts.map +1 -0
  43. package/lib/stores/{shared/ExecutionPlanState.js → ExecutionPlanState.js} +0 -0
  44. package/lib/stores/ExecutionPlanState.js.map +1 -0
  45. package/lib/stores/LegendApplicationDocumentation.d.ts +2 -1
  46. package/lib/stores/LegendApplicationDocumentation.d.ts.map +1 -1
  47. package/lib/stores/LegendApplicationDocumentation.js +1 -0
  48. package/lib/stores/LegendApplicationDocumentation.js.map +1 -1
  49. package/lib/stores/WebApplicationNavigator.d.ts +62 -30
  50. package/lib/stores/WebApplicationNavigator.d.ts.map +1 -1
  51. package/lib/stores/WebApplicationNavigator.js +80 -15
  52. package/lib/stores/WebApplicationNavigator.js.map +1 -1
  53. package/lib/{components/ApplicationTestID.js → stores/WebApplicationRouter.d.ts} +4 -5
  54. package/lib/stores/WebApplicationRouter.d.ts.map +1 -0
  55. package/{src/components/ApplicationTestID.ts → lib/stores/WebApplicationRouter.js} +4 -4
  56. package/lib/stores/WebApplicationRouter.js.map +1 -0
  57. package/package.json +9 -11
  58. package/src/components/LegendApplicationComponentFrameworkProvider.tsx +18 -14
  59. package/src/components/NotificationManager.tsx +1 -1
  60. package/src/components/WebApplicationNavigatorProvider.tsx +1 -1
  61. package/src/components/{shared/execution-plan-viewer → execution-plan-viewer}/ExecutionPlanViewer.tsx +3 -3
  62. package/src/components/{shared/execution-plan-viewer → execution-plan-viewer}/SQLExecutionNodeViewer.tsx +3 -3
  63. package/src/components/shared/DocumentationLink.tsx +25 -1
  64. package/src/components/shared/{PackageableElementOptionRenderer.tsx → PackageableElementOptionLabel.tsx} +4 -4
  65. package/src/components/shared/TextInputEditor.tsx +1 -2
  66. package/src/index.ts +9 -9
  67. package/src/stores/ApplicationStore.ts +15 -12
  68. package/src/stores/AssistantService.ts +1 -1
  69. package/src/stores/{shared/ExecutionPlanState.ts → ExecutionPlanState.ts} +1 -1
  70. package/src/stores/LegendApplicationDocumentation.ts +1 -0
  71. package/src/stores/WebApplicationNavigator.ts +149 -39
  72. package/{lib/components/ApplicationTestID.d.ts → src/stores/WebApplicationRouter.ts} +12 -4
  73. package/tsconfig.json +7 -16
  74. package/lib/components/ApplicationTestID.d.ts.map +0 -1
  75. package/lib/components/ApplicationTestID.js.map +0 -1
  76. package/lib/components/shared/BasicValueSpecificationEditor.d.ts +0 -52
  77. package/lib/components/shared/BasicValueSpecificationEditor.d.ts.map +0 -1
  78. package/lib/components/shared/BasicValueSpecificationEditor.js +0 -323
  79. package/lib/components/shared/BasicValueSpecificationEditor.js.map +0 -1
  80. package/lib/components/shared/CustomDatePicker.d.ts +0 -38
  81. package/lib/components/shared/CustomDatePicker.d.ts.map +0 -1
  82. package/lib/components/shared/CustomDatePicker.js +0 -616
  83. package/lib/components/shared/CustomDatePicker.js.map +0 -1
  84. package/lib/components/shared/LambdaEditor.d.ts +0 -92
  85. package/lib/components/shared/LambdaEditor.d.ts.map +0 -1
  86. package/lib/components/shared/LambdaEditor.js +0 -434
  87. package/lib/components/shared/LambdaEditor.js.map +0 -1
  88. package/lib/components/shared/LambdaParameterValuesEditor.d.ts +0 -25
  89. package/lib/components/shared/LambdaParameterValuesEditor.d.ts.map +0 -1
  90. package/lib/components/shared/LambdaParameterValuesEditor.js +0 -52
  91. package/lib/components/shared/LambdaParameterValuesEditor.js.map +0 -1
  92. package/lib/components/shared/PackageableElementOptionRenderer.d.ts.map +0 -1
  93. package/lib/components/shared/PackageableElementOptionRenderer.js.map +0 -1
  94. package/lib/components/shared/execution-plan-viewer/ExecutionPlanViewer.d.ts.map +0 -1
  95. package/lib/components/shared/execution-plan-viewer/ExecutionPlanViewer.js.map +0 -1
  96. package/lib/components/shared/execution-plan-viewer/SQLExecutionNodeViewer.d.ts.map +0 -1
  97. package/lib/components/shared/execution-plan-viewer/SQLExecutionNodeViewer.js.map +0 -1
  98. package/lib/stores/CJS__Fuse.cjs +0 -35
  99. package/lib/stores/CJS__Fuse.cjs.map +0 -1
  100. package/lib/stores/CJS__Fuse.d.cts +0 -28
  101. package/lib/stores/CJS__Fuse.d.cts.map +0 -1
  102. package/lib/stores/shared/ExecutionPlanState.d.ts.map +0 -1
  103. package/lib/stores/shared/ExecutionPlanState.js.map +0 -1
  104. package/lib/stores/shared/LambdaEditorState.d.ts +0 -40
  105. package/lib/stores/shared/LambdaEditorState.d.ts.map +0 -1
  106. package/lib/stores/shared/LambdaEditorState.js +0 -81
  107. package/lib/stores/shared/LambdaEditorState.js.map +0 -1
  108. package/lib/stores/shared/LambdaParameterState.d.ts +0 -62
  109. package/lib/stores/shared/LambdaParameterState.d.ts.map +0 -1
  110. package/lib/stores/shared/LambdaParameterState.js +0 -160
  111. package/lib/stores/shared/LambdaParameterState.js.map +0 -1
  112. package/lib/stores/shared/ValueSpecificationModifierHelper.d.ts +0 -27
  113. package/lib/stores/shared/ValueSpecificationModifierHelper.d.ts.map +0 -1
  114. package/lib/stores/shared/ValueSpecificationModifierHelper.js +0 -49
  115. package/lib/stores/shared/ValueSpecificationModifierHelper.js.map +0 -1
  116. package/src/components/shared/BasicValueSpecificationEditor.tsx +0 -828
  117. package/src/components/shared/CustomDatePicker.tsx +0 -1292
  118. package/src/components/shared/LambdaEditor.tsx +0 -854
  119. package/src/components/shared/LambdaParameterValuesEditor.tsx +0 -118
  120. package/src/stores/CJS__Fuse.cts +0 -28
  121. package/src/stores/shared/LambdaEditorState.ts +0 -118
  122. package/src/stores/shared/LambdaParameterState.ts +0 -253
  123. package/src/stores/shared/ValueSpecificationModifierHelper.ts +0 -104
@@ -1,854 +0,0 @@
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 { useRef, useEffect, useState, useMemo } from 'react';
18
- import {
19
- editor as monacoEditorAPI,
20
- type IDisposable,
21
- type IKeyboardEvent,
22
- } from 'monaco-editor';
23
- import { observer } from 'mobx-react-lite';
24
- import { useResizeDetector } from 'react-resize-detector';
25
- import {
26
- clsx,
27
- setErrorMarkers,
28
- disposeEditor,
29
- disableEditorHotKeys,
30
- baseTextEditorSettings,
31
- getEditorValue,
32
- normalizeLineEnding,
33
- FilledWindowMaximizeIcon,
34
- LongArrowAltDownIcon,
35
- LongArrowAltUpIcon,
36
- Dialog,
37
- } from '@finos/legend-art';
38
- import type { LambdaEditorState } from '../../stores/shared/LambdaEditorState.js';
39
- import {
40
- debounce,
41
- noop,
42
- type DebouncedFunc,
43
- type GeneratorFn,
44
- } from '@finos/legend-shared';
45
- import { flowResult } from 'mobx';
46
- import { ParserError, type EngineError, type Type } from '@finos/legend-graph';
47
- import { APPLICATION_TEST_ID } from '../ApplicationTestID.js';
48
- import { useApplicationStore } from '../ApplicationStoreProvider.js';
49
- import { EDITOR_LANGUAGE, EDITOR_THEME, TAB_SIZE } from '../../const.js';
50
-
51
- export type LambdaEditorOnKeyDownEventHandler = {
52
- matcher: (event: IKeyboardEvent) => boolean;
53
- action: (event: IKeyboardEvent) => void;
54
- };
55
-
56
- const LambdaErrorFeedback: React.FC<{
57
- error?: EngineError | undefined;
58
- discardChanges: () => void;
59
- }> = (props) => {
60
- const { error, discardChanges } = props;
61
-
62
- if (!error) {
63
- return null;
64
- }
65
- return (
66
- <div className="lambda-editor__error-feedback">
67
- <div className="lambda-editor__error-feedback__error__message">
68
- {error.message}
69
- </div>
70
- {error instanceof ParserError && (
71
- <div className="lambda-editor__error-feedback__parsing-error__content">
72
- <button
73
- className="lambda-editor__error-feedback__parsing-error__discard-changes-btn"
74
- onClick={discardChanges}
75
- tabIndex={-1}
76
- >
77
- Discard Changes
78
- </button>
79
- </div>
80
- )}
81
- </div>
82
- );
83
- };
84
-
85
- const LambdaEditorInline = observer(
86
- (props: {
87
- className?: string | undefined;
88
- disabled: boolean;
89
- lambdaEditorState: LambdaEditorState;
90
- transformStringToLambda: DebouncedFunc<() => GeneratorFn<void>> | undefined;
91
- expectedType?: Type | undefined;
92
- matchedExpectedType?: (() => boolean) | undefined;
93
- onExpectedTypeLabelSelect?: (() => void) | undefined;
94
- useBaseTextEditorSettings?: boolean | undefined;
95
- hideErrorBar?: boolean | undefined;
96
- forceBackdrop: boolean;
97
- disableExpansion?: boolean | undefined;
98
- forceExpansion?: boolean | undefined;
99
- disablePopUp?: boolean | undefined;
100
- backdropSetter?: ((val: boolean) => void) | undefined;
101
- onKeyDownEventHandlers: LambdaEditorOnKeyDownEventHandler[];
102
- openInPopUp: () => void;
103
- onEditorFocusEventHandler?: (() => void) | undefined;
104
- }) => {
105
- const {
106
- className,
107
- disabled,
108
- lambdaEditorState,
109
- transformStringToLambda,
110
- expectedType,
111
- onExpectedTypeLabelSelect,
112
- matchedExpectedType,
113
- forceBackdrop,
114
- backdropSetter,
115
- disableExpansion,
116
- forceExpansion,
117
- disablePopUp,
118
- useBaseTextEditorSettings,
119
- hideErrorBar,
120
- onKeyDownEventHandlers,
121
- openInPopUp,
122
- onEditorFocusEventHandler,
123
- } = props;
124
- const applicationStore = useApplicationStore();
125
- const onDidChangeModelContentEventDisposer = useRef<
126
- IDisposable | undefined
127
- >(undefined);
128
- const onKeyDownEventDisposer = useRef<IDisposable | undefined>(undefined);
129
- const onDidFocusEditorWidgetDisposer = useRef<IDisposable | undefined>(
130
- undefined,
131
- );
132
- const value = normalizeLineEnding(lambdaEditorState.lambdaString);
133
- const parserError = lambdaEditorState.parserError;
134
- const compilationError = lambdaEditorState.compilationError;
135
- const selectTypeLabel = (): void => onExpectedTypeLabelSelect?.();
136
- const [isExpanded, setExpanded] = useState(Boolean(forceExpansion));
137
- const [editor, setEditor] = useState<
138
- monacoEditorAPI.IStandaloneCodeEditor | undefined
139
- >();
140
- const textInputRef = useRef<HTMLDivElement>(null);
141
-
142
- const transformLambdaToString = async (pretty: boolean): Promise<void> => {
143
- transformStringToLambda?.cancel();
144
- return flowResult(
145
- lambdaEditorState.convertLambdaObjectToGrammarString(pretty),
146
- ).catch(applicationStore.alertUnhandledError);
147
- };
148
- const discardChanges = applicationStore.guardUnhandledError(() =>
149
- transformLambdaToString(isExpanded),
150
- );
151
- const toggleExpandedMode = (): void => {
152
- if (!forceExpansion && !parserError) {
153
- transformLambdaToString(!isExpanded).catch(
154
- applicationStore.alertUnhandledError,
155
- );
156
- setExpanded(!isExpanded);
157
- }
158
- };
159
-
160
- const { ref, width, height } = useResizeDetector<HTMLDivElement>();
161
- useEffect(() => {
162
- if (width !== undefined && height !== undefined) {
163
- editor?.layout({ width, height });
164
- }
165
- }, [editor, width, height]);
166
-
167
- useEffect(() => {
168
- if (!editor && textInputRef.current) {
169
- const element = textInputRef.current;
170
- const lambdaEditorOptions: monacoEditorAPI.IStandaloneEditorConstructionOptions =
171
- useBaseTextEditorSettings
172
- ? {}
173
- : {
174
- renderLineHighlight: 'none',
175
- lineHeight: 24,
176
- overviewRulerBorder: false, // hide overview ruler (no current way to hide this completely yet)
177
- overviewRulerLanes: 0,
178
- hideCursorInOverviewRuler: false,
179
- glyphMargin: false,
180
- folding: false,
181
- minimap: { enabled: false },
182
- lineNumbers: 'off',
183
- lineNumbersMinChars: 0,
184
- lineDecorationsWidth: 5,
185
- snippetSuggestions: 'none',
186
- scrollbar: { vertical: 'hidden' },
187
- };
188
- const _editor = monacoEditorAPI.create(element, {
189
- ...baseTextEditorSettings,
190
- language: EDITOR_LANGUAGE.PURE,
191
- theme: applicationStore.TEMPORARY__isLightThemeEnabled
192
- ? EDITOR_THEME.TEMPORARY__VSCODE_LIGHT
193
- : EDITOR_THEME.LEGEND,
194
- ...lambdaEditorOptions,
195
- });
196
- disableEditorHotKeys(_editor);
197
- setEditor(_editor);
198
- }
199
- }, [editor, applicationStore, useBaseTextEditorSettings]);
200
-
201
- // set styling for expanded mode
202
- useEffect(() => {
203
- if (editor) {
204
- const currentClassName = editor.getRawOptions().extraEditorClassName;
205
- const isInExpanded = currentClassName === 'lambda-editor__expanded';
206
- if (isInExpanded !== isExpanded) {
207
- editor.updateOptions(
208
- isExpanded
209
- ? {
210
- extraEditorClassName:
211
- 'lambda-editor__editor__input__expanded',
212
- }
213
- : {
214
- extraEditorClassName:
215
- 'lambda-editor__editor__input__compressed',
216
- },
217
- );
218
- // set the value here so we don't lose the error when toggling between expand/collape modes
219
- const currentValue = getEditorValue(editor);
220
- editor.setValue(currentValue);
221
- }
222
- }
223
- }, [editor, isExpanded]);
224
-
225
- // set backdrop to force user to fix parser error when it happens
226
- useEffect(() => {
227
- if (backdropSetter) {
228
- if (parserError) {
229
- backdropSetter(true);
230
- } else if (!forceBackdrop) {
231
- // make sure the backdrop is no longer `needed` for blocking by another parser error before hiding it
232
- // NOTE: this has a serious drawback, see the documentation for `forceBackdrop` prop of `LambdaEditor`
233
- // for better context
234
- backdropSetter(false);
235
- }
236
- }
237
- }, [parserError, forceBackdrop, backdropSetter]);
238
-
239
- if (editor) {
240
- /**
241
- * See the extensive note about this instantiation in `LambdaEditor`. The fact that `transformStringToLambda` can change
242
- * since it does not solely depends on `LambdaEditorState` but also the `disabled` flag means that the update function
243
- * can go stale, so we cannot place this `onDidChangeModelContent` in a one-time called instantiation of the editor
244
- * (i.e. the first `useEffect` where we create the editor). As such, we have to use refs and disposer to update this every time
245
- * the lambda editor is re-rendered.
246
- *
247
- * A potential bug that could come up if we place this logic in the `useEffect` for instantiating the editor is:
248
- * 1. Initially set the `disabled` to true, then switch it back to `false` (using a timer or something)
249
- * 2. Type something in the lambda editor, the transform function is `undefined` and does not update the underlying lambda
250
- */
251
- onDidChangeModelContentEventDisposer.current?.dispose();
252
- onDidChangeModelContentEventDisposer.current =
253
- editor.onDidChangeModelContent(() => {
254
- const currentVal = getEditorValue(editor);
255
- /**
256
- * Avoid unecessary setting of lambda string. Also, this prevents clearing the non-parser error on first render.
257
- * Since this method is guaranteed to be called one time during the first rendering when we first set the
258
- * value for the lambda editor, we do not want to clear any existing non-parser error in case it is set by methods
259
- * like reveal error in each editor
260
- */
261
- if (currentVal !== value) {
262
- lambdaEditorState.setLambdaString(currentVal);
263
- /**
264
- * Here we clear the error as user changes the input
265
- * NOTE: we don't reset the parser error here, we could, but with that, we have to assume that the parsing check is
266
- * pretty quick--almost near real time, but if after typing new character, we clear the parsing error and the user
267
- * still make mistake, then the warning message will appear to flash, which is bad UX, so for now, we leave it be
268
- */
269
- lambdaEditorState.setCompilationError(undefined);
270
- }
271
- /**
272
- * This method MUST run on the first rendering of the lambda editor, as it will update the lambda object. This is
273
- * needed for new lambda where a lot of time is just a stub lambda. Without having this method called on the first
274
- * rendering, that stub lambda will remain stub lambda until user starts typing something in the lambda editor.
275
- * This stub lambda sometimes does not even get registered in change detection and causing the user to lose data
276
- * Although, technically a stub lambda is useless, so this is not too serious, but it may come across as buggy
277
- */
278
- transformStringToLambda?.cancel();
279
- if (transformStringToLambda) {
280
- const stringToLambdaTransformation = transformStringToLambda();
281
- if (stringToLambdaTransformation) {
282
- flowResult(stringToLambdaTransformation).catch(
283
- applicationStore.alertUnhandledError,
284
- );
285
- }
286
- }
287
- });
288
-
289
- // set hotkeys (before calling the action, finish parsing the current text value)
290
- onKeyDownEventDisposer.current?.dispose(); // dispose to avoid trigger hotkeys multiple times
291
- /**
292
- * NOTE: We can use `setCommand` here but that does not expose the event so we cannot `stopPropagation`, and we need to
293
- * use `stopPropagation` to prevent the event top bubble up to global hotkeys listener.
294
- * If we really want to use `setCommand` the other approach is to set <HotKeys/> around this lambda editor to override F9
295
- * perhaps that's the cleaner approach because we use `react-hotkeys` to handle it's business, but there is an on-going
296
- * issue with <HotKeys/> keybindings are lost when component rerenders and this happen as users type because we call `setValue`
297
- * See https://github.com/greena13/react-hotkeys/issues/209
298
- *
299
- * The main role of this section is to disable `monaco-editor` command and override with global actions, such as generate, compile,
300
- * toggle text mode, etc. The important thing is before we do so, we would like to finish the parsing of the current string, otherwise,
301
- * those operations can end up flushing the current state and trashing the user input, which is bad, as such, we make sure the
302
- * parsing passes before actually calling those global operations.
303
- */
304
- onKeyDownEventDisposer.current = editor.onKeyDown((event) => {
305
- onKeyDownEventHandlers.forEach((handler) => {
306
- if (handler.matcher(event)) {
307
- event.preventDefault();
308
- event.stopPropagation();
309
- transformStringToLambda?.cancel();
310
- handler.action(event);
311
- }
312
- });
313
- });
314
-
315
- onDidFocusEditorWidgetDisposer.current?.dispose();
316
- onDidFocusEditorWidgetDisposer.current = editor.onDidFocusEditorWidget(
317
- () => {
318
- onEditorFocusEventHandler?.();
319
- },
320
- );
321
-
322
- // Set the text value
323
- const currentValue = getEditorValue(editor);
324
- const editorModel = editor.getModel();
325
- const currentConfig = editor.getRawOptions();
326
- if (currentValue !== value) {
327
- editor.setValue(value);
328
- }
329
- if (currentConfig.readOnly !== disabled) {
330
- editor.updateOptions({
331
- readOnly: disabled,
332
- });
333
- }
334
-
335
- // Set the errors
336
- if (editorModel) {
337
- editorModel.updateOptions({ tabSize: TAB_SIZE });
338
- const error = parserError ?? compilationError;
339
- if (error?.sourceInformation) {
340
- setErrorMarkers(
341
- editorModel,
342
- error.message,
343
- error.sourceInformation.startLine,
344
- error.sourceInformation.startColumn,
345
- error.sourceInformation.endLine,
346
- error.sourceInformation.endColumn,
347
- );
348
- } else {
349
- monacoEditorAPI.setModelMarkers(editorModel, 'Error', []);
350
- }
351
- }
352
- }
353
-
354
- useEffect(
355
- () => (): void => {
356
- if (editor) {
357
- disposeEditor(editor);
358
- }
359
- },
360
- [editor],
361
- ); // dispose editor
362
-
363
- return (
364
- <>
365
- <div
366
- className={clsx('lambda-editor', className, {
367
- 'lambda-editor__expanded': isExpanded,
368
- })}
369
- >
370
- <div
371
- ref={ref}
372
- data-testid={APPLICATION_TEST_ID.LAMBDA_EDITOR__EDITOR_INPUT}
373
- className="lambda-editor__editor__input"
374
- >
375
- <div className="text-editor__body" ref={textInputRef} />
376
- </div>
377
- {Boolean(expectedType) && (
378
- <div className="lambda-editor__editor__info">
379
- {onExpectedTypeLabelSelect && (
380
- <button
381
- className={clsx(
382
- 'lambda-editor__editor__expected-return-type lambda-editor__editor__expected-return-type--clickable',
383
- {
384
- 'lambda-editor__editor__expected-return-type--highlighted':
385
- matchedExpectedType?.(),
386
- },
387
- )}
388
- onClick={selectTypeLabel}
389
- tabIndex={-1}
390
- title={'Toggle highlight expected type'}
391
- >
392
- {expectedType?.name ?? 'unknown'}
393
- </button>
394
- )}
395
- {!onExpectedTypeLabelSelect && (
396
- <div
397
- className={clsx(
398
- 'lambda-editor__editor__expected-return-type',
399
- {
400
- 'lambda-editor__editor__expected-return-type--highlighted':
401
- matchedExpectedType?.(),
402
- },
403
- )}
404
- >
405
- {expectedType?.name ?? 'unknown'}
406
- </div>
407
- )}
408
- </div>
409
- )}
410
- {!disableExpansion && !forceExpansion && (
411
- <button
412
- className="lambda-editor__editor__expand-btn"
413
- onClick={toggleExpandedMode}
414
- disabled={Boolean(parserError)}
415
- tabIndex={-1}
416
- title="Toggle Expand"
417
- >
418
- {isExpanded ? <LongArrowAltUpIcon /> : <LongArrowAltDownIcon />}
419
- </button>
420
- )}
421
- {!disablePopUp && (
422
- <button
423
- className="lambda-editor__action"
424
- onClick={openInPopUp}
425
- disabled={Boolean(parserError)}
426
- tabIndex={-1}
427
- title="Open..."
428
- >
429
- <FilledWindowMaximizeIcon />
430
- </button>
431
- )}
432
- </div>
433
- {!hideErrorBar && (
434
- <LambdaErrorFeedback
435
- error={parserError ?? compilationError}
436
- discardChanges={discardChanges}
437
- />
438
- )}
439
- </>
440
- );
441
- },
442
- );
443
-
444
- const LambdaEditorPopUp = observer(
445
- (props: {
446
- className?: string | undefined;
447
- disabled: boolean;
448
- lambdaEditorState: LambdaEditorState;
449
- transformStringToLambda: DebouncedFunc<() => GeneratorFn<void>> | undefined;
450
- onKeyDownEventHandlers: LambdaEditorOnKeyDownEventHandler[];
451
- onClose: () => void;
452
- }) => {
453
- const {
454
- className,
455
- disabled,
456
- lambdaEditorState,
457
- transformStringToLambda,
458
- onKeyDownEventHandlers,
459
- onClose,
460
- } = props;
461
- const applicationStore = useApplicationStore();
462
- const onKeyDownEventDisposer = useRef<IDisposable | undefined>(undefined);
463
- const onDidChangeModelContentEventDisposer = useRef<
464
- IDisposable | undefined
465
- >(undefined);
466
- const value = normalizeLineEnding(lambdaEditorState.lambdaString);
467
- const parserError = lambdaEditorState.parserError;
468
- const compilationError = lambdaEditorState.compilationError;
469
- const [editor, setEditor] = useState<
470
- monacoEditorAPI.IStandaloneCodeEditor | undefined
471
- >();
472
- const textInputRef = useRef<HTMLDivElement>(null);
473
-
474
- const transformLambdaToString = async (pretty: boolean): Promise<void> => {
475
- transformStringToLambda?.cancel();
476
- return flowResult(
477
- lambdaEditorState.convertLambdaObjectToGrammarString(pretty),
478
- ).catch(applicationStore.alertUnhandledError);
479
- };
480
- const discardChanges = applicationStore.guardUnhandledError(() =>
481
- transformLambdaToString(true),
482
- );
483
-
484
- const { ref, width, height } = useResizeDetector<HTMLDivElement>();
485
- useEffect(() => {
486
- if (width !== undefined && height !== undefined) {
487
- editor?.layout({ width, height });
488
- }
489
- }, [editor, width, height]);
490
-
491
- const onEnter = (): void => {
492
- if (!editor && textInputRef.current) {
493
- const element = textInputRef.current;
494
- const _editor = monacoEditorAPI.create(element, {
495
- ...baseTextEditorSettings,
496
- language: EDITOR_LANGUAGE.PURE,
497
- theme: EDITOR_THEME.LEGEND,
498
- });
499
- disableEditorHotKeys(_editor);
500
- setEditor(_editor);
501
- }
502
- };
503
-
504
- if (editor) {
505
- /**
506
- * See the extensive note about this instantiation in `LambdaEditor`. The fact that `transformStringToLambda` can change
507
- * since it does not solely depends on `LambdaEditorState` but also the `disabled` flag means that the update function
508
- * can go stale, so we cannot place this `onDidChangeModelContent` in a one-time called instantiation of the editor
509
- * (i.e. the first `useEffect` where we create the editor). As such, we have to use refs and disposer to update this every time
510
- * the lambda editor is re-rendered.
511
- *
512
- * A potential bug that could come up if we place this logic in the `useEffect` for instantiating the editor is:
513
- * 1. Initially set the `disabled` to true, then switch it back to `false` (using a timer or something)
514
- * 2. Type something in the lambda editor, the transform function is `undefined` and does not update the underlying lambda
515
- */
516
- onDidChangeModelContentEventDisposer.current?.dispose();
517
- onDidChangeModelContentEventDisposer.current =
518
- editor.onDidChangeModelContent(() => {
519
- const currentVal = getEditorValue(editor);
520
- /**
521
- * Avoid unecessary setting of lambda string. Also, this prevents clearing the non-parser error on first render.
522
- * Since this method is guaranteed to be called one time during the first rendering when we first set the
523
- * value for the lambda editor, we do not want to clear any existing non-parser error in case it is set by methods
524
- * like reveal error in each editor
525
- */
526
- if (currentVal !== value) {
527
- lambdaEditorState.setLambdaString(currentVal);
528
- /**
529
- * Here we clear the error as user changes the input
530
- * NOTE: we don't reset the parser error here, we could, but with that, we have to assume that the parsing check is
531
- * pretty quick--almost near real time, but if after typing new character, we clear the parsing error and the user
532
- * still make mistake, then the warning message will appear to flash, which is bad UX, so for now, we leave it be
533
- */
534
- lambdaEditorState.setCompilationError(undefined);
535
- }
536
- /**
537
- * This method MUST run on the first rendering of the lambda editor, as it will update the lambda object. This is
538
- * needed for new lambda where a lot of time is just a stub lambda. Without having this method called on the first
539
- * rendering, that stub lambda will remain stub lambda until user starts typing something in the lambda editor.
540
- * This stub lambda sometimes does not even get registered in change detection and causing the user to lose data
541
- * Although, technically a stub lambda is useless, so this is not too serious, but it may come across as buggy
542
- */
543
- transformStringToLambda?.cancel();
544
- if (transformStringToLambda) {
545
- const stringToLambdaTransformation = transformStringToLambda();
546
- if (stringToLambdaTransformation) {
547
- flowResult(stringToLambdaTransformation).catch(
548
- applicationStore.alertUnhandledError,
549
- );
550
- }
551
- }
552
- });
553
-
554
- // set hotkeys (before calling the action, finish parsing the current text value)
555
- onKeyDownEventDisposer.current?.dispose(); // dispose to avoid trigger hotkeys multiple times
556
- /**
557
- * NOTE: We can use `setCommand` here but that does not expose the event so we cannot `stopPropagation`, and we need to
558
- * use `stopPropagation` to prevent the event top bubble up to global hotkeys listener.
559
- * If we really want to use `setCommand` the other approach is to set <HotKeys/> around this lambda editor to override F9
560
- * perhaps that's the cleaner approach because we use `react-hotkeys` to handle it's business, but there is an on-going
561
- * issue with <HotKeys/> keybindings are lost when component rerenders and this happen as users type because we call `setValue`
562
- * See https://github.com/greena13/react-hotkeys/issues/209
563
- *
564
- * The main role of this section is to disable `monaco-editor` command and override with global actions, such as generate, compile,
565
- * toggle text mode, etc. The important thing is before we do so, we would like to finish the parsing of the current string, otherwise,
566
- * those operations can end up flushing the current state and trashing the user input, which is bad, as such, we make sure the
567
- * parsing passes before actually calling those global operations.
568
- */
569
- onKeyDownEventDisposer.current = editor.onKeyDown((event) => {
570
- onKeyDownEventHandlers.forEach((handler) => {
571
- if (handler.matcher(event)) {
572
- event.preventDefault();
573
- event.stopPropagation();
574
- transformStringToLambda?.cancel();
575
- handler.action(event);
576
- }
577
- });
578
- });
579
-
580
- // Set the text value
581
- const currentValue = getEditorValue(editor);
582
- const editorModel = editor.getModel();
583
- const currentConfig = editor.getRawOptions();
584
- if (currentValue !== value) {
585
- editor.setValue(value);
586
- }
587
- if (currentConfig.readOnly !== disabled) {
588
- editor.updateOptions({
589
- readOnly: disabled,
590
- });
591
- }
592
-
593
- // Set the errors
594
- if (editorModel) {
595
- editorModel.updateOptions({ tabSize: TAB_SIZE });
596
- const error = parserError ?? compilationError;
597
- if (error?.sourceInformation) {
598
- setErrorMarkers(
599
- editorModel,
600
- error.message,
601
- error.sourceInformation.startLine,
602
- error.sourceInformation.startColumn,
603
- error.sourceInformation.endLine,
604
- error.sourceInformation.endColumn,
605
- );
606
- } else {
607
- monacoEditorAPI.setModelMarkers(editorModel, 'Error', []);
608
- }
609
- }
610
- }
611
-
612
- useEffect(() => {
613
- flowResult(
614
- lambdaEditorState.convertLambdaObjectToGrammarString(true),
615
- ).catch(applicationStore.alertUnhandledError);
616
- }, [applicationStore, lambdaEditorState]);
617
-
618
- useEffect(
619
- () => (): void => {
620
- if (editor) {
621
- disposeEditor(editor);
622
- }
623
- },
624
- [editor],
625
- ); // dispose editor
626
-
627
- return (
628
- <Dialog
629
- open={true}
630
- TransitionProps={{
631
- onEnter,
632
- }}
633
- onClose={noop} // disallow closing dialog by using Esc key or clicking on the backdrop
634
- classes={{
635
- root: 'editor-modal__root-container',
636
- container: 'editor-modal__container',
637
- paper: 'editor-modal__content',
638
- }}
639
- >
640
- <div
641
- className={clsx(
642
- 'modal modal--dark editor-modal lambda-editor__popup__modal',
643
- {
644
- 'lambda-editor__popup__modal--has-error': Boolean(
645
- lambdaEditorState.parserError,
646
- ),
647
- },
648
- )}
649
- >
650
- <div className="modal__header">
651
- <div className="modal__title">Edit Lambda</div>
652
- {lambdaEditorState.parserError && (
653
- <div className="modal__title__error-badge">
654
- Failed to parse lambda
655
- </div>
656
- )}
657
- </div>
658
- <div className="modal__body">
659
- <div className={clsx('lambda-editor__popup__content', className)}>
660
- <div
661
- ref={ref}
662
- data-testid={APPLICATION_TEST_ID.LAMBDA_EDITOR__EDITOR_INPUT}
663
- className="lambda-editor__editor__input"
664
- >
665
- <div className="text-editor__body" ref={textInputRef} />
666
- </div>
667
- </div>
668
- </div>
669
- <div className="modal__footer">
670
- <button
671
- className="btn btn--dark btn--caution"
672
- onClick={discardChanges}
673
- >
674
- Discard changes
675
- </button>
676
- <button
677
- className="btn btn--dark"
678
- onClick={onClose}
679
- disabled={Boolean(lambdaEditorState.parserError)}
680
- >
681
- Close
682
- </button>
683
- </div>
684
- </div>
685
- </Dialog>
686
- );
687
- },
688
- );
689
-
690
- /**
691
- * This is not strictly meant for lambda. The idea is to create an editor that allows
692
- * editing _something_ but allows user to edit via text.
693
- */
694
- export const LambdaEditor = observer(
695
- (props: {
696
- className?: string | undefined;
697
- disabled: boolean;
698
- lambdaEditorState: LambdaEditorState;
699
- /**
700
- * TODO: when we pass in these expected type we should match a type as expected type if it's covariance, i.e. it is a subtype of
701
- * the expected type. Note that we also have to handle that relationship for Primitive type
702
- * See https://dzone.com/articles/covariance-and-contravariance
703
- */
704
- expectedType?: Type | undefined;
705
- matchedExpectedType?: (() => boolean) | undefined;
706
- onExpectedTypeLabelSelect?: (() => void) | undefined;
707
- /**
708
- * As backdrop element is often shared in the application, and there could be multiple
709
- * editor using that backdrop, we could end up in situation where some such editors
710
- * have parser errors and some don't (this can happen when user make edits very quickly 2 lambda
711
- * editor and causes parsers error simultaneously). In this case, we want to make sure when
712
- * parser error is fixed in one editor, the backdrop is not dismissed immediately.
713
- *
714
- * NOTE: the current approach has a critical flaw, where on the same screen, there could be multiple
715
- * sets of lambda editors with different values for `forceBackdrop`. So really, the only way to
716
- * accomondate for this is to have `forceBackdrop` as a global value. Or we should get rid of this
717
- * backdrop mechanism altogether as it's not really a good UX pattern. i.e. quick evaluation makes
718
- * us believe that this is a good option, user will lose what they type, but the most recent parsable
719
- * input will still be captured.
720
- */
721
- forceBackdrop: boolean;
722
- /**
723
- * (de)activator for backdrop that is usually used to block any background interactions
724
- * while there is a parser error in the editor
725
- */
726
- backdropSetter?: ((val: boolean) => void) | undefined;
727
- /**
728
- * To whether or not disable expasipn toggler
729
- */
730
- disableExpansion?: boolean | undefined;
731
- /**
732
- * To whether show the inline editor in expanded mode initially and
733
- * disable expansion toggler
734
- *
735
- * This flag will override the effect of `forceExpansion`
736
- */
737
- forceExpansion?: boolean | undefined;
738
- /**
739
- * To whether or not disable popup mode
740
- */
741
- disablePopUp?: boolean | undefined;
742
- /**
743
- * To whether or not style inline editor
744
- */
745
- useBaseTextEditorSettings?: boolean | undefined;
746
- /**
747
- * To whether or not hide parser error bar in inline mode
748
- */
749
- hideErrorBar?: boolean | undefined;
750
- /**
751
- * Allow adding hotkeys handler to the editor, this is usually used
752
- * to allow activating global hotkeys while typing in the editor
753
- */
754
- onKeyDownEventHandlers?: LambdaEditorOnKeyDownEventHandler[];
755
- onEditorFocusEventHandler?: (() => void) | undefined;
756
- }) => {
757
- const {
758
- className,
759
- lambdaEditorState,
760
- disabled,
761
- forceBackdrop,
762
- backdropSetter,
763
- expectedType,
764
- onExpectedTypeLabelSelect,
765
- matchedExpectedType,
766
- disableExpansion,
767
- forceExpansion,
768
- disablePopUp,
769
- useBaseTextEditorSettings,
770
- hideErrorBar,
771
- onKeyDownEventHandlers,
772
- onEditorFocusEventHandler,
773
- } = props;
774
- const [showPopUp, setShowPopUp] = useState(false);
775
- const openInPopUp = (): void => setShowPopUp(true);
776
- const closePopUp = (): void => setShowPopUp(false);
777
- const debouncedTransformStringToLambda = useMemo(
778
- () =>
779
- disabled
780
- ? undefined
781
- : debounce(
782
- () => lambdaEditorState.convertLambdaGrammarStringToObject(),
783
- 1000,
784
- ),
785
- [lambdaEditorState, disabled],
786
- );
787
-
788
- if (!disablePopUp && showPopUp) {
789
- return (
790
- <>
791
- <div className="lambda-editor" />
792
- <LambdaEditorPopUp
793
- className={className}
794
- disabled={disabled}
795
- lambdaEditorState={lambdaEditorState}
796
- transformStringToLambda={debouncedTransformStringToLambda}
797
- onKeyDownEventHandlers={onKeyDownEventHandlers ?? []}
798
- onClose={closePopUp}
799
- />
800
- </>
801
- );
802
- }
803
- return (
804
- <LambdaEditorInline
805
- /**
806
- * See the usage of `transformStringToLambda` as well as the instatiation of the editor in `LambdaEditorInner`.
807
- * One of the big problem is that the editor uses lambda editor state (there are some non-trivial logic there, that
808
- * handles string <-> lambda object conversion), but there are certain operations in this app that can potentially
809
- * remove and recreate the lambda editor state, such as global generate or global compile, for such case, `LambdaEditorInner`
810
- * receives a new lambda editor state, but since React is smart about redrawing the DOM, it will not recreate the instance
811
- * of `monaco-editor` (see the useEffect() block), as such, the editor is using a stale state. That is definitely a bug;
812
- * and to demonstrate, we can try the following sequence of actions:
813
- *
814
- * 1. Type something that parses in the lambda editor
815
- * 2. Wait till the parse call finishes, hit F10 (trigger state update)
816
- * 3. Type something to cause parser error
817
- * 4. We will see that nothing happens. If we inspect network call, we will see a call returns with parsing error, but
818
- * the editor is refering to the old state, hence no reporting
819
- *
820
- * As such, the most appropriate resolution is to intentionally force remount of lambda editor when user perform a state
821
- * refresh operations. To do this, we use React `key` field with UUID bound to the state, as the state is recreated, the UUID
822
- * changes and the editor is recreated. However, this alone is not enough because we use `useMemo` for the debounced
823
- * transform function, which relies on another value other than the state (i.e. `disabled` flag), the same problem happens
824
- * as the function goes stale if `disabled` flag changes value. For that, see the implementation trick below (involving refs and disposer)
825
- *
826
- * So technically, we don't need to do the force-remount using `key`, but to be cleaner, we prevent other poential bugs, we do it anyway
827
- * to reset everything.
828
- */
829
- key={lambdaEditorState.uuid}
830
- className={className}
831
- disabled={disabled}
832
- lambdaEditorState={lambdaEditorState}
833
- transformStringToLambda={debouncedTransformStringToLambda}
834
- expectedType={expectedType}
835
- matchedExpectedType={matchedExpectedType}
836
- onExpectedTypeLabelSelect={onExpectedTypeLabelSelect}
837
- forceBackdrop={forceBackdrop}
838
- backdropSetter={backdropSetter}
839
- disableExpansion={disableExpansion}
840
- forceExpansion={
841
- disableExpansion !== undefined
842
- ? !disableExpansion && forceExpansion
843
- : forceExpansion
844
- }
845
- disablePopUp={disablePopUp}
846
- useBaseTextEditorSettings={useBaseTextEditorSettings}
847
- hideErrorBar={hideErrorBar}
848
- onKeyDownEventHandlers={onKeyDownEventHandlers ?? []}
849
- openInPopUp={openInPopUp}
850
- onEditorFocusEventHandler={onEditorFocusEventHandler}
851
- />
852
- );
853
- },
854
- );