@workbench-kit/react 0.0.1-prototype.0 → 0.0.2-prototype.0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/package.json +33 -9
  2. package/src/index.ts +545 -43
  3. package/src/jdw/JdwPreview.tsx +38 -0
  4. package/src/jdw/JsonCodeEditorPane.tsx +274 -0
  5. package/src/jdw/builtins/renderBuiltinWidgetLeaf.tsx +29 -0
  6. package/src/jdw/builtins/renderBuiltinWidgetNode.tsx +126 -0
  7. package/src/jdw/createBuiltinJdwRegistry.ts +111 -0
  8. package/src/jdw/cssRenderBackend.tsx +134 -0
  9. package/src/jdw/document.ts +38 -0
  10. package/src/jdw/fixtures/jdw-fixtures.ts +52 -0
  11. package/src/jdw/fixtures/jdw-sample-screens.ts +522 -0
  12. package/src/jdw/index.ts +45 -0
  13. package/src/jdw/parse.ts +1 -0
  14. package/src/jdw/renderJdw.tsx +32 -0
  15. package/src/json-config/JsonConfigWorkbench.tsx +316 -0
  16. package/src/json-config/index.ts +6 -0
  17. package/src/json-config/json-config-editor-state.ts +42 -0
  18. package/src/layout/Panel.tsx +146 -1
  19. package/src/layout/SideBarViewFrame.tsx +367 -328
  20. package/src/layout/WorkbenchCanvas.tsx +849 -0
  21. package/src/layout/WorkbenchFullscreen.tsx +368 -0
  22. package/src/layout/WorkbenchLayout.tsx +5 -0
  23. package/src/layout/WorkbenchLayoutBase.tsx +294 -0
  24. package/src/layout/WorkbenchPropertyPanel.tsx +497 -0
  25. package/src/layout/WorkbenchSidebarActions.tsx +274 -0
  26. package/src/layout/WorkbenchTree.tsx +351 -0
  27. package/src/layout/layoutHelpers.ts +25 -0
  28. package/src/modal/ConfirmDialog.tsx +61 -53
  29. package/src/modal/Modal.tsx +295 -35
  30. package/src/overlay/ContextMenu.tsx +146 -145
  31. package/src/primitives/AbsoluteBox.tsx +46 -0
  32. package/src/primitives/Badge.tsx +12 -12
  33. package/src/primitives/Button.tsx +44 -14
  34. package/src/primitives/Checkbox.tsx +11 -5
  35. package/src/primitives/EmptyState.tsx +27 -26
  36. package/src/primitives/Field.tsx +38 -38
  37. package/src/primitives/IconButton.tsx +5 -2
  38. package/src/primitives/List.tsx +111 -0
  39. package/src/primitives/NumberInput.tsx +38 -0
  40. package/src/primitives/ScrollArea.tsx +42 -0
  41. package/src/primitives/Select.tsx +1 -12
  42. package/src/primitives/StatusBar.tsx +46 -0
  43. package/src/primitives/TextArea.tsx +36 -0
  44. package/src/primitives/TextInput.tsx +11 -2
  45. package/src/primitives/WorkbenchChrome.tsx +424 -0
  46. package/src/primitives/WorkbenchEditor.tsx +394 -0
  47. package/src/primitives/index.ts +105 -0
  48. package/src/primitives/select/Select.tsx +266 -0
  49. package/src/primitives/select/index.ts +2 -0
  50. package/src/primitives/select/options.ts +38 -0
  51. package/src/primitives/select/overlay.ts +63 -0
  52. package/src/primitives/select/select.app.css +9 -0
  53. package/src/primitives/select/select.css +140 -0
  54. package/src/primitives/select/types.ts +18 -0
  55. package/src/primitives/styles.css +955 -0
  56. package/src/scrollbars.css +69 -0
  57. package/src/styles.css +7738 -1762
  58. package/src/utils/codicon.ts +17 -0
  59. package/src/widget-asset/WidgetAssetEditor.tsx +140 -0
  60. package/src/widget-asset/WidgetAssetMetadataForm.tsx +82 -0
  61. package/src/widget-asset/WidgetAssetModeControls.tsx +42 -0
  62. package/src/widget-asset/WidgetAssetSourceEditor.tsx +50 -0
  63. package/src/widget-asset/WidgetAssetWorkbench.tsx +112 -0
  64. package/src/widget-asset/index.ts +17 -0
  65. package/src/widget-asset/widget-asset-document.ts +37 -0
  66. package/src/widget-asset/widget-asset-mode.ts +3 -0
  67. package/src/widget-studio/builtin-widget-asset-catalog.ts +12 -0
  68. package/src/widget-studio/create-widget-studio-workspace-editor.tsx +79 -0
  69. package/src/widget-studio/index.ts +7 -0
  70. package/src/widget-studio/resolve-widget-studio-asset-catalog.ts +26 -0
  71. package/src/widget-tree/WidgetAssetPalette.tsx +77 -0
  72. package/src/widget-tree/WidgetInspectorPanel.tsx +266 -0
  73. package/src/widget-tree/WidgetSourceEditor.tsx +56 -0
  74. package/src/widget-tree/WidgetTreeLab.tsx +272 -0
  75. package/src/widget-tree/WidgetTreeModeControls.tsx +44 -0
  76. package/src/widget-tree/WidgetTreeSidePanel.tsx +49 -0
  77. package/src/widget-tree/WidgetTreeView.tsx +97 -0
  78. package/src/widget-tree/WidgetTreeWorkbench.tsx +111 -0
  79. package/src/widget-tree/WidgetTreeWorkspaceShell.tsx +154 -0
  80. package/src/widget-tree/demo-registry.ts +32 -0
  81. package/src/widget-tree/demo-widget-assets.ts +4 -0
  82. package/src/widget-tree/index.ts +27 -0
  83. package/src/widget-tree/widget-tree-document.ts +20 -0
  84. package/src/widget-tree/widget-tree-layout.ts +31 -0
  85. package/src/widget-tree/widget-tree-mode.ts +7 -0
  86. package/src/widget-tree/widget-tree-play-helpers.ts +74 -0
  87. package/src/workbench/ActivityBar.tsx +52 -52
  88. package/src/workbench/ArtifactShell.tsx +397 -0
  89. package/src/workbench/CommandPalette.tsx +819 -0
  90. package/src/workbench/ConfirmationFlow.tsx +245 -0
  91. package/src/workbench/MarkdownPreview.tsx +151 -0
  92. package/src/workbench/ShortcutCommandBridge.tsx +368 -0
  93. package/src/workbench/SplitView.tsx +136 -136
  94. package/src/workbench/StatusBar.tsx +178 -123
  95. package/src/workbench/StructuredArtifactEditor.tsx +205 -0
  96. package/src/workbench/Timeline.tsx +302 -0
  97. package/src/workbench/WorkbenchCanvasShell.tsx +941 -0
  98. package/src/workbench/WorkbenchShell.tsx +74 -70
  99. package/src/workbench/WorkbenchStandaloneShell.tsx +318 -291
  100. package/src/workbench/auth/WorkbenchAuthGate.tsx +61 -0
  101. package/src/workbench/auth/WorkbenchLoginView.tsx +635 -0
  102. package/src/workbench/auth/index.ts +19 -0
  103. package/src/workbench/chat/ChatComposer.tsx +165 -148
  104. package/src/workbench/chat/ChatMessageItem.tsx +10 -1
  105. package/src/workbench/chat/index.ts +9 -0
  106. package/src/workbench/chat/slashCommand.ts +28 -0
  107. package/src/workbench/chat/useChatRuntimeState.ts +76 -0
  108. package/src/workbench/chat/useSlashCommandSuggest.ts +101 -0
  109. package/src/workbench/commands.ts +520 -490
  110. package/src/workbench/index.ts +443 -98
  111. package/src/workbench/keyboard.ts +53 -0
  112. package/src/workbench/schema/index.ts +48 -0
  113. package/src/workbench/schema/workbenchDocument.ts +31 -0
  114. package/src/workbench/schema/workbenchDocumentActions.ts +6 -0
  115. package/src/workbench/schema/workbenchDocumentAdapter.ts +9 -0
  116. package/src/workbench/schema/workbenchDocumentPatch.ts +13 -0
  117. package/src/workbench/schema/workbenchDocumentRenderer.tsx +702 -0
  118. package/src/workbench/settings/NavigationPanel.tsx +47 -0
  119. package/src/workbench/settings/SchemaForm.tsx +420 -0
  120. package/src/workbench/settings/SectionedPanel.tsx +415 -0
  121. package/src/workbench/settings/StructuredDataForm.tsx +539 -0
  122. package/src/workbench/settings/StructuredDataSchemaPanel.tsx +559 -0
  123. package/src/workbench/settings/StructuredDataSchemaPanelEmbed.tsx +21 -0
  124. package/src/workbench/settings/StructuredDataSchemaPanelFrame.tsx +76 -0
  125. package/src/workbench/settings/StructuredDataTableView.tsx +127 -0
  126. package/src/workbench/settings/WorkbenchPanelRegion.tsx +46 -0
  127. package/src/workbench/settings/WorkbenchSettingsModal.tsx +21 -12
  128. package/src/workbench/settings/WorkbenchSettingsNav.tsx +38 -22
  129. package/src/workbench/settings/index.ts +135 -0
  130. package/src/workbench/settings/panel-region.css +21 -0
  131. package/src/workbench/settings/sectionedPanelScrollSpy.ts +143 -0
  132. package/src/workbench/settings/structured-data-schema-panel.css +330 -0
  133. package/src/workbench/settings/structured-data-table-view.css +85 -0
  134. package/src/workbench/settings/structuredDataSchema.ts +886 -0
  135. package/src/workbench/shell.ts +15 -0
  136. package/src/workbench/shellState.ts +203 -203
  137. package/src/workbench/standalone.ts +101 -94
  138. package/src/workbench/status.ts +129 -0
  139. package/src/workbench/theme.ts +44 -0
  140. package/src/workbench/workspace/MultiProviderExplorer.tsx +625 -0
  141. package/src/workbench/workspace/WorkspaceDraftsContext.tsx +111 -0
  142. package/src/workbench/workspace/WorkspaceEditor.tsx +47 -12
  143. package/src/workbench/workspace/WorkspaceEditorPanel.tsx +334 -353
  144. package/src/workbench/workspace/WorkspaceExplorer.tsx +532 -524
  145. package/src/workbench/workspace/WorkspaceFileIcon.tsx +32 -2
  146. package/src/workbench/workspace/WorkspaceSearchPanel.tsx +113 -113
  147. package/src/workbench/workspace/WorkspaceSearchResults.tsx +47 -47
  148. package/src/workbench/workspace/index.ts +156 -98
  149. package/src/workbench/workspace/mimeType.ts +45 -0
  150. package/src/workbench/workspace/path.ts +10 -10
  151. package/src/workbench/workspace/search.ts +6 -6
  152. package/src/workbench/workspace/tree.ts +1 -1
  153. package/src/workbench/workspace/types.ts +10 -10
  154. package/src/workbench/workspace/useVirtualWorkspace.ts +111 -98
  155. package/src/workbench/workspace/workspaceJsonDiagnostics.ts +83 -0
