@moloodev/editor-sdk 0.2.1 → 0.2.2
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/package.json +5 -4
- package/src/host/host-chrome-manifest.ts +135 -0
- package/src/host/host-chrome.tsx +304 -0
- package/src/host/index.ts +18 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moloodev/editor-sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./src/view.ts",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"./origin-headers": "./src/origin-headers.ts",
|
|
11
11
|
"./layout": "./src/layout/index.ts",
|
|
12
12
|
"./layout/frame": "./src/layout/frame.ts",
|
|
13
|
+
"./host": "./src/host/index.ts",
|
|
13
14
|
"./commands": "./src/commands/index.ts",
|
|
14
15
|
"./commands/editor-document": "./src/commands/manifest/editor-document.ts",
|
|
15
16
|
"./commands/when": "./src/commands/when/validate.ts",
|
|
@@ -28,8 +29,8 @@
|
|
|
28
29
|
"y-protocols": "^1.0.7",
|
|
29
30
|
"yjs": "^13.6.32",
|
|
30
31
|
"zod": "^4.5.4",
|
|
31
|
-
"@moloodev/platform": "0.2.
|
|
32
|
-
"@moloodev/ui": "0.2.
|
|
32
|
+
"@moloodev/platform": "0.2.2",
|
|
33
|
+
"@moloodev/ui": "0.2.2"
|
|
33
34
|
},
|
|
34
35
|
"peerDependencies": {
|
|
35
36
|
"react": "^19.0.0",
|
|
@@ -47,7 +48,7 @@
|
|
|
47
48
|
"react-dom": "^19.2.8",
|
|
48
49
|
"tsx": "^4.23.13",
|
|
49
50
|
"typescript": "^7.0.2",
|
|
50
|
-
"@moloodev/typescript-config": "0.2.
|
|
51
|
+
"@moloodev/typescript-config": "0.2.2"
|
|
51
52
|
},
|
|
52
53
|
"publishConfig": {
|
|
53
54
|
"access": "public"
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isBrowserReservedKey,
|
|
3
|
+
isClipboardForbiddenKey,
|
|
4
|
+
mergeManifests,
|
|
5
|
+
normalizeKey,
|
|
6
|
+
type ContributionManifest,
|
|
7
|
+
} from "../commands";
|
|
8
|
+
import {
|
|
9
|
+
parseChromeManifest,
|
|
10
|
+
type ChromeManifest,
|
|
11
|
+
type EditorEvent,
|
|
12
|
+
type EditorLayoutValues,
|
|
13
|
+
} from "../protocol-entry";
|
|
14
|
+
import type { Spec } from "../layout/types";
|
|
15
|
+
import { sectionIdsOf, validateLayoutSpec } from "../layout/validate-layout-spec";
|
|
16
|
+
|
|
17
|
+
/** Keys the host keeps for the browser or clipboard; an editor view may not bind them. */
|
|
18
|
+
export function isHostReservedKey(key: string): boolean {
|
|
19
|
+
const normalized = normalizeKey(key);
|
|
20
|
+
return isBrowserReservedKey(normalized) || isClipboardForbiddenKey(normalized);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const HOST_CHROME_SCOPE = "host-chrome";
|
|
24
|
+
|
|
25
|
+
export function scopeManifest(
|
|
26
|
+
manifest: ChromeManifest,
|
|
27
|
+
activation?: "exact",
|
|
28
|
+
): ContributionManifest {
|
|
29
|
+
return {
|
|
30
|
+
id: HOST_CHROME_SCOPE,
|
|
31
|
+
...(activation ? { activation } : {}),
|
|
32
|
+
...(manifest.contributes?.commands
|
|
33
|
+
? { commands: manifest.contributes.commands }
|
|
34
|
+
: {}),
|
|
35
|
+
...(manifest.contributes?.menus
|
|
36
|
+
? { menus: manifest.contributes.menus }
|
|
37
|
+
: {}),
|
|
38
|
+
...(manifest.contributes?.keybindings
|
|
39
|
+
? { keybindings: manifest.contributes.keybindings }
|
|
40
|
+
: {}),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function hostChromeMergeInput(
|
|
45
|
+
manifest: ChromeManifest,
|
|
46
|
+
): ContributionManifest[] {
|
|
47
|
+
return [scopeManifest(manifest)];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type CommandStateBody = Extract<
|
|
51
|
+
EditorEvent,
|
|
52
|
+
{ type: "command.state" }
|
|
53
|
+
>["body"];
|
|
54
|
+
|
|
55
|
+
export type HostChrome =
|
|
56
|
+
| { ok: true; manifest: ChromeManifest; spec: Spec }
|
|
57
|
+
| { ok: false; code: "schema" };
|
|
58
|
+
|
|
59
|
+
function stringField(value: unknown, key: string): string | undefined {
|
|
60
|
+
if (typeof value !== "object" || value === null) return undefined;
|
|
61
|
+
for (const [name, field] of Object.entries(value)) {
|
|
62
|
+
if (name === key && typeof field === "string") return field;
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function childField(value: unknown, key: string): unknown {
|
|
68
|
+
if (typeof value !== "object" || value === null) return undefined;
|
|
69
|
+
for (const [name, field] of Object.entries(value)) {
|
|
70
|
+
if (name === key) return field;
|
|
71
|
+
}
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function layoutForSpec(layout: EditorLayoutValues, spec: Spec): EditorLayoutValues {
|
|
76
|
+
if (layout.collapsed === undefined) return { panels: layout.panels };
|
|
77
|
+
const allowed = new Set<string>();
|
|
78
|
+
for (const element of Object.values(spec.elements)) {
|
|
79
|
+
if (stringField(element, "type") !== "Panel") continue;
|
|
80
|
+
for (const id of sectionIdsOf(childField(element, "props"))) allowed.add(id);
|
|
81
|
+
}
|
|
82
|
+
const collapsed: { [sectionId: string]: boolean } = {};
|
|
83
|
+
for (const [id, value] of Object.entries(layout.collapsed)) {
|
|
84
|
+
if (allowed.has(id)) collapsed[id] = value;
|
|
85
|
+
}
|
|
86
|
+
if (Object.keys(collapsed).length === 0) return { panels: layout.panels };
|
|
87
|
+
return { panels: layout.panels, collapsed };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function panelContentIds(spec: Spec): string[] {
|
|
91
|
+
const ids: string[] = [];
|
|
92
|
+
for (const element of Object.values(spec.elements)) {
|
|
93
|
+
if (stringField(element, "type") !== "Panel") continue;
|
|
94
|
+
const contentId = stringField(childField(element, "props"), "contentId");
|
|
95
|
+
if (contentId) ids.push(contentId);
|
|
96
|
+
}
|
|
97
|
+
return ids;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function commandIdsOf(manifest: ChromeManifest): Set<string> {
|
|
101
|
+
const ids = new Set<string>();
|
|
102
|
+
for (const command of manifest.contributes?.commands ?? []) ids.add(command.id);
|
|
103
|
+
return ids;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function validateHostChrome(value: unknown): HostChrome {
|
|
107
|
+
const parsed = parseChromeManifest(value);
|
|
108
|
+
if (!parsed.ok) return { ok: false, code: "schema" };
|
|
109
|
+
let spec: Spec;
|
|
110
|
+
try {
|
|
111
|
+
spec = validateLayoutSpec(parsed.message.layout);
|
|
112
|
+
} catch {
|
|
113
|
+
return { ok: false, code: "schema" };
|
|
114
|
+
}
|
|
115
|
+
const ids = panelContentIds(spec);
|
|
116
|
+
const idSet = new Set(ids);
|
|
117
|
+
if (idSet.size !== ids.length) return { ok: false, code: "schema" };
|
|
118
|
+
const titleKeys = Object.keys(parsed.message.titles);
|
|
119
|
+
if (titleKeys.length !== idSet.size) return { ok: false, code: "schema" };
|
|
120
|
+
for (const key of titleKeys) {
|
|
121
|
+
if (!idSet.has(key)) return { ok: false, code: "schema" };
|
|
122
|
+
}
|
|
123
|
+
for (const key of Object.keys(parsed.message.panelHeaders ?? {})) {
|
|
124
|
+
if (!idSet.has(key)) return { ok: false, code: "schema" };
|
|
125
|
+
}
|
|
126
|
+
for (const binding of parsed.message.contributes?.keybindings ?? []) {
|
|
127
|
+
if (isHostReservedKey(binding.key)) return { ok: false, code: "schema" };
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
mergeManifests(hostChromeMergeInput(parsed.message));
|
|
131
|
+
} catch {
|
|
132
|
+
return { ok: false, code: "schema" };
|
|
133
|
+
}
|
|
134
|
+
return { ok: true, manifest: parsed.message, spec };
|
|
135
|
+
}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { useEffect, useLayoutEffect, useMemo, type ReactNode } from "react";
|
|
2
|
+
import { createStateStore } from "@json-render/core";
|
|
3
|
+
import { CommandsProvider, ScopeProvider, useCommands } from "../commands";
|
|
4
|
+
import type { ChromeManifest } from "../protocol-entry";
|
|
5
|
+
import { commandLayoutState, type CommandLayoutEntry } from "../layout/chrome/command-layout-state";
|
|
6
|
+
import { EditorMenuBar } from "../layout/chrome/editor-menu";
|
|
7
|
+
import {
|
|
8
|
+
LayoutBindingProvider,
|
|
9
|
+
type CommandIconComponent,
|
|
10
|
+
type LayoutActionHandler,
|
|
11
|
+
} from "../layout/layout-binding";
|
|
12
|
+
import type { Spec } from "../layout/types";
|
|
13
|
+
import { KindIcon } from "@moloodev/ui/kind-icon";
|
|
14
|
+
import { TooltipProvider } from "@moloodev/ui";
|
|
15
|
+
import {
|
|
16
|
+
hostChromeMergeInput,
|
|
17
|
+
scopeManifest,
|
|
18
|
+
validateHostChrome,
|
|
19
|
+
type CommandStateBody,
|
|
20
|
+
} from "./host-chrome-manifest";
|
|
21
|
+
|
|
22
|
+
const iconByName = new Map<string, CommandIconComponent>();
|
|
23
|
+
|
|
24
|
+
export type HostChromeHandlers = {
|
|
25
|
+
openStartDialog: () => void;
|
|
26
|
+
openBrowser: () => void;
|
|
27
|
+
openPreferences?: () => void;
|
|
28
|
+
/** When set, `editor.import` opens the host file picker instead of reaching the view. */
|
|
29
|
+
openImport?: () => void;
|
|
30
|
+
/** Runs before `editor.resetLayout` is forwarded to the view. */
|
|
31
|
+
resetLayout?: () => void;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type EditorHostChromeProps = {
|
|
35
|
+
value: unknown;
|
|
36
|
+
host: HostChromeHandlers;
|
|
37
|
+
onViewCommand: (commandId: string) => void;
|
|
38
|
+
commandState?: Readonly<Record<string, CommandStateBody>>;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function stringField(value: unknown, key: string): string | undefined {
|
|
42
|
+
if (typeof value !== "object" || value === null) return undefined;
|
|
43
|
+
for (const [name, field] of Object.entries(value)) {
|
|
44
|
+
if (name === key && typeof field === "string") return field;
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function childField(value: unknown, key: string): unknown {
|
|
50
|
+
if (typeof value !== "object" || value === null) return undefined;
|
|
51
|
+
for (const [name, field] of Object.entries(value)) {
|
|
52
|
+
if (name === key) return field;
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function toolbarIcons(spec: Spec): Map<string, string> {
|
|
58
|
+
const icons = new Map<string, string>();
|
|
59
|
+
for (const element of Object.values(spec.elements)) {
|
|
60
|
+
if (stringField(element, "type") !== "ToolbarButton") continue;
|
|
61
|
+
const props = childField(element, "props");
|
|
62
|
+
const commandId = stringField(props, "commandId");
|
|
63
|
+
const icon = stringField(props, "icon");
|
|
64
|
+
if (!commandId || !icon || icons.has(commandId)) continue;
|
|
65
|
+
icons.set(commandId, icon);
|
|
66
|
+
}
|
|
67
|
+
return icons;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function chromeIcon(name: string): CommandIconComponent {
|
|
71
|
+
const found = iconByName.get(name);
|
|
72
|
+
if (found) return found;
|
|
73
|
+
function Icon({
|
|
74
|
+
className,
|
|
75
|
+
strokeWidth,
|
|
76
|
+
}: {
|
|
77
|
+
className?: string;
|
|
78
|
+
strokeWidth?: number | string;
|
|
79
|
+
}) {
|
|
80
|
+
return (
|
|
81
|
+
<KindIcon
|
|
82
|
+
icon={name}
|
|
83
|
+
className={className}
|
|
84
|
+
strokeWidth={typeof strokeWidth === "number" ? strokeWidth : undefined}
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
iconByName.set(name, Icon);
|
|
89
|
+
return Icon;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function overlayCommandState(
|
|
93
|
+
commands: Record<string, CommandLayoutEntry>,
|
|
94
|
+
commandState: Readonly<Record<string, CommandStateBody>> | undefined,
|
|
95
|
+
): Record<string, CommandLayoutEntry> {
|
|
96
|
+
// Frame command.state resolves visibility and availability from the view's own when context.
|
|
97
|
+
if (!commandState) return commands;
|
|
98
|
+
let next: Record<string, CommandLayoutEntry> | undefined;
|
|
99
|
+
for (const [id, state] of Object.entries(commandState)) {
|
|
100
|
+
const current = (next ?? commands)[id];
|
|
101
|
+
if (!current) continue;
|
|
102
|
+
next ??= { ...commands };
|
|
103
|
+
next[id] = {
|
|
104
|
+
visible: state.visible,
|
|
105
|
+
disabled: state.disabled,
|
|
106
|
+
pressed: state.pressed,
|
|
107
|
+
label: state.label,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return next ?? commands;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function dispatchHostCommand(
|
|
114
|
+
commandId: string,
|
|
115
|
+
declared: ReadonlySet<string>,
|
|
116
|
+
host: HostChromeHandlers,
|
|
117
|
+
onViewCommand: (commandId: string) => void,
|
|
118
|
+
): void {
|
|
119
|
+
if (!declared.has(commandId)) return;
|
|
120
|
+
if (commandId === "editor.new") {
|
|
121
|
+
host.openStartDialog();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (commandId === "editor.open") {
|
|
125
|
+
host.openBrowser();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (commandId === "editor.preferences" && host.openPreferences) {
|
|
129
|
+
host.openPreferences();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (commandId === "editor.import" && host.openImport) {
|
|
133
|
+
host.openImport();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (commandId === "editor.resetLayout") host.resetLayout?.();
|
|
137
|
+
onViewCommand(commandId);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function chromeCommandHandlers(
|
|
141
|
+
declared: ReadonlySet<string>,
|
|
142
|
+
host: HostChromeHandlers,
|
|
143
|
+
onViewCommand: (commandId: string) => void,
|
|
144
|
+
): { executeCommand: LayoutActionHandler } {
|
|
145
|
+
return {
|
|
146
|
+
executeCommand: (params) => {
|
|
147
|
+
const commandId =
|
|
148
|
+
params && typeof params.commandId === "string" ? params.commandId : "";
|
|
149
|
+
dispatchHostCommand(commandId, declared, host, onViewCommand);
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function HostCommandHandlers({
|
|
155
|
+
manifest,
|
|
156
|
+
host,
|
|
157
|
+
onViewCommand,
|
|
158
|
+
}: {
|
|
159
|
+
manifest: ChromeManifest;
|
|
160
|
+
host: HostChromeHandlers;
|
|
161
|
+
onViewCommand: (commandId: string) => void;
|
|
162
|
+
}) {
|
|
163
|
+
const { registerHandler } = useCommands();
|
|
164
|
+
const commandIds = useMemo(
|
|
165
|
+
() => manifest.contributes?.commands?.map((command) => command.id) ?? [],
|
|
166
|
+
[manifest],
|
|
167
|
+
);
|
|
168
|
+
const declared = useMemo(() => new Set(commandIds), [commandIds]);
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
// ScopeProvider installs editor.undo/redo in its own effect. This sibling's
|
|
171
|
+
// effect runs after ScopeProvider's and keeps every declared id on this map.
|
|
172
|
+
const unsubs = commandIds.map((id) =>
|
|
173
|
+
registerHandler(id, () => {
|
|
174
|
+
dispatchHostCommand(id, declared, host, onViewCommand);
|
|
175
|
+
}),
|
|
176
|
+
);
|
|
177
|
+
return () => {
|
|
178
|
+
for (const unsub of unsubs) unsub();
|
|
179
|
+
};
|
|
180
|
+
}, [commandIds, declared, host, onViewCommand, registerHandler]);
|
|
181
|
+
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function HostCommands({
|
|
186
|
+
manifest,
|
|
187
|
+
host,
|
|
188
|
+
onViewCommand,
|
|
189
|
+
children,
|
|
190
|
+
}: {
|
|
191
|
+
manifest: ChromeManifest;
|
|
192
|
+
host: HostChromeHandlers;
|
|
193
|
+
onViewCommand: (commandId: string) => void;
|
|
194
|
+
children: ReactNode;
|
|
195
|
+
}) {
|
|
196
|
+
return (
|
|
197
|
+
<>
|
|
198
|
+
<ScopeProvider manifest={scopeManifest(manifest, "exact")}>
|
|
199
|
+
{children}
|
|
200
|
+
</ScopeProvider>
|
|
201
|
+
<HostCommandHandlers manifest={manifest} host={host} onViewCommand={onViewCommand} />
|
|
202
|
+
</>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function ChromeFrame({
|
|
207
|
+
spec,
|
|
208
|
+
host,
|
|
209
|
+
onViewCommand,
|
|
210
|
+
commandState,
|
|
211
|
+
}: {
|
|
212
|
+
spec: Spec;
|
|
213
|
+
host: HostChromeHandlers;
|
|
214
|
+
onViewCommand: (commandId: string) => void;
|
|
215
|
+
commandState?: Readonly<Record<string, CommandStateBody>>;
|
|
216
|
+
}) {
|
|
217
|
+
const { commands, getContext, keybindings } = useCommands();
|
|
218
|
+
const ctx = getContext();
|
|
219
|
+
const declared = useMemo(
|
|
220
|
+
() => new Set(commands.map((command) => command.id)),
|
|
221
|
+
[commands],
|
|
222
|
+
);
|
|
223
|
+
const layoutCommands = useMemo(
|
|
224
|
+
() => overlayCommandState(commandLayoutState(commands, ctx).commands, commandState),
|
|
225
|
+
[commandState, commands, ctx],
|
|
226
|
+
);
|
|
227
|
+
const store = useMemo(() => createStateStore({ commands: layoutCommands }), []);
|
|
228
|
+
useLayoutEffect(() => {
|
|
229
|
+
if (store.get("/commands") !== layoutCommands) {
|
|
230
|
+
store.set("/commands", layoutCommands);
|
|
231
|
+
}
|
|
232
|
+
}, [layoutCommands, store]);
|
|
233
|
+
const icons = useMemo(() => toolbarIcons(spec), [spec]);
|
|
234
|
+
const handlers = useMemo(
|
|
235
|
+
() => chromeCommandHandlers(declared, host, onViewCommand),
|
|
236
|
+
[declared, host, onViewCommand],
|
|
237
|
+
);
|
|
238
|
+
const keysByCommand = useMemo(
|
|
239
|
+
() =>
|
|
240
|
+
new Map(keybindings.map((binding) => [binding.commandId, binding.key])),
|
|
241
|
+
[keybindings],
|
|
242
|
+
);
|
|
243
|
+
const resolveIcon = useMemo(
|
|
244
|
+
() => (icon: string | undefined, commandId: string) => {
|
|
245
|
+
const name = icon ?? icons.get(commandId);
|
|
246
|
+
if (!name) return null;
|
|
247
|
+
return chromeIcon(name);
|
|
248
|
+
},
|
|
249
|
+
[icons],
|
|
250
|
+
);
|
|
251
|
+
const shortcutFor = useMemo(
|
|
252
|
+
() => (commandId: string) => keysByCommand.get(commandId),
|
|
253
|
+
[keysByCommand],
|
|
254
|
+
);
|
|
255
|
+
const binding = useMemo(
|
|
256
|
+
() => ({
|
|
257
|
+
store,
|
|
258
|
+
handlers,
|
|
259
|
+
resolveIcon,
|
|
260
|
+
shortcutFor,
|
|
261
|
+
}),
|
|
262
|
+
[handlers, resolveIcon, shortcutFor, store],
|
|
263
|
+
);
|
|
264
|
+
return (
|
|
265
|
+
<LayoutBindingProvider value={binding}>
|
|
266
|
+
<EditorMenuBar
|
|
267
|
+
commandState={layoutCommands}
|
|
268
|
+
onExecute={(commandId) => handlers.executeCommand({ commandId })}
|
|
269
|
+
/>
|
|
270
|
+
</LayoutBindingProvider>
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function EditorHostChrome({
|
|
275
|
+
value,
|
|
276
|
+
host,
|
|
277
|
+
onViewCommand,
|
|
278
|
+
commandState,
|
|
279
|
+
}: EditorHostChromeProps) {
|
|
280
|
+
const validated = useMemo(() => validateHostChrome(value), [value]);
|
|
281
|
+
const seeded = useMemo(
|
|
282
|
+
() => (validated.ok ? hostChromeMergeInput(validated.manifest) : []),
|
|
283
|
+
[validated],
|
|
284
|
+
);
|
|
285
|
+
if (!validated.ok) return null;
|
|
286
|
+
return (
|
|
287
|
+
<TooltipProvider>
|
|
288
|
+
<CommandsProvider manifests={seeded}>
|
|
289
|
+
<HostCommands
|
|
290
|
+
manifest={validated.manifest}
|
|
291
|
+
host={host}
|
|
292
|
+
onViewCommand={onViewCommand}
|
|
293
|
+
>
|
|
294
|
+
<ChromeFrame
|
|
295
|
+
spec={validated.spec}
|
|
296
|
+
host={host}
|
|
297
|
+
onViewCommand={onViewCommand}
|
|
298
|
+
commandState={commandState}
|
|
299
|
+
/>
|
|
300
|
+
</HostCommands>
|
|
301
|
+
</CommandsProvider>
|
|
302
|
+
</TooltipProvider>
|
|
303
|
+
);
|
|
304
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export {
|
|
2
|
+
EditorHostChrome,
|
|
3
|
+
chromeCommandHandlers,
|
|
4
|
+
dispatchHostCommand,
|
|
5
|
+
type EditorHostChromeProps,
|
|
6
|
+
type HostChromeHandlers,
|
|
7
|
+
} from "./host-chrome";
|
|
8
|
+
export {
|
|
9
|
+
commandIdsOf,
|
|
10
|
+
hostChromeMergeInput,
|
|
11
|
+
isHostReservedKey,
|
|
12
|
+
layoutForSpec,
|
|
13
|
+
panelContentIds,
|
|
14
|
+
scopeManifest,
|
|
15
|
+
validateHostChrome,
|
|
16
|
+
type CommandStateBody,
|
|
17
|
+
type HostChrome,
|
|
18
|
+
} from "./host-chrome-manifest";
|