@workbench-kit/shell-react 0.0.2-prototype.0.2.10
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 +98 -0
- package/package.json +51 -0
- package/src/chat/ai-mock-proposals.ts +39 -0
- package/src/chat/command-input.ts +52 -0
- package/src/chat/command-policy.ts +54 -0
- package/src/chat/command-surface.tsx +155 -0
- package/src/chat/use-chat-command-proposals.ts +194 -0
- package/src/chat/view-data.ts +20 -0
- package/src/chat/view.tsx +220 -0
- package/src/commands/inspector-surface.tsx +46 -0
- package/src/commands/use-command-descriptors.ts +41 -0
- package/src/commands/use-context-key-revision.ts +18 -0
- package/src/commands/view-data.ts +17 -0
- package/src/commands/view.tsx +43 -0
- package/src/devtools/WorkbenchDevtoolsPanel.tsx +124 -0
- package/src/devtools/WorkbenchDevtoolsShell.tsx +17 -0
- package/src/devtools/index.ts +11 -0
- package/src/devtools/use-workbench-devtools-snapshot.ts +84 -0
- package/src/devtools/workbench-devtools-snapshot.ts +140 -0
- package/src/devtools/workbench-devtools.css +82 -0
- package/src/editor/area-dnd.ts +69 -0
- package/src/editor/area-model.ts +71 -0
- package/src/editor/area.css +454 -0
- package/src/editor/area.tsx +676 -0
- package/src/editor/host-surface.tsx +691 -0
- package/src/editor/markdown-document-view-data.ts +16 -0
- package/src/editor/pane-visibility.ts +130 -0
- package/src/editor/resource.ts +36 -0
- package/src/editor/state-storage.ts +182 -0
- package/src/editor/tab-context-menu.ts +118 -0
- package/src/editor/use-editor.ts +138 -0
- package/src/editor/view-providers.tsx +504 -0
- package/src/editor/workspace-file-availability.ts +15 -0
- package/src/editor/workspace-reconcile.tsx +31 -0
- package/src/explorer/context-menu.ts +114 -0
- package/src/explorer/create-command-workspace-explorer-port.ts +86 -0
- package/src/explorer/reveal.ts +112 -0
- package/src/explorer/search-view-data.ts +15 -0
- package/src/explorer/search-view.tsx +65 -0
- package/src/explorer/use-active-workspace-path.ts +15 -0
- package/src/explorer/view-data.ts +58 -0
- package/src/explorer/view.tsx +227 -0
- package/src/extensions/context-menu.ts +52 -0
- package/src/extensions/management-model.ts +313 -0
- package/src/extensions/use-extension-management.ts +241 -0
- package/src/extensions/view-data.ts +19 -0
- package/src/extensions/view.tsx +63 -0
- package/src/field-remap/chrome-labels.ts +74 -0
- package/src/field-remap/convert-note-editor.tsx +206 -0
- package/src/field-remap/convert-palette.tsx +126 -0
- package/src/field-remap/demo.tsx +105 -0
- package/src/field-remap/detail-panel.tsx +659 -0
- package/src/field-remap/editor-surface.tsx +36 -0
- package/src/field-remap/flow-adapter.ts +838 -0
- package/src/field-remap/flow-ops.ts +442 -0
- package/src/field-remap/flow.tsx +948 -0
- package/src/field-remap/graph.tsx +559 -0
- package/src/field-remap/index.ts +88 -0
- package/src/field-remap/io-class-browse.tsx +145 -0
- package/src/field-remap/jsonata-transform.ts +166 -0
- package/src/field-remap/panel.tsx +466 -0
- package/src/field-remap/samples.ts +507 -0
- package/src/field-remap/shape-io-editor.tsx +170 -0
- package/src/field-remap/table-mapping-adapter.ts +59 -0
- package/src/field-remap/transform-options-editor.tsx +312 -0
- package/src/field-remap/tree.tsx +404 -0
- package/src/field-remap/view-data.ts +16 -0
- package/src/field-remap/view.css +1367 -0
- package/src/field-remap/view.tsx +86 -0
- package/src/index.ts +294 -0
- package/src/jdw/document-view-data.ts +31 -0
- package/src/jdw/json-form-source-patch.ts +323 -0
- package/src/jdw/lab-view-data.ts +25 -0
- package/src/jdw/lab-view.css +21 -0
- package/src/jdw/lab-view.tsx +79 -0
- package/src/jdw/widget-form-view.tsx +68 -0
- package/src/jdw/widget-preview-view.tsx +54 -0
- package/src/management/keybinding-overrides-storage.ts +49 -0
- package/src/management/keybinding-settings.tsx +16 -0
- package/src/management/palette-commands.ts +38 -0
- package/src/management/preference-settings-storage.ts +50 -0
- package/src/management/settings-ids.ts +12 -0
- package/src/management/settings.tsx +96 -0
- package/src/management/use-command-management.ts +191 -0
- package/src/management/use-keybinding-management.ts +130 -0
- package/src/shell/model.tsx +149 -0
- package/src/shell/provider.tsx +606 -0
- package/src/shell/secondary-actions.tsx +59 -0
- package/src/shell/settings-constants.ts +9 -0
- package/src/shell/settings.tsx +599 -0
- package/src/shell/shell.tsx +680 -0
- package/src/shell/titlebar-layout-controls.tsx +4 -0
- package/src/shell/view-host.tsx +212 -0
- package/src/workbench/appearance-storage.ts +107 -0
- package/src/workbench/command-host.tsx +243 -0
- package/src/workbench/command-palette.ts +208 -0
- package/src/workbench/keybinding-bridge.ts +54 -0
- package/src/workbench/layout-storage.ts +134 -0
- package/src/workbench/profile-modal.tsx +118 -0
- package/src/workbench/shell-command-registration.ts +58 -0
- package/src/workbench/startup-gate.tsx +136 -0
- package/src/workbench/status-sections.ts +43 -0
- package/src/workbench/use-persisted-appearance.ts +33 -0
- package/src/workbench/user-commands.ts +46 -0
- package/src/workbench/workspace-view-state.ts +35 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { useMemo, type JSX } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
projectSourceFields,
|
|
4
|
+
projectTargetSlots,
|
|
5
|
+
type SourceField,
|
|
6
|
+
type TargetSlot,
|
|
7
|
+
} from '@workbench-kit/field-remap';
|
|
8
|
+
|
|
9
|
+
export type FieldRemapIoChrome = 'browse' | 'edit' | 'none';
|
|
10
|
+
|
|
11
|
+
export interface FieldRemapIoClassBrowseProps {
|
|
12
|
+
readonly sources: readonly SourceField[];
|
|
13
|
+
readonly targets: readonly TargetSlot[];
|
|
14
|
+
/**
|
|
15
|
+
* When `false` (default), omit `hidden` ports/fields.
|
|
16
|
+
* When `true`, keep them and show a Hidden badge.
|
|
17
|
+
*/
|
|
18
|
+
readonly includeHidden?: boolean;
|
|
19
|
+
readonly sourcesTitle?: string;
|
|
20
|
+
readonly targetsTitle?: string;
|
|
21
|
+
readonly className?: string;
|
|
22
|
+
readonly emptyLabel?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function resolveFieldRemapIoChrome(
|
|
26
|
+
ioChrome: FieldRemapIoChrome | undefined,
|
|
27
|
+
editableShapes: boolean,
|
|
28
|
+
): FieldRemapIoChrome {
|
|
29
|
+
if (ioChrome) {
|
|
30
|
+
return ioChrome;
|
|
31
|
+
}
|
|
32
|
+
return editableShapes ? 'edit' : 'none';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function formatClassRef(classRef: { readonly id: string; readonly version: number }): string {
|
|
36
|
+
return `${classRef.id}@${classRef.version}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function FieldTree({
|
|
40
|
+
nodes,
|
|
41
|
+
emptyLabel,
|
|
42
|
+
}: {
|
|
43
|
+
readonly nodes: readonly (SourceField | TargetSlot)[];
|
|
44
|
+
readonly emptyLabel: string;
|
|
45
|
+
}): JSX.Element {
|
|
46
|
+
if (nodes.length === 0) {
|
|
47
|
+
return <p className="workbench-field-remap-io-browse__empty">{emptyLabel}</p>;
|
|
48
|
+
}
|
|
49
|
+
return (
|
|
50
|
+
<ul className="workbench-field-remap-io-browse__fields">
|
|
51
|
+
{nodes.map((node) => (
|
|
52
|
+
<li key={node.id}>
|
|
53
|
+
<div className="workbench-field-remap-io-browse__row">
|
|
54
|
+
<code className="workbench-field-remap-io-browse__path">{node.path ?? node.label}</code>
|
|
55
|
+
<span className="workbench-field-remap-io-browse__meta">
|
|
56
|
+
{node.dataType ? <span>{node.dataType}</span> : null}
|
|
57
|
+
{node.classRef ? (
|
|
58
|
+
<span className="workbench-field-remap-io-browse__badge" title="classRef">
|
|
59
|
+
{formatClassRef(node.classRef)}
|
|
60
|
+
</span>
|
|
61
|
+
) : null}
|
|
62
|
+
{node.hidden === true ? (
|
|
63
|
+
<span className="workbench-field-remap-io-browse__badge" title="hidden">
|
|
64
|
+
Hidden
|
|
65
|
+
</span>
|
|
66
|
+
) : null}
|
|
67
|
+
</span>
|
|
68
|
+
</div>
|
|
69
|
+
{node.children?.length ? (
|
|
70
|
+
<FieldTree emptyLabel={emptyLabel} nodes={node.children} />
|
|
71
|
+
) : null}
|
|
72
|
+
</li>
|
|
73
|
+
))}
|
|
74
|
+
</ul>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function PortSection({
|
|
79
|
+
title,
|
|
80
|
+
nodes,
|
|
81
|
+
emptyLabel,
|
|
82
|
+
}: {
|
|
83
|
+
readonly title: string;
|
|
84
|
+
readonly nodes: readonly (SourceField | TargetSlot)[];
|
|
85
|
+
readonly emptyLabel: string;
|
|
86
|
+
}): JSX.Element {
|
|
87
|
+
return (
|
|
88
|
+
<section aria-label={title} className="workbench-field-remap-io-browse__section">
|
|
89
|
+
<h3 className="workbench-field-remap-io-browse__section-title">{title}</h3>
|
|
90
|
+
{nodes.length === 0 ? (
|
|
91
|
+
<p className="workbench-field-remap-io-browse__empty">{emptyLabel}</p>
|
|
92
|
+
) : (
|
|
93
|
+
nodes.map((node) => (
|
|
94
|
+
<div className="workbench-field-remap-io-browse__port" key={node.id}>
|
|
95
|
+
<p className="workbench-field-remap-io-browse__port-title">
|
|
96
|
+
<span className="workbench-field-remap-io-browse__port-id">{node.label}</span>
|
|
97
|
+
<span className="workbench-field-remap-io-browse__port-meta">
|
|
98
|
+
{node.classRef ? formatClassRef(node.classRef) : node.id}
|
|
99
|
+
{node.hidden === true ? ' · Hidden' : ''}
|
|
100
|
+
</span>
|
|
101
|
+
</p>
|
|
102
|
+
<FieldTree
|
|
103
|
+
emptyLabel={emptyLabel}
|
|
104
|
+
nodes={node.children?.length ? node.children : [node]}
|
|
105
|
+
/>
|
|
106
|
+
</div>
|
|
107
|
+
))
|
|
108
|
+
)}
|
|
109
|
+
</section>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Read-only I/O chrome: browse source/target trees with optional `classRef` and `hidden`.
|
|
115
|
+
* Prefer this over {@link FieldRemapShapeIoEditor} when hosts author topology on Flow and
|
|
116
|
+
* only need to inspect declared shapes.
|
|
117
|
+
*/
|
|
118
|
+
export function FieldRemapIoClassBrowse({
|
|
119
|
+
sources,
|
|
120
|
+
targets,
|
|
121
|
+
includeHidden = false,
|
|
122
|
+
sourcesTitle = 'Inputs',
|
|
123
|
+
targetsTitle = 'Outputs',
|
|
124
|
+
className,
|
|
125
|
+
emptyLabel = 'No fields',
|
|
126
|
+
}: FieldRemapIoClassBrowseProps): JSX.Element {
|
|
127
|
+
const projectedSources = useMemo(
|
|
128
|
+
() => projectSourceFields(sources, { includeHidden }),
|
|
129
|
+
[includeHidden, sources],
|
|
130
|
+
);
|
|
131
|
+
const projectedTargets = useMemo(
|
|
132
|
+
() => projectTargetSlots(targets, { includeHidden }),
|
|
133
|
+
[includeHidden, targets],
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<div
|
|
138
|
+
className={['workbench-field-remap-io-browse', className].filter(Boolean).join(' ')}
|
|
139
|
+
data-testid="field-remap-io-browse"
|
|
140
|
+
>
|
|
141
|
+
<PortSection emptyLabel={emptyLabel} nodes={projectedSources} title={sourcesTitle} />
|
|
142
|
+
<PortSection emptyLabel={emptyLabel} nodes={projectedTargets} title={targetsTitle} />
|
|
143
|
+
</div>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import jsonata from 'jsonata';
|
|
2
|
+
import type { ValueTransformDefinition } from '@workbench-kit/field-remap';
|
|
3
|
+
|
|
4
|
+
/** Host-registered JSONata expression transform (Stedi-style advanced mapping). */
|
|
5
|
+
export const JSONATA_TRANSFORM_ID = 'expr:jsonata' as const;
|
|
6
|
+
|
|
7
|
+
/** Default wall-clock budget for a single expression evaluation. */
|
|
8
|
+
export const DEFAULT_JSONATA_TIMEOUT_MS = 2_000;
|
|
9
|
+
|
|
10
|
+
/** Default maximum expression source length (characters). */
|
|
11
|
+
export const DEFAULT_JSONATA_MAX_EXPRESSION_LENGTH = 4_096;
|
|
12
|
+
|
|
13
|
+
export type JsonataTransformErrorPolicy = 'passthrough' | 'throw';
|
|
14
|
+
|
|
15
|
+
export interface CreateJsonataValueTransformOptions {
|
|
16
|
+
/** Wall-clock timeout for `evaluate` (default {@link DEFAULT_JSONATA_TIMEOUT_MS}). */
|
|
17
|
+
readonly timeoutMs?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Reject expressions longer than this many characters
|
|
20
|
+
* (default {@link DEFAULT_JSONATA_MAX_EXPRESSION_LENGTH}).
|
|
21
|
+
*/
|
|
22
|
+
readonly maxExpressionLength?: number;
|
|
23
|
+
/**
|
|
24
|
+
* - `throw` (default): surface timeout / compile / evaluate failures to the host
|
|
25
|
+
* - `passthrough`: return the original value (legacy silent identity)
|
|
26
|
+
*/
|
|
27
|
+
readonly onError?: JsonataTransformErrorPolicy;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function createJsonataValueTransform(
|
|
31
|
+
options: CreateJsonataValueTransformOptions = {},
|
|
32
|
+
): ValueTransformDefinition {
|
|
33
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_JSONATA_TIMEOUT_MS;
|
|
34
|
+
const maxExpressionLength = options.maxExpressionLength ?? DEFAULT_JSONATA_MAX_EXPRESSION_LENGTH;
|
|
35
|
+
const onError = options.onError ?? 'throw';
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
id: JSONATA_TRANSFORM_ID,
|
|
39
|
+
label: 'JSONata expression',
|
|
40
|
+
description:
|
|
41
|
+
'Evaluate a JSONata expression against the source value (host-registered; bounded).',
|
|
42
|
+
category: 'expression',
|
|
43
|
+
inputTypes: ['string', 'number', 'boolean', 'object', 'array', 'unknown'],
|
|
44
|
+
outputType: 'unknown',
|
|
45
|
+
optionFields: [
|
|
46
|
+
{
|
|
47
|
+
key: 'expression',
|
|
48
|
+
label: 'Expression',
|
|
49
|
+
kind: 'string',
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
apply: async (value, context) => {
|
|
53
|
+
const expression =
|
|
54
|
+
typeof context.options?.expression === 'string' ? context.options.expression.trim() : '';
|
|
55
|
+
if (!expression) {
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (expression.length > maxExpressionLength) {
|
|
60
|
+
return handleJsonataError(
|
|
61
|
+
new Error(
|
|
62
|
+
`JSONata expression exceeds max length (${expression.length} > ${maxExpressionLength}).`,
|
|
63
|
+
),
|
|
64
|
+
value,
|
|
65
|
+
onError,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const compiled = jsonata(expression);
|
|
71
|
+
// jsonata@2.x evaluate returns a Promise (1.x was synchronous).
|
|
72
|
+
return await raceJsonataEvaluation(compiled.evaluate(value), {
|
|
73
|
+
timeoutMs,
|
|
74
|
+
signal: context.signal,
|
|
75
|
+
});
|
|
76
|
+
} catch (error) {
|
|
77
|
+
return handleJsonataError(error, value, onError);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Default bounded transform (`onError: 'throw'`, 2s timeout, 4k expression cap). */
|
|
84
|
+
export const jsonataValueTransform: ValueTransformDefinition = createJsonataValueTransform();
|
|
85
|
+
|
|
86
|
+
export class JsonataTransformTimeoutError extends Error {
|
|
87
|
+
override readonly name = 'JsonataTransformTimeoutError';
|
|
88
|
+
|
|
89
|
+
constructor(timeoutMs: number) {
|
|
90
|
+
super(`JSONata evaluation timed out after ${timeoutMs}ms.`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Race a JSONata evaluation against timeout / AbortSignal (exported for unit tests). */
|
|
95
|
+
export async function raceJsonataEvaluation<T>(
|
|
96
|
+
evaluation: PromiseLike<T>,
|
|
97
|
+
bounds: { readonly timeoutMs: number; readonly signal?: AbortSignal },
|
|
98
|
+
): Promise<T> {
|
|
99
|
+
const { timeoutMs, signal } = bounds;
|
|
100
|
+
if (signal?.aborted) {
|
|
101
|
+
throw signal.reason instanceof Error
|
|
102
|
+
? signal.reason
|
|
103
|
+
: Object.assign(new Error('The operation was aborted.'), { name: 'AbortError' });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
107
|
+
return evaluation;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return new Promise<T>((resolve, reject) => {
|
|
111
|
+
let settled = false;
|
|
112
|
+
const timer = setTimeout(() => {
|
|
113
|
+
if (settled) return;
|
|
114
|
+
settled = true;
|
|
115
|
+
cleanup();
|
|
116
|
+
reject(new JsonataTransformTimeoutError(timeoutMs));
|
|
117
|
+
}, timeoutMs);
|
|
118
|
+
|
|
119
|
+
const onAbort = () => {
|
|
120
|
+
if (settled) return;
|
|
121
|
+
settled = true;
|
|
122
|
+
cleanup();
|
|
123
|
+
reject(
|
|
124
|
+
signal?.reason instanceof Error
|
|
125
|
+
? signal.reason
|
|
126
|
+
: Object.assign(new Error('The operation was aborted.'), { name: 'AbortError' }),
|
|
127
|
+
);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const cleanup = () => {
|
|
131
|
+
clearTimeout(timer);
|
|
132
|
+
signal?.removeEventListener('abort', onAbort);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
136
|
+
|
|
137
|
+
Promise.resolve(evaluation).then(
|
|
138
|
+
(result) => {
|
|
139
|
+
if (settled) return;
|
|
140
|
+
settled = true;
|
|
141
|
+
cleanup();
|
|
142
|
+
resolve(result);
|
|
143
|
+
},
|
|
144
|
+
(error: unknown) => {
|
|
145
|
+
if (settled) return;
|
|
146
|
+
settled = true;
|
|
147
|
+
cleanup();
|
|
148
|
+
reject(error);
|
|
149
|
+
},
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function handleJsonataError(
|
|
155
|
+
error: unknown,
|
|
156
|
+
value: unknown,
|
|
157
|
+
onError: JsonataTransformErrorPolicy,
|
|
158
|
+
): unknown {
|
|
159
|
+
if (onError === 'passthrough') {
|
|
160
|
+
return value;
|
|
161
|
+
}
|
|
162
|
+
if (error instanceof Error) {
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
throw new Error(String(error));
|
|
166
|
+
}
|