@@ -0,0 +1,17 @@
1
+ import { cx } from './cx';
2
+
3
+ export function getCodiconClassName(icon: string | undefined): string | undefined {
4
+ const token = icon?.trim();
5
+ if (!token) return undefined;
6
+
7
+ return token.startsWith('codicon-') ? token : `codicon-${token}`;
8
+ }
9
+
10
+ export function cxCodicon(
11
+ icon: string | undefined,
12
+ ...classNames: Array<false | null | string | undefined>
13
+ ) {
14
+ const iconClassName = getCodiconClassName(icon);
15
+
16
+ return iconClassName ? cx('codicon', iconClassName, ...classNames) : cx(...classNames);
17
+ }
@@ -0,0 +1,140 @@
1
+ import { useMemo } from 'react';
2
+ import type { WidgetPlacementAsset, WidgetRegistryContract } from '@workbench-kit/contracts';
3
+ import {
4
+ createWidgetAssetDocument,
5
+ formatWidgetAssetContent,
6
+ formatWidgetAssetManifest,
7
+ formatWidgetDocumentJson,
8
+ isWidgetAssetContentPath,
9
+ isWidgetAssetManifestPath,
10
+ type GenericWidget,
11
+ type WorkspaceAssetFileRef,
12
+ } from '@workbench-kit/jdw';
13
+
14
+ import { JdwPreview } from '../jdw/JdwPreview.js';
15
+ import { WidgetAssetSourceEditor } from './WidgetAssetSourceEditor.js';
16
+ import type { WorkspaceEditorTheme } from '../workbench/workspace/WorkspaceEditor.js';
17
+ import { WorkbenchPropertyHint, WorkbenchPropertyPanel } from '../layout/WorkbenchPropertyPanel';
18
+ import { WidgetInspectorPanel } from '../widget-tree/WidgetInspectorPanel.js';
19
+ import { WidgetAssetMetadataForm } from './WidgetAssetMetadataForm.js';
20
+ import type { WidgetAssetViewMode } from './widget-asset-mode.js';
21
+
22
+ export interface WidgetAssetEditorProps {
23
+ readonly path?: string | undefined;
24
+ readonly value: string;
25
+ readonly onChange: (value: string) => void;
26
+ readonly onSave?: (() => void) | undefined;
27
+ readonly registry?: WidgetRegistryContract<unknown> | undefined;
28
+ readonly readOnly?: boolean | undefined;
29
+ readonly theme?: WorkspaceEditorTheme | undefined;
30
+ readonly viewMode?: WidgetAssetViewMode | undefined;
31
+ readonly workspaceFiles?: readonly WorkspaceAssetFileRef[] | undefined;
32
+ }
33
+
34
+ export function WidgetAssetEditor({
35
+ path,
36
+ value,
37
+ onChange,
38
+ onSave,
39
+ registry,
40
+ readOnly = false,
41
+ theme = 'dark',
42
+ viewMode = 'design',
43
+ workspaceFiles,
44
+ }: WidgetAssetEditorProps) {
45
+ const document = useMemo(
46
+ () =>
47
+ createWidgetAssetDocument(value, {
48
+ ...(path ? { path } : {}),
49
+ ...(workspaceFiles ? { workspaceFiles } : {}),
50
+ }),
51
+ [path, value, workspaceFiles],
52
+ );
53
+
54
+ const sourceEditor = (
55
+ <WidgetAssetSourceEditor
56
+ parseError={document.parseError}
57
+ path={path}
58
+ readOnly={readOnly}
59
+ theme={theme}
60
+ value={value}
61
+ onChange={onChange}
62
+ onSave={onSave}
63
+ />
64
+ );
65
+
66
+ if (viewMode === 'code') {
67
+ return (
68
+ <div
69
+ className="widget-asset-editor widget-asset-editor--code"
70
+ data-testid="widget-asset-editor"
71
+ >
72
+ {sourceEditor}
73
+ </div>
74
+ );
75
+ }
76
+
77
+ if (document.parseError !== null || document.asset === null) {
78
+ const hint =
79
+ document.parseError ??
80
+ 'Open manifest.json with a sibling content.json in the workspace to use design mode.';
81
+
82
+ return (
83
+ <div
84
+ className="widget-asset-editor widget-asset-editor--error"
85
+ data-testid="widget-asset-editor"
86
+ >
87
+ <WorkbenchPropertyPanel>
88
+ <WorkbenchPropertyHint>{hint}</WorkbenchPropertyHint>
89
+ </WorkbenchPropertyPanel>
90
+ {sourceEditor}
91
+ </div>
92
+ );
93
+ }
94
+
95
+ const asset = document.asset;
96
+ const editingManifest = Boolean(path && isWidgetAssetManifestPath(path));
97
+ const editingContent = Boolean(path && isWidgetAssetContentPath(path));
98
+ const previewJson =
99
+ editingContent && path ? value : formatWidgetDocumentJson(asset.content as GenericWidget);
100
+
101
+ const commitAssetMetadata = (nextAsset: WidgetPlacementAsset) => {
102
+ onChange(formatWidgetAssetManifest(nextAsset));
103
+ };
104
+
105
+ return (
106
+ <div
107
+ className="widget-asset-editor widget-asset-editor--design"
108
+ data-testid="widget-asset-editor"
109
+ >
110
+ <div className="widget-asset-editor__design">
111
+ {editingManifest ? (
112
+ <section className="widget-asset-editor__meta">
113
+ <WidgetAssetMetadataForm
114
+ asset={asset}
115
+ readOnly={readOnly}
116
+ onChange={commitAssetMetadata}
117
+ />
118
+ </section>
119
+ ) : null}
120
+ {editingContent ? (
121
+ <section className="widget-asset-editor__template">
122
+ <WidgetInspectorPanel
123
+ path={[]}
124
+ readOnly={readOnly}
125
+ widget={asset.content as GenericWidget}
126
+ widgetRegistry={registry}
127
+ onPatch={(nextWidget) => onChange(formatWidgetAssetContent(nextWidget))}
128
+ />
129
+ </section>
130
+ ) : null}
131
+ <section aria-label="Asset preview" className="widget-asset-editor__preview">
132
+ <header className="widget-asset-editor__preview-header">Placement preview</header>
133
+ <div className="widget-asset-editor__preview-body">
134
+ <JdwPreview json={previewJson} registry={registry} />
135
+ </div>
136
+ </section>
137
+ </div>
138
+ </div>
139
+ );
140
+ }
@@ -0,0 +1,82 @@
1
+ import type { WidgetPlacementAsset, WidgetPlacementAssetKind } from '@workbench-kit/contracts';
2
+
3
+ import {
4
+ WorkbenchPropertySelectRow,
5
+ WorkbenchPropertyStack,
6
+ WorkbenchPropertySection,
7
+ WorkbenchPropertyTextRow,
8
+ } from '../layout/WorkbenchPropertyPanel';
9
+
10
+ const CATEGORY_OPTIONS = [
11
+ { label: 'Content', value: 'content' },
12
+ { label: 'Layout', value: 'layout' },
13
+ { label: 'Template', value: 'template' },
14
+ ];
15
+
16
+ const KIND_OPTIONS: { label: string; value: WidgetPlacementAssetKind }[] = [
17
+ { label: 'Leaf', value: 'leaf' },
18
+ { label: 'Container', value: 'container' },
19
+ { label: 'Template', value: 'template' },
20
+ ];
21
+
22
+ export interface WidgetAssetMetadataFormProps {
23
+ readonly asset: WidgetPlacementAsset;
24
+ readonly readOnly?: boolean | undefined;
25
+ readonly onChange: (next: WidgetPlacementAsset) => void;
26
+ }
27
+
28
+ export function WidgetAssetMetadataForm({
29
+ asset,
30
+ onChange,
31
+ readOnly = false,
32
+ }: WidgetAssetMetadataFormProps) {
33
+ const patch = (patchValue: Partial<WidgetPlacementAsset>) => {
34
+ onChange({ ...asset, ...patchValue });
35
+ };
36
+
37
+ return (
38
+ <WorkbenchPropertyStack>
39
+ <WorkbenchPropertySection title="Asset">
40
+ <WorkbenchPropertyTextRow
41
+ label="ID"
42
+ readOnly={readOnly}
43
+ value={asset.id}
44
+ onValueChange={(next) => patch({ id: next })}
45
+ />
46
+ <WorkbenchPropertyTextRow
47
+ label="Label"
48
+ readOnly={readOnly}
49
+ value={asset.label}
50
+ onValueChange={(next) => patch({ label: next })}
51
+ />
52
+ <WorkbenchPropertyTextRow
53
+ label="Description"
54
+ readOnly={readOnly}
55
+ value={asset.description ?? ''}
56
+ onValueChange={(next) => patch({ description: next.length > 0 ? next : undefined })}
57
+ />
58
+ <WorkbenchPropertySelectRow
59
+ label="Category"
60
+ disabled={readOnly}
61
+ options={CATEGORY_OPTIONS}
62
+ value={asset.category}
63
+ onValueChange={(next) => patch({ category: next })}
64
+ />
65
+ <WorkbenchPropertySelectRow
66
+ label="Kind"
67
+ disabled={readOnly}
68
+ options={KIND_OPTIONS}
69
+ value={asset.kind ?? 'leaf'}
70
+ onValueChange={(next) => patch({ kind: next })}
71
+ />
72
+ <WorkbenchPropertyTextRow
73
+ label="Icon"
74
+ placeholder="codicon-symbol-text"
75
+ readOnly={readOnly}
76
+ value={asset.icon ?? ''}
77
+ onValueChange={(next) => patch({ icon: next.length > 0 ? next : undefined })}
78
+ />
79
+ </WorkbenchPropertySection>
80
+ </WorkbenchPropertyStack>
81
+ );
82
+ }
@@ -0,0 +1,42 @@
1
+ import { IconButton } from '../primitives/IconButton';
2
+ import { cx } from '../utils/cx';
3
+ import type { WidgetAssetViewMode } from './widget-asset-mode.js';
4
+
5
+ export interface WidgetAssetModeControlsProps {
6
+ readonly className?: string | undefined;
7
+ readonly mode: WidgetAssetViewMode;
8
+ readonly onModeChange: (mode: WidgetAssetViewMode) => void;
9
+ }
10
+
11
+ export function WidgetAssetModeControls({
12
+ className,
13
+ mode,
14
+ onModeChange,
15
+ }: WidgetAssetModeControlsProps) {
16
+ return (
17
+ <div
18
+ className={cx('ui-workbench-artifact-shell__modes', 'widget-asset-mode-controls', className)}
19
+ >
20
+ <IconButton
21
+ aria-pressed={mode === 'design'}
22
+ className={cx(
23
+ 'ui-workbench-artifact-shell__mode',
24
+ mode === 'design' && 'ui-workbench-artifact-shell__mode--active',
25
+ )}
26
+ icon="codicon-edit"
27
+ label="Design"
28
+ onClick={() => onModeChange('design')}
29
+ />
30
+ <IconButton
31
+ aria-pressed={mode === 'code'}
32
+ className={cx(
33
+ 'ui-workbench-artifact-shell__mode',
34
+ mode === 'code' && 'ui-workbench-artifact-shell__mode--active',
35
+ )}
36
+ icon="codicon-code"
37
+ label="Code"
38
+ onClick={() => onModeChange('code')}
39
+ />
40
+ </div>
41
+ );
42
+ }
@@ -0,0 +1,50 @@
1
+ import { useMemo } from 'react';
2
+
3
+ import { JsonCodeEditorPane } from '../jdw/JsonCodeEditorPane.js';
4
+ import type { WorkspaceEditorTheme } from '../workbench/workspace/WorkspaceEditor.js';
5
+ import type { WorkspaceFile } from '../workbench/workspace/types.js';
6
+ import { resolveWidgetAssetMimeType } from './widget-asset-document.js';
7
+
8
+ export interface WidgetAssetSourceEditorProps {
9
+ readonly path?: string | undefined;
10
+ readonly value: string;
11
+ readonly onChange: (value: string) => void;
12
+ readonly onSave?: (() => void) | undefined;
13
+ readonly parseError?: string | null | undefined;
14
+ readonly readOnly?: boolean | undefined;
15
+ readonly theme?: WorkspaceEditorTheme | undefined;
16
+ }
17
+
18
+ export function WidgetAssetSourceEditor({
19
+ path = 'assets/new-asset/manifest.json',
20
+ value,
21
+ onChange,
22
+ onSave,
23
+ parseError = null,
24
+ readOnly = false,
25
+ theme = 'dark',
26
+ }: WidgetAssetSourceEditorProps) {
27
+ const file = useMemo<WorkspaceFile>(
28
+ () => ({
29
+ content: value,
30
+ mimeType: resolveWidgetAssetMimeType(path),
31
+ path,
32
+ }),
33
+ [path, value],
34
+ );
35
+
36
+ return (
37
+ <div className="widget-asset-source" data-testid="widget-asset-source">
38
+ <JsonCodeEditorPane
39
+ documentParseError={parseError}
40
+ file={file}
41
+ readOnly={readOnly}
42
+ showProblemsPanel={false}
43
+ theme={theme}
44
+ value={value}
45
+ onChange={onChange}
46
+ onSave={onSave}
47
+ />
48
+ </div>
49
+ );
50
+ }
@@ -0,0 +1,112 @@
1
+ import { useState, type ReactNode } from 'react';
2
+ import type { WidgetRegistryContract } from '@workbench-kit/contracts';
3
+
4
+ import { Panel, PanelBody, PanelHeader } from '../layout/Panel';
5
+ import { Button } from '../primitives/Button';
6
+ import { Toolbar } from '../primitives/Toolbar';
7
+ import type { WorkspaceEditorTheme } from '../workbench/workspace/WorkspaceEditor.js';
8
+ import { fileNameOfPath } from '../workbench/workspace/path';
9
+ import type { WorkspaceFile } from '../workbench/workspace/types.js';
10
+ import { WidgetAssetEditor } from './WidgetAssetEditor.js';
11
+ import { WidgetAssetModeControls } from './WidgetAssetModeControls.js';
12
+ import { DEFAULT_WIDGET_ASSET_VIEW_MODE, type WidgetAssetViewMode } from './widget-asset-mode.js';
13
+
14
+ export interface WidgetAssetWorkbenchProps {
15
+ readonly path?: string | undefined;
16
+ readonly title?: ReactNode | undefined;
17
+ readonly value: string;
18
+ readonly onChange: (value: string) => void;
19
+ readonly onSave?: (() => void) | undefined;
20
+ readonly onDiscard?: (() => void) | undefined;
21
+ readonly dirty?: boolean | undefined;
22
+ readonly readOnly?: boolean | undefined;
23
+ readonly registry?: WidgetRegistryContract<unknown> | undefined;
24
+ readonly theme?: WorkspaceEditorTheme | undefined;
25
+ readonly defaultViewMode?: WidgetAssetViewMode | undefined;
26
+ readonly viewMode?: WidgetAssetViewMode | undefined;
27
+ readonly onViewModeChange?: ((mode: WidgetAssetViewMode) => void) | undefined;
28
+ readonly workspaceFiles?: readonly WorkspaceFile[] | undefined;
29
+ }
30
+
31
+ export function WidgetAssetWorkbench({
32
+ path,
33
+ title,
34
+ value,
35
+ onChange,
36
+ onSave,
37
+ onDiscard,
38
+ dirty = false,
39
+ readOnly = false,
40
+ registry,
41
+ theme = 'dark',
42
+ defaultViewMode = DEFAULT_WIDGET_ASSET_VIEW_MODE,
43
+ viewMode,
44
+ onViewModeChange,
45
+ workspaceFiles,
46
+ }: WidgetAssetWorkbenchProps) {
47
+ const [uncontrolledViewMode, setUncontrolledViewMode] =
48
+ useState<WidgetAssetViewMode>(defaultViewMode);
49
+ const resolvedViewMode = viewMode ?? uncontrolledViewMode;
50
+
51
+ const setViewMode = (nextMode: WidgetAssetViewMode) => {
52
+ if (viewMode === undefined) {
53
+ setUncontrolledViewMode(nextMode);
54
+ }
55
+ onViewModeChange?.(nextMode);
56
+ };
57
+
58
+ const resolvedTitle = title ?? (path ? fileNameOfPath(path) : 'Widget asset');
59
+
60
+ return (
61
+ <Panel
62
+ className="widget-asset-workbench"
63
+ data-mode={resolvedViewMode}
64
+ data-testid="widget-asset-workbench"
65
+ >
66
+ <PanelHeader
67
+ actions={
68
+ <Toolbar>
69
+ {dirty && !readOnly ? (
70
+ <>
71
+ {onDiscard ? <Button onClick={onDiscard}>Discard</Button> : null}
72
+ {onSave ? (
73
+ <Button variant="primary" onClick={onSave}>
74
+ Save
75
+ </Button>
76
+ ) : null}
77
+ </>
78
+ ) : null}
79
+ <WidgetAssetModeControls mode={resolvedViewMode} onModeChange={setViewMode} />
80
+ </Toolbar>
81
+ }
82
+ >
83
+ <span className="widget-asset-workbench__title">
84
+ {resolvedTitle}
85
+ {path ? (
86
+ <span className="widget-asset-workbench__path" title={path}>
87
+ {path}
88
+ </span>
89
+ ) : null}
90
+ {dirty ? (
91
+ <span className="widget-asset-workbench__dirty-indicator" title="Unsaved changes">
92
+
93
+ </span>
94
+ ) : null}
95
+ </span>
96
+ </PanelHeader>
97
+ <PanelBody className="widget-asset-workbench__body">
98
+ <WidgetAssetEditor
99
+ path={path}
100
+ readOnly={readOnly}
101
+ registry={registry}
102
+ theme={theme}
103
+ value={value}
104
+ viewMode={resolvedViewMode}
105
+ workspaceFiles={workspaceFiles}
106
+ onChange={onChange}
107
+ onSave={onSave}
108
+ />
109
+ </PanelBody>
110
+ </Panel>
111
+ );
112
+ }
@@ -0,0 +1,17 @@
1
+ export { WidgetAssetEditor } from './WidgetAssetEditor.js';
2
+ export type { WidgetAssetEditorProps } from './WidgetAssetEditor.js';
3
+ export { WidgetAssetWorkbench } from './WidgetAssetWorkbench.js';
4
+ export type { WidgetAssetWorkbenchProps } from './WidgetAssetWorkbench.js';
5
+ export { WidgetAssetMetadataForm } from './WidgetAssetMetadataForm.js';
6
+ export type { WidgetAssetMetadataFormProps } from './WidgetAssetMetadataForm.js';
7
+ export { WidgetAssetModeControls } from './WidgetAssetModeControls.js';
8
+ export type { WidgetAssetModeControlsProps } from './WidgetAssetModeControls.js';
9
+ export { DEFAULT_WIDGET_ASSET_VIEW_MODE, type WidgetAssetViewMode } from './widget-asset-mode.js';
10
+ export {
11
+ WIDGET_ASSET_CONTENT_MIME,
12
+ WIDGET_ASSET_MANIFEST_MIME,
13
+ WIDGET_ASSET_SCHEMA_MIME,
14
+ isWidgetAssetDocument,
15
+ resolveWidgetAssetMimeType,
16
+ } from './widget-asset-document.js';
17
+ export type { WidgetAssetDocumentRef } from './widget-asset-document.js';
@@ -0,0 +1,37 @@
1
+ import {
2
+ isWidgetAssetContentPath,
3
+ isWidgetAssetPackageFilePath,
4
+ isWidgetAssetSchemaPath,
5
+ } from '@workbench-kit/jdw';
6
+
7
+ export const WIDGET_ASSET_MANIFEST_MIME =
8
+ 'application/vnd.workbench-kit.widget-asset-manifest+json';
9
+ export const WIDGET_ASSET_CONTENT_MIME = 'application/vnd.workbench-kit.widget-asset-content+json';
10
+ export const WIDGET_ASSET_SCHEMA_MIME = 'application/vnd.workbench-kit.widget-asset-schema+json';
11
+
12
+ export interface WidgetAssetDocumentRef {
13
+ readonly path: string;
14
+ readonly mimeType?: string | undefined;
15
+ }
16
+
17
+ export function resolveWidgetAssetMimeType(path: string): string {
18
+ if (isWidgetAssetContentPath(path)) {
19
+ return WIDGET_ASSET_CONTENT_MIME;
20
+ }
21
+ if (isWidgetAssetSchemaPath(path)) {
22
+ return WIDGET_ASSET_SCHEMA_MIME;
23
+ }
24
+ return WIDGET_ASSET_MANIFEST_MIME;
25
+ }
26
+
27
+ export function isWidgetAssetDocument(file: WidgetAssetDocumentRef): boolean {
28
+ if (
29
+ file.mimeType === WIDGET_ASSET_MANIFEST_MIME ||
30
+ file.mimeType === WIDGET_ASSET_CONTENT_MIME ||
31
+ file.mimeType === WIDGET_ASSET_SCHEMA_MIME
32
+ ) {
33
+ return true;
34
+ }
35
+
36
+ return isWidgetAssetPackageFilePath(file.path);
37
+ }
@@ -0,0 +1,3 @@
1
+ export type WidgetAssetViewMode = 'design' | 'code';
2
+
3
+ export const DEFAULT_WIDGET_ASSET_VIEW_MODE: WidgetAssetViewMode = 'design';
@@ -0,0 +1,12 @@
1
+ import { widgetStudioBuiltinAssetFiles } from '@workbench-kit/adapters';
2
+ import type { WidgetAssetCatalogContract } from '@workbench-kit/contracts';
3
+ import { createWidgetAssetCatalogFromWorkspaceFiles } from '@workbench-kit/jdw';
4
+ /** Built-in palette assets shipped with the workbench demo and widget studio. */
5
+ export function createBuiltinWidgetAssetCatalog(): WidgetAssetCatalogContract {
6
+ return createWidgetAssetCatalogFromWorkspaceFiles(
7
+ widgetStudioBuiltinAssetFiles.map((file) => ({
8
+ path: file.path,
9
+ content: file.content,
10
+ })),
11
+ );
12
+ }
@@ -0,0 +1,79 @@
1
+ import type { WidgetAssetCatalogContract, WidgetRegistryContract } from '@workbench-kit/contracts';
2
+
3
+ import { WorkspaceEditor } from '../workbench/workspace/WorkspaceEditor.js';
4
+ import type { WorkspaceEditorPanelRenderEditorContext } from '../workbench/workspace/WorkspaceEditorPanel.js';
5
+ import type { WorkspaceFile } from '../workbench/workspace/types.js';
6
+ import { WidgetAssetWorkbench } from '../widget-asset/WidgetAssetWorkbench.js';
7
+ import { isWidgetAssetDocument } from '../widget-asset/widget-asset-document.js';
8
+ import { WidgetTreeWorkbench } from '../widget-tree/WidgetTreeWorkbench.js';
9
+ import { isWidgetTreeDocument } from '../widget-tree/widget-tree-document.js';
10
+ import { resolveWidgetStudioAssetCatalog } from './resolve-widget-studio-asset-catalog.js';
11
+
12
+ export interface WidgetStudioRenderContext extends WorkspaceEditorPanelRenderEditorContext {
13
+ readonly workspaceFiles?: readonly WorkspaceFile[] | undefined;
14
+ }
15
+
16
+ export interface CreateWidgetStudioWorkspaceEditorOptions {
17
+ readonly registry?: WidgetRegistryContract<unknown> | undefined;
18
+ readonly resolveAssetCatalog?:
19
+ | ((files: readonly WorkspaceFile[]) => WidgetAssetCatalogContract)
20
+ | undefined;
21
+ }
22
+
23
+ export function createWidgetStudioWorkspaceEditorRenderer(
24
+ options: CreateWidgetStudioWorkspaceEditorOptions = {},
25
+ ) {
26
+ const { registry, resolveAssetCatalog = resolveWidgetStudioAssetCatalog } = options;
27
+
28
+ return (context: WidgetStudioRenderContext) => {
29
+ const workspaceFiles = context.workspaceFiles ?? [];
30
+ const assetCatalog = resolveAssetCatalog(workspaceFiles);
31
+
32
+ if (isWidgetAssetDocument(context.file)) {
33
+ return (
34
+ <WidgetAssetWorkbench
35
+ dirty={context.isDirty}
36
+ path={context.file.path}
37
+ readOnly={false}
38
+ registry={registry}
39
+ theme={context.theme}
40
+ value={context.content}
41
+ workspaceFiles={workspaceFiles}
42
+ onChange={context.onChange}
43
+ onDiscard={context.onDiscard}
44
+ onSave={() => context.onSave(context.content)}
45
+ />
46
+ );
47
+ }
48
+
49
+ if (isWidgetTreeDocument(context.file)) {
50
+ return (
51
+ <WidgetTreeWorkbench
52
+ assetCatalog={assetCatalog}
53
+ dirty={context.isDirty}
54
+ path={context.file.path}
55
+ readOnly={false}
56
+ registry={registry}
57
+ theme={context.theme}
58
+ value={context.content}
59
+ onChange={context.onChange}
60
+ onDiscard={context.onDiscard}
61
+ onSave={() => context.onSave(context.content)}
62
+ />
63
+ );
64
+ }
65
+
66
+ return (
67
+ <WorkspaceEditor
68
+ key={context.file.path}
69
+ file={context.file}
70
+ readOnly={false}
71
+ showHeader={false}
72
+ theme={context.theme}
73
+ value={context.content}
74
+ onChange={context.onChange}
75
+ onSave={(content) => context.onSave(content)}
76
+ />
77
+ );
78
+ };
79
+ }
@@ -0,0 +1,7 @@
1
+ export { createWidgetStudioWorkspaceEditorRenderer } from './create-widget-studio-workspace-editor.js';
2
+ export type {
3
+ CreateWidgetStudioWorkspaceEditorOptions,
4
+ WidgetStudioRenderContext,
5
+ } from './create-widget-studio-workspace-editor.js';
6
+ export { createBuiltinWidgetAssetCatalog } from './builtin-widget-asset-catalog.js';
7
+ export { resolveWidgetStudioAssetCatalog } from './resolve-widget-studio-asset-catalog.js';
@@ -0,0 +1,26 @@
1
+ import type { WidgetAssetCatalogContract } from '@workbench-kit/contracts';
2
+ import {
3
+ createWidgetAssetCatalogFromWorkspaceFiles,
4
+ mergeWidgetAssetCatalogs,
5
+ } from '@workbench-kit/jdw';
6
+
7
+ import type { WorkspaceFile } from '../workbench/workspace/types.js';
8
+ import { createBuiltinWidgetAssetCatalog } from './builtin-widget-asset-catalog.js';
9
+
10
+ /**
11
+ * Resolves the widget studio palette from built-in assets plus workspace asset packages
12
+ * (`<slug>/manifest.json` + `content.json`). Workspace assets override built-ins when they
13
+ * share the same `name`.
14
+ */
15
+ export function resolveWidgetStudioAssetCatalog(
16
+ files: readonly WorkspaceFile[],
17
+ ): WidgetAssetCatalogContract {
18
+ const workspaceCatalog = createWidgetAssetCatalogFromWorkspaceFiles(
19
+ files.map((file) => ({
20
+ path: file.path,
21
+ content: file.content,
22
+ })),
23
+ );
24
+
25
+ return mergeWidgetAssetCatalogs(createBuiltinWidgetAssetCatalog(), workspaceCatalog);
26
+ }