flexdesk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +195 -0
- package/THIRD_PARTY_LICENSES.md +53 -0
- package/css/base.css +3439 -0
- package/css/flexdesk.css +5797 -0
- package/css/overrides.css +2349 -0
- package/css/reset.css +46 -0
- package/css/tokens.css +137 -0
- package/dist/charts.js +780 -0
- package/dist/charts.js.map +7 -0
- package/dist/chunk-3PHCPZHT.js +222 -0
- package/dist/chunk-3PHCPZHT.js.map +7 -0
- package/dist/chunk-CT4YXXLP.js +1770 -0
- package/dist/chunk-CT4YXXLP.js.map +7 -0
- package/dist/chunk-DRYCDMEG.js +2217 -0
- package/dist/chunk-DRYCDMEG.js.map +7 -0
- package/dist/chunk-DVU44T77.js +855 -0
- package/dist/chunk-DVU44T77.js.map +7 -0
- package/dist/chunk-FL5KFNQH.js +640 -0
- package/dist/chunk-FL5KFNQH.js.map +7 -0
- package/dist/chunk-FOOS3T5L.js +117 -0
- package/dist/chunk-FOOS3T5L.js.map +7 -0
- package/dist/chunk-JYWURG5T.js +32 -0
- package/dist/chunk-JYWURG5T.js.map +7 -0
- package/dist/chunk-TLZUUFOE.js +276 -0
- package/dist/chunk-TLZUUFOE.js.map +7 -0
- package/dist/chunk-UCJ2WD4D.js +625 -0
- package/dist/chunk-UCJ2WD4D.js.map +7 -0
- package/dist/chunk-WVFGV5FT.js +205 -0
- package/dist/chunk-WVFGV5FT.js.map +7 -0
- package/dist/core.js +1098 -0
- package/dist/core.js.map +7 -0
- package/dist/editor.js +1661 -0
- package/dist/editor.js.map +7 -0
- package/dist/flexdesk.css +5797 -0
- package/dist/host.js +132 -0
- package/dist/host.js.map +7 -0
- package/dist/reset.css +46 -0
- package/dist/tile_registry-6WZPTUZV.js +28 -0
- package/dist/tile_registry-6WZPTUZV.js.map +7 -0
- package/dist/tiles.js +2068 -0
- package/dist/tiles.js.map +7 -0
- package/dist/tokens.css +137 -0
- package/dist/widgets.js +5438 -0
- package/dist/widgets.js.map +7 -0
- package/dist/wm.js +4830 -0
- package/dist/wm.js.map +7 -0
- package/package.json +80 -0
- package/src/charts/chart_types.js +699 -0
- package/src/charts/data_series_plot_window.js +761 -0
- package/src/charts/downsample.js +119 -0
- package/src/charts/plot_config.js +176 -0
- package/src/charts/plot_popout_window.js +1560 -0
- package/src/charts/plotly_wrapper.js +432 -0
- package/src/core/event_bus.js +230 -0
- package/src/core/logging.js +300 -0
- package/src/core/settings.js +813 -0
- package/src/core/state_guard.js +382 -0
- package/src/core/state_guard_config.js +309 -0
- package/src/core/state_machine.js +258 -0
- package/src/editor/editor_pane.js +264 -0
- package/src/editor/monaco_editor_factory.js +449 -0
- package/src/editor/monaco_loader.js +74 -0
- package/src/editor/notebook_search.js +460 -0
- package/src/editor/notebook_tab_bar.js +456 -0
- package/src/editor/notebook_undo_manager.js +108 -0
- package/src/editor/split_pane_container.js +197 -0
- package/src/help/help_modal.js +409 -0
- package/src/help/help_registry.js +65 -0
- package/src/host/host.js +69 -0
- package/src/host/pywebview_host.js +133 -0
- package/src/tiles/config_schema.js +256 -0
- package/src/tiles/layout_persistence.js +662 -0
- package/src/tiles/tile_base.js +785 -0
- package/src/tiles/tile_grid.js +905 -0
- package/src/tiles/tile_registry.js +136 -0
- package/src/tiling/command_palette.js +328 -0
- package/src/tiling/content_registry.js +110 -0
- package/src/tiling/desktops.js +133 -0
- package/src/tiling/entity_sources.js +179 -0
- package/src/tiling/keymap.js +156 -0
- package/src/tiling/kind_taxonomy.js +172 -0
- package/src/tiling/landing_table.js +717 -0
- package/src/tiling/loading_overlay.js +33 -0
- package/src/tiling/nav_panel.js +142 -0
- package/src/tiling/page_factory.js +215 -0
- package/src/tiling/panel_keys.js +145 -0
- package/src/tiling/shell.js +437 -0
- package/src/tiling/tile_breadcrumb.js +222 -0
- package/src/tiling/tile_renderer.js +459 -0
- package/src/tiling/tile_tab_menu.js +340 -0
- package/src/tiling/tile_tree.js +880 -0
- package/src/tiling/wm.js +1352 -0
- package/src/ui/base/component_base.js +30 -0
- package/src/ui/base/controller_base.js +48 -0
- package/src/ui/base/page_base.js +102 -0
- package/src/ui/components/about_dialog.js +224 -0
- package/src/ui/components/action_dropdown.js +389 -0
- package/src/ui/components/attribute_list_editor.js +652 -0
- package/src/ui/components/autocomplete_field.js +732 -0
- package/src/ui/components/computing_status_window.js +81 -0
- package/src/ui/components/confirm_dialog.js +307 -0
- package/src/ui/components/context_menu.js +87 -0
- package/src/ui/components/data_table.js +2228 -0
- package/src/ui/components/detail_header.js +177 -0
- package/src/ui/components/drag_reorder.js +0 -0
- package/src/ui/components/gallery_picker.js +147 -0
- package/src/ui/components/inline_renamer.js +129 -0
- package/src/ui/components/managed_window.js +804 -0
- package/src/ui/components/modal.js +512 -0
- package/src/ui/components/notification_history.js +264 -0
- package/src/ui/components/slide_out_panel.js +185 -0
- package/src/ui/components/slider_field.js +122 -0
- package/src/ui/components/sortable_list.js +309 -0
- package/src/ui/components/table_state_store.js +81 -0
- package/src/ui/components/toast.js +59 -0
- package/src/ui/components/tree_view.js +460 -0
- package/src/ui/components/window_taskbar.js +143 -0
- package/src/ui/controllers/panel_state_machine.js +634 -0
- package/src/ui/controllers/window_chrome_controller.js +659 -0
- package/src/ui/notification_center.js +576 -0
- package/src/ui/utils/overlay_scrollbar.js +492 -0
- package/src/ui/utils/raf_resize_observer.js +31 -0
- package/src/ui/utils/tooltip_service.js +406 -0
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Monaco Editor Factory
|
|
3
|
+
*
|
|
4
|
+
* Creates and manages Monaco editor instances for cells.
|
|
5
|
+
* Handles:
|
|
6
|
+
* - Creating full editors (multi-line, for CodeCells)
|
|
7
|
+
* - Creating single-line inline editors (for ParameterCell values, expression fields)
|
|
8
|
+
* - Theme registration (ecosim-dark, ecosim-light)
|
|
9
|
+
* - Shared editor options
|
|
10
|
+
* - Instance lifecycle (dispose on cell removal)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { monacoReady, initMonaco } from './monaco_loader.js';
|
|
14
|
+
// The default language is the CONSUMER's. This imported ECOLANG_ID — EcoAgent's
|
|
15
|
+
// expression language — so a generic Monaco factory shipped knowing the name of one
|
|
16
|
+
// application's DSL. Register your language with Monaco and name it here.
|
|
17
|
+
let _defaultLanguage = 'plaintext';
|
|
18
|
+
|
|
19
|
+
/** @param {string} id A language id already registered with Monaco. */
|
|
20
|
+
export function setDefaultLanguage(id) {
|
|
21
|
+
if (typeof id === 'string' && id) _defaultLanguage = id;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Dispose a Monaco editor, swallowing the benign "Canceled" error from pending async ops. */
|
|
25
|
+
function safeDispose(editor) {
|
|
26
|
+
try { editor.dispose(); } catch (e) { if (e?.message !== 'Canceled') throw e; }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ─── Theme definitions ─────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
const ECOSIM_DARK_THEME = {
|
|
32
|
+
base: 'vs-dark',
|
|
33
|
+
inherit: true,
|
|
34
|
+
rules: [
|
|
35
|
+
{ token: 'comment', foreground: '6a9955', fontStyle: 'italic' },
|
|
36
|
+
{ token: 'keyword', foreground: '569cd6', fontStyle: 'bold' },
|
|
37
|
+
{ token: 'keyword.directive', foreground: 'c586c0', fontStyle: 'bold' },
|
|
38
|
+
{ token: 'keyword.operator', foreground: '569cd6' },
|
|
39
|
+
{ token: 'constant', foreground: 'c586c0' },
|
|
40
|
+
{ token: 'number', foreground: 'b5cea8' },
|
|
41
|
+
{ token: 'string', foreground: 'ce9178' },
|
|
42
|
+
{ token: 'type.identifier', foreground: '4ec9b0', fontStyle: 'bold' },
|
|
43
|
+
{ token: 'support.function', foreground: 'a6e22e' },
|
|
44
|
+
{ token: 'identifier', foreground: '4fc1ff' },
|
|
45
|
+
{ token: 'operator', foreground: 'd4d4d4' },
|
|
46
|
+
{ token: 'delimiter', foreground: '808080' },
|
|
47
|
+
],
|
|
48
|
+
colors: {
|
|
49
|
+
// Layer-2 input well — almost black, matches the EcoAgent
|
|
50
|
+
// three-layer contrast pattern (chrome #1a1a1a, content
|
|
51
|
+
// #212121, input/code #0a0a0a). Makes editable surfaces
|
|
52
|
+
// visually recess from the surrounding cards.
|
|
53
|
+
'editor.background': '#0a0a0a',
|
|
54
|
+
'editor.foreground': '#e0e0e0',
|
|
55
|
+
'editor.lineHighlightBackground': '#161616',
|
|
56
|
+
'editor.selectionBackground': '#264f78',
|
|
57
|
+
'editor.inactiveSelectionBackground': '#1f3350',
|
|
58
|
+
'editorLineNumber.foreground': '#5a5a5a',
|
|
59
|
+
'editorLineNumber.activeForeground': '#c6c6c6',
|
|
60
|
+
'editorCursor.foreground': '#39ff14',
|
|
61
|
+
'editor.findMatchBackground': '#9e6a03',
|
|
62
|
+
'editor.findMatchHighlightBackground': '#f2cc6030',
|
|
63
|
+
'editorIndentGuide.background1': '#2e2e2e',
|
|
64
|
+
'editorIndentGuide.activeBackground1': '#5a5a5a',
|
|
65
|
+
'editorGutter.background': '#0a0a0a',
|
|
66
|
+
// Pop-ups stay slightly lighter so they read as floating
|
|
67
|
+
// surfaces above the editor body, not part of it.
|
|
68
|
+
'editorWidget.background': '#1a1a1a',
|
|
69
|
+
'editorSuggestWidget.background': '#1a1a1a',
|
|
70
|
+
'editorSuggestWidget.border': '#383838',
|
|
71
|
+
'editorSuggestWidget.selectedBackground': '#094771',
|
|
72
|
+
'scrollbarSlider.background': '#4a4a4d40',
|
|
73
|
+
'scrollbarSlider.hoverBackground': '#4a4a4d70',
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const ECOSIM_LIGHT_THEME = {
|
|
78
|
+
base: 'vs',
|
|
79
|
+
inherit: true,
|
|
80
|
+
rules: [
|
|
81
|
+
{ token: 'comment', foreground: '6a737d', fontStyle: 'italic' },
|
|
82
|
+
{ token: 'keyword', foreground: 'd73a49', fontStyle: 'bold' },
|
|
83
|
+
{ token: 'keyword.directive', foreground: '6f42c1', fontStyle: 'bold' },
|
|
84
|
+
{ token: 'keyword.operator', foreground: 'd73a49' },
|
|
85
|
+
{ token: 'constant', foreground: 'e36209' },
|
|
86
|
+
{ token: 'number', foreground: '005cc5' },
|
|
87
|
+
{ token: 'string', foreground: '22863a' },
|
|
88
|
+
{ token: 'type.identifier', foreground: '005cc5', fontStyle: 'bold' },
|
|
89
|
+
{ token: 'support.function', foreground: '6f42c1' },
|
|
90
|
+
{ token: 'identifier', foreground: '24292e' },
|
|
91
|
+
{ token: 'operator', foreground: 'd73a49' },
|
|
92
|
+
{ token: 'delimiter', foreground: '586069' },
|
|
93
|
+
],
|
|
94
|
+
colors: {
|
|
95
|
+
'editor.background': '#ffffff',
|
|
96
|
+
'editor.foreground': '#24292e',
|
|
97
|
+
'editor.lineHighlightBackground': '#f6f8fa',
|
|
98
|
+
'editor.selectionBackground': '#c8d3e6',
|
|
99
|
+
'editorLineNumber.foreground': '#babbbd',
|
|
100
|
+
'editorLineNumber.activeForeground': '#586069',
|
|
101
|
+
'editorCursor.foreground': '#044289',
|
|
102
|
+
'editorGutter.background': '#ffffff',
|
|
103
|
+
'editorWidget.background': '#f6f8fa',
|
|
104
|
+
'editorSuggestWidget.background': '#f6f8fa',
|
|
105
|
+
'editorSuggestWidget.border': '#e1e4e8',
|
|
106
|
+
'scrollbarSlider.background': '#babbbd40',
|
|
107
|
+
'scrollbarSlider.hoverBackground': '#babbbd70',
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
let _themesRegistered = false;
|
|
112
|
+
|
|
113
|
+
function ensureThemes(monaco) {
|
|
114
|
+
if (_themesRegistered) return;
|
|
115
|
+
_themesRegistered = true;
|
|
116
|
+
monaco.editor.defineTheme('ecosim-dark', ECOSIM_DARK_THEME);
|
|
117
|
+
monaco.editor.defineTheme('ecosim-light', ECOSIM_LIGHT_THEME);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ─── Shared editor options ─────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
function baseOptions(overrides = {}) {
|
|
123
|
+
return {
|
|
124
|
+
language: _defaultLanguage,
|
|
125
|
+
theme: 'ecosim-dark',
|
|
126
|
+
fontFamily: 'Consolas, "Courier New", monospace',
|
|
127
|
+
fontSize: 13,
|
|
128
|
+
lineHeight: 19.5,
|
|
129
|
+
tabSize: 4,
|
|
130
|
+
insertSpaces: true,
|
|
131
|
+
automaticLayout: true,
|
|
132
|
+
minimap: { enabled: false },
|
|
133
|
+
scrollBeyondLastLine: false,
|
|
134
|
+
renderLineHighlight: 'gutter',
|
|
135
|
+
matchBrackets: 'always',
|
|
136
|
+
padding: { top: 8, bottom: 8 },
|
|
137
|
+
overviewRulerLanes: 0,
|
|
138
|
+
hideCursorInOverviewRuler: true,
|
|
139
|
+
overviewRulerBorder: false,
|
|
140
|
+
scrollbar: { handleMouseWheel: false },
|
|
141
|
+
folding: true,
|
|
142
|
+
lineDecorationsWidth: 0,
|
|
143
|
+
// Reserve a couple extra gutter columns so the right-aligned line
|
|
144
|
+
// numbers get some breathing room on the left instead of sitting flush
|
|
145
|
+
// against the editor edge (digits stay right-aligned; the slack lands
|
|
146
|
+
// on the left). Covers up to 5-digit files too.
|
|
147
|
+
lineNumbersMinChars: 5,
|
|
148
|
+
suggest: {
|
|
149
|
+
showKeywords: true,
|
|
150
|
+
showFunctions: true,
|
|
151
|
+
showVariables: true,
|
|
152
|
+
showConstants: true,
|
|
153
|
+
showModules: true,
|
|
154
|
+
snippetsPreventQuickSuggestions: false,
|
|
155
|
+
},
|
|
156
|
+
quickSuggestions: { other: true, comments: false, strings: false },
|
|
157
|
+
parameterHints: { enabled: true },
|
|
158
|
+
wordWrap: 'off',
|
|
159
|
+
fixedOverflowWidgets: true,
|
|
160
|
+
...overrides,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function singleLineOptions(overrides = {}) {
|
|
165
|
+
return baseOptions({
|
|
166
|
+
lineNumbers: 'off',
|
|
167
|
+
lineDecorationsWidth: 0,
|
|
168
|
+
lineNumbersMinChars: 0,
|
|
169
|
+
glyphMargin: false,
|
|
170
|
+
folding: false,
|
|
171
|
+
renderLineHighlight: 'none',
|
|
172
|
+
scrollbar: { vertical: 'hidden', horizontal: 'hidden', handleMouseWheel: false },
|
|
173
|
+
wordWrap: 'off',
|
|
174
|
+
overviewRulerLanes: 0,
|
|
175
|
+
padding: { top: 3, bottom: 3 },
|
|
176
|
+
...overrides,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ─── Factory ───────────────────────────────────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* @typedef {{ editor: object, dispose: () => void, getValue: () => string, setValue: (v: string) => void, onDidChange: (cb: () => void) => object }} EditorHandle
|
|
184
|
+
*/
|
|
185
|
+
|
|
186
|
+
export class MonacoEditorFactory {
|
|
187
|
+
#monaco = null;
|
|
188
|
+
#currentTheme = 'ecosim-dark';
|
|
189
|
+
|
|
190
|
+
constructor() {}
|
|
191
|
+
|
|
192
|
+
async init() {
|
|
193
|
+
this.#monaco = await monacoReady;
|
|
194
|
+
ensureThemes(this.#monaco);
|
|
195
|
+
return this;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
get monaco() { return this.#monaco; }
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Set global theme for all editors.
|
|
202
|
+
* @param {'ecosim-dark'|'ecosim-light'} theme
|
|
203
|
+
*/
|
|
204
|
+
setTheme(theme) {
|
|
205
|
+
this.#currentTheme = theme;
|
|
206
|
+
this.#monaco?.editor.setTheme(theme);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Create a full multi-line editor (for CodeCell).
|
|
211
|
+
*
|
|
212
|
+
* @param {HTMLElement} container
|
|
213
|
+
* @param {string} initialValue
|
|
214
|
+
* @param {{ language?: string, readOnly?: boolean, lineNumbers?: string } & object} [overrides]
|
|
215
|
+
* @returns {EditorHandle}
|
|
216
|
+
*/
|
|
217
|
+
createEditor(container, initialValue = '', overrides = {}) {
|
|
218
|
+
if (!this.#monaco) throw new Error('MonacoEditorFactory not initialized');
|
|
219
|
+
|
|
220
|
+
const { noAutoHeight, ...editorOverrides } = overrides;
|
|
221
|
+
|
|
222
|
+
const editor = this.#monaco.editor.create(container, {
|
|
223
|
+
...baseOptions({ theme: this.#currentTheme }),
|
|
224
|
+
value: initialValue,
|
|
225
|
+
...editorOverrides,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Auto-resize to content height.
|
|
229
|
+
// When the "limit code cell height" setting is active (data-code-max-height="true"
|
|
230
|
+
// on the editor container), cap at 400px so Monaco handles internal scrolling.
|
|
231
|
+
// Otherwise, grow to fit all content with no upper bound.
|
|
232
|
+
//
|
|
233
|
+
// Monaco's handleMouseWheel is toggled dynamically: disabled when the editor
|
|
234
|
+
// auto-sizes to fit content (so wheel events propagate to the notebook scroller),
|
|
235
|
+
// enabled only when content is capped and there is actual internal overflow.
|
|
236
|
+
//
|
|
237
|
+
// When noAutoHeight is true, skip auto-sizing entirely and let the container
|
|
238
|
+
// control dimensions (e.g. flex layout). Mouse wheel scrolling stays enabled.
|
|
239
|
+
let minHeight = 80;
|
|
240
|
+
let heightDisposable = null;
|
|
241
|
+
let updateHeight = null;
|
|
242
|
+
if (noAutoHeight) {
|
|
243
|
+
editor.updateOptions({ scrollbar: { handleMouseWheel: true } });
|
|
244
|
+
} else {
|
|
245
|
+
updateHeight = () => {
|
|
246
|
+
const maxHeightEnabled = container.closest('.notebook-editor-container')?.dataset.codeMaxHeight === 'true';
|
|
247
|
+
const maxH = maxHeightEnabled ? 400 : Infinity;
|
|
248
|
+
const rawContent = editor.getContentHeight();
|
|
249
|
+
const contentHeight = Math.min(maxH, Math.max(minHeight, rawContent));
|
|
250
|
+
container.style.height = `${contentHeight}px`;
|
|
251
|
+
editor.layout();
|
|
252
|
+
|
|
253
|
+
// Enable Monaco wheel handling only when content overflows the capped height
|
|
254
|
+
const needsInternalScroll = maxHeightEnabled && rawContent > maxH;
|
|
255
|
+
editor.updateOptions({ scrollbar: { handleMouseWheel: needsInternalScroll } });
|
|
256
|
+
};
|
|
257
|
+
heightDisposable = editor.onDidContentSizeChange(updateHeight);
|
|
258
|
+
updateHeight();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Re-evaluate height when the code-max-height setting changes on the ancestor
|
|
262
|
+
const editorContainer = container.closest('.notebook-editor-container');
|
|
263
|
+
let maxHeightObserver = null;
|
|
264
|
+
if (updateHeight && editorContainer) {
|
|
265
|
+
maxHeightObserver = new MutationObserver(() => updateHeight());
|
|
266
|
+
maxHeightObserver.observe(editorContainer, { attributes: true, attributeFilter: ['data-code-max-height'] });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Scroll the find widget into view when it becomes visible.
|
|
270
|
+
// The editor auto-sizes to content (no internal scroll), so the notebook
|
|
271
|
+
// container scrolls instead. The find widget sits at top:0 of the editor,
|
|
272
|
+
// which may be above the viewport for tall cells.
|
|
273
|
+
let findWidgetObserver = null;
|
|
274
|
+
const observeFindWidget = () => {
|
|
275
|
+
const overflowGuard = container.querySelector('.overflow-guard');
|
|
276
|
+
if (!overflowGuard) return;
|
|
277
|
+
|
|
278
|
+
findWidgetObserver = new MutationObserver(() => {
|
|
279
|
+
const fw = container.querySelector('.find-widget.visible');
|
|
280
|
+
if (fw) fw.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
|
281
|
+
});
|
|
282
|
+
findWidgetObserver.observe(overflowGuard, {
|
|
283
|
+
subtree: true,
|
|
284
|
+
attributes: true,
|
|
285
|
+
attributeFilter: ['class'],
|
|
286
|
+
});
|
|
287
|
+
};
|
|
288
|
+
// Defer slightly — Monaco builds DOM asynchronously after create()
|
|
289
|
+
requestAnimationFrame(observeFindWidget);
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
editor,
|
|
293
|
+
getValue: () => editor.getValue(),
|
|
294
|
+
setValue: (v) => { if (editor.getValue() !== v) editor.setValue(v); },
|
|
295
|
+
onDidChange: (cb) => editor.onDidChangeModelContent(cb),
|
|
296
|
+
focus: () => editor.focus(),
|
|
297
|
+
layout: () => editor.layout(),
|
|
298
|
+
setMinHeight: (h) => { minHeight = h; },
|
|
299
|
+
updateHeight,
|
|
300
|
+
dispose: () => {
|
|
301
|
+
findWidgetObserver?.disconnect();
|
|
302
|
+
maxHeightObserver?.disconnect();
|
|
303
|
+
heightDisposable?.dispose();
|
|
304
|
+
safeDispose(editor);
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Create a single-line inline editor (for ParameterCell value, expression fields).
|
|
311
|
+
*
|
|
312
|
+
* @param {HTMLElement} container
|
|
313
|
+
* @param {string} initialValue
|
|
314
|
+
* @param {{ language?: string, placeholder?: string } & object} [overrides]
|
|
315
|
+
* @returns {EditorHandle}
|
|
316
|
+
*/
|
|
317
|
+
createInlineEditor(container, initialValue = '', overrides = {}) {
|
|
318
|
+
if (!this.#monaco) throw new Error('MonacoEditorFactory not initialized');
|
|
319
|
+
|
|
320
|
+
container.style.height = '26px';
|
|
321
|
+
|
|
322
|
+
const editor = this.#monaco.editor.create(container, {
|
|
323
|
+
...singleLineOptions({ theme: this.#currentTheme }),
|
|
324
|
+
value: initialValue,
|
|
325
|
+
...overrides,
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// Prevent newlines in single-line mode
|
|
329
|
+
editor.onKeyDown((e) => {
|
|
330
|
+
if (e.keyCode === this.#monaco.KeyCode.Enter) {
|
|
331
|
+
e.preventDefault();
|
|
332
|
+
e.stopPropagation();
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
editor,
|
|
338
|
+
getValue: () => editor.getValue(),
|
|
339
|
+
setValue: (v) => { if (editor.getValue() !== v) editor.setValue(v); },
|
|
340
|
+
onDidChange: (cb) => editor.onDidChangeModelContent(cb),
|
|
341
|
+
focus: () => editor.focus(),
|
|
342
|
+
layout: () => editor.layout(),
|
|
343
|
+
dispose: () => safeDispose(editor),
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Create a plain text editor (for documentation cells with markdown).
|
|
349
|
+
* Uses VS Code's built-in markdown language, no EcoLang features.
|
|
350
|
+
*
|
|
351
|
+
* @param {HTMLElement} container
|
|
352
|
+
* @param {string} initialValue
|
|
353
|
+
* @returns {EditorHandle}
|
|
354
|
+
*/
|
|
355
|
+
createMarkdownEditor(container, initialValue = '') {
|
|
356
|
+
return this.createEditor(container, initialValue, {
|
|
357
|
+
language: 'markdown',
|
|
358
|
+
wordWrap: 'on',
|
|
359
|
+
lineNumbers: 'off',
|
|
360
|
+
folding: false,
|
|
361
|
+
quickSuggestions: false,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Create a read-only DSL viewer (shown in the "DSL" tab of a Godley/parameter cell).
|
|
367
|
+
*
|
|
368
|
+
* @param {HTMLElement} container
|
|
369
|
+
* @param {string} dslContent
|
|
370
|
+
* @returns {EditorHandle}
|
|
371
|
+
*/
|
|
372
|
+
createDslViewer(container, dslContent = '') {
|
|
373
|
+
return this.createEditor(container, dslContent, {
|
|
374
|
+
readOnly: true,
|
|
375
|
+
lineNumbers: 'off',
|
|
376
|
+
glyphMargin: false,
|
|
377
|
+
folding: false,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Create a side-by-side diff editor for comparing two versions of a file.
|
|
383
|
+
*
|
|
384
|
+
* @param {HTMLElement} container
|
|
385
|
+
* @param {string} originalContent - Left side (e.g. HEAD version)
|
|
386
|
+
* @param {string} modifiedContent - Right side (e.g. working copy)
|
|
387
|
+
* @param {{ language?: string, readOnly?: boolean }} [options]
|
|
388
|
+
* @returns {{ editor: object, dispose: Function, setModels: Function }}
|
|
389
|
+
*/
|
|
390
|
+
createDiffEditor(container, originalContent = '', modifiedContent = '', options = {}) {
|
|
391
|
+
const monaco = this.#monaco;
|
|
392
|
+
const language = options.language || 'plaintext';
|
|
393
|
+
|
|
394
|
+
const originalModel = monaco.editor.createModel(originalContent, language);
|
|
395
|
+
const modifiedModel = monaco.editor.createModel(modifiedContent, language);
|
|
396
|
+
|
|
397
|
+
const diffEditor = monaco.editor.createDiffEditor(container, {
|
|
398
|
+
theme: this.#currentTheme,
|
|
399
|
+
readOnly: options.readOnly !== false,
|
|
400
|
+
renderSideBySide: true,
|
|
401
|
+
automaticLayout: true,
|
|
402
|
+
minimap: { enabled: false },
|
|
403
|
+
scrollBeyondLastLine: false,
|
|
404
|
+
lineNumbers: 'on',
|
|
405
|
+
glyphMargin: false,
|
|
406
|
+
folding: true,
|
|
407
|
+
renderOverviewRuler: false,
|
|
408
|
+
enableSplitViewResizing: true,
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
diffEditor.setModel({ original: originalModel, modified: modifiedModel });
|
|
412
|
+
|
|
413
|
+
return {
|
|
414
|
+
editor: diffEditor,
|
|
415
|
+
setModels(original, modified, lang) {
|
|
416
|
+
originalModel.dispose();
|
|
417
|
+
modifiedModel.dispose();
|
|
418
|
+
const newOrig = monaco.editor.createModel(original, lang || language);
|
|
419
|
+
const newMod = monaco.editor.createModel(modified, lang || language);
|
|
420
|
+
diffEditor.setModel({ original: newOrig, modified: newMod });
|
|
421
|
+
},
|
|
422
|
+
dispose() {
|
|
423
|
+
safeDispose(diffEditor);
|
|
424
|
+
safeDispose(originalModel);
|
|
425
|
+
safeDispose(modifiedModel);
|
|
426
|
+
},
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/** Singleton instance, initialized lazily. */
|
|
432
|
+
let _factoryInstance = null;
|
|
433
|
+
|
|
434
|
+
// Where the app keeps Monaco. Set once, at boot.
|
|
435
|
+
let _monacoBasePath = null;
|
|
436
|
+
|
|
437
|
+
/** @param {string} basePath */
|
|
438
|
+
export function setMonacoBasePath(basePath) {
|
|
439
|
+
if (typeof basePath === 'string' && basePath) _monacoBasePath = basePath;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
export async function getEditorFactory() {
|
|
443
|
+
if (!_factoryInstance) {
|
|
444
|
+
// Ensure Monaco is initialized — initMonaco is idempotent, safe to call from any entry point
|
|
445
|
+
initMonaco(_monacoBasePath);
|
|
446
|
+
_factoryInstance = await new MonacoEditorFactory().init();
|
|
447
|
+
}
|
|
448
|
+
return _factoryInstance;
|
|
449
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Monaco Loader — wraps the AMD require() bootstrap into an ES module Promise.
|
|
3
|
+
*
|
|
4
|
+
* Monaco ships as AMD modules. vs/loader.js is injected dynamically on demand
|
|
5
|
+
* (NOT via a global <script> tag) so it does not pollute window.define before
|
|
6
|
+
* Plotly loads. Plotly auto-detects AMD define and would wrap itself as a module
|
|
7
|
+
* instead of setting window.Plotly if loader.js ran at page load.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
let _resolveMonaco;
|
|
11
|
+
let _monacoReady = false;
|
|
12
|
+
|
|
13
|
+
/** @type {Promise<typeof monaco>} */
|
|
14
|
+
export const monacoReady = new Promise(resolve => { _resolveMonaco = resolve; });
|
|
15
|
+
|
|
16
|
+
function injectLoaderScript(src) {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
const s = document.createElement('script');
|
|
19
|
+
s.src = src;
|
|
20
|
+
s.onload = resolve;
|
|
21
|
+
s.onerror = () => reject(new Error(`Failed to load Monaco loader: ${src}`));
|
|
22
|
+
document.head.appendChild(s);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function injectMonacoCss(basePath) {
|
|
27
|
+
const id = 'monaco-editor-css';
|
|
28
|
+
if (document.getElementById(id)) return;
|
|
29
|
+
const link = document.createElement('link');
|
|
30
|
+
link.id = id;
|
|
31
|
+
link.rel = 'stylesheet';
|
|
32
|
+
link.href = `${basePath}/vs/editor/editor.main.css`;
|
|
33
|
+
document.head.appendChild(link);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Call once when the notebook page initialises.
|
|
38
|
+
* Where Monaco lives is the CONSUMER's business.
|
|
39
|
+
*
|
|
40
|
+
* `basePath` defaulted to 'vendor/monaco' — a path into ONE application's tree,
|
|
41
|
+
* which would 404 for anyone else. Exactly the bug plotly_wrapper.js had, and it
|
|
42
|
+
* takes exactly the same fix: no default, and say so if nobody supplies one.
|
|
43
|
+
*
|
|
44
|
+
* Monaco is a PEER dependency — 14 MB this bundle deliberately does not contain.
|
|
45
|
+
*
|
|
46
|
+
* @param {string} basePath Path prefix for the vs/ directory. REQUIRED.
|
|
47
|
+
*/
|
|
48
|
+
export async function initMonaco(basePath) {
|
|
49
|
+
if (!basePath) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
'[twm/editor] initMonaco() needs the path to your copy of Monaco, e.g. '
|
|
52
|
+
+ "initMonaco('vendor/monaco'). Monaco is a peer dependency; the library "
|
|
53
|
+
+ 'does not guess where you put it.');
|
|
54
|
+
}
|
|
55
|
+
if (_monacoReady) return monacoReady;
|
|
56
|
+
_monacoReady = true;
|
|
57
|
+
|
|
58
|
+
// Inject CSS and loader.js dynamically — kept out of the HTML to avoid global bleed
|
|
59
|
+
injectMonacoCss(basePath);
|
|
60
|
+
if (!window.require) {
|
|
61
|
+
await injectLoaderScript(`${basePath}/vs/loader.js`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
window.require.config({
|
|
65
|
+
paths: { vs: `${basePath}/vs` },
|
|
66
|
+
'vs/nls': { availableLanguages: {} },
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
window.require(['vs/editor/editor.main'], () => {
|
|
70
|
+
_resolveMonaco(window.monaco);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return monacoReady;
|
|
74
|
+
}
|