@calliopelabs/ui 0.14.0 → 0.18.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/README.md +235 -1
- package/dist/agent/AgentChat.d.ts +8 -1
- package/dist/agent/AgentChat.js +43 -2
- package/dist/agent/WorkflowRenderers.d.ts +61 -0
- package/dist/agent/WorkflowRenderers.js +102 -0
- package/dist/agent/controller.d.ts +19 -0
- package/dist/agent/controller.js +33 -0
- package/dist/agent/copilotkit/CopilotKitAgentProvider.js +2 -2
- package/dist/agent/index.d.ts +4 -2
- package/dist/agent/index.js +3 -1
- package/dist/agent/mock.d.ts +26 -4
- package/dist/agent/mock.js +68 -37
- package/dist/agent/renderers.d.ts +49 -0
- package/dist/agent/renderers.js +27 -0
- package/dist/appearance/brands.d.ts +17 -0
- package/dist/appearance/brands.js +14 -0
- package/dist/chat/MessageBubble.js +1 -1
- package/dist/chat/MessageList.d.ts +9 -2
- package/dist/chat/MessageList.js +11 -8
- package/dist/css/appearance.js +1 -1
- package/dist/css/calliope.css +5 -0
- package/dist/css/tokens.css +5 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.js +8 -1
- package/dist/primitives/Badge.d.ts +14 -0
- package/dist/primitives/Badge.js +25 -0
- package/dist/primitives/ContextMenu.d.ts +18 -0
- package/dist/primitives/ContextMenu.js +34 -0
- package/dist/primitives/Menubar.d.ts +21 -0
- package/dist/primitives/Menubar.js +43 -0
- package/dist/primitives/ScrollArea.d.ts +16 -0
- package/dist/primitives/ScrollArea.js +11 -0
- package/dist/primitives/Toast.d.ts +37 -0
- package/dist/primitives/Toast.js +43 -0
- package/dist/primitives/Tooltip.d.ts +16 -0
- package/dist/primitives/Tooltip.js +26 -0
- package/dist/provider/messages.d.ts +217 -0
- package/dist/provider/messages.js +238 -0
- package/dist/shell/RailPanel.js +1 -1
- package/dist/theme/css-layer.js +2 -1
- package/dist/theme/tokens.generated.d.ts +3 -0
- package/dist/theme/tokens.generated.js +3 -0
- package/dist/workbench/ActivityBar.d.ts +37 -0
- package/dist/workbench/ActivityBar.js +57 -0
- package/dist/workbench/BottomPanel.d.ts +30 -0
- package/dist/workbench/BottomPanel.js +20 -0
- package/dist/workbench/CodeEditor.d.ts +48 -0
- package/dist/workbench/CodeEditor.js +153 -0
- package/dist/workbench/CommandPalette.d.ts +49 -0
- package/dist/workbench/CommandPalette.js +114 -0
- package/dist/workbench/DataChart.d.ts +45 -0
- package/dist/workbench/DataChart.js +146 -0
- package/dist/workbench/DataGrid.d.ts +89 -0
- package/dist/workbench/DataGrid.js +590 -0
- package/dist/workbench/DataProfile.d.ts +17 -0
- package/dist/workbench/DataProfile.js +51 -0
- package/dist/workbench/EditorTabs.d.ts +62 -0
- package/dist/workbench/EditorTabs.js +115 -0
- package/dist/workbench/FileBrowser.d.ts +62 -0
- package/dist/workbench/FileBrowser.js +117 -0
- package/dist/workbench/FileGrid.d.ts +13 -0
- package/dist/workbench/FileGrid.js +114 -0
- package/dist/workbench/FileIcon.d.ts +10 -0
- package/dist/workbench/FileIcon.js +24 -0
- package/dist/workbench/FileList.d.ts +35 -0
- package/dist/workbench/FileList.js +160 -0
- package/dist/workbench/FilePreview.d.ts +66 -0
- package/dist/workbench/FilePreview.js +108 -0
- package/dist/workbench/FileTree.d.ts +37 -0
- package/dist/workbench/FileTree.js +218 -0
- package/dist/workbench/GridCell.d.ts +19 -0
- package/dist/workbench/GridCell.js +50 -0
- package/dist/workbench/GridDetails.d.ts +31 -0
- package/dist/workbench/GridDetails.js +78 -0
- package/dist/workbench/MenuBar.d.ts +49 -0
- package/dist/workbench/MenuBar.js +25 -0
- package/dist/workbench/PaneGroup.d.ts +50 -0
- package/dist/workbench/PaneGroup.js +146 -0
- package/dist/workbench/PendingChangesBar.d.ts +20 -0
- package/dist/workbench/PendingChangesBar.js +19 -0
- package/dist/workbench/Shortcuts.d.ts +50 -0
- package/dist/workbench/Shortcuts.js +109 -0
- package/dist/workbench/SqlEditor.d.ts +21 -0
- package/dist/workbench/SqlEditor.js +79 -0
- package/dist/workbench/StatusBar.d.ts +28 -0
- package/dist/workbench/StatusBar.js +26 -0
- package/dist/workbench/TruncationBanner.d.ts +11 -0
- package/dist/workbench/TruncationBanner.js +12 -0
- package/dist/workbench/UploadDropzone.d.ts +52 -0
- package/dist/workbench/UploadDropzone.js +115 -0
- package/dist/workbench/WorkbenchShell.d.ts +36 -0
- package/dist/workbench/WorkbenchShell.js +20 -0
- package/dist/workbench/dnd.d.ts +17 -0
- package/dist/workbench/dnd.js +77 -0
- package/dist/workbench/download.d.ts +11 -0
- package/dist/workbench/download.js +28 -0
- package/dist/workbench/editor-theme.d.ts +42 -0
- package/dist/workbench/editor-theme.js +113 -0
- package/dist/workbench/grid-format.d.ts +59 -0
- package/dist/workbench/grid-format.js +169 -0
- package/dist/workbench/grid-model.d.ts +75 -0
- package/dist/workbench/grid-model.js +259 -0
- package/dist/workbench/grid-selection.d.ts +69 -0
- package/dist/workbench/grid-selection.js +183 -0
- package/dist/workbench/grid-stats.d.ts +58 -0
- package/dist/workbench/grid-stats.js +185 -0
- package/dist/workbench/index.d.ts +44 -0
- package/dist/workbench/index.js +47 -0
- package/dist/workbench/keys.d.ts +23 -0
- package/dist/workbench/keys.js +105 -0
- package/dist/workbench/model.d.ts +72 -0
- package/dist/workbench/model.js +214 -0
- package/dist/workbench/palette.d.ts +17 -0
- package/dist/workbench/palette.js +45 -0
- package/dist/workbench/pending.d.ts +92 -0
- package/dist/workbench/pending.js +207 -0
- package/dist/workbench/selection.d.ts +16 -0
- package/dist/workbench/selection.js +46 -0
- package/dist/workbench/sql-completion.d.ts +107 -0
- package/dist/workbench/sql-completion.js +475 -0
- package/dist/workbench/typeahead.d.ts +15 -0
- package/dist/workbench/typeahead.js +22 -0
- package/dist/workflow/WorkflowRuntimeControls.d.ts +21 -0
- package/dist/workflow/WorkflowRuntimeControls.js +54 -0
- package/dist/workflow/runtime-controls.d.ts +29 -0
- package/dist/workflow/runtime-controls.js +10 -0
- package/package.json +17 -2
|
@@ -144,6 +144,43 @@ function problemText({ code, values: v }) {
|
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
export const defaultMessages = {
|
|
147
|
+
workflowRuntime: {
|
|
148
|
+
title: "Agent controls",
|
|
149
|
+
state: (state) => ({
|
|
150
|
+
running: "Agent running",
|
|
151
|
+
paused: "Agent paused",
|
|
152
|
+
"pause-requested": "Pause requested",
|
|
153
|
+
"resume-requested": "Resume requested",
|
|
154
|
+
"steering-queued": "Steering queued",
|
|
155
|
+
"steering-applied": "Steering applied",
|
|
156
|
+
refused: "Control refused",
|
|
157
|
+
"delivery-unknown": "Delivery unconfirmed",
|
|
158
|
+
unavailable: "Controls unavailable",
|
|
159
|
+
finished: "Agent finished",
|
|
160
|
+
})[state],
|
|
161
|
+
description: (state) => ({
|
|
162
|
+
running: "Control this agent while it works on the original goal.",
|
|
163
|
+
paused: "The agent confirmed its pause. Resume continues the same goal.",
|
|
164
|
+
"pause-requested": "Waiting for the agent to confirm its pause.",
|
|
165
|
+
"resume-requested": "Waiting for the agent to confirm it resumed.",
|
|
166
|
+
"steering-queued": "The agent has not yet confirmed this steering message.",
|
|
167
|
+
"steering-applied": "The agent applied the steering message to this goal.",
|
|
168
|
+
refused: "The command was refused. Check the current agent state before continuing.",
|
|
169
|
+
"delivery-unknown": "Check the original command's status before sending another command.",
|
|
170
|
+
unavailable: "The current agent controls could not be read.",
|
|
171
|
+
finished: "This agent is no longer accepting commands.",
|
|
172
|
+
})[state],
|
|
173
|
+
loading: "Loading agent controls…",
|
|
174
|
+
empty: "The original agent is not available.",
|
|
175
|
+
readOnly: "An operator can control this agent.",
|
|
176
|
+
pause: "Pause agent",
|
|
177
|
+
resume: "Resume agent",
|
|
178
|
+
refresh: "Check agent status",
|
|
179
|
+
steering: "Steering message",
|
|
180
|
+
steeringHint: "Up to 4,000 characters. Steering preserves the original goal and limits.",
|
|
181
|
+
invalidSteering: "Enter a nonempty message of up to 4,000 valid characters.",
|
|
182
|
+
send: "Send steering",
|
|
183
|
+
},
|
|
147
184
|
workflowQuestion: {
|
|
148
185
|
title: "Question for this run",
|
|
149
186
|
worker: (worker) => `Worker: ${worker}`,
|
|
@@ -236,6 +273,202 @@ export const defaultMessages = {
|
|
|
236
273
|
dialog: {
|
|
237
274
|
close: "Close",
|
|
238
275
|
},
|
|
276
|
+
workbench: {
|
|
277
|
+
files: "Files",
|
|
278
|
+
folders: "Folders",
|
|
279
|
+
root: "Home",
|
|
280
|
+
location: "Location",
|
|
281
|
+
filesIn: (folder) => `Files in ${folder}`,
|
|
282
|
+
view: "View",
|
|
283
|
+
listView: "List",
|
|
284
|
+
gridView: "Grid",
|
|
285
|
+
name: "Name",
|
|
286
|
+
size: "Size",
|
|
287
|
+
modified: "Modified",
|
|
288
|
+
noSize: "No size",
|
|
289
|
+
loading: "Loading files",
|
|
290
|
+
loadFailed: "Could not load this folder",
|
|
291
|
+
retry: "Retry",
|
|
292
|
+
retryFolder: "Could not load. Retry",
|
|
293
|
+
empty: "This folder is empty",
|
|
294
|
+
emptyDescription: "Drop files here to upload them.",
|
|
295
|
+
noFolders: "No folders",
|
|
296
|
+
itemCount: (count) => `${count.toLocaleString()} ${count === 1 ? "item" : "items"}`,
|
|
297
|
+
selected: (count) => `${count.toLocaleString()} selected`,
|
|
298
|
+
dropToUpload: "Drop files to upload",
|
|
299
|
+
dropHere: "Drop files here",
|
|
300
|
+
browse: "Choose files",
|
|
301
|
+
maxSize: (size) => `Up to ${size} each`,
|
|
302
|
+
uploads: "Uploads",
|
|
303
|
+
uploading: (count) => `Uploading ${count.toLocaleString()} ${count === 1 ? "file" : "files"}`,
|
|
304
|
+
uploadsDone: "Uploads finished",
|
|
305
|
+
uploadState: (state) => ({
|
|
306
|
+
queued: "Waiting",
|
|
307
|
+
uploading: "Uploading",
|
|
308
|
+
done: "Done",
|
|
309
|
+
error: "Failed",
|
|
310
|
+
canceled: "Cancelled",
|
|
311
|
+
})[state],
|
|
312
|
+
cancelUpload: (name) => `Cancel upload of ${name}`,
|
|
313
|
+
dismissUpload: (name) => `Clear ${name}`,
|
|
314
|
+
previewOf: (name) => `Preview of ${name}`,
|
|
315
|
+
previewLoading: "Loading preview",
|
|
316
|
+
previewFailed: "Could not preview this file",
|
|
317
|
+
noPreview: "No preview for this file",
|
|
318
|
+
closePreview: "Close preview",
|
|
319
|
+
contentsOf: (name) => `Contents of ${name}`,
|
|
320
|
+
showing: (shown, total) => total === null
|
|
321
|
+
? `Showing the first ${shown.toLocaleString()}`
|
|
322
|
+
: `Showing ${shown.toLocaleString()} of ${total.toLocaleString()}`,
|
|
323
|
+
partial: "Showing the start of the file",
|
|
324
|
+
column: (index) => `Column ${index}`,
|
|
325
|
+
noRows: "No rows",
|
|
326
|
+
menuBar: "Menu bar",
|
|
327
|
+
activityBar: "Activity bar",
|
|
328
|
+
openEditors: "Open editors",
|
|
329
|
+
unsaved: "unsaved",
|
|
330
|
+
closeTab: (title) => `Close ${title}`,
|
|
331
|
+
allTabs: (count) => `All open tabs (${count.toLocaleString()})`,
|
|
332
|
+
resize: (pane) => `Resize ${pane}`,
|
|
333
|
+
maximizePanel: "Maximize panel",
|
|
334
|
+
restorePanel: "Restore panel",
|
|
335
|
+
closePanel: "Close panel",
|
|
336
|
+
statusBar: "Status bar",
|
|
337
|
+
commandPalette: "Command palette",
|
|
338
|
+
searchCommands: "Type a command or search",
|
|
339
|
+
noCommands: "No matching commands",
|
|
340
|
+
commandsLoading: "Loading",
|
|
341
|
+
recent: "Recent",
|
|
342
|
+
keyboardShortcuts: "Keyboard shortcuts",
|
|
343
|
+
keyboardShortcutsDescription: "Every keyboard shortcut this app responds to",
|
|
344
|
+
keyJoin: "+",
|
|
345
|
+
sideBar: "Side bar",
|
|
346
|
+
editor: "Editor",
|
|
347
|
+
panel: "Panel",
|
|
348
|
+
secondarySideBar: "Secondary side bar",
|
|
349
|
+
gridLabel: "Results",
|
|
350
|
+
gridRows: (shown, total) => shown === total
|
|
351
|
+
? `${total.toLocaleString()} ${total === 1 ? "row" : "rows"}`
|
|
352
|
+
: `${shown.toLocaleString()} of ${total.toLocaleString()} rows`,
|
|
353
|
+
gridColumns: (count) => `${count.toLocaleString()} ${count === 1 ? "column" : "columns"}`,
|
|
354
|
+
gridFilter: "Filter rows",
|
|
355
|
+
gridClearFilter: "Clear filter",
|
|
356
|
+
gridHidden: (count) => `${count.toLocaleString()} hidden`,
|
|
357
|
+
gridSelected: (count) => `${count.toLocaleString()} selected`,
|
|
358
|
+
gridSelectAll: "Select all cells",
|
|
359
|
+
gridExport: "Export",
|
|
360
|
+
gridExportScope: (rows, columns) => `The current view: ${rows.toLocaleString()} ${rows === 1 ? "row" : "rows"}, ${columns.toLocaleString()} ${columns === 1 ? "column" : "columns"}`,
|
|
361
|
+
gridExportFormat: (format) => ({
|
|
362
|
+
csv: "CSV",
|
|
363
|
+
tsv: "TSV",
|
|
364
|
+
json: "JSON",
|
|
365
|
+
jsonl: "JSON Lines",
|
|
366
|
+
sql: "SQL INSERT",
|
|
367
|
+
markdown: "Markdown",
|
|
368
|
+
})[format],
|
|
369
|
+
gridSortBy: (column) => `Sort by ${column}`,
|
|
370
|
+
gridSortAscending: "Sort ascending",
|
|
371
|
+
gridSortDescending: "Sort descending",
|
|
372
|
+
gridClearSort: "Clear sort",
|
|
373
|
+
gridColumnMenu: (column) => `${column} column menu`,
|
|
374
|
+
gridFilterByValue: (value) => value === null
|
|
375
|
+
? "Filter by value"
|
|
376
|
+
: `Filter by \u201c${value.length > 20 ? `${value.slice(0, 20)}\u2026` : value}\u201d`,
|
|
377
|
+
gridHideColumn: "Hide column",
|
|
378
|
+
gridShowAllColumns: (hidden) => `Show all columns (${hidden.toLocaleString()} hidden)`,
|
|
379
|
+
gridCopyColumnName: "Copy column name",
|
|
380
|
+
gridResizeColumn: (column) => `Resize ${column}`,
|
|
381
|
+
gridType: (type) => type,
|
|
382
|
+
gridPrimaryKey: "Primary key",
|
|
383
|
+
gridForeignKey: (references) => (references ? `Foreign key to ${references}` : "Foreign key"),
|
|
384
|
+
gridNullable: "nullable",
|
|
385
|
+
gridNotNull: "not null",
|
|
386
|
+
gridNull: "NULL",
|
|
387
|
+
gridEmptyText: "empty",
|
|
388
|
+
gridBinary: (bytes) => `binary, ${bytes.toLocaleString()} bytes`,
|
|
389
|
+
gridNoRows: "No rows",
|
|
390
|
+
gridNoMatches: "No rows match the filter",
|
|
391
|
+
gridLoading: "Loading rows",
|
|
392
|
+
gridLoadFailed: "Could not load the rows",
|
|
393
|
+
gridCopy: "Copy",
|
|
394
|
+
gridCopyWithHeaders: "Copy with headers",
|
|
395
|
+
gridCopyAs: "Copy as",
|
|
396
|
+
gridCopyFormat: (format) => ({
|
|
397
|
+
tsv: "TSV",
|
|
398
|
+
"tsv-headers": "TSV with headers",
|
|
399
|
+
csv: "CSV",
|
|
400
|
+
json: "JSON",
|
|
401
|
+
insert: "INSERT statements",
|
|
402
|
+
markdown: "Markdown",
|
|
403
|
+
})[format],
|
|
404
|
+
gridRowDetail: "Row detail",
|
|
405
|
+
gridCellDetail: "Expand cell",
|
|
406
|
+
gridClearSelection: "Clear selection",
|
|
407
|
+
gridStatistics: "Selection statistics",
|
|
408
|
+
gridStat: (stat) => ({
|
|
409
|
+
rows: "Rows",
|
|
410
|
+
nulls: "Nulls",
|
|
411
|
+
distinct: "Distinct",
|
|
412
|
+
min: "Min",
|
|
413
|
+
max: "Max",
|
|
414
|
+
avg: "Avg",
|
|
415
|
+
sum: "Sum",
|
|
416
|
+
})[stat],
|
|
417
|
+
gridNoNumeric: "No numeric values selected",
|
|
418
|
+
gridTruncated: (shown, total) => total === null
|
|
419
|
+
? `Truncated at the ${shown.toLocaleString()}-row limit; more rows match.`
|
|
420
|
+
: `Showing ${shown.toLocaleString()} of ${total.toLocaleString()} rows, truncated at the ${shown.toLocaleString()}-row limit.`,
|
|
421
|
+
gridCharacters: (count) => `${count.toLocaleString()} ${count === 1 ? "character" : "characters"}`,
|
|
422
|
+
gridBytes: (count, truncated) => `${count.toLocaleString()} bytes${truncated ? ", preview truncated" : ""}`,
|
|
423
|
+
gridDownload: "Download",
|
|
424
|
+
gridCopied: "Copied",
|
|
425
|
+
gridCopyValue: "Copy",
|
|
426
|
+
gridRowOf: (row, total) => `Row ${row.toLocaleString()} of ${total.toLocaleString()}`,
|
|
427
|
+
gridPreviousRow: "Previous row",
|
|
428
|
+
gridNextRow: "Next row",
|
|
429
|
+
gridCopyRow: "Copy row",
|
|
430
|
+
gridAddRow: "Add row",
|
|
431
|
+
gridDeleteRows: (count) => count === 1 ? "Delete row" : `Delete ${count.toLocaleString()} rows`,
|
|
432
|
+
gridDuplicateRow: "Duplicate row",
|
|
433
|
+
gridRevertRow: "Revert row",
|
|
434
|
+
gridEditCell: (column) => `Edit ${column}`,
|
|
435
|
+
gridRowState: (state) => state,
|
|
436
|
+
gridPendingChanges: "Pending changes",
|
|
437
|
+
gridPendingSummary: (inserts, updates, deletes) => [
|
|
438
|
+
`${inserts.toLocaleString()} ${inserts === 1 ? "insert" : "inserts"}`,
|
|
439
|
+
`${updates.toLocaleString()} ${updates === 1 ? "update" : "updates"}`,
|
|
440
|
+
`${deletes.toLocaleString()} ${deletes === 1 ? "delete" : "deletes"}`,
|
|
441
|
+
].join(", "),
|
|
442
|
+
gridCommit: "Commit",
|
|
443
|
+
gridCommitting: "Committing",
|
|
444
|
+
gridDiscard: "Discard",
|
|
445
|
+
gridCommitFailed: (message, row) => `${row === null ? "" : `Row ${row.toLocaleString()}: `}${message} Nothing was written.`,
|
|
446
|
+
gridNoPrimaryKey: "No primary key: rows can be added, not changed or deleted.",
|
|
447
|
+
profileLabel: "Column profile",
|
|
448
|
+
profileSummary: (rows, columns, nulls) => `Profile of ${rows.toLocaleString()} ${rows === 1 ? "row" : "rows"}, ${columns.toLocaleString()} ${columns === 1 ? "column" : "columns"}, ${nulls.toLocaleString()} ${nulls === 1 ? "null" : "nulls"}`,
|
|
449
|
+
profileKind: (kind) => kind,
|
|
450
|
+
profileComplete: (share) => `${share.toLocaleString(undefined, { style: "percent", maximumFractionDigits: 1 })} complete`,
|
|
451
|
+
profileMean: "Mean",
|
|
452
|
+
profileMedian: "Median",
|
|
453
|
+
profileTopValues: "Top values",
|
|
454
|
+
profileNoData: "No data to profile",
|
|
455
|
+
profileCap: (cap, fetched) => `Profiling the first ${cap.toLocaleString()} of ${fetched.toLocaleString()} rows.`,
|
|
456
|
+
chartLabel: "Chart",
|
|
457
|
+
chartType: "Chart type",
|
|
458
|
+
chartKind: (kind) => ({ bar: "Bar", line: "Line", area: "Area", scatter: "Scatter", pie: "Pie" })[kind],
|
|
459
|
+
chartX: "X axis",
|
|
460
|
+
chartY: "Y axis",
|
|
461
|
+
chartPickY: "Choose at least one Y axis column",
|
|
462
|
+
chartNoData: "No data to chart",
|
|
463
|
+
chartLoading: "Loading the chart",
|
|
464
|
+
chartLoadFailed: "Could not load the chart library",
|
|
465
|
+
chartCap: (cap, fetched) => `Charting the first ${cap.toLocaleString()} of ${fetched.toLocaleString()} rows.`,
|
|
466
|
+
editorLoading: "Loading the editor",
|
|
467
|
+
},
|
|
468
|
+
toast: {
|
|
469
|
+
region: "Notifications ({hotkey})",
|
|
470
|
+
close: "Dismiss notification",
|
|
471
|
+
},
|
|
239
472
|
confirm: {
|
|
240
473
|
confirm: "Confirm",
|
|
241
474
|
cancel: "Cancel",
|
|
@@ -851,6 +1084,11 @@ export const defaultMessages = {
|
|
|
851
1084
|
emptyDescription: "Ask about what is on this page, or pick a starting point.",
|
|
852
1085
|
waitingOnYou: "Answer the card above to continue.",
|
|
853
1086
|
shortcut: (keys) => `Shortcut ${keys}`,
|
|
1087
|
+
interrupt: "The agent is waiting for you",
|
|
1088
|
+
decline: "Decline",
|
|
1089
|
+
back: "Back",
|
|
1090
|
+
sendReview: "Send review",
|
|
1091
|
+
sharedState: "Shared state",
|
|
854
1092
|
},
|
|
855
1093
|
};
|
|
856
1094
|
export function mergeMessages(overrides) {
|
package/dist/shell/RailPanel.js
CHANGED
|
@@ -27,7 +27,7 @@ export function RailPanel({ label, title, actions, footer, children, collapsed,
|
|
|
27
27
|
}
|
|
28
28
|
/* A focusable separator is the WAI-ARIA window splitter, an interactive
|
|
29
29
|
widget; jsx-a11y counts every separator as static. */
|
|
30
|
-
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions
|
|
30
|
+
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
|
|
31
31
|
function ResizeHandle({ label, width, minWidth, maxWidth, onWidthChange, }) {
|
|
32
32
|
const drag = React.useRef(null);
|
|
33
33
|
return (_jsx("div", { role: "separator", "aria-orientation": "vertical", "aria-label": label, "aria-valuenow": width, "aria-valuemin": minWidth, "aria-valuemax": maxWidth, tabIndex: 0, onKeyDown: (event) => {
|
package/dist/theme/css-layer.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BOOT_SOURCE, bootConfig } from "../appearance/boot.js";
|
|
2
|
-
import { astroliftBrand, chatStudioBrand, cykickBrand, loadrBrand, workbenchBrand, zentinelleBrand, } from "../appearance/brands.js";
|
|
2
|
+
import { astroliftBrand, chatStudioBrand, cykickBrand, deepDataAgentBrand, loadrBrand, workbenchBrand, zentinelleBrand, } from "../appearance/brands.js";
|
|
3
3
|
import { CORNERS, defineProduct } from "../appearance/product.js";
|
|
4
4
|
import { accentDeclarations, block, renderThemeCss } from "./css.js";
|
|
5
5
|
/**
|
|
@@ -16,6 +16,7 @@ export const CSS_LAYER_PRODUCTS = [
|
|
|
16
16
|
chatStudioBrand,
|
|
17
17
|
loadrBrand,
|
|
18
18
|
cykickBrand,
|
|
19
|
+
deepDataAgentBrand,
|
|
19
20
|
].map((config) => defineProduct(config));
|
|
20
21
|
const TOKENS_HEADER = `/*
|
|
21
22
|
* @calliopelabs/ui CSS layer: tokens.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export interface ActivityItem {
|
|
3
|
+
id: string;
|
|
4
|
+
/** The word under the icon ("Explore"). */
|
|
5
|
+
label: string;
|
|
6
|
+
/** The full name, spoken and shown on hover ("Database explorer"). Defaults to `label`. */
|
|
7
|
+
title?: string;
|
|
8
|
+
icon: React.ReactNode;
|
|
9
|
+
/** A count or marker on the icon (running tasks). */
|
|
10
|
+
badge?: React.ReactNode;
|
|
11
|
+
/** Pressed state for an item that is a toggle (the AI panel) rather than a view. */
|
|
12
|
+
pressed?: boolean;
|
|
13
|
+
/** Run instead of selecting the item as the view. */
|
|
14
|
+
onSelect?: () => void;
|
|
15
|
+
}
|
|
16
|
+
export interface ActivityBarProps {
|
|
17
|
+
/** The views, top down. */
|
|
18
|
+
items: readonly ActivityItem[];
|
|
19
|
+
/** Items pinned to the bottom (tasks, settings). */
|
|
20
|
+
footer?: readonly ActivityItem[];
|
|
21
|
+
/** The view on screen, or null when the side panel is closed. */
|
|
22
|
+
active: string | null;
|
|
23
|
+
/**
|
|
24
|
+
* A view was chosen. Choosing the active one again passes null: the
|
|
25
|
+
* host closes the side panel, as an IDE does.
|
|
26
|
+
*/
|
|
27
|
+
onActiveChange: (id: string | null) => void;
|
|
28
|
+
/** Show the word under each icon. Defaults to true. */
|
|
29
|
+
showLabels?: boolean;
|
|
30
|
+
className?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The rail of views down the left of an IDE layout: a vertical toolbar with
|
|
34
|
+
* one tab stop (Up and Down move, Home and End jump), each item a toggle
|
|
35
|
+
* button that says whether its view is open.
|
|
36
|
+
*/
|
|
37
|
+
export declare function ActivityBar({ items, footer, active, onActiveChange, showLabels, className, }: ActivityBarProps): React.JSX.Element;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../lib/cn.js";
|
|
5
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "../primitives/Tooltip.js";
|
|
6
|
+
import { useUI } from "../provider/UIProvider.js";
|
|
7
|
+
/**
|
|
8
|
+
* The rail of views down the left of an IDE layout: a vertical toolbar with
|
|
9
|
+
* one tab stop (Up and Down move, Home and End jump), each item a toggle
|
|
10
|
+
* button that says whether its view is open.
|
|
11
|
+
*/
|
|
12
|
+
export function ActivityBar({ items, footer = [], active, onActiveChange, showLabels = true, className, }) {
|
|
13
|
+
const { messages } = useUI();
|
|
14
|
+
const all = [...items, ...footer];
|
|
15
|
+
const [focusId, setFocusId] = React.useState(undefined);
|
|
16
|
+
const tabStop = all.some((i) => i.id === focusId)
|
|
17
|
+
? focusId
|
|
18
|
+
: (all.find((i) => i.id === active)?.id ?? all[0]?.id);
|
|
19
|
+
const refs = React.useRef(new Map());
|
|
20
|
+
const onKeyDown = (event) => {
|
|
21
|
+
const index = all.findIndex((i) => i.id === tabStop);
|
|
22
|
+
let next = null;
|
|
23
|
+
if (event.key === "ArrowDown")
|
|
24
|
+
next = (index + 1) % all.length;
|
|
25
|
+
else if (event.key === "ArrowUp")
|
|
26
|
+
next = (index - 1 + all.length) % all.length;
|
|
27
|
+
else if (event.key === "Home")
|
|
28
|
+
next = 0;
|
|
29
|
+
else if (event.key === "End")
|
|
30
|
+
next = all.length - 1;
|
|
31
|
+
if (next === null)
|
|
32
|
+
return;
|
|
33
|
+
event.preventDefault();
|
|
34
|
+
const id = all[next]?.id;
|
|
35
|
+
if (id === undefined)
|
|
36
|
+
return;
|
|
37
|
+
setFocusId(id);
|
|
38
|
+
refs.current.get(id)?.focus();
|
|
39
|
+
};
|
|
40
|
+
const button = (item) => {
|
|
41
|
+
const pressed = item.pressed ?? item.id === active;
|
|
42
|
+
const title = item.title ?? item.label;
|
|
43
|
+
const count = typeof item.badge === "string" || typeof item.badge === "number" ? ` (${item.badge})` : "";
|
|
44
|
+
return (_jsxs(Tooltip, { delayDuration: 400, children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("button", { ref: (el) => {
|
|
45
|
+
if (el)
|
|
46
|
+
refs.current.set(item.id, el);
|
|
47
|
+
else
|
|
48
|
+
refs.current.delete(item.id);
|
|
49
|
+
}, type: "button", tabIndex: item.id === tabStop ? 0 : -1, "aria-pressed": pressed, "aria-label": `${title}${count}`, onFocus: () => setFocusId(item.id), onClick: () => {
|
|
50
|
+
if (item.onSelect)
|
|
51
|
+
item.onSelect();
|
|
52
|
+
else
|
|
53
|
+
onActiveChange(item.id === active ? null : item.id);
|
|
54
|
+
}, className: cn("relative flex w-full flex-col items-center gap-0.5 py-2 text-sidebar-muted-foreground outline-hidden transition-colors hover:text-sidebar-foreground focus-visible:ring-2 focus-visible:ring-sidebar-ring focus-visible:ring-inset", pressed && "text-sidebar-primary hover:text-sidebar-primary"), children: [pressed && (_jsx("span", { "aria-hidden": true, className: "absolute top-1/2 left-0 h-8 w-0.5 -translate-y-1/2 rounded-full bg-sidebar-primary" })), _jsxs("span", { "aria-hidden": true, className: "relative flex [&_svg]:size-4.5", children: [item.icon, item.badge !== undefined && item.badge !== null && item.badge !== false && (_jsx("span", { className: "absolute -top-1.5 -right-2 min-w-4 rounded-full bg-sidebar-primary px-1 text-center text-2xs leading-4 text-sidebar-primary-foreground", children: item.badge }))] }), showLabels && (_jsx("span", { "aria-hidden": true, className: "text-2xs leading-none tracking-tight", children: item.label }))] }) }), _jsx(TooltipContent, { side: "right", children: title })] }, item.id));
|
|
55
|
+
};
|
|
56
|
+
return (_jsxs("div", { role: "toolbar", "aria-orientation": "vertical", "aria-label": messages.workbench.activityBar, onKeyDown: onKeyDown, className: cn("flex h-full w-14 shrink-0 flex-col border-r border-sidebar-border bg-sidebar text-sidebar-foreground", className), children: [_jsx("div", { className: "flex w-full flex-col", children: items.map(button) }), footer.length > 0 && (_jsx("div", { className: "mt-auto flex w-full flex-col pb-2", children: footer.map(button) }))] }));
|
|
57
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
export interface BottomPanelView {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
icon?: React.ReactNode;
|
|
6
|
+
/** A count beside the label (history entries, running tasks). */
|
|
7
|
+
count?: number;
|
|
8
|
+
/** Something is running in it: the count takes the info tone. */
|
|
9
|
+
busy?: boolean;
|
|
10
|
+
content: React.ReactNode;
|
|
11
|
+
}
|
|
12
|
+
export interface BottomPanelProps {
|
|
13
|
+
views: readonly BottomPanelView[];
|
|
14
|
+
/** The view on screen. Controlled when given. */
|
|
15
|
+
value?: string;
|
|
16
|
+
defaultValue?: string;
|
|
17
|
+
onValueChange?: (id: string) => void;
|
|
18
|
+
/** The panel fills the editor area. Shows the maximize or restore button when `onMaximizedChange` is set. */
|
|
19
|
+
maximized?: boolean;
|
|
20
|
+
onMaximizedChange?: (maximized: boolean) => void;
|
|
21
|
+
/** Shows a close button. */
|
|
22
|
+
onClose?: () => void;
|
|
23
|
+
className?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The panel under the editors (history, tasks, output, problems): views as
|
|
27
|
+
* ARIA tabs, with maximize, restore and close. The host decides whether it
|
|
28
|
+
* is open and how tall it is (in a `PaneGroup`); views stay host content.
|
|
29
|
+
*/
|
|
30
|
+
export declare function BottomPanel({ views, value, defaultValue, onValueChange, maximized, onMaximizedChange, onClose, className, }: BottomPanelProps): React.JSX.Element;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { Maximize2Icon, Minimize2Icon, XIcon } from "lucide-react";
|
|
4
|
+
import { cn } from "../lib/cn.js";
|
|
5
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../primitives/Tabs.js";
|
|
6
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "../primitives/Tooltip.js";
|
|
7
|
+
import { useUI } from "../provider/UIProvider.js";
|
|
8
|
+
function IconButton({ label, onClick, children, }) {
|
|
9
|
+
return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": label, onClick: onClick, className: "rounded-lg p-1 text-muted-foreground outline-hidden transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 focus-visible:ring-ring [&_svg]:size-3.5", children: children }) }), _jsx(TooltipContent, { children: label })] }));
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The panel under the editors (history, tasks, output, problems): views as
|
|
13
|
+
* ARIA tabs, with maximize, restore and close. The host decides whether it
|
|
14
|
+
* is open and how tall it is (in a `PaneGroup`); views stay host content.
|
|
15
|
+
*/
|
|
16
|
+
export function BottomPanel({ views, value, defaultValue, onValueChange, maximized = false, onMaximizedChange, onClose, className, }) {
|
|
17
|
+
const { messages } = useUI();
|
|
18
|
+
const m = messages.workbench;
|
|
19
|
+
return (_jsxs(Tabs, { value: value, defaultValue: defaultValue ?? views[0]?.id, onValueChange: onValueChange, className: cn("flex h-full min-h-0 flex-col gap-0 bg-background", className), children: [_jsxs("div", { className: "flex h-8 shrink-0 items-center border-b bg-surface-1 pr-1", children: [_jsx(TabsList, { className: "h-full flex-1 border-b-0", children: views.map((view) => (_jsxs(TabsTrigger, { value: view.id, className: "flex items-center gap-1.5 px-3 py-1 text-xs [&_svg]:size-3", children: [view.icon && (_jsx("span", { "aria-hidden": true, className: "flex", children: view.icon })), view.label, view.count !== undefined && view.count > 0 && (_jsxs(_Fragment, { children: [" ", _jsx("span", { className: cn("rounded-full px-1.5 text-2xs tabular-nums", view.busy ? "bg-info/15 text-info-fg" : "bg-muted text-muted-foreground"), children: view.count.toLocaleString() })] }))] }, view.id))) }), onMaximizedChange && (_jsx(IconButton, { label: maximized ? m.restorePanel : m.maximizePanel, onClick: () => onMaximizedChange(!maximized), children: maximized ? _jsx(Minimize2Icon, { "aria-hidden": true }) : _jsx(Maximize2Icon, { "aria-hidden": true }) })), onClose && (_jsx(IconButton, { label: m.closePanel, onClick: onClose, children: _jsx(XIcon, { "aria-hidden": true }) }))] }), views.map((view) => (_jsx(TabsContent, { value: view.id, className: "min-h-0 flex-1 overflow-hidden", children: view.content }, view.id)))] }));
|
|
20
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type * as Monaco from "monaco-editor";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
export type MonacoApi = typeof Monaco;
|
|
4
|
+
export type MonacoEditor = Monaco.editor.IStandaloneCodeEditor;
|
|
5
|
+
type Loader = () => Promise<MonacoApi | {
|
|
6
|
+
default: MonacoApi;
|
|
7
|
+
}>;
|
|
8
|
+
/**
|
|
9
|
+
* Tell the editors how to load Monaco, once, before any mounts:
|
|
10
|
+
* `configureMonaco({ load })`, where `load` imports `monaco-editor`. The import is
|
|
11
|
+
* the host's, so its bundler resolves Monaco, splits it into its own chunk
|
|
12
|
+
* and fetches it the first time an editor mounts; an app that never shows
|
|
13
|
+
* an editor never loads it, and one without `monaco-editor` installed still
|
|
14
|
+
* builds. The host also sets `self.MonacoEnvironment` for Monaco's workers
|
|
15
|
+
* (see the README). Without a loader, editors fall back to a plain text area.
|
|
16
|
+
*/
|
|
17
|
+
export declare function configureMonaco(options: {
|
|
18
|
+
load: Loader;
|
|
19
|
+
}): void;
|
|
20
|
+
export interface CodeEditorProps {
|
|
21
|
+
value: string;
|
|
22
|
+
onChange?: (value: string) => void;
|
|
23
|
+
/** A Monaco language id: "sql", "json", "python", "typescript". Defaults to "plaintext". */
|
|
24
|
+
language?: string;
|
|
25
|
+
/** The editor's accessible name ("Query", "Pipeline spec"). */
|
|
26
|
+
label: string;
|
|
27
|
+
readOnly?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Ctrl or Command with Enter: gets the selected text, or the whole buffer
|
|
30
|
+
* when nothing is selected.
|
|
31
|
+
*/
|
|
32
|
+
onRun?: (text: string) => void;
|
|
33
|
+
/** Passed to Monaco as they are; the editor sets sensible defaults first. */
|
|
34
|
+
options?: Monaco.editor.IStandaloneEditorConstructionOptions;
|
|
35
|
+
/** The editor is ready. Return a cleanup to run when it unmounts. */
|
|
36
|
+
onMount?: (editor: MonacoEditor, monaco: MonacoApi) => void | (() => void);
|
|
37
|
+
/** Load Monaco for this editor only, instead of `configureMonaco`'s loader. Read on mount. */
|
|
38
|
+
loader?: Loader;
|
|
39
|
+
className?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A code editor on Monaco, loaded the first time one mounts and themed from
|
|
43
|
+
* the library's tokens for the current ground (it follows a change of
|
|
44
|
+
* ground or mode). Monaco is an optional peer dependency: without it, or
|
|
45
|
+
* while it loads, a plain text area or a loading state stands in.
|
|
46
|
+
*/
|
|
47
|
+
export declare function CodeEditor({ value, onChange, language, label, readOnly, onRun, options, onMount, loader, className, }: CodeEditorProps): React.JSX.Element;
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../lib/cn.js";
|
|
5
|
+
import { useUI } from "../provider/UIProvider.js";
|
|
6
|
+
import { LoadingState } from "../states/LoadingState.js";
|
|
7
|
+
import { editorTheme, readEditorPalette } from "./editor-theme.js";
|
|
8
|
+
let configured = null;
|
|
9
|
+
const loads = new WeakMap();
|
|
10
|
+
/**
|
|
11
|
+
* Tell the editors how to load Monaco, once, before any mounts:
|
|
12
|
+
* `configureMonaco({ load })`, where `load` imports `monaco-editor`. The import is
|
|
13
|
+
* the host's, so its bundler resolves Monaco, splits it into its own chunk
|
|
14
|
+
* and fetches it the first time an editor mounts; an app that never shows
|
|
15
|
+
* an editor never loads it, and one without `monaco-editor` installed still
|
|
16
|
+
* builds. The host also sets `self.MonacoEnvironment` for Monaco's workers
|
|
17
|
+
* (see the README). Without a loader, editors fall back to a plain text area.
|
|
18
|
+
*/
|
|
19
|
+
export function configureMonaco(options) {
|
|
20
|
+
configured = options.load;
|
|
21
|
+
}
|
|
22
|
+
function loadMonaco(loader) {
|
|
23
|
+
let promise = loads.get(loader);
|
|
24
|
+
if (!promise) {
|
|
25
|
+
promise = loader().then((m) => ("default" in m && m.default ? m.default : m));
|
|
26
|
+
// A failed load may succeed later (a flaky network): do not cache it.
|
|
27
|
+
promise.catch(() => loads.delete(loader));
|
|
28
|
+
loads.set(loader, promise);
|
|
29
|
+
}
|
|
30
|
+
return promise;
|
|
31
|
+
}
|
|
32
|
+
const THEME = "calliope";
|
|
33
|
+
/** Define and apply the theme from the tokens under `element`. Monaco themes are page-wide. */
|
|
34
|
+
function applyTheme(monaco, element) {
|
|
35
|
+
const palette = readEditorPalette(element);
|
|
36
|
+
if (palette) {
|
|
37
|
+
monaco.editor.defineTheme(THEME, editorTheme(palette));
|
|
38
|
+
monaco.editor.setTheme(THEME);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
const dark = document.documentElement.getAttribute("data-mode") === "dark";
|
|
42
|
+
monaco.editor.setTheme(dark ? "vs-dark" : "vs");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* A code editor on Monaco, loaded the first time one mounts and themed from
|
|
47
|
+
* the library's tokens for the current ground (it follows a change of
|
|
48
|
+
* ground or mode). Monaco is an optional peer dependency: without it, or
|
|
49
|
+
* while it loads, a plain text area or a loading state stands in.
|
|
50
|
+
*/
|
|
51
|
+
export function CodeEditor({ value, onChange, language = "plaintext", label, readOnly = false, onRun, options, onMount, loader, className, }) {
|
|
52
|
+
const { messages } = useUI();
|
|
53
|
+
const hostRef = React.useRef(null);
|
|
54
|
+
const editorRef = React.useRef(null);
|
|
55
|
+
const monacoRef = React.useRef(null);
|
|
56
|
+
const latest = React.useRef({ value, onChange, onRun, onMount });
|
|
57
|
+
const [status, setStatus] = React.useState(() => (loader ?? configured) ? "loading" : "fallback");
|
|
58
|
+
React.useLayoutEffect(() => {
|
|
59
|
+
latest.current = { value, onChange, onRun, onMount };
|
|
60
|
+
});
|
|
61
|
+
// The loader of the first render: an inline arrow must not recreate the editor.
|
|
62
|
+
const loaderRef = React.useRef(loader ?? configured);
|
|
63
|
+
// Mount once. Value, language and read-only follow below.
|
|
64
|
+
React.useEffect(() => {
|
|
65
|
+
const load = loaderRef.current;
|
|
66
|
+
const host = hostRef.current;
|
|
67
|
+
if (!load || !host)
|
|
68
|
+
return;
|
|
69
|
+
let cancelled = false;
|
|
70
|
+
let cleanup;
|
|
71
|
+
let observer;
|
|
72
|
+
loadMonaco(load)
|
|
73
|
+
.then((monaco) => {
|
|
74
|
+
if (cancelled)
|
|
75
|
+
return;
|
|
76
|
+
monacoRef.current = monaco;
|
|
77
|
+
applyTheme(monaco, host);
|
|
78
|
+
const mono = getComputedStyle(host).getPropertyValue("--font-mono").trim();
|
|
79
|
+
const editor = monaco.editor.create(host, {
|
|
80
|
+
value: latest.current.value,
|
|
81
|
+
language,
|
|
82
|
+
readOnly,
|
|
83
|
+
ariaLabel: label,
|
|
84
|
+
automaticLayout: true,
|
|
85
|
+
minimap: { enabled: false },
|
|
86
|
+
scrollBeyondLastLine: false,
|
|
87
|
+
fontSize: 13,
|
|
88
|
+
fontFamily: mono || undefined,
|
|
89
|
+
tabSize: 2,
|
|
90
|
+
renderLineHighlight: "line",
|
|
91
|
+
fixedOverflowWidgets: true,
|
|
92
|
+
...options,
|
|
93
|
+
});
|
|
94
|
+
editorRef.current = editor;
|
|
95
|
+
editor.onDidChangeModelContent(() => {
|
|
96
|
+
const next = editor.getValue();
|
|
97
|
+
if (next !== latest.current.value)
|
|
98
|
+
latest.current.onChange?.(next);
|
|
99
|
+
});
|
|
100
|
+
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, () => {
|
|
101
|
+
const selection = editor.getSelection();
|
|
102
|
+
const model = editor.getModel();
|
|
103
|
+
const picked = selection && model && !selection.isEmpty() ? model.getValueInRange(selection) : "";
|
|
104
|
+
latest.current.onRun?.(picked || editor.getValue());
|
|
105
|
+
});
|
|
106
|
+
// The theme follows the ground: re-read the tokens when <html> changes.
|
|
107
|
+
observer = new MutationObserver(() => applyTheme(monaco, host));
|
|
108
|
+
observer.observe(document.documentElement, { attributes: true });
|
|
109
|
+
const unmount = latest.current.onMount?.(editor, monaco);
|
|
110
|
+
cleanup = typeof unmount === "function" ? unmount : undefined;
|
|
111
|
+
setStatus("ready");
|
|
112
|
+
})
|
|
113
|
+
.catch(() => {
|
|
114
|
+
if (!cancelled)
|
|
115
|
+
setStatus("fallback");
|
|
116
|
+
});
|
|
117
|
+
return () => {
|
|
118
|
+
cancelled = true;
|
|
119
|
+
observer?.disconnect();
|
|
120
|
+
cleanup?.();
|
|
121
|
+
editorRef.current?.dispose();
|
|
122
|
+
editorRef.current = null;
|
|
123
|
+
};
|
|
124
|
+
// Recreating the editor on every option change would lose the undo stack
|
|
125
|
+
// and the cursor; those that can change are applied in place below.
|
|
126
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
127
|
+
}, []);
|
|
128
|
+
React.useEffect(() => {
|
|
129
|
+
const editor = editorRef.current;
|
|
130
|
+
const model = editor?.getModel();
|
|
131
|
+
if (!editor || !model || model.getValue() === value)
|
|
132
|
+
return;
|
|
133
|
+
// An edit, not setValue, so the person can undo past a host change.
|
|
134
|
+
editor.pushUndoStop();
|
|
135
|
+
model.pushEditOperations([], [{ range: model.getFullModelRange(), text: value }], () => null);
|
|
136
|
+
editor.pushUndoStop();
|
|
137
|
+
}, [value, status]);
|
|
138
|
+
React.useEffect(() => {
|
|
139
|
+
const model = editorRef.current?.getModel();
|
|
140
|
+
if (model && monacoRef.current)
|
|
141
|
+
monacoRef.current.editor.setModelLanguage(model, language);
|
|
142
|
+
}, [language, status]);
|
|
143
|
+
React.useEffect(() => {
|
|
144
|
+
editorRef.current?.updateOptions({ readOnly, ariaLabel: label });
|
|
145
|
+
}, [readOnly, label, status]);
|
|
146
|
+
return (_jsxs("div", { "data-slot": "code-editor", "data-status": status, className: cn("relative h-full min-h-24 w-full overflow-hidden bg-background", className), children: [_jsx("div", { ref: hostRef, className: cn("absolute inset-0", status !== "ready" && "invisible") }), status === "loading" && (_jsx(LoadingState, { className: "absolute inset-0", label: messages.workbench.editorLoading })), status === "fallback" && (_jsx("textarea", { "aria-label": label, value: value, readOnly: readOnly, spellCheck: false, onChange: (event) => onChange?.(event.target.value), onKeyDown: (event) => {
|
|
147
|
+
if (event.key === "Enter" && (event.metaKey || event.ctrlKey) && onRun) {
|
|
148
|
+
event.preventDefault();
|
|
149
|
+
const t = event.currentTarget;
|
|
150
|
+
onRun(t.value.slice(t.selectionStart, t.selectionEnd) || t.value);
|
|
151
|
+
}
|
|
152
|
+
}, className: "absolute inset-0 resize-none bg-background p-3 font-mono text-sm text-foreground outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset" }))] }));
|
|
153
|
+
}
|