@snap-rail/framework-shadcn 0.0.0-stage → 0.2.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 +20 -2
- package/docs/framework-authoring.md +59 -0
- package/lib/fallback.js +307 -0
- package/lib/index.js +3036 -0
- package/lib/runtime.js +2827 -0
- package/lib/semantic.js +96 -0
- package/lib/session.js +48 -0
- package/lib/settings-pages.js +68 -0
- package/lib/slots.js +95 -0
- package/lib/types/Shell.d.ts +13 -0
- package/lib/types/Shell.js +17 -0
- package/lib/types/client.d.ts +41 -0
- package/lib/types/client.js +39 -0
- package/lib/types/fallback.d.ts +15 -0
- package/lib/types/fallback.js +52 -0
- package/lib/types/index.d.ts +17 -0
- package/lib/types/index.js +17 -0
- package/lib/types/runtime.d.ts +53 -0
- package/lib/types/runtime.js +75 -0
- package/lib/types/semantic.d.ts +55 -0
- package/lib/types/semantic.js +63 -0
- package/lib/types/session.d.ts +43 -0
- package/lib/types/session.js +76 -0
- package/lib/types/settings-dialog.d.ts +17 -0
- package/lib/types/settings-dialog.js +138 -0
- package/lib/types/settings-pages.d.ts +68 -0
- package/lib/types/settings-pages.js +85 -0
- package/lib/types/settings-plugins-page.d.ts +29 -0
- package/lib/types/settings-plugins-page.js +466 -0
- package/lib/types/settings-update-page.d.ts +17 -0
- package/lib/types/settings-update-page.js +124 -0
- package/lib/types/slots.d.ts +114 -0
- package/lib/types/slots.js +119 -0
- package/lib/types/ui/badge.d.ts +17 -0
- package/lib/types/ui/badge.js +29 -0
- package/lib/types/ui/button.d.ts +20 -0
- package/lib/types/ui/button.js +39 -0
- package/lib/types/ui/card.d.ts +14 -0
- package/lib/types/ui/card.js +18 -0
- package/lib/types/ui/checkbox.d.ts +10 -0
- package/lib/types/ui/checkbox.js +13 -0
- package/lib/types/ui/collapsible.d.ts +24 -0
- package/lib/types/ui/collapsible.js +31 -0
- package/lib/types/ui/dialog.d.ts +22 -0
- package/lib/types/ui/dialog.js +29 -0
- package/lib/types/ui/drag-scroll.d.ts +13 -0
- package/lib/types/ui/drag-scroll.js +85 -0
- package/lib/types/ui/hooks.d.ts +16 -0
- package/lib/types/ui/hooks.js +26 -0
- package/lib/types/ui/icons.d.ts +21 -0
- package/lib/types/ui/icons.js +18 -0
- package/lib/types/ui/index.d.ts +32 -0
- package/lib/types/ui/index.js +32 -0
- package/lib/types/ui/input.d.ts +8 -0
- package/lib/types/ui/input.js +6 -0
- package/lib/types/ui/label.d.ts +9 -0
- package/lib/types/ui/label.js +12 -0
- package/lib/types/ui/led.d.ts +13 -0
- package/lib/types/ui/led.js +11 -0
- package/lib/types/ui/number-input.d.ts +35 -0
- package/lib/types/ui/number-input.js +48 -0
- package/lib/types/ui/progress.d.ts +10 -0
- package/lib/types/ui/progress.js +14 -0
- package/lib/types/ui/radio-group.d.ts +12 -0
- package/lib/types/ui/radio-group.js +17 -0
- package/lib/types/ui/schema-form.d.ts +51 -0
- package/lib/types/ui/schema-form.js +124 -0
- package/lib/types/ui/scroll-area.d.ts +10 -0
- package/lib/types/ui/scroll-area.js +13 -0
- package/lib/types/ui/switch.d.ts +10 -0
- package/lib/types/ui/switch.js +13 -0
- package/lib/types/ui/table.d.ts +20 -0
- package/lib/types/ui/table.js +28 -0
- package/lib/types/ui/tabs.d.ts +16 -0
- package/lib/types/ui/tabs.js +23 -0
- package/lib/types/ui/textarea.d.ts +10 -0
- package/lib/types/ui/textarea.js +6 -0
- package/lib/types/ui/theme.d.ts +30 -0
- package/lib/types/ui/theme.js +33 -0
- package/lib/types/ui/tooltip.d.ts +23 -0
- package/lib/types/ui/tooltip.js +15 -0
- package/lib/types/ui/touch-select.d.ts +29 -0
- package/lib/types/ui/touch-select.js +37 -0
- package/lib/types/ui/utils.d.ts +13 -0
- package/lib/types/ui/utils.js +16 -0
- package/lib/types/variables.d.ts +168 -0
- package/lib/types/variables.js +282 -0
- package/lib/types/workflows.d.ts +98 -0
- package/lib/types/workflows.js +95 -0
- package/lib/ui/index.js +1101 -0
- package/lib/ui/theme.css +271 -0
- package/lib/variables.js +260 -0
- package/lib/workflows.js +85 -0
- package/package.json +138 -3
- package/src/ui/theme.css +271 -0
- package/templates/ui-extension/README.md +17 -0
- package/templates/ui-view/README.md +17 -0
package/lib/semantic.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import "@snap-rail/cordis";
|
|
2
|
+
//#region lib/types/slots.js
|
|
3
|
+
/** Stable semantic slot names shared by every layout implementation. */
|
|
4
|
+
const UI_SLOT_IDS = {
|
|
5
|
+
layout: "shell.layout",
|
|
6
|
+
titlebar: "shell.titlebar",
|
|
7
|
+
titlebarActions: "shell.titlebar.actions",
|
|
8
|
+
menu: "shell.menu",
|
|
9
|
+
sidebar: "shell.sidebar",
|
|
10
|
+
main: "shell.main",
|
|
11
|
+
view: "shell.view",
|
|
12
|
+
statusbar: "shell.statusbar",
|
|
13
|
+
overlay: "shell.overlay"
|
|
14
|
+
};
|
|
15
|
+
/** Runtime set used by manifest/semantic validation as well as TypeScript. */
|
|
16
|
+
const STANDARD_UI_SLOT_IDS = new Set(Object.values(UI_SLOT_IDS));
|
|
17
|
+
/** A custom slot is owned by a publisher and a local slot name. */
|
|
18
|
+
const CUSTOM_UI_SLOT_PATTERN = /^custom:[a-z0-9][a-z0-9-]*\.[a-z0-9][a-z0-9-]*$/;
|
|
19
|
+
/** Check the public slot naming contract at runtime. */
|
|
20
|
+
function isValidUiSlotId(value) {
|
|
21
|
+
return STANDARD_UI_SLOT_IDS.has(value) || CUSTOM_UI_SLOT_PATTERN.test(value);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The mode table for the standard slots — the framework owns it.
|
|
25
|
+
*
|
|
26
|
+
* `titlebar` and `main` are `single`: exactly one tenant renders (a skin or
|
|
27
|
+
* replacement displaces the default and falls back on unload). `layout`
|
|
28
|
+
* stays `stack` on purpose — the settings dialog is a permanent overlay
|
|
29
|
+
* occupant beside the active layout, and the fallback shell yields by
|
|
30
|
+
* counting; every other slot collects contributions.
|
|
31
|
+
*/
|
|
32
|
+
const UI_SLOT_MODES = {
|
|
33
|
+
[UI_SLOT_IDS.layout]: "stack",
|
|
34
|
+
[UI_SLOT_IDS.titlebar]: "single",
|
|
35
|
+
[UI_SLOT_IDS.titlebarActions]: "stack",
|
|
36
|
+
[UI_SLOT_IDS.menu]: "stack",
|
|
37
|
+
[UI_SLOT_IDS.sidebar]: "stack",
|
|
38
|
+
[UI_SLOT_IDS.main]: "single",
|
|
39
|
+
[UI_SLOT_IDS.view]: "stack",
|
|
40
|
+
[UI_SLOT_IDS.statusbar]: "stack",
|
|
41
|
+
[UI_SLOT_IDS.overlay]: "stack"
|
|
42
|
+
};
|
|
43
|
+
new Map(Object.entries(UI_SLOT_MODES));
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region lib/types/semantic.js
|
|
46
|
+
/** Mount the semantic contribution registry on top of the slot registry. */
|
|
47
|
+
const semanticPlugin = {
|
|
48
|
+
name: "framework semantic ui contract",
|
|
49
|
+
inject: ["uiSlots"],
|
|
50
|
+
apply(ctx) {
|
|
51
|
+
const slots = ctx.uiSlots;
|
|
52
|
+
const views = /* @__PURE__ */ new Map();
|
|
53
|
+
const layouts = /* @__PURE__ */ new Map();
|
|
54
|
+
const navigation = /* @__PURE__ */ new Map();
|
|
55
|
+
let sequence = 0;
|
|
56
|
+
const ordered = (values) => [...values].sort((a, b) => a.value.order - b.value.order || a.seq - b.seq).map((item) => item.value);
|
|
57
|
+
const register = (table, caller, id, value, disposeSlot) => {
|
|
58
|
+
table.set(id, {
|
|
59
|
+
value,
|
|
60
|
+
seq: sequence++
|
|
61
|
+
});
|
|
62
|
+
const kind = table === views ? "view" : table === layouts ? "layout" : "navigation";
|
|
63
|
+
ctx.emit("ui/semantic-changed", kind);
|
|
64
|
+
const remove = () => {
|
|
65
|
+
if (!table.delete(id)) return;
|
|
66
|
+
disposeSlot?.();
|
|
67
|
+
ctx.emit("ui/semantic-changed", kind);
|
|
68
|
+
};
|
|
69
|
+
caller.effect(() => remove);
|
|
70
|
+
return remove;
|
|
71
|
+
};
|
|
72
|
+
const validateSlot = (kind, id, slot) => {
|
|
73
|
+
if (!isValidUiSlotId(slot)) ctx.emit("ui/semantic-invalid", kind, id, slot);
|
|
74
|
+
};
|
|
75
|
+
ctx.provide("uiContract", {
|
|
76
|
+
registerView(caller, contribution) {
|
|
77
|
+
validateSlot("view", contribution.id, contribution.slot);
|
|
78
|
+
const disposeSlot = slots.register(caller, contribution.slot, contribution);
|
|
79
|
+
return register(views, caller, contribution.id, contribution, disposeSlot);
|
|
80
|
+
},
|
|
81
|
+
registerLayout(caller, contribution) {
|
|
82
|
+
for (const slot of contribution.slots) validateSlot("layout", contribution.id, slot);
|
|
83
|
+
const disposeSlot = slots.register(caller, UI_SLOT_IDS.layout, contribution);
|
|
84
|
+
return register(layouts, caller, contribution.id, contribution, disposeSlot);
|
|
85
|
+
},
|
|
86
|
+
registerNavigation(caller, contribution) {
|
|
87
|
+
return register(navigation, caller, contribution.id, contribution);
|
|
88
|
+
},
|
|
89
|
+
listViews: () => ordered(views.values()),
|
|
90
|
+
listLayouts: () => ordered(layouts.values()),
|
|
91
|
+
listNavigation: () => ordered(navigation.values())
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
//#endregion
|
|
96
|
+
export { semanticPlugin as default };
|
package/lib/session.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { SESSION_OPERATOR_KEY, settingsChangedSchema } from "@snap-rail/station-rpc/contract";
|
|
2
|
+
import "@snap-rail/cordis";
|
|
3
|
+
import { subscribeFrame } from "@snap-rail/connection";
|
|
4
|
+
//#region lib/types/session.js
|
|
5
|
+
/** Mounts the `ctx.session` mirror; state lives in the apply closure. */
|
|
6
|
+
const sessionPlugin = {
|
|
7
|
+
name: "client-session",
|
|
8
|
+
inject: ["client"],
|
|
9
|
+
apply(ctx) {
|
|
10
|
+
const { link } = ctx.client;
|
|
11
|
+
let operator = null;
|
|
12
|
+
let restored = false;
|
|
13
|
+
const sync = (next) => {
|
|
14
|
+
operator = next;
|
|
15
|
+
ctx.emit("session/changed", operator);
|
|
16
|
+
};
|
|
17
|
+
link.call("session.current", {}).then((result) => {
|
|
18
|
+
if (result.ok) sync(result.value.operator);
|
|
19
|
+
}).catch(() => {}).finally(() => {
|
|
20
|
+
restored = true;
|
|
21
|
+
ctx.emit("session/restored");
|
|
22
|
+
});
|
|
23
|
+
ctx.effect(() => subscribeFrame(link, "settings/changed", settingsChangedSchema, (change) => {
|
|
24
|
+
if (change.key !== SESSION_OPERATOR_KEY) return;
|
|
25
|
+
link.call("session.current", {}).then((result) => {
|
|
26
|
+
if (result.ok && result.value.operator !== operator) sync(result.value.operator);
|
|
27
|
+
}).catch(() => {});
|
|
28
|
+
}));
|
|
29
|
+
ctx.provide("session", {
|
|
30
|
+
current: () => operator,
|
|
31
|
+
restored: () => restored,
|
|
32
|
+
async login(raw) {
|
|
33
|
+
const trimmed = raw.trim();
|
|
34
|
+
if (trimmed === "") throw new Error("工号不能为空");
|
|
35
|
+
const result = await link.call("session.login", { operator: trimmed });
|
|
36
|
+
if (!result.ok) throw new Error(`登录失败(${result.error.code})`);
|
|
37
|
+
sync(trimmed);
|
|
38
|
+
},
|
|
39
|
+
async logout() {
|
|
40
|
+
const result = await link.call("session.logout", {});
|
|
41
|
+
if (!result.ok) throw new Error(`退出失败(${result.error.code})`);
|
|
42
|
+
sync(null);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
//#endregion
|
|
48
|
+
export { sessionPlugin as default };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import "@snap-rail/cordis";
|
|
2
|
+
//#region lib/types/settings-pages.js
|
|
3
|
+
/**
|
|
4
|
+
* Mounts the `ctx.settingsPages` registry. All state lives in
|
|
5
|
+
* construction-time closures: traceable context proxies rebind `this` on
|
|
6
|
+
* every method access, so `this`-reached state cannot back a service here
|
|
7
|
+
* (the uiSlots lesson).
|
|
8
|
+
*/
|
|
9
|
+
const settingsPlugin = {
|
|
10
|
+
name: "client-settings",
|
|
11
|
+
apply(ctx) {
|
|
12
|
+
const pages = /* @__PURE__ */ new Map();
|
|
13
|
+
let nextSeq = 0;
|
|
14
|
+
let open = false;
|
|
15
|
+
let requestedId;
|
|
16
|
+
const firstId = () => [...pages.values()].sort((a, b) => a.def.order - b.def.order || a.seq - b.seq)[0]?.def.id;
|
|
17
|
+
const activeId = () => {
|
|
18
|
+
if (!open) return void 0;
|
|
19
|
+
if (requestedId !== void 0 && pages.has(requestedId)) return requestedId;
|
|
20
|
+
return firstId();
|
|
21
|
+
};
|
|
22
|
+
const openDialog = (id) => {
|
|
23
|
+
if (id !== void 0) requestedId = id;
|
|
24
|
+
if (open && id === void 0) return;
|
|
25
|
+
open = true;
|
|
26
|
+
ctx.emit("settings/open-changed");
|
|
27
|
+
};
|
|
28
|
+
const closeDialog = () => {
|
|
29
|
+
if (!open) return;
|
|
30
|
+
open = false;
|
|
31
|
+
ctx.emit("settings/open-changed");
|
|
32
|
+
};
|
|
33
|
+
ctx.provide("settingsPages", {
|
|
34
|
+
list() {
|
|
35
|
+
const current = activeId();
|
|
36
|
+
return [...pages.values()].sort((a, b) => a.def.order - b.def.order || a.seq - b.seq).map(({ def }) => ({
|
|
37
|
+
...def,
|
|
38
|
+
active: def.id === current
|
|
39
|
+
}));
|
|
40
|
+
},
|
|
41
|
+
register(caller, def) {
|
|
42
|
+
const replaced = pages.has(def.id);
|
|
43
|
+
pages.set(def.id, {
|
|
44
|
+
def,
|
|
45
|
+
seq: nextSeq++
|
|
46
|
+
});
|
|
47
|
+
ctx.emit(replaced ? "settings/open-changed" : "settings/pages-changed");
|
|
48
|
+
const remove = () => {
|
|
49
|
+
if (pages.delete(def.id)) ctx.emit("settings/pages-changed");
|
|
50
|
+
};
|
|
51
|
+
caller.effect(() => remove);
|
|
52
|
+
return remove;
|
|
53
|
+
},
|
|
54
|
+
open: openDialog,
|
|
55
|
+
close: closeDialog,
|
|
56
|
+
toggle() {
|
|
57
|
+
if (open) closeDialog();
|
|
58
|
+
else openDialog();
|
|
59
|
+
},
|
|
60
|
+
isOpen() {
|
|
61
|
+
return open;
|
|
62
|
+
},
|
|
63
|
+
active: activeId
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
//#endregion
|
|
68
|
+
export { settingsPlugin as default };
|
package/lib/slots.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import "@snap-rail/cordis";
|
|
2
|
+
//#region lib/types/slots.js
|
|
3
|
+
/** Stable semantic slot names shared by every layout implementation. */
|
|
4
|
+
const UI_SLOT_IDS = {
|
|
5
|
+
layout: "shell.layout",
|
|
6
|
+
titlebar: "shell.titlebar",
|
|
7
|
+
titlebarActions: "shell.titlebar.actions",
|
|
8
|
+
menu: "shell.menu",
|
|
9
|
+
sidebar: "shell.sidebar",
|
|
10
|
+
main: "shell.main",
|
|
11
|
+
view: "shell.view",
|
|
12
|
+
statusbar: "shell.statusbar",
|
|
13
|
+
overlay: "shell.overlay"
|
|
14
|
+
};
|
|
15
|
+
/** Runtime set used by manifest/semantic validation as well as TypeScript. */
|
|
16
|
+
const STANDARD_UI_SLOT_IDS = new Set(Object.values(UI_SLOT_IDS));
|
|
17
|
+
/** A custom slot is owned by a publisher and a local slot name. */
|
|
18
|
+
const CUSTOM_UI_SLOT_PATTERN = /^custom:[a-z0-9][a-z0-9-]*\.[a-z0-9][a-z0-9-]*$/;
|
|
19
|
+
/** Check the public slot naming contract at runtime. */
|
|
20
|
+
function isValidUiSlotId(value) {
|
|
21
|
+
return STANDARD_UI_SLOT_IDS.has(value) || CUSTOM_UI_SLOT_PATTERN.test(value);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The mode table for the standard slots — the framework owns it.
|
|
25
|
+
*
|
|
26
|
+
* `titlebar` and `main` are `single`: exactly one tenant renders (a skin or
|
|
27
|
+
* replacement displaces the default and falls back on unload). `layout`
|
|
28
|
+
* stays `stack` on purpose — the settings dialog is a permanent overlay
|
|
29
|
+
* occupant beside the active layout, and the fallback shell yields by
|
|
30
|
+
* counting; every other slot collects contributions.
|
|
31
|
+
*/
|
|
32
|
+
const UI_SLOT_MODES = {
|
|
33
|
+
[UI_SLOT_IDS.layout]: "stack",
|
|
34
|
+
[UI_SLOT_IDS.titlebar]: "single",
|
|
35
|
+
[UI_SLOT_IDS.titlebarActions]: "stack",
|
|
36
|
+
[UI_SLOT_IDS.menu]: "stack",
|
|
37
|
+
[UI_SLOT_IDS.sidebar]: "stack",
|
|
38
|
+
[UI_SLOT_IDS.main]: "single",
|
|
39
|
+
[UI_SLOT_IDS.view]: "stack",
|
|
40
|
+
[UI_SLOT_IDS.statusbar]: "stack",
|
|
41
|
+
[UI_SLOT_IDS.overlay]: "stack"
|
|
42
|
+
};
|
|
43
|
+
const STANDARD_SLOT_MODES = new Map(Object.entries(UI_SLOT_MODES));
|
|
44
|
+
/** The occupancy mode of a slot; `custom:*` slots are `stack`. */
|
|
45
|
+
function slotModeOf(slot) {
|
|
46
|
+
return STANDARD_SLOT_MODES.get(slot) ?? "stack";
|
|
47
|
+
}
|
|
48
|
+
/** Mounts the `ctx.uiSlots` registry.
|
|
49
|
+
*
|
|
50
|
+
* The facade binds everything to construction-time closures: traceable
|
|
51
|
+
* context proxies rebind `this` on every method access, so `this`-reached
|
|
52
|
+
* state cannot back a service here (mirrors the field seam's external core).
|
|
53
|
+
*/
|
|
54
|
+
const slotsPlugin = {
|
|
55
|
+
name: "framework slots",
|
|
56
|
+
apply(ctx) {
|
|
57
|
+
const slots = /* @__PURE__ */ new Map();
|
|
58
|
+
const entryOf = (slot) => {
|
|
59
|
+
let entry = slots.get(slot);
|
|
60
|
+
if (entry === void 0) {
|
|
61
|
+
entry = {
|
|
62
|
+
occupants: /* @__PURE__ */ new Map(),
|
|
63
|
+
nextSeq: 0
|
|
64
|
+
};
|
|
65
|
+
slots.set(slot, entry);
|
|
66
|
+
}
|
|
67
|
+
return entry;
|
|
68
|
+
};
|
|
69
|
+
ctx.provide("uiSlots", {
|
|
70
|
+
list(slot) {
|
|
71
|
+
const ranked = [...entryOf(slot).occupants.values()].sort((a, b) => a.occupant.order - b.occupant.order || a.seq - b.seq);
|
|
72
|
+
if (slotModeOf(slot) === "single" && ranked.length > 0) return [ranked.reduce((best, item) => {
|
|
73
|
+
const byPrecedence = (item.occupant.precedence ?? 0) - (best.occupant.precedence ?? 0);
|
|
74
|
+
return byPrecedence > 0 || byPrecedence === 0 && item.seq > best.seq ? item : best;
|
|
75
|
+
}).occupant];
|
|
76
|
+
return ranked.map((item) => item.occupant);
|
|
77
|
+
},
|
|
78
|
+
register(caller, slot, occupant) {
|
|
79
|
+
const entry = entryOf(slot);
|
|
80
|
+
entry.occupants.set(occupant.id, {
|
|
81
|
+
occupant,
|
|
82
|
+
seq: entry.nextSeq++
|
|
83
|
+
});
|
|
84
|
+
ctx.emit("ui/slot-changed", slot);
|
|
85
|
+
const remove = () => {
|
|
86
|
+
if (entry.occupants.delete(occupant.id)) ctx.emit("ui/slot-changed", slot);
|
|
87
|
+
};
|
|
88
|
+
caller.effect(() => remove);
|
|
89
|
+
return remove;
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
//#endregion
|
|
95
|
+
export { CUSTOM_UI_SLOT_PATTERN, STANDARD_UI_SLOT_IDS, UI_SLOT_IDS, UI_SLOT_MODES, slotsPlugin as default, isValidUiSlotId, slotModeOf };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The slot-driven shell: renders every registered layout occupant, or the
|
|
3
|
+
* degradation notice when none did. Any `ui/slot-changed` emission re-renders
|
|
4
|
+
* the whole shell — occupants stay tiny; reconciliation is React's problem.
|
|
5
|
+
*
|
|
6
|
+
* @module @snap-rail/framework-shadcn/runtime/Shell
|
|
7
|
+
*/
|
|
8
|
+
import type { Context } from '@snap-rail/cordis';
|
|
9
|
+
import { type ReactNode } from 'react';
|
|
10
|
+
/** Shell root; mounted once by {@link ../index.tsx | createClientRuntime}. */
|
|
11
|
+
export declare function Shell(props: {
|
|
12
|
+
ctx: Context;
|
|
13
|
+
}): ReactNode;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, useEffect, useState } from 'react';
|
|
3
|
+
import { UI_SLOT_IDS } from "./slots.js";
|
|
4
|
+
/** Shell root; mounted once by {@link ../index.tsx | createClientRuntime}. */
|
|
5
|
+
export function Shell(props) {
|
|
6
|
+
const [tick, setTick] = useState(0);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
const detach = props.ctx.on('ui/slot-changed', () => setTick(value => value + 1));
|
|
9
|
+
return () => { detach(); };
|
|
10
|
+
}, [props.ctx]);
|
|
11
|
+
const layouts = props.ctx.uiSlots.list(UI_SLOT_IDS.layout);
|
|
12
|
+
const content = layouts.length > 0
|
|
13
|
+
? layouts.map(occupant => _jsx(Fragment, { children: occupant.render() }, occupant.id))
|
|
14
|
+
: _jsx("div", { className: "m-auto text-muted-foreground", children: "\u6CA1\u6709\u5DF2\u52A0\u8F7D\u7684\u5E03\u5C40\u63D2\u4EF6\u3002" });
|
|
15
|
+
// `tick` participates so slot changes re-run the render.
|
|
16
|
+
return _jsx("div", { className: "flex h-full flex-col", "data-tick": tick, children: content });
|
|
17
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The framework's installable client face: one bundle carrying every face —
|
|
3
|
+
* the runtime, the slot registry, the shared primitives, the client
|
|
4
|
+
* services, and the fallback layout — re-exported as the contributed-seed
|
|
5
|
+
* map. The shell requires this face once and seeds each
|
|
6
|
+
* `@snap-rail/framework-shadcn/<key>` id from the map, so every installed
|
|
7
|
+
* component shares these exact instances (one slots registry, one primitives
|
|
8
|
+
* set, one runtime entry).
|
|
9
|
+
*
|
|
10
|
+
* @module @snap-rail/framework-shadcn/client
|
|
11
|
+
*/
|
|
12
|
+
import * as fallback from './fallback.tsx';
|
|
13
|
+
import * as runtime from './runtime.tsx';
|
|
14
|
+
import * as session from './session.ts';
|
|
15
|
+
import * as settingsPages from './settings-pages.ts';
|
|
16
|
+
import * as slots from './slots.tsx';
|
|
17
|
+
import * as semantic from './semantic.tsx';
|
|
18
|
+
import * as ui from './ui/index.ts';
|
|
19
|
+
import * as variables from './variables.ts';
|
|
20
|
+
import * as workflows from './workflows.ts';
|
|
21
|
+
/** The faces this framework contributes as shared seeds. */
|
|
22
|
+
export interface FrameworkSeedFaces {
|
|
23
|
+
ui: typeof ui;
|
|
24
|
+
slots: typeof slots;
|
|
25
|
+
semantic: typeof semantic;
|
|
26
|
+
'settings-pages': typeof settingsPages;
|
|
27
|
+
session: typeof session;
|
|
28
|
+
variables: typeof variables;
|
|
29
|
+
workflows: typeof workflows;
|
|
30
|
+
runtime: typeof runtime;
|
|
31
|
+
fallback: typeof fallback;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The client-face default export: the contributed-seed map. No plugin
|
|
35
|
+
* object rides along — the framework mounts nothing into an occupant seat;
|
|
36
|
+
* the runtime face's `createClientRuntime` is the integration point.
|
|
37
|
+
*/
|
|
38
|
+
declare const frameworkClientFace: {
|
|
39
|
+
seeds: FrameworkSeedFaces;
|
|
40
|
+
};
|
|
41
|
+
export default frameworkClientFace;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The framework's installable client face: one bundle carrying every face —
|
|
3
|
+
* the runtime, the slot registry, the shared primitives, the client
|
|
4
|
+
* services, and the fallback layout — re-exported as the contributed-seed
|
|
5
|
+
* map. The shell requires this face once and seeds each
|
|
6
|
+
* `@snap-rail/framework-shadcn/<key>` id from the map, so every installed
|
|
7
|
+
* component shares these exact instances (one slots registry, one primitives
|
|
8
|
+
* set, one runtime entry).
|
|
9
|
+
*
|
|
10
|
+
* @module @snap-rail/framework-shadcn/client
|
|
11
|
+
*/
|
|
12
|
+
import * as fallback from "./fallback.js";
|
|
13
|
+
import * as runtime from "./runtime.js";
|
|
14
|
+
import * as session from "./session.js";
|
|
15
|
+
import * as settingsPages from "./settings-pages.js";
|
|
16
|
+
import * as slots from "./slots.js";
|
|
17
|
+
import * as semantic from "./semantic.js";
|
|
18
|
+
import * as ui from "./ui/index.js";
|
|
19
|
+
import * as variables from "./variables.js";
|
|
20
|
+
import * as workflows from "./workflows.js";
|
|
21
|
+
/**
|
|
22
|
+
* The client-face default export: the contributed-seed map. No plugin
|
|
23
|
+
* object rides along — the framework mounts nothing into an occupant seat;
|
|
24
|
+
* the runtime face's `createClientRuntime` is the integration point.
|
|
25
|
+
*/
|
|
26
|
+
const frameworkClientFace = {
|
|
27
|
+
seeds: {
|
|
28
|
+
ui,
|
|
29
|
+
slots,
|
|
30
|
+
semantic,
|
|
31
|
+
'settings-pages': settingsPages,
|
|
32
|
+
session,
|
|
33
|
+
variables,
|
|
34
|
+
workflows,
|
|
35
|
+
runtime,
|
|
36
|
+
fallback,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
export default frameworkClientFace;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The fallback shell (兜底壳): the built-in minimal layout that shows when
|
|
3
|
+
* no business suite is active — a bare titlebar (window controls + the
|
|
4
|
+
* settings entry, so a fresh install can always reach the plugin manager)
|
|
5
|
+
* and an empty state pointing there. It yields the moment a suite layout
|
|
6
|
+
* registers: it renders only while it is the sole full-screen layout
|
|
7
|
+
* occupant (the settings dialog's own occupant is a portal, not a layout).
|
|
8
|
+
*
|
|
9
|
+
* @module @snap-rail/framework-shadcn/fallback
|
|
10
|
+
*/
|
|
11
|
+
import { type Plugin } from '@snap-rail/cordis';
|
|
12
|
+
import './settings-pages.ts';
|
|
13
|
+
/** The fallback shell occupant. */
|
|
14
|
+
declare const fallbackShellPlugin: Plugin.Object<void>;
|
|
15
|
+
export default fallbackShellPlugin;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The fallback shell (兜底壳): the built-in minimal layout that shows when
|
|
4
|
+
* no business suite is active — a bare titlebar (window controls + the
|
|
5
|
+
* settings entry, so a fresh install can always reach the plugin manager)
|
|
6
|
+
* and an empty state pointing there. It yields the moment a suite layout
|
|
7
|
+
* registers: it renders only while it is the sole full-screen layout
|
|
8
|
+
* occupant (the settings dialog's own occupant is a portal, not a layout).
|
|
9
|
+
*
|
|
10
|
+
* @module @snap-rail/framework-shadcn/fallback
|
|
11
|
+
*/
|
|
12
|
+
import { Context } from '@snap-rail/cordis';
|
|
13
|
+
import "./settings-pages.js";
|
|
14
|
+
import { UI_SLOT_IDS } from "./slots.js";
|
|
15
|
+
import { Button, useRefresh } from "./ui/index.js";
|
|
16
|
+
import { Fragment } from 'react';
|
|
17
|
+
/** The settings dialog's occupant id (a Dialog portal, not a layout). */
|
|
18
|
+
const SETTINGS_OCCUPANT_ID = 'settings-dialog';
|
|
19
|
+
/** Window-control glyphs; inline so the shell needs no icon dependency. */
|
|
20
|
+
function ControlIcons(props) {
|
|
21
|
+
const base = 'flex h-10 w-10 items-center justify-center text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground';
|
|
22
|
+
return (_jsxs("div", { className: "flex items-center", children: [_jsx("button", { type: "button", "aria-label": "\u6700\u5C0F\u5316", className: base, onClick: props.onMinimize, children: _jsx("svg", { viewBox: "0 0 12 12", className: "h-3 w-3", fill: "none", children: _jsx("path", { d: "M2 6h8", stroke: "currentColor", strokeWidth: "1.4" }) }) }), _jsx("button", { type: "button", "aria-label": "\u6700\u5927\u5316", className: base, onClick: props.onToggleMaximize, children: _jsx("svg", { viewBox: "0 0 12 12", className: "h-3 w-3", fill: "none", children: _jsx("rect", { x: "2.5", y: "2.5", width: "7", height: "7", stroke: "currentColor", strokeWidth: "1.2" }) }) }), _jsx("button", { type: "button", "aria-label": "\u5173\u95ED", className: `${base} hover:bg-destructive hover:text-white`, onClick: props.onClose, children: _jsx("svg", { viewBox: "0 0 12 12", className: "h-3 w-3", fill: "none", children: _jsx("path", { d: "m3 3 6 6M9 3l-6 6", stroke: "currentColor", strokeWidth: "1.4" }) }) })] }));
|
|
23
|
+
}
|
|
24
|
+
/** The fallback body: minimal titlebar + install guidance. */
|
|
25
|
+
function FallbackShell(props) {
|
|
26
|
+
useRefresh(props.ctx, ['ui/slot-changed', 'settings/open-changed']);
|
|
27
|
+
const layouts = props.ctx.uiSlots.list(UI_SLOT_IDS.layout);
|
|
28
|
+
const fullScreen = layouts.filter(occupant => occupant.id !== SETTINGS_OCCUPANT_ID);
|
|
29
|
+
// A suite layout took over: render nothing.
|
|
30
|
+
if (fullScreen.length > 1)
|
|
31
|
+
return null;
|
|
32
|
+
const windowControl = (action) => {
|
|
33
|
+
void props.link.call('window.control', { action }).catch(() => undefined);
|
|
34
|
+
};
|
|
35
|
+
return (_jsxs("div", { className: "flex h-full flex-col", "data-region": "fallback-shell", children: [_jsxs("div", { className: "flex h-10 shrink-0 items-center justify-between border-b border-border pl-4 select-none", style: { WebkitAppRegion: 'drag' }, children: [_jsx("span", { className: "text-sm text-muted-foreground", children: "snap-rail" }), _jsxs("div", { className: "flex items-center", style: { WebkitAppRegion: 'no-drag' }, children: [props.ctx.uiSlots.list(UI_SLOT_IDS.titlebarActions).map(occupant => (_jsx(Fragment, { children: occupant.render() }, occupant.id))), _jsx("button", { type: "button", "aria-label": "\u8BBE\u7F6E", className: "flex h-10 w-10 items-center justify-center text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground", onClick: () => props.ctx.settingsPages.open(), children: _jsxs("svg", { viewBox: "0 0 24 24", className: "h-5 w-5", fill: "none", stroke: "currentColor", strokeWidth: "1.6", children: [_jsx("circle", { cx: "12", cy: "12", r: "3.2" }), _jsx("path", { d: "M19.4 15a1.7 1.7 0 0 0 .34 1.87l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.7 1.7 0 0 0-1.87-.34 1.7 1.7 0 0 0-1 1.55V21a2 2 0 1 1-4 0v-.09a1.7 1.7 0 0 0-1-1.55 1.7 1.7 0 0 0-1.87.34l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.7 1.7 0 0 0 .34-1.87 1.7 1.7 0 0 0-1.55-1H3a2 2 0 1 1 0-4h.09a1.7 1.7 0 0 0 1.55-1 1.7 1.7 0 0 0-.34-1.87l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.7 1.7 0 0 0 1.87.34h0a1.7 1.7 0 0 0 1-1.55V3a2 2 0 1 1 4 0v.09a1.7 1.7 0 0 0 1 1.55h0a1.7 1.7 0 0 0 1.87-.34l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.7 1.7 0 0 0-.34 1.87v0a1.7 1.7 0 0 0 1.55 1H21a2 2 0 1 1 0 4h-.09a1.7 1.7 0 0 0-1.55 1Z" })] }) }), _jsx(ControlIcons, { onMinimize: () => windowControl('minimize'), onToggleMaximize: () => windowControl('toggle-maximize'), onClose: () => windowControl('close') })] })] }), _jsx("div", { className: "flex min-h-0 flex-1 items-center justify-center p-6", children: _jsxs("div", { className: "grid max-w-md gap-4 text-center", children: [_jsx("div", { className: "text-xl font-medium text-foreground", children: "\u5C1A\u672A\u6FC0\u6D3B\u4E1A\u52A1\u5957\u4EF6" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "\u6253\u5F00\u8BBE\u7F6E\u7684\u300C\u63D2\u4EF6\u7BA1\u7406\u300D\u9875\uFF0C\u5B89\u88C5\u5E76\u6FC0\u6D3B\u4E00\u4E2A\u4E1A\u52A1\u5957\u4EF6\uFF1B\u901A\u8BAF\u9A71\u52A8\u4E5F\u5728\u540C\u4E00\u5904\u5B89\u88C5\u3002" }), _jsx("div", { children: _jsx(Button, { "data-fallback-open-settings": true, onClick: () => props.ctx.settingsPages.open(), children: "\u6253\u5F00\u8BBE\u7F6E" }) })] }) })] }));
|
|
36
|
+
}
|
|
37
|
+
/** The fallback shell occupant. */
|
|
38
|
+
const fallbackShellPlugin = {
|
|
39
|
+
name: 'framework fallback',
|
|
40
|
+
inject: ['uiSlots', 'settingsPages'],
|
|
41
|
+
apply(ctx) {
|
|
42
|
+
const client = ctx.get('client');
|
|
43
|
+
ctx.uiSlots.register(ctx, UI_SLOT_IDS.layout, {
|
|
44
|
+
id: 'fallback-shell',
|
|
45
|
+
order: 200,
|
|
46
|
+
render() {
|
|
47
|
+
return _jsx(FallbackShell, { ctx: ctx, link: client.link });
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
export default fallbackShellPlugin;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The framework root barrel: one import site for the whole UI substrate —
|
|
3
|
+
* the renderer runtime, the slot registry, the shared shadcn-style
|
|
4
|
+
* primitives, the theme tokens, and the settings/session/variables/workflows
|
|
5
|
+
* services. Component plugins bind to this package through
|
|
6
|
+
* `snapRail.framework`; replacing it is an ecosystem switch, not a hot swap.
|
|
7
|
+
*
|
|
8
|
+
* @module @snap-rail/framework-shadcn
|
|
9
|
+
*/
|
|
10
|
+
export * from './runtime.tsx';
|
|
11
|
+
export * from './slots.tsx';
|
|
12
|
+
export * from './semantic.tsx';
|
|
13
|
+
export * from './settings-pages.ts';
|
|
14
|
+
export * from './session.ts';
|
|
15
|
+
export * from './variables.ts';
|
|
16
|
+
export * from './workflows.ts';
|
|
17
|
+
export * from './fallback.tsx';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The framework root barrel: one import site for the whole UI substrate —
|
|
3
|
+
* the renderer runtime, the slot registry, the shared shadcn-style
|
|
4
|
+
* primitives, the theme tokens, and the settings/session/variables/workflows
|
|
5
|
+
* services. Component plugins bind to this package through
|
|
6
|
+
* `snapRail.framework`; replacing it is an ecosystem switch, not a hot swap.
|
|
7
|
+
*
|
|
8
|
+
* @module @snap-rail/framework-shadcn
|
|
9
|
+
*/
|
|
10
|
+
export * from "./runtime.js";
|
|
11
|
+
export * from "./slots.js";
|
|
12
|
+
export * from "./semantic.js";
|
|
13
|
+
export * from "./settings-pages.js";
|
|
14
|
+
export * from "./session.js";
|
|
15
|
+
export * from "./variables.js";
|
|
16
|
+
export * from "./workflows.js";
|
|
17
|
+
export * from "./fallback.js";
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The client renderer host: takes over the React root from the kernel,
|
|
3
|
+
* mounts the phase-1 occupant plugins into one client Cordis context, and
|
|
4
|
+
* renders the slot-driven shell that re-renders whenever any occupied slot
|
|
5
|
+
* changes. When no layout plugin registered, the shell degrades to a notice
|
|
6
|
+
* instead of an empty window.
|
|
7
|
+
*
|
|
8
|
+
* @module @snap-rail/framework-shadcn/runtime
|
|
9
|
+
*/
|
|
10
|
+
import { Context, type Plugin } from '@snap-rail/cordis';
|
|
11
|
+
import type { ClientHandle } from '@snap-rail/client-kernel';
|
|
12
|
+
import type { HostLink } from '@snap-rail/connection';
|
|
13
|
+
export type { ClientHandle } from '@snap-rail/client-kernel';
|
|
14
|
+
declare module '@snap-rail/cordis' {
|
|
15
|
+
interface Context {
|
|
16
|
+
/** Shared client-side services every occupant may consume. */
|
|
17
|
+
client: ClientServices;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** Services mounted once for all occupant plugins. */
|
|
21
|
+
export interface ClientServices {
|
|
22
|
+
/** The typed host link shared by every occupant. */
|
|
23
|
+
link: HostLink;
|
|
24
|
+
}
|
|
25
|
+
/** One occupant seat: the plugin plus the config carved from its plugins.yml row. */
|
|
26
|
+
export interface OccupantSpec {
|
|
27
|
+
/** The occupant plugin (layout, chrome, process pages). */
|
|
28
|
+
plugin: Plugin;
|
|
29
|
+
/** The occupant's config when its user-layer row carries one. */
|
|
30
|
+
config?: unknown;
|
|
31
|
+
}
|
|
32
|
+
/** Options for {@link createClientRuntime}. */
|
|
33
|
+
export interface RuntimeOptions {
|
|
34
|
+
/**
|
|
35
|
+
* Occupant plugins (layout, chrome, process pages) — a bare plugin or one
|
|
36
|
+
* paired with config. Phase 1 ships them from an in-app list; the
|
|
37
|
+
* boot-graph-driven loader replaces this seat when plugin directories ship
|
|
38
|
+
* outside the bundle.
|
|
39
|
+
*/
|
|
40
|
+
plugins: readonly (Plugin | OccupantSpec)[];
|
|
41
|
+
}
|
|
42
|
+
export interface RuntimeHandle {
|
|
43
|
+
ctx: Context;
|
|
44
|
+
dispose(): Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Take over the booted root: stop the startup page, mount the client plugin
|
|
48
|
+
* tree, render the shell.
|
|
49
|
+
*
|
|
50
|
+
* @param handle - the kernel's boot result.
|
|
51
|
+
* @param options - see {@link RuntimeOptions}.
|
|
52
|
+
*/
|
|
53
|
+
export declare function createClientRuntime(handle: ClientHandle, options: RuntimeOptions): Promise<RuntimeHandle>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The client renderer host: takes over the React root from the kernel,
|
|
4
|
+
* mounts the phase-1 occupant plugins into one client Cordis context, and
|
|
5
|
+
* renders the slot-driven shell that re-renders whenever any occupied slot
|
|
6
|
+
* changes. When no layout plugin registered, the shell degrades to a notice
|
|
7
|
+
* instead of an empty window.
|
|
8
|
+
*
|
|
9
|
+
* @module @snap-rail/framework-shadcn/runtime
|
|
10
|
+
*/
|
|
11
|
+
import { Context } from '@snap-rail/cordis';
|
|
12
|
+
// Seeds are consumed through named imports only: a default import compiles —
|
|
13
|
+
// under rolldown's CJS node-mode interop — into `.default` = the required
|
|
14
|
+
// namespace itself, never the module's own default export, and the timer
|
|
15
|
+
// service class arrives as a plain object cordis refuses (the r1 black
|
|
16
|
+
// screen). plugin-kit's seedImportGuard now fails such builds; this named
|
|
17
|
+
// import is the contract-conformant shape.
|
|
18
|
+
import { TimerService as timerPlugin } from '@snap-rail/cordis-plugin-timer';
|
|
19
|
+
import fallbackPlugin from "./fallback.js";
|
|
20
|
+
import settingsPlugin from "./settings-pages.js";
|
|
21
|
+
import settingsDialogPlugin from "./settings-dialog.js";
|
|
22
|
+
import sessionPlugin from "./session.js";
|
|
23
|
+
import slotsPlugin from "./slots.js";
|
|
24
|
+
import semanticPlugin from "./semantic.js";
|
|
25
|
+
import variablesPlugin from "./variables.js";
|
|
26
|
+
import workflowsPlugin from "./workflows.js";
|
|
27
|
+
import { createRoot } from 'react-dom/client';
|
|
28
|
+
import { Shell } from "./Shell.js";
|
|
29
|
+
/**
|
|
30
|
+
* Take over the booted root: stop the startup page, mount the client plugin
|
|
31
|
+
* tree, render the shell.
|
|
32
|
+
*
|
|
33
|
+
* @param handle - the kernel's boot result.
|
|
34
|
+
* @param options - see {@link RuntimeOptions}.
|
|
35
|
+
*/
|
|
36
|
+
export async function createClientRuntime(handle, options) {
|
|
37
|
+
const ctx = new Context();
|
|
38
|
+
ctx.provide('client', { link: handle.link });
|
|
39
|
+
// Spine services first: timer gives occupants disposable intervals, slots
|
|
40
|
+
// carries the panel seam, session and workflows sit under every occupant,
|
|
41
|
+
// and settingsPages/variables are the seams dialog pages and demand
|
|
42
|
+
// plugins consume. The settings shell rides right after its seam — the
|
|
43
|
+
// dialog, the theme adopter, and the first-party plugin/theme/update
|
|
44
|
+
// pages live in this bundle, so a framework-only install can already
|
|
45
|
+
// manage its own plugins.
|
|
46
|
+
await ctx.plugin(timerPlugin);
|
|
47
|
+
await ctx.plugin(slotsPlugin);
|
|
48
|
+
await ctx.plugin(semanticPlugin);
|
|
49
|
+
await ctx.plugin(settingsPlugin);
|
|
50
|
+
await ctx.plugin(settingsDialogPlugin);
|
|
51
|
+
await ctx.plugin(variablesPlugin);
|
|
52
|
+
await ctx.plugin(sessionPlugin);
|
|
53
|
+
await ctx.plugin(workflowsPlugin);
|
|
54
|
+
for (const seat of options.plugins) {
|
|
55
|
+
const spec = 'plugin' in seat ? seat : { plugin: seat };
|
|
56
|
+
// Plugins without a Config ignore the second argument; an empty object
|
|
57
|
+
// lets zod-defaulted configs (model tables, schedules) fill themselves.
|
|
58
|
+
const plugin = spec.plugin;
|
|
59
|
+
await ctx.plugin(plugin, spec.config === undefined ? {} : spec.config);
|
|
60
|
+
}
|
|
61
|
+
// The framework's own fallback layout rides last: it renders only while it
|
|
62
|
+
// is the sole full-screen layout occupant and yields the moment a suite
|
|
63
|
+
// layout registers.
|
|
64
|
+
await ctx.plugin(fallbackPlugin);
|
|
65
|
+
// Take over the element: the startup root unmounts, a fresh shell roots.
|
|
66
|
+
handle.root.unmount();
|
|
67
|
+
const shellRoot = createRoot(handle.element);
|
|
68
|
+
shellRoot.render(_jsx(Shell, { ctx: ctx }));
|
|
69
|
+
return {
|
|
70
|
+
ctx,
|
|
71
|
+
dispose: () => ctx.fiber.dispose().then(() => {
|
|
72
|
+
shellRoot.unmount();
|
|
73
|
+
}),
|
|
74
|
+
};
|
|
75
|
+
}
|