@happyvertical/smrt-svelte 0.55.9 → 0.55.11
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/AGENTS.md +2 -1
- package/agents/cookbook-shell.md +176 -0
- package/agents/overview-presentation.md +8 -0
- package/agents/overview-surfaces.md +133 -17
- package/agents/screens.md +5 -0
- package/agents/workspace.md +6 -0
- package/dist/components/activity/ActivityList.svelte +35 -0
- package/dist/components/activity/ActivityList.svelte.d.ts +11 -0
- package/dist/components/activity/ActivityList.svelte.d.ts.map +1 -0
- package/dist/components/activity/index.d.ts +3 -0
- package/dist/components/activity/index.d.ts.map +1 -0
- package/dist/components/activity/index.js +1 -0
- package/dist/components/activity/safe-href.d.ts +3 -0
- package/dist/components/activity/safe-href.d.ts.map +1 -0
- package/dist/components/activity/safe-href.js +12 -0
- package/dist/components/activity/types.d.ts +9 -0
- package/dist/components/activity/types.d.ts.map +1 -0
- package/dist/components/activity/types.js +1 -0
- package/dist/components/cookbook/CookbookApp.svelte +236 -0
- package/dist/components/cookbook/CookbookApp.svelte.d.ts +5 -0
- package/dist/components/cookbook/CookbookApp.svelte.d.ts.map +1 -0
- package/dist/components/cookbook/CookbookSectionOverview.svelte +71 -0
- package/dist/components/cookbook/CookbookSectionOverview.svelte.d.ts +20 -0
- package/dist/components/cookbook/CookbookSectionOverview.svelte.d.ts.map +1 -0
- package/dist/components/cookbook/CookbookSectionPage.svelte +133 -0
- package/dist/components/cookbook/CookbookSectionPage.svelte.d.ts +5 -0
- package/dist/components/cookbook/CookbookSectionPage.svelte.d.ts.map +1 -0
- package/dist/components/cookbook/brand-theme.d.ts +11 -0
- package/dist/components/cookbook/brand-theme.d.ts.map +1 -0
- package/dist/components/cookbook/brand-theme.js +24 -0
- package/dist/components/cookbook/build.d.ts +8 -0
- package/dist/components/cookbook/build.d.ts.map +1 -0
- package/dist/components/cookbook/build.js +277 -0
- package/dist/components/cookbook/catalog.d.ts +37 -0
- package/dist/components/cookbook/catalog.d.ts.map +1 -0
- package/dist/components/cookbook/catalog.js +99 -0
- package/dist/components/cookbook/context.d.ts +23 -0
- package/dist/components/cookbook/context.d.ts.map +1 -0
- package/dist/components/cookbook/context.js +15 -0
- package/dist/components/cookbook/index.d.ts +22 -0
- package/dist/components/cookbook/index.d.ts.map +1 -0
- package/dist/components/cookbook/index.js +20 -0
- package/dist/components/cookbook/page.d.ts +23 -0
- package/dist/components/cookbook/page.d.ts.map +1 -0
- package/dist/components/cookbook/page.js +32 -0
- package/dist/components/cookbook/registry.d.ts +37 -0
- package/dist/components/cookbook/registry.d.ts.map +1 -0
- package/dist/components/cookbook/registry.js +38 -0
- package/dist/components/cookbook/sections.d.ts +60 -0
- package/dist/components/cookbook/sections.d.ts.map +1 -0
- package/dist/components/cookbook/sections.js +88 -0
- package/dist/components/cookbook/server.d.ts +61 -0
- package/dist/components/cookbook/server.d.ts.map +1 -0
- package/dist/components/cookbook/server.js +108 -0
- package/dist/components/cookbook/theme.d.ts +21 -0
- package/dist/components/cookbook/theme.d.ts.map +1 -0
- package/dist/components/cookbook/theme.js +53 -0
- package/dist/components/cookbook/types.d.ts +204 -0
- package/dist/components/cookbook/types.d.ts.map +1 -0
- package/dist/components/cookbook/types.js +1 -0
- package/dist/components/notifications/LocalizedNotifications.fixture.svelte +16 -0
- package/dist/components/notifications/LocalizedNotifications.fixture.svelte.d.ts +11 -0
- package/dist/components/notifications/LocalizedNotifications.fixture.svelte.d.ts.map +1 -0
- package/dist/components/notifications/NotificationBell.svelte +136 -0
- package/dist/components/notifications/NotificationBell.svelte.d.ts +13 -0
- package/dist/components/notifications/NotificationBell.svelte.d.ts.map +1 -0
- package/dist/components/notifications/index.d.ts +3 -0
- package/dist/components/notifications/index.d.ts.map +1 -0
- package/dist/components/notifications/index.js +1 -0
- package/dist/components/notifications/types.d.ts +27 -0
- package/dist/components/notifications/types.d.ts.map +1 -0
- package/dist/components/notifications/types.js +1 -0
- package/dist/components/overview/OverviewAssistantUndo.svelte +77 -0
- package/dist/components/overview/OverviewAssistantUndo.svelte.d.ts +13 -0
- package/dist/components/overview/OverviewAssistantUndo.svelte.d.ts.map +1 -0
- package/dist/components/overview/OverviewGrid.svelte +5 -2
- package/dist/components/overview/OverviewGrid.svelte.d.ts.map +1 -1
- package/dist/components/overview/assistant-surface.d.ts +90 -0
- package/dist/components/overview/assistant-surface.d.ts.map +1 -0
- package/dist/components/overview/assistant-surface.js +65 -0
- package/dist/components/overview/assistant.svelte.d.ts +115 -0
- package/dist/components/overview/assistant.svelte.d.ts.map +1 -0
- package/dist/components/overview/assistant.svelte.js +260 -0
- package/dist/components/overview/controller.svelte.d.ts +8 -0
- package/dist/components/overview/controller.svelte.d.ts.map +1 -1
- package/dist/components/overview/controller.svelte.js +76 -13
- package/dist/components/overview/grid-types.d.ts +6 -2
- package/dist/components/overview/grid-types.d.ts.map +1 -1
- package/dist/components/overview/index.d.ts +2 -0
- package/dist/components/overview/index.d.ts.map +1 -1
- package/dist/components/overview/index.js +2 -0
- package/dist/components/overview/operations.d.ts +169 -0
- package/dist/components/overview/operations.d.ts.map +1 -0
- package/dist/components/overview/operations.js +358 -0
- package/dist/components/overview/server.d.ts +2 -0
- package/dist/components/overview/server.d.ts.map +1 -1
- package/dist/components/overview/server.js +2 -0
- package/dist/components/workspace/admin-shell/settings-delta.d.ts +21 -0
- package/dist/components/workspace/admin-shell/settings-delta.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/settings-delta.js +142 -0
- package/dist/components/workspace/index.d.ts +1 -0
- package/dist/components/workspace/index.d.ts.map +1 -1
- package/dist/components/workspace/index.js +1 -0
- package/dist/components/workspace/server/index.d.ts +6 -2
- package/dist/components/workspace/server/index.d.ts.map +1 -1
- package/dist/components/workspace/server/index.js +5 -2
- package/dist/i18n/server.d.ts +1 -0
- package/dist/i18n/server.d.ts.map +1 -1
- package/dist/i18n/server.js +1 -0
- package/dist/i18n/strings.notifications.d.ts +12 -0
- package/dist/i18n/strings.notifications.d.ts.map +1 -0
- package/dist/i18n/strings.notifications.js +13 -0
- package/dist/i18n/strings.overview.d.ts +9 -0
- package/dist/i18n/strings.overview.d.ts.map +1 -1
- package/dist/i18n/strings.overview.js +11 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/package.json +31 -14
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { defineOverview, isEmptyOverride, parseOverviewOverride, } from '../overview/model.js';
|
|
2
|
+
import { OVERVIEW_PAGE_ID_PATTERN, } from '../overview/types.js';
|
|
3
|
+
import { buildNavSections, DEFAULT_ENTRY_ICON, defaultEntryPath, defaultSectionPath, entryDescription, FEATURE_SECTION, navItemId, sectionLayoutId, } from './sections.js';
|
|
4
|
+
import { resolveCookbookTheme } from './theme.js';
|
|
5
|
+
/** `PurchaseOrder` -> `Purchase Order`. */
|
|
6
|
+
export function humanize(name) {
|
|
7
|
+
return name.replace(/([a-z0-9])([A-Z])/g, '$1 $2');
|
|
8
|
+
}
|
|
9
|
+
/** `Customer` -> `Customers`, `Category` -> `Categories`. */
|
|
10
|
+
export function pluralize(noun) {
|
|
11
|
+
if (/[^aeiou]y$/i.test(noun))
|
|
12
|
+
return `${noun.slice(0, -1)}ies`;
|
|
13
|
+
if (/(s|x|z|ch|sh)$/i.test(noun))
|
|
14
|
+
return `${noun}es`;
|
|
15
|
+
return `${noun}s`;
|
|
16
|
+
}
|
|
17
|
+
/** The recipes the cookbook selects, in catalog order; unknown ids are reported. */
|
|
18
|
+
function selectRecipes(options, issues) {
|
|
19
|
+
const { cookbook, catalog, recipeOrder } = options;
|
|
20
|
+
const byId = new Map(catalog.recipes.map((r) => [r.id, r]));
|
|
21
|
+
const wanted = new Set(cookbook.recipes);
|
|
22
|
+
const order = [...(recipeOrder ?? []), ...catalog.recipes.map((r) => r.id)];
|
|
23
|
+
const picked = [];
|
|
24
|
+
const done = new Set();
|
|
25
|
+
for (const id of order) {
|
|
26
|
+
const recipe = byId.get(id);
|
|
27
|
+
if (!recipe || !wanted.has(id) || done.has(id))
|
|
28
|
+
continue;
|
|
29
|
+
done.add(id);
|
|
30
|
+
picked.push(recipe);
|
|
31
|
+
}
|
|
32
|
+
for (const id of cookbook.recipes) {
|
|
33
|
+
if (!done.has(id))
|
|
34
|
+
issues.push(`Unknown recipe "${id}".`);
|
|
35
|
+
}
|
|
36
|
+
return picked;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The section's lead model: the first nav entry of the first recipe that
|
|
40
|
+
* suggests the section. Its count and records go on the section page.
|
|
41
|
+
*/
|
|
42
|
+
function leadModel(recipes, models) {
|
|
43
|
+
for (const recipe of recipes) {
|
|
44
|
+
for (const entry of recipe.nav) {
|
|
45
|
+
const model = models.get(entry.model);
|
|
46
|
+
if (model)
|
|
47
|
+
return model;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
function overviewDefaults(lead, dataWidgets) {
|
|
53
|
+
const widgets = [
|
|
54
|
+
{ id: 'shortcuts', type: 'shortcuts', span: 4, options: {} },
|
|
55
|
+
];
|
|
56
|
+
if (dataWidgets && lead) {
|
|
57
|
+
const name = humanize(lead.name);
|
|
58
|
+
widgets.push({
|
|
59
|
+
id: 'count',
|
|
60
|
+
type: 'metric',
|
|
61
|
+
span: 1,
|
|
62
|
+
options: { title: pluralize(name), model: lead.id, measure: 'count' },
|
|
63
|
+
}, {
|
|
64
|
+
id: 'latest',
|
|
65
|
+
type: 'records',
|
|
66
|
+
span: 3,
|
|
67
|
+
options: { title: `${name} records`, model: lead.id, limit: 5 },
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return widgets;
|
|
71
|
+
}
|
|
72
|
+
/** The cookbook layout as the shell takes it: `null` when it has none. */
|
|
73
|
+
function layoutOf(cookbook) {
|
|
74
|
+
return cookbook.layout ? cookbook.layout : null;
|
|
75
|
+
}
|
|
76
|
+
/** Derive the shell configuration from a cookbook (see {@link CookbookShell}). */
|
|
77
|
+
export function buildCookbookShell(options) {
|
|
78
|
+
const { cookbook, catalog } = options;
|
|
79
|
+
const issues = [];
|
|
80
|
+
const href = options.href ?? ((path) => path);
|
|
81
|
+
const entryPath = options.entryPath ?? defaultEntryPath;
|
|
82
|
+
const sectionPath = options.sectionPath ?? defaultSectionPath;
|
|
83
|
+
const models = new Map(catalog.models.map((m) => [m.id, m]));
|
|
84
|
+
const recipes = selectRecipes(options, issues);
|
|
85
|
+
const entries = new Map();
|
|
86
|
+
const sections = [];
|
|
87
|
+
const navGroups = [];
|
|
88
|
+
const addEntry = (recipe, spec) => {
|
|
89
|
+
const id = navItemId(spec.model.packageId, spec.model.name, spec.key);
|
|
90
|
+
if (entries.has(id))
|
|
91
|
+
return undefined;
|
|
92
|
+
const path = entryPath({
|
|
93
|
+
packageId: spec.model.packageId,
|
|
94
|
+
modelName: spec.model.name,
|
|
95
|
+
...(spec.key ? { key: spec.key } : {}),
|
|
96
|
+
});
|
|
97
|
+
const entry = {
|
|
98
|
+
id,
|
|
99
|
+
label: spec.label,
|
|
100
|
+
icon: spec.icon ?? DEFAULT_ENTRY_ICON,
|
|
101
|
+
description: entryDescription(spec.description, spec.model),
|
|
102
|
+
model: spec.model,
|
|
103
|
+
path,
|
|
104
|
+
href: href(path),
|
|
105
|
+
recipeId: recipe?.id ?? null,
|
|
106
|
+
...(spec.noun ? { noun: spec.noun } : {}),
|
|
107
|
+
...(spec.filter ? { filter: spec.filter } : {}),
|
|
108
|
+
};
|
|
109
|
+
entries.set(id, entry);
|
|
110
|
+
return entry;
|
|
111
|
+
};
|
|
112
|
+
const toItem = (entry) => ({
|
|
113
|
+
id: entry.id,
|
|
114
|
+
href: entry.href,
|
|
115
|
+
label: entry.label,
|
|
116
|
+
icon: entry.icon,
|
|
117
|
+
description: entry.description,
|
|
118
|
+
});
|
|
119
|
+
const pushSection = (id, label, icon, description, recipeIds, items) => {
|
|
120
|
+
const layoutId = sectionLayoutId(id);
|
|
121
|
+
const path = sectionPath(layoutId);
|
|
122
|
+
sections.push({
|
|
123
|
+
id: layoutId,
|
|
124
|
+
label,
|
|
125
|
+
...(icon ? { icon } : {}),
|
|
126
|
+
...(description ? { description } : {}),
|
|
127
|
+
entryIds: items.map((e) => e.id),
|
|
128
|
+
recipeIds,
|
|
129
|
+
path,
|
|
130
|
+
});
|
|
131
|
+
navGroups.push({
|
|
132
|
+
id: layoutId,
|
|
133
|
+
heading: label,
|
|
134
|
+
...(icon ? { icon } : {}),
|
|
135
|
+
href: href(path),
|
|
136
|
+
items: items.map(toItem),
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
// Recipe sections: a section none of whose recipes has a menu entry (an
|
|
140
|
+
// assistant, a settings panel) is left out rather than shown empty.
|
|
141
|
+
const leads = new Map();
|
|
142
|
+
for (const section of buildNavSections(recipes)) {
|
|
143
|
+
const items = [];
|
|
144
|
+
for (const recipe of section.recipes) {
|
|
145
|
+
for (const nav of recipe.nav) {
|
|
146
|
+
const model = models.get(nav.model);
|
|
147
|
+
if (!model) {
|
|
148
|
+
issues.push(`Recipe ${recipe.id} lists unknown model ${nav.model}.`);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const entry = addEntry(recipe, {
|
|
152
|
+
model,
|
|
153
|
+
label: nav.label,
|
|
154
|
+
icon: nav.icon,
|
|
155
|
+
description: nav.description,
|
|
156
|
+
key: nav.key,
|
|
157
|
+
noun: nav.noun,
|
|
158
|
+
filter: nav.filter,
|
|
159
|
+
});
|
|
160
|
+
if (entry)
|
|
161
|
+
items.push(entry);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (items.length === 0)
|
|
165
|
+
continue;
|
|
166
|
+
leads.set(sectionLayoutId(section.id), leadModel(section.recipes, models));
|
|
167
|
+
pushSection(section.id, section.label, section.icon, section.description ??
|
|
168
|
+
section.recipes.find((r) => r.group?.summary)?.group?.summary, section.recipes.map((r) => r.id), items);
|
|
169
|
+
}
|
|
170
|
+
// Features: exposed models no recipe covers, in one section after the recipes'.
|
|
171
|
+
const featureItems = [];
|
|
172
|
+
for (const id of cookbook.features ?? []) {
|
|
173
|
+
const model = models.get(id);
|
|
174
|
+
if (!model?.exposed) {
|
|
175
|
+
issues.push(`Feature ${id} is not an exposed model.`);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const entry = addEntry(null, { model, label: humanize(model.name) });
|
|
179
|
+
if (entry)
|
|
180
|
+
featureItems.push(entry);
|
|
181
|
+
}
|
|
182
|
+
if (featureItems.length > 0) {
|
|
183
|
+
leads.set(sectionLayoutId(FEATURE_SECTION.id), featureItems[0]?.model);
|
|
184
|
+
pushSection(FEATURE_SECTION.id, FEATURE_SECTION.label, FEATURE_SECTION.icon, FEATURE_SECTION.description, [], featureItems);
|
|
185
|
+
}
|
|
186
|
+
// Section page overviews: one per suggested section and per custom section
|
|
187
|
+
// of the layout, so a cookbook's `overviews` key names the page it
|
|
188
|
+
// customises and stays valid when the section is renamed.
|
|
189
|
+
const layout = layoutOf(cookbook);
|
|
190
|
+
const modelIds = catalog.models.map((m) => m.id);
|
|
191
|
+
const overviews = {};
|
|
192
|
+
const overviewIds = [
|
|
193
|
+
...sections.map((s) => s.id),
|
|
194
|
+
...(layout?.customSections ?? []).map((s) => s.id),
|
|
195
|
+
];
|
|
196
|
+
for (const id of new Set(overviewIds)) {
|
|
197
|
+
// A hand-edited custom id outside the pattern has no overview: its page
|
|
198
|
+
// shows the menu.
|
|
199
|
+
if (!OVERVIEW_PAGE_ID_PATTERN.test(id))
|
|
200
|
+
continue;
|
|
201
|
+
const definition = defineOverview({
|
|
202
|
+
id,
|
|
203
|
+
...(modelIds.length > 0 ? { models: modelIds } : {}),
|
|
204
|
+
defaults: overviewDefaults(leads.get(id), options.dataWidgets === true),
|
|
205
|
+
});
|
|
206
|
+
const stored = cookbook.overviews?.[id];
|
|
207
|
+
const parsed = parseOverviewOverride(stored);
|
|
208
|
+
for (const issue of parsed.issues) {
|
|
209
|
+
issues.push(`Overview ${id}: ${issue.message}`);
|
|
210
|
+
}
|
|
211
|
+
overviews[id] = {
|
|
212
|
+
definition,
|
|
213
|
+
override: isEmptyOverride(parsed.override) ? null : stored,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
for (const id of Object.keys(cookbook.overviews ?? {})) {
|
|
217
|
+
if (!overviews[id]) {
|
|
218
|
+
issues.push(`Overview "${id}" names no section; its override is ignored.`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// Recipe surfaces placed into shell slots and routes.
|
|
222
|
+
const shellWidgets = [];
|
|
223
|
+
const routes = [];
|
|
224
|
+
const settingsPanels = [];
|
|
225
|
+
const widgets = [];
|
|
226
|
+
for (const recipe of recipes) {
|
|
227
|
+
const found = [];
|
|
228
|
+
for (const surface of recipe.surfaces ?? []) {
|
|
229
|
+
if (surface.kind === 'shell-widget') {
|
|
230
|
+
shellWidgets.push({
|
|
231
|
+
recipeId: recipe.id,
|
|
232
|
+
slot: surface.slot,
|
|
233
|
+
export: surface.export,
|
|
234
|
+
label: surface.label,
|
|
235
|
+
...(surface.icon ? { icon: surface.icon } : {}),
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
else if (surface.kind === 'route') {
|
|
239
|
+
routes.push({
|
|
240
|
+
recipeId: recipe.id,
|
|
241
|
+
path: surface.path,
|
|
242
|
+
export: surface.export,
|
|
243
|
+
label: surface.label,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
else if (surface.kind === 'settings-panel') {
|
|
247
|
+
settingsPanels.push({
|
|
248
|
+
recipeId: recipe.id,
|
|
249
|
+
export: surface.export,
|
|
250
|
+
label: surface.label,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
else if (surface.kind === 'widget')
|
|
254
|
+
found.push(surface);
|
|
255
|
+
}
|
|
256
|
+
if (found.length > 0)
|
|
257
|
+
widgets.push({ id: recipe.id, surfaces: found });
|
|
258
|
+
}
|
|
259
|
+
return {
|
|
260
|
+
title: options.title ?? cookbook.name ?? 'SMRT',
|
|
261
|
+
navMode: 'sections',
|
|
262
|
+
navGroups,
|
|
263
|
+
layout,
|
|
264
|
+
sections,
|
|
265
|
+
entries,
|
|
266
|
+
theme: resolveCookbookTheme(cookbook.theme),
|
|
267
|
+
overviews,
|
|
268
|
+
shellWidgets,
|
|
269
|
+
routes,
|
|
270
|
+
settingsPanels,
|
|
271
|
+
widgets,
|
|
272
|
+
policies: cookbook.policies ?? [],
|
|
273
|
+
...(options.settingsHref ? { settingsHref: options.settingsHref } : {}),
|
|
274
|
+
sectionPath: (id) => href(sectionPath(id)),
|
|
275
|
+
issues,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { CookbookCatalog } from './types.js';
|
|
2
|
+
/** The subset of a package `manifest.json` the shell reads. */
|
|
3
|
+
export interface CookbookManifest {
|
|
4
|
+
packageName: string;
|
|
5
|
+
objects?: Record<string, RawManifestObject>;
|
|
6
|
+
recipes?: unknown;
|
|
7
|
+
}
|
|
8
|
+
interface RawManifestObject {
|
|
9
|
+
className?: string;
|
|
10
|
+
qualifiedName?: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
fields?: Record<string, {
|
|
13
|
+
type?: string;
|
|
14
|
+
}>;
|
|
15
|
+
decoratorConfig?: Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* `@happyvertical/smrt-commerce` -> `commerce`. Other scopes keep their
|
|
19
|
+
* unscoped name with a leading `smrt-` dropped, so ids stay short and stable.
|
|
20
|
+
*/
|
|
21
|
+
export declare function packageIdOf(packageName: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* The catalog of the given manifests. Recipes keep their declaration order
|
|
24
|
+
* across manifests (the order given); a repeated recipe id keeps the first.
|
|
25
|
+
*/
|
|
26
|
+
export declare function catalogFromManifests(manifests: readonly CookbookManifest[]): CookbookCatalog;
|
|
27
|
+
/**
|
|
28
|
+
* Narrow a catalog to what a cookbook uses, so a server load serializes only
|
|
29
|
+
* that: the selected recipes, and the models they list or the cookbook names
|
|
30
|
+
* as features.
|
|
31
|
+
*/
|
|
32
|
+
export declare function narrowCatalog(catalog: CookbookCatalog, selection: {
|
|
33
|
+
recipes: readonly string[];
|
|
34
|
+
features: readonly string[];
|
|
35
|
+
}): CookbookCatalog;
|
|
36
|
+
export {};
|
|
37
|
+
//# sourceMappingURL=catalog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../../src/components/cookbook/catalog.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,eAAe,EAGhB,MAAM,YAAY,CAAC;AAEpB,+DAA+D;AAC/D,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAC5C,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,UAAU,iBAAiB;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3C,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC3C;AAKD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAQvD;AAoDD;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,SAAS,gBAAgB,EAAE,GACrC,eAAe,CA0BjB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,eAAe,EACxB,SAAS,EAAE;IAAE,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GACrE,eAAe,CASjB"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const PACKAGE_PREFIXES = ['@happyvertical/smrt-'];
|
|
2
|
+
const NON_VALUE_TYPES = new Set(['oneToMany', 'meta']);
|
|
3
|
+
/**
|
|
4
|
+
* `@happyvertical/smrt-commerce` -> `commerce`. Other scopes keep their
|
|
5
|
+
* unscoped name with a leading `smrt-` dropped, so ids stay short and stable.
|
|
6
|
+
*/
|
|
7
|
+
export function packageIdOf(packageName) {
|
|
8
|
+
for (const prefix of PACKAGE_PREFIXES) {
|
|
9
|
+
if (packageName.startsWith(prefix))
|
|
10
|
+
return packageName.slice(prefix.length);
|
|
11
|
+
}
|
|
12
|
+
const bare = packageName.includes('/')
|
|
13
|
+
? packageName.slice(packageName.indexOf('/') + 1)
|
|
14
|
+
: packageName;
|
|
15
|
+
return bare.replace(/^smrt-/, '');
|
|
16
|
+
}
|
|
17
|
+
/** A model, not a collection class: it holds at least one value field. */
|
|
18
|
+
function isModel(raw) {
|
|
19
|
+
return Object.values(raw.fields ?? {}).some((f) => !NON_VALUE_TYPES.has(f.type ?? ''));
|
|
20
|
+
}
|
|
21
|
+
/** An omitted `api`/`mcp`/`cli` key means full CRUD; all three `false` means none. */
|
|
22
|
+
function isExposed(config) {
|
|
23
|
+
const c = config ?? {};
|
|
24
|
+
return !(c.api === false && c.mcp === false && c.cli === false);
|
|
25
|
+
}
|
|
26
|
+
function isRecipe(value) {
|
|
27
|
+
if (!value || typeof value !== 'object')
|
|
28
|
+
return false;
|
|
29
|
+
const r = value;
|
|
30
|
+
return (typeof r.id === 'string' &&
|
|
31
|
+
typeof r.label === 'string' &&
|
|
32
|
+
Array.isArray(r.nav) &&
|
|
33
|
+
Array.isArray(r.models));
|
|
34
|
+
}
|
|
35
|
+
/** Drop what the shell never reads (help markdown, synonyms, providers, ...). */
|
|
36
|
+
function trimRecipe(recipe) {
|
|
37
|
+
const { id, label, nav, models, group, section, surfaces, requires, requiresAny, } = recipe;
|
|
38
|
+
return {
|
|
39
|
+
id,
|
|
40
|
+
label,
|
|
41
|
+
nav,
|
|
42
|
+
models,
|
|
43
|
+
...(group ? { group } : {}),
|
|
44
|
+
...(section ? { section } : {}),
|
|
45
|
+
...(surfaces && surfaces.length > 0 ? { surfaces } : {}),
|
|
46
|
+
...(requires ? { requires } : {}),
|
|
47
|
+
...(requiresAny ? { requiresAny } : {}),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The catalog of the given manifests. Recipes keep their declaration order
|
|
52
|
+
* across manifests (the order given); a repeated recipe id keeps the first.
|
|
53
|
+
*/
|
|
54
|
+
export function catalogFromManifests(manifests) {
|
|
55
|
+
const recipes = [];
|
|
56
|
+
const seen = new Set();
|
|
57
|
+
const models = [];
|
|
58
|
+
for (const manifest of manifests) {
|
|
59
|
+
const packageId = packageIdOf(manifest.packageName);
|
|
60
|
+
for (const raw of Object.values(manifest.objects ?? {})) {
|
|
61
|
+
if (!raw.qualifiedName || !raw.className || !isModel(raw))
|
|
62
|
+
continue;
|
|
63
|
+
const description = raw.description?.trim();
|
|
64
|
+
models.push({
|
|
65
|
+
id: raw.qualifiedName,
|
|
66
|
+
name: raw.className,
|
|
67
|
+
packageId,
|
|
68
|
+
exposed: isExposed(raw.decoratorConfig),
|
|
69
|
+
...(description ? { description } : {}),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
if (Array.isArray(manifest.recipes)) {
|
|
73
|
+
for (const recipe of manifest.recipes) {
|
|
74
|
+
if (!isRecipe(recipe) || seen.has(recipe.id))
|
|
75
|
+
continue;
|
|
76
|
+
seen.add(recipe.id);
|
|
77
|
+
recipes.push(trimRecipe(recipe));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return { recipes, models };
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Narrow a catalog to what a cookbook uses, so a server load serializes only
|
|
85
|
+
* that: the selected recipes, and the models they list or the cookbook names
|
|
86
|
+
* as features.
|
|
87
|
+
*/
|
|
88
|
+
export function narrowCatalog(catalog, selection) {
|
|
89
|
+
const wanted = new Set(selection.recipes);
|
|
90
|
+
const recipes = catalog.recipes.filter((r) => wanted.has(r.id));
|
|
91
|
+
const modelIds = new Set(selection.features);
|
|
92
|
+
for (const recipe of recipes) {
|
|
93
|
+
for (const id of recipe.models)
|
|
94
|
+
modelIds.add(id);
|
|
95
|
+
for (const entry of recipe.nav)
|
|
96
|
+
modelIds.add(entry.model);
|
|
97
|
+
}
|
|
98
|
+
return { recipes, models: catalog.models.filter((m) => modelIds.has(m.id)) };
|
|
99
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { OverviewModelChoice } from '../overview/grid-types.js';
|
|
2
|
+
import type { WidgetComponents, WidgetRegistry } from '../overview/registry.js';
|
|
3
|
+
import type { OverviewOverride } from '../overview/types.js';
|
|
4
|
+
import type { CookbookShell } from './types.js';
|
|
5
|
+
/** What `CookbookApp` shares with `CookbookSectionPage` below it. */
|
|
6
|
+
export interface CookbookContext {
|
|
7
|
+
readonly shell: CookbookShell;
|
|
8
|
+
/** `null` until the recipe widgets are registered. */
|
|
9
|
+
readonly registry: WidgetRegistry | null;
|
|
10
|
+
readonly components: WidgetComponents;
|
|
11
|
+
/** Models the overview option editor offers. */
|
|
12
|
+
readonly models: readonly OverviewModelChoice[];
|
|
13
|
+
/** The page's stored override (untrusted JSON), reactive. */
|
|
14
|
+
overrideFor(sectionId: string): unknown;
|
|
15
|
+
/** Store an edit; `null` resets the page to its defaults. */
|
|
16
|
+
setOverride(sectionId: string, override: OverviewOverride | null): void;
|
|
17
|
+
/** Host capabilities passed to widget loaders (a data source, ...). */
|
|
18
|
+
widgetContext(): Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
export declare function setCookbookContext(context: CookbookContext): void;
|
|
21
|
+
export declare function tryGetCookbookContext(): CookbookContext | undefined;
|
|
22
|
+
export declare function getCookbookContext(): CookbookContext;
|
|
23
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/components/cookbook/context.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAChF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,qEAAqE;AACrE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,sDAAsD;IACtD,QAAQ,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC;IACtC,gDAAgD;IAChD,QAAQ,CAAC,MAAM,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAChD,6DAA6D;IAC7D,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;IACxC,6DAA6D;IAC7D,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,GAAG,IAAI,GAAG,IAAI,CAAC;IACxE,uEAAuE;IACvE,aAAa,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC1C;AAID,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAEjE;AAED,wBAAgB,qBAAqB,IAAI,eAAe,GAAG,SAAS,CAEnE;AAED,wBAAgB,kBAAkB,IAAI,eAAe,CAQpD"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getContext, setContext } from 'svelte';
|
|
2
|
+
const KEY = Symbol('smrt-cookbook');
|
|
3
|
+
export function setCookbookContext(context) {
|
|
4
|
+
setContext(KEY, context);
|
|
5
|
+
}
|
|
6
|
+
export function tryGetCookbookContext() {
|
|
7
|
+
return getContext(KEY);
|
|
8
|
+
}
|
|
9
|
+
export function getCookbookContext() {
|
|
10
|
+
const context = tryGetCookbookContext();
|
|
11
|
+
if (!context) {
|
|
12
|
+
throw new Error('CookbookSectionPage must be rendered inside <CookbookApp>.');
|
|
13
|
+
}
|
|
14
|
+
return context;
|
|
15
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @happyvertical/smrt-svelte/cookbook
|
|
3
|
+
*
|
|
4
|
+
* A cookbook-driven app shell (#3749): `buildCookbookShell` turns a cookbook
|
|
5
|
+
* plus a recipe catalog into `AppShell` configuration (sections, entries,
|
|
6
|
+
* layout, theme, section overviews, recipe surfaces); `CookbookApp` renders it
|
|
7
|
+
* and `CookbookSectionPage` is a section's own page. Browser-safe: use
|
|
8
|
+
* `@happyvertical/smrt-svelte/cookbook/server` to load the cookbook and the
|
|
9
|
+
* installed packages' manifests in a `+layout.server.ts`.
|
|
10
|
+
*/
|
|
11
|
+
export { applyCookbookTheme } from './brand-theme.js';
|
|
12
|
+
export { buildCookbookShell, humanize, pluralize } from './build.js';
|
|
13
|
+
export { default as CookbookApp } from './CookbookApp.svelte';
|
|
14
|
+
export { default as CookbookSectionPage } from './CookbookSectionPage.svelte';
|
|
15
|
+
export { type CookbookManifest, catalogFromManifests, narrowCatalog, packageIdOf, } from './catalog.js';
|
|
16
|
+
export { type CookbookContext, getCookbookContext, tryGetCookbookContext, } from './context.js';
|
|
17
|
+
export { type CookbookOverviewStore, createSectionOverview, loadSectionWidget, } from './page.js';
|
|
18
|
+
export { type CookbookRegistry, type CookbookRegistryOptions, type CookbookWidgetContext, createCookbookRegistry, loadShortcuts, } from './registry.js';
|
|
19
|
+
export { buildNavSections, DEFAULT_ENTRY_ICON, DEFAULT_SECTION_ICON, defaultEntryPath, defaultSectionPath, FEATURE_SECTION, navItemId, navSectionOf, sectionIdFromSlug, sectionLayoutId, sectionSlug, } from './sections.js';
|
|
20
|
+
export { brandThemeId, DEFAULT_COLOR_SCHEME, DEFAULT_THEME_PRESET, normalizeHex, resolveCookbookTheme, THEME_FONTS, } from './theme.js';
|
|
21
|
+
export type * from './types.js';
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/cookbook/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAC9E,OAAO,EACL,KAAK,gBAAgB,EACrB,oBAAoB,EACpB,aAAa,EACb,WAAW,GACZ,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,KAAK,eAAe,EACpB,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,KAAK,qBAAqB,EAC1B,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC1B,sBAAsB,EACtB,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,SAAS,EACT,YAAY,EACZ,iBAAiB,EACjB,eAAe,EACf,WAAW,GACZ,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,YAAY,EACZ,oBAAoB,EACpB,oBAAoB,EACpB,YAAY,EACZ,oBAAoB,EACpB,WAAW,GACZ,MAAM,YAAY,CAAC;AACpB,mBAAmB,YAAY,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @happyvertical/smrt-svelte/cookbook
|
|
3
|
+
*
|
|
4
|
+
* A cookbook-driven app shell (#3749): `buildCookbookShell` turns a cookbook
|
|
5
|
+
* plus a recipe catalog into `AppShell` configuration (sections, entries,
|
|
6
|
+
* layout, theme, section overviews, recipe surfaces); `CookbookApp` renders it
|
|
7
|
+
* and `CookbookSectionPage` is a section's own page. Browser-safe: use
|
|
8
|
+
* `@happyvertical/smrt-svelte/cookbook/server` to load the cookbook and the
|
|
9
|
+
* installed packages' manifests in a `+layout.server.ts`.
|
|
10
|
+
*/
|
|
11
|
+
export { applyCookbookTheme } from './brand-theme.js';
|
|
12
|
+
export { buildCookbookShell, humanize, pluralize } from './build.js';
|
|
13
|
+
export { default as CookbookApp } from './CookbookApp.svelte';
|
|
14
|
+
export { default as CookbookSectionPage } from './CookbookSectionPage.svelte';
|
|
15
|
+
export { catalogFromManifests, narrowCatalog, packageIdOf, } from './catalog.js';
|
|
16
|
+
export { getCookbookContext, tryGetCookbookContext, } from './context.js';
|
|
17
|
+
export { createSectionOverview, loadSectionWidget, } from './page.js';
|
|
18
|
+
export { createCookbookRegistry, loadShortcuts, } from './registry.js';
|
|
19
|
+
export { buildNavSections, DEFAULT_ENTRY_ICON, DEFAULT_SECTION_ICON, defaultEntryPath, defaultSectionPath, FEATURE_SECTION, navItemId, navSectionOf, sectionIdFromSlug, sectionLayoutId, sectionSlug, } from './sections.js';
|
|
20
|
+
export { brandThemeId, DEFAULT_COLOR_SCHEME, DEFAULT_THEME_PRESET, normalizeHex, resolveCookbookTheme, THEME_FONTS, } from './theme.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a section page wires its overview to the cookbook (#3749): the
|
|
3
|
+
* controller reads the page's stored override and writes every edit back, so
|
|
4
|
+
* the cookbook is the single source of truth. Ported from smrt-planner's
|
|
5
|
+
* `overviews/page.ts`.
|
|
6
|
+
*/
|
|
7
|
+
import { type OverviewController } from '../overview/controller.svelte.js';
|
|
8
|
+
import type { WidgetRegistry } from '../overview/registry.js';
|
|
9
|
+
import type { OverviewDefinition, OverviewOverride, OverviewWidget } from '../overview/types.js';
|
|
10
|
+
/** The part of the app an overview reads and writes. */
|
|
11
|
+
export interface CookbookOverviewStore {
|
|
12
|
+
overview(id: string): unknown;
|
|
13
|
+
setOverview(id: string, override: OverviewOverride | null): void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* One widget's data through the same sanitize-and-load path (timeout,
|
|
17
|
+
* serializable check) as a page load. A failed load throws, which the grid
|
|
18
|
+
* shows as that widget's error tile.
|
|
19
|
+
*/
|
|
20
|
+
export declare function loadSectionWidget(definition: OverviewDefinition, registry: WidgetRegistry, context: Record<string, unknown>, widget: OverviewWidget, signal?: AbortSignal): Promise<unknown>;
|
|
21
|
+
/** The controller of a section's overview, persisted through `store`. */
|
|
22
|
+
export declare function createSectionOverview(definition: OverviewDefinition, registry: WidgetRegistry, store: CookbookOverviewStore, context: () => Record<string, unknown>): OverviewController;
|
|
23
|
+
//# sourceMappingURL=page.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../../src/components/cookbook/page.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,kCAAkC,CAAC;AAE1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EACV,kBAAkB,EAClB,gBAAgB,EAChB,cAAc,EACf,MAAM,sBAAsB,CAAC;AAE9B,wDAAwD;AACxD,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9B,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,GAAG,IAAI,GAAG,IAAI,CAAC;CAClE;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,kBAAkB,EAC9B,QAAQ,EAAE,cAAc,EACxB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,MAAM,EAAE,cAAc,EACtB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,OAAO,CAAC,CAYlB;AAED,yEAAyE;AACzE,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,kBAAkB,EAC9B,QAAQ,EAAE,cAAc,EACxB,KAAK,EAAE,qBAAqB,EAC5B,OAAO,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACrC,kBAAkB,CAUpB"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a section page wires its overview to the cookbook (#3749): the
|
|
3
|
+
* controller reads the page's stored override and writes every edit back, so
|
|
4
|
+
* the cookbook is the single source of truth. Ported from smrt-planner's
|
|
5
|
+
* `overviews/page.ts`.
|
|
6
|
+
*/
|
|
7
|
+
import { createOverview, } from '../overview/controller.svelte.js';
|
|
8
|
+
import { loadOverview } from '../overview/load.js';
|
|
9
|
+
/**
|
|
10
|
+
* One widget's data through the same sanitize-and-load path (timeout,
|
|
11
|
+
* serializable check) as a page load. A failed load throws, which the grid
|
|
12
|
+
* shows as that widget's error tile.
|
|
13
|
+
*/
|
|
14
|
+
export async function loadSectionWidget(definition, registry, context, widget, signal) {
|
|
15
|
+
const loaded = await loadOverview({ widgets: [widget] }, definition, registry, { ...context, signal, locale: 'en-US' });
|
|
16
|
+
const result = loaded.widgets[0];
|
|
17
|
+
if (!result || result.status === 'error') {
|
|
18
|
+
throw new Error(result?.error?.code ?? 'load_failed');
|
|
19
|
+
}
|
|
20
|
+
return result.data;
|
|
21
|
+
}
|
|
22
|
+
/** The controller of a section's overview, persisted through `store`. */
|
|
23
|
+
export function createSectionOverview(definition, registry, store, context) {
|
|
24
|
+
const id = definition.id;
|
|
25
|
+
return createOverview({
|
|
26
|
+
definition,
|
|
27
|
+
registry,
|
|
28
|
+
override: () => store.overview(id),
|
|
29
|
+
onchange: (override) => store.setOverview(id, override),
|
|
30
|
+
loadWidget: (widget, signal) => loadSectionWidget(definition, registry, context(), widget, signal),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The widget registry of a cookbook app's section pages (#3749): the core
|
|
3
|
+
* widgets (a shortcuts loader that reads the shell's resolved sections, plus
|
|
4
|
+
* whatever data loaders the host injects) and the recipes' `widget` surfaces.
|
|
5
|
+
*/
|
|
6
|
+
import { type RecipeExportResolver, type RecipeWidgetRegistration, type RecipeWidgetSource } from '../overview/recipe-widgets.js';
|
|
7
|
+
import { type WidgetRegistry } from '../overview/registry.js';
|
|
8
|
+
import type { WidgetLoadContext } from '../overview/types.js';
|
|
9
|
+
import { type CoreWidgetLoaders } from '../overview/widgets/core.js';
|
|
10
|
+
import { type ShortcutNavSection, type ShortcutsWidgetData } from '../overview/widgets/data.js';
|
|
11
|
+
/** What a section page hands its widgets' loaders besides the host's own context. */
|
|
12
|
+
export interface CookbookWidgetContext {
|
|
13
|
+
/** The shell's resolved sections (renames, hides and moves applied). */
|
|
14
|
+
sections: readonly ShortcutNavSection[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* `shortcuts`: the section's menu entries as cards, as the shell resolves
|
|
18
|
+
* them. `section` names another section by its id without the `section:` /
|
|
19
|
+
* `custom:` prefix; absent, the overview's own section.
|
|
20
|
+
*/
|
|
21
|
+
export declare function loadShortcuts(options: Record<string, unknown>, ctx: WidgetLoadContext): ShortcutsWidgetData;
|
|
22
|
+
export interface CookbookRegistryOptions {
|
|
23
|
+
/** Data loaders for `metric`, `records` and `chart` (omitted ones are not registered). */
|
|
24
|
+
loaders?: Omit<CoreWidgetLoaders, 'shortcuts'>;
|
|
25
|
+
/** Recipes whose `widget` surfaces to register. */
|
|
26
|
+
widgets?: readonly RecipeWidgetSource[];
|
|
27
|
+
/** The host's module resolver for recipe exports. */
|
|
28
|
+
resolveExport?: RecipeExportResolver;
|
|
29
|
+
}
|
|
30
|
+
export interface CookbookRegistry {
|
|
31
|
+
registry: WidgetRegistry;
|
|
32
|
+
/** Recipe widgets registered or skipped, for diagnostics. */
|
|
33
|
+
recipeWidgets: RecipeWidgetRegistration | null;
|
|
34
|
+
}
|
|
35
|
+
/** An isolated registry for one cookbook app. */
|
|
36
|
+
export declare function createCookbookRegistry(options?: CookbookRegistryOptions): Promise<CookbookRegistry>;
|
|
37
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/components/cookbook/registry.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,kBAAkB,EAExB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EACL,KAAK,iBAAiB,EAEvB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EAEzB,MAAM,6BAA6B,CAAC;AAErC,qFAAqF;AACrF,MAAM,WAAW,qBAAqB;IACpC,wEAAwE;IACxE,QAAQ,EAAE,SAAS,kBAAkB,EAAE,CAAC;CACzC;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,GAAG,EAAE,iBAAiB,GACrB,mBAAmB,CAYrB;AAED,MAAM,WAAW,uBAAuB;IACtC,0FAA0F;IAC1F,OAAO,CAAC,EAAE,IAAI,CAAC,iBAAiB,EAAE,WAAW,CAAC,CAAC;IAC/C,mDAAmD;IACnD,OAAO,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACxC,qDAAqD;IACrD,aAAa,CAAC,EAAE,oBAAoB,CAAC;CACtC;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,cAAc,CAAC;IACzB,6DAA6D;IAC7D,aAAa,EAAE,wBAAwB,GAAG,IAAI,CAAC;CAChD;AAED,iDAAiD;AACjD,wBAAsB,sBAAsB,CAC1C,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,gBAAgB,CAAC,CAe3B"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The widget registry of a cookbook app's section pages (#3749): the core
|
|
3
|
+
* widgets (a shortcuts loader that reads the shell's resolved sections, plus
|
|
4
|
+
* whatever data loaders the host injects) and the recipes' `widget` surfaces.
|
|
5
|
+
*/
|
|
6
|
+
import { registerRecipeWidgets, } from '../overview/recipe-widgets.js';
|
|
7
|
+
import { createWidgetRegistry, } from '../overview/registry.js';
|
|
8
|
+
import { registerCoreWidgets, } from '../overview/widgets/core.js';
|
|
9
|
+
import { shortcutsFromNav, } from '../overview/widgets/data.js';
|
|
10
|
+
/**
|
|
11
|
+
* `shortcuts`: the section's menu entries as cards, as the shell resolves
|
|
12
|
+
* them. `section` names another section by its id without the `section:` /
|
|
13
|
+
* `custom:` prefix; absent, the overview's own section.
|
|
14
|
+
*/
|
|
15
|
+
export function loadShortcuts(options, ctx) {
|
|
16
|
+
const { sections } = ctx;
|
|
17
|
+
if (!sections)
|
|
18
|
+
throw new Error('the cookbook widget context is missing');
|
|
19
|
+
const named = typeof options.section === 'string' ? options.section : '';
|
|
20
|
+
const sectionId = named
|
|
21
|
+
? sections.find((s) => s.id === `section:${named}` || s.id === `custom:${named}`)?.id
|
|
22
|
+
: ctx.overviewId;
|
|
23
|
+
if (!sectionId)
|
|
24
|
+
throw new Error(`unknown section ${named}`);
|
|
25
|
+
return shortcutsFromNav(sections, sectionId);
|
|
26
|
+
}
|
|
27
|
+
/** An isolated registry for one cookbook app. */
|
|
28
|
+
export async function createCookbookRegistry(options = {}) {
|
|
29
|
+
const registry = createWidgetRegistry();
|
|
30
|
+
registerCoreWidgets(registry, {
|
|
31
|
+
...options.loaders,
|
|
32
|
+
shortcuts: loadShortcuts,
|
|
33
|
+
});
|
|
34
|
+
const recipeWidgets = options.widgets && options.widgets.length > 0 && options.resolveExport
|
|
35
|
+
? await registerRecipeWidgets(registry, options.widgets, options.resolveExport)
|
|
36
|
+
: null;
|
|
37
|
+
return { registry, recipeWidgets };
|
|
38
|
+
}
|