@betterportal/config-manager 10.1.43 → 10.1.44
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/lib/.bsb/clients/service-betterportal-config-manager.d.ts +1 -1
- package/lib/.bsb/clients/service-betterportal-config-manager.js +1 -1
- package/lib/bp-contracts/org.betterportal.config-manager.json +69 -55
- package/lib/plugins/service-betterportal-config-manager/.bp-generated/registry.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/.bp-generated/registry.js +42 -41
- package/lib/plugins/service-betterportal-config-manager/.bp-generated/registry.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/adminApi.js +3 -3
- package/lib/plugins/service-betterportal-config-manager/adminApi.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bootstrapEndpoint.js +1 -1
- package/lib/plugins/service-betterportal-config-manager/bootstrapEndpoint.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bootstrapWizardHtml.js +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/{tenants/_theme.bootstrap1 → auth/_renderer.bootstrap5}/GET.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/auth/_renderer.bootstrap5/GET.js.map +1 -0
- package/lib/plugins/service-betterportal-config-manager/bp-routes/config/_renderer.bootstrap5/GET.d.ts.map +1 -0
- package/lib/plugins/service-betterportal-config-manager/bp-routes/config/_renderer.bootstrap5/GET.js.map +1 -0
- package/lib/plugins/service-betterportal-config-manager/bp-routes/config/_renderer.embedded/GET.d.ts.map +1 -0
- package/lib/plugins/service-betterportal-config-manager/bp-routes/config/_renderer.embedded/GET.js.map +1 -0
- package/lib/plugins/service-betterportal-config-manager/bp-routes/fragments/_renderer.bootstrap5/GET.d.ts.map +1 -0
- package/lib/plugins/service-betterportal-config-manager/bp-routes/fragments/_renderer.bootstrap5/GET.js.map +1 -0
- package/lib/plugins/service-betterportal-config-manager/bp-routes/{preview/_theme.bootstrap1 → menu/_renderer.bootstrap5}/GET.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/menu/_renderer.bootstrap5/GET.js.map +1 -0
- package/lib/plugins/service-betterportal-config-manager/bp-routes/preview/_renderer.bootstrap5/GET.d.ts.map +1 -0
- package/lib/plugins/service-betterportal-config-manager/bp-routes/preview/_renderer.bootstrap5/GET.js.map +1 -0
- package/lib/plugins/service-betterportal-config-manager/bp-routes/preview/route.impl.d.ts +2 -2
- package/lib/plugins/service-betterportal-config-manager/bp-routes/preview/route.impl.js +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/preview/route.impl.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/_renderer.bootstrap5/GET.d.ts.map +1 -0
- package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/{_theme.bootstrap1 → _renderer.bootstrap5}/GET.js +2 -2
- package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/_renderer.bootstrap5/GET.js.map +1 -0
- package/lib/plugins/service-betterportal-config-manager/bp-routes/services/_renderer.bootstrap5/GET.d.ts.map +1 -0
- package/lib/plugins/service-betterportal-config-manager/bp-routes/services/{_theme.bootstrap1 → _renderer.bootstrap5}/GET.js +2 -2
- package/lib/plugins/service-betterportal-config-manager/bp-routes/services/_renderer.bootstrap5/GET.js.map +1 -0
- package/lib/plugins/service-betterportal-config-manager/bp-routes/services/route.impl.d.ts +0 -2
- package/lib/plugins/service-betterportal-config-manager/bp-routes/services/route.impl.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/services/route.impl.js +1 -2
- package/lib/plugins/service-betterportal-config-manager/bp-routes/services/route.impl.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/settings/_renderer.bootstrap5/GET.d.ts.map +1 -0
- package/lib/plugins/service-betterportal-config-manager/bp-routes/settings/_renderer.bootstrap5/GET.js.map +1 -0
- package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/{_theme.bootstrap1 → _renderer.bootstrap5}/DELETE.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/{_theme.bootstrap1 → _renderer.bootstrap5}/DELETE.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/_renderer.bootstrap5/GET.d.ts.map +1 -0
- package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/_renderer.bootstrap5/GET.js +72 -0
- package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/_renderer.bootstrap5/GET.js.map +1 -0
- package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/{_theme.bootstrap1 → _renderer.bootstrap5}/POST.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/{_theme.bootstrap1 → _renderer.bootstrap5}/POST.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/{_theme.bootstrap1 → _renderer.bootstrap5}/PUT.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/{_theme.bootstrap1 → _renderer.bootstrap5}/PUT.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/route.impl.d.ts +20 -0
- package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/route.impl.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/route.impl.js +50 -32
- package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/route.impl.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/fragmentsEditor.d.ts +1 -1
- package/lib/plugins/service-betterportal-config-manager/fragmentsEditor.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/fragmentsEditor.js +207 -341
- package/lib/plugins/service-betterportal-config-manager/fragmentsEditor.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/index.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/index.js +5 -6
- package/lib/plugins/service-betterportal-config-manager/index.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/storage/core.d.ts +0 -1
- package/lib/plugins/service-betterportal-config-manager/storage/core.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/storage/core.js +27 -33
- package/lib/plugins/service-betterportal-config-manager/storage/core.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/syncApi.d.ts +11 -4
- package/lib/plugins/service-betterportal-config-manager/syncApi.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/syncApi.js +40 -10
- package/lib/plugins/service-betterportal-config-manager/syncApi.js.map +1 -1
- package/lib/schemas/service-betterportal-config-manager.json +1 -1
- package/lib/schemas/service-betterportal-config-manager.plugin.json +1 -1
- package/package.json +3 -3
- package/lib/plugins/service-betterportal-config-manager/bp-routes/auth/_theme.bootstrap1/GET.d.ts.map +0 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/auth/_theme.bootstrap1/GET.js.map +0 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/config/_theme.bootstrap1/GET.d.ts.map +0 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/config/_theme.bootstrap1/GET.js.map +0 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/config/_theme.embedded/GET.d.ts.map +0 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/config/_theme.embedded/GET.js.map +0 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/fragments/_theme.bootstrap1/GET.d.ts.map +0 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/fragments/_theme.bootstrap1/GET.js.map +0 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/menu/_theme.bootstrap1/GET.d.ts.map +0 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/menu/_theme.bootstrap1/GET.js.map +0 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/preview/_theme.bootstrap1/GET.js.map +0 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/_theme.bootstrap1/GET.d.ts.map +0 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/_theme.bootstrap1/GET.js.map +0 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/services/_theme.bootstrap1/GET.d.ts.map +0 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/services/_theme.bootstrap1/GET.js.map +0 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/settings/_theme.bootstrap1/GET.d.ts.map +0 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/settings/_theme.bootstrap1/GET.js.map +0 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/_theme.bootstrap1/GET.js +0 -72
- package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/_theme.bootstrap1/GET.js.map +0 -1
- /package/lib/plugins/service-betterportal-config-manager/bp-routes/auth/{_theme.bootstrap1 → _renderer.bootstrap5}/GET.d.ts +0 -0
- /package/lib/plugins/service-betterportal-config-manager/bp-routes/auth/{_theme.bootstrap1 → _renderer.bootstrap5}/GET.js +0 -0
- /package/lib/plugins/service-betterportal-config-manager/bp-routes/config/{_theme.bootstrap1 → _renderer.bootstrap5}/GET.d.ts +0 -0
- /package/lib/plugins/service-betterportal-config-manager/bp-routes/config/{_theme.bootstrap1 → _renderer.bootstrap5}/GET.js +0 -0
- /package/lib/plugins/service-betterportal-config-manager/bp-routes/config/{_theme.embedded → _renderer.embedded}/GET.d.ts +0 -0
- /package/lib/plugins/service-betterportal-config-manager/bp-routes/config/{_theme.embedded → _renderer.embedded}/GET.js +0 -0
- /package/lib/plugins/service-betterportal-config-manager/bp-routes/fragments/{_theme.bootstrap1 → _renderer.bootstrap5}/GET.d.ts +0 -0
- /package/lib/plugins/service-betterportal-config-manager/bp-routes/fragments/{_theme.bootstrap1 → _renderer.bootstrap5}/GET.js +0 -0
- /package/lib/plugins/service-betterportal-config-manager/bp-routes/menu/{_theme.bootstrap1 → _renderer.bootstrap5}/GET.d.ts +0 -0
- /package/lib/plugins/service-betterportal-config-manager/bp-routes/menu/{_theme.bootstrap1 → _renderer.bootstrap5}/GET.js +0 -0
- /package/lib/plugins/service-betterportal-config-manager/bp-routes/preview/{_theme.bootstrap1 → _renderer.bootstrap5}/GET.d.ts +0 -0
- /package/lib/plugins/service-betterportal-config-manager/bp-routes/preview/{_theme.bootstrap1 → _renderer.bootstrap5}/GET.js +0 -0
- /package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/{_theme.bootstrap1 → _renderer.bootstrap5}/GET.d.ts +0 -0
- /package/lib/plugins/service-betterportal-config-manager/bp-routes/services/{_theme.bootstrap1 → _renderer.bootstrap5}/GET.d.ts +0 -0
- /package/lib/plugins/service-betterportal-config-manager/bp-routes/settings/{_theme.bootstrap1 → _renderer.bootstrap5}/GET.d.ts +0 -0
- /package/lib/plugins/service-betterportal-config-manager/bp-routes/settings/{_theme.bootstrap1 → _renderer.bootstrap5}/GET.js +0 -0
- /package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/{_theme.bootstrap1 → _renderer.bootstrap5}/DELETE.d.ts +0 -0
- /package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/{_theme.bootstrap1 → _renderer.bootstrap5}/DELETE.js +0 -0
- /package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/{_theme.bootstrap1 → _renderer.bootstrap5}/GET.d.ts +0 -0
- /package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/{_theme.bootstrap1 → _renderer.bootstrap5}/POST.d.ts +0 -0
- /package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/{_theme.bootstrap1 → _renderer.bootstrap5}/POST.js +0 -0
- /package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/{_theme.bootstrap1 → _renderer.bootstrap5}/PUT.d.ts +0 -0
- /package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/{_theme.bootstrap1 → _renderer.bootstrap5}/PUT.js +0 -0
|
@@ -1,367 +1,233 @@
|
|
|
1
1
|
import { htmlResponse, jsonResponse } from "@betterportal/framework";
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import { getCachedManifestForService } from "./syncApi.js";
|
|
3
|
+
const API_BASE = "/.well-known/bp/admin/fragments-editor";
|
|
4
4
|
const RELATIVE_URL_PARSE_BASE = "http://betterportal.invalid";
|
|
5
|
-
async function
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
out[k] = v; });
|
|
12
|
-
return out;
|
|
5
|
+
async function readForm(event) {
|
|
6
|
+
const data = await event.req.formData().catch(() => null);
|
|
7
|
+
const result = {};
|
|
8
|
+
data?.forEach((value, key) => { if (typeof value === "string")
|
|
9
|
+
result[key] = value; });
|
|
10
|
+
return result;
|
|
13
11
|
}
|
|
14
|
-
function escapeHtml(
|
|
15
|
-
return
|
|
12
|
+
function escapeHtml(value) {
|
|
13
|
+
return value.replace(/[&<>"']/g, (character) => ({
|
|
14
|
+
"&": "&", "<": "<", ">": ">", '"': """, "'": "'"
|
|
15
|
+
})[character]);
|
|
16
16
|
}
|
|
17
|
-
function
|
|
18
|
-
return config.apps.find((
|
|
17
|
+
function appFor(config, appId) {
|
|
18
|
+
return config.apps.find((app) => app.id === appId) ?? null;
|
|
19
19
|
}
|
|
20
|
-
function
|
|
21
|
-
const tenant =
|
|
22
|
-
|
|
20
|
+
function servicesFor(config, app) {
|
|
21
|
+
const tenant = config.tenants.find((candidate) => candidate.id === app.tenantId);
|
|
22
|
+
const shared = (config.sharedServiceActivations ?? []).flatMap((activation) => {
|
|
23
|
+
if (!activation.enabled || activation.tenantId !== app.tenantId || (activation.appId && activation.appId !== app.id))
|
|
24
|
+
return [];
|
|
25
|
+
const service = config.sharedServiceCatalog.find((candidate) => candidate.enabled && candidate.id === activation.sharedServiceId);
|
|
26
|
+
return service ? [{ ...service, id: activation.id, hostname: service.baseUrl }] : [];
|
|
27
|
+
});
|
|
28
|
+
return [
|
|
29
|
+
...(tenant?.services ?? []),
|
|
30
|
+
...(config.platformServices ?? []).filter((service) => tenant?.activatedPlatformServices?.includes(service.id)),
|
|
31
|
+
...shared
|
|
32
|
+
].filter((service) => service.enabled);
|
|
33
|
+
}
|
|
34
|
+
function serviceFor(config, app, serviceId) {
|
|
35
|
+
return servicesFor(config, app).find((service) => service.id === serviceId) ?? null;
|
|
36
|
+
}
|
|
37
|
+
function serviceLabel(config, app, serviceId) {
|
|
38
|
+
const service = serviceFor(config, app, serviceId);
|
|
39
|
+
return service ? `${service.title || service.serviceId || service.id} · ${service.serviceId || service.id}` : serviceId;
|
|
40
|
+
}
|
|
41
|
+
function settingsFor(app, shellServiceId) {
|
|
42
|
+
app.shellFragments = app.shellFragments ?? {};
|
|
43
|
+
app.shellFragments[shellServiceId] = app.shellFragments[shellServiceId] ?? {};
|
|
44
|
+
return app.shellFragments[shellServiceId];
|
|
45
|
+
}
|
|
46
|
+
function effectiveBlockItems(app, shellServiceId, definition) {
|
|
47
|
+
const setting = app.shellFragments?.[shellServiceId]?.[definition.id];
|
|
48
|
+
if (setting?.mode === "none")
|
|
23
49
|
return [];
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
50
|
+
if (setting?.mode === "items")
|
|
51
|
+
return [...setting.items];
|
|
52
|
+
if (setting?.mode === "override")
|
|
53
|
+
return [setting.item];
|
|
54
|
+
const legacy = app.fragments?.[definition.id] ?? [];
|
|
55
|
+
if (legacy.length)
|
|
56
|
+
return legacy.filter((item) => item.enabled).map((item) => ({
|
|
57
|
+
source: "service",
|
|
58
|
+
...item,
|
|
59
|
+
fragmentId: item.fragmentId.includes(".") ? item.fragmentId : `${definition.id}.${item.fragmentId}`
|
|
60
|
+
}));
|
|
61
|
+
const legacySlots = (app.slots ?? []).flatMap((slot) => {
|
|
62
|
+
if (!slot.enabled || !slot.slotId.startsWith(`${definition.id}.`))
|
|
63
|
+
return [];
|
|
64
|
+
const route = app.routes.find((candidate) => candidate.enabled !== false
|
|
65
|
+
&& candidate.serviceId === slot.serviceId
|
|
66
|
+
&& candidate.viewId === slot.viewId);
|
|
67
|
+
const targetPath = route?.resolvedServicePath ?? route?.targetPath;
|
|
68
|
+
return targetPath ? [{ source: "service", serviceId: slot.serviceId, fragmentId: slot.slotId, targetPath }] : [];
|
|
69
|
+
});
|
|
70
|
+
if (legacySlots.length)
|
|
71
|
+
return legacySlots;
|
|
72
|
+
return definition.defaultItems.map((fragmentId) => ({ source: "shell", fragmentId }));
|
|
45
73
|
}
|
|
46
|
-
function
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return s.title || s.serviceId || s.id;
|
|
51
|
-
}
|
|
52
|
-
const activation = (config.sharedServiceActivations ?? []).find((x) => x.id === serviceId);
|
|
53
|
-
if (activation) {
|
|
54
|
-
const shared = (config.sharedServiceCatalog ?? []).find((x) => x.id === activation.sharedServiceId);
|
|
55
|
-
if (shared)
|
|
56
|
-
return `${shared.title || shared.id} (shared)`;
|
|
57
|
-
}
|
|
58
|
-
const ps = (config.platformServices ?? []).find((x) => x.id === serviceId);
|
|
59
|
-
return ps ? (ps.title || ps.id) : serviceId;
|
|
74
|
+
function encodeItem(item) {
|
|
75
|
+
return item.source === "shell"
|
|
76
|
+
? `h:${encodeURIComponent(item.fragmentId)}`
|
|
77
|
+
: `s:${item.serviceId}:${encodeURIComponent(item.fragmentId)}:${encodeURIComponent(item.targetPath)}`;
|
|
60
78
|
}
|
|
61
|
-
function
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
if (
|
|
65
|
-
return
|
|
79
|
+
function decodeItem(value) {
|
|
80
|
+
try {
|
|
81
|
+
const parts = value.split(":");
|
|
82
|
+
if (parts[0] === "h" && parts[1])
|
|
83
|
+
return { source: "shell", fragmentId: decodeURIComponent(parts[1]) };
|
|
84
|
+
if (parts[0] === "s" && parts.length === 4)
|
|
85
|
+
return {
|
|
86
|
+
source: "service",
|
|
87
|
+
serviceId: parts[1],
|
|
88
|
+
fragmentId: decodeURIComponent(parts[2]),
|
|
89
|
+
targetPath: decodeURIComponent(parts[3])
|
|
90
|
+
};
|
|
66
91
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const shared = (config.sharedServiceCatalog ?? []).find((x) => x.id === activation.sharedServiceId);
|
|
70
|
-
if (shared)
|
|
71
|
-
return shared.baseUrl ?? null;
|
|
92
|
+
catch {
|
|
93
|
+
return null;
|
|
72
94
|
}
|
|
73
|
-
|
|
74
|
-
return ps?.hostname ?? null;
|
|
95
|
+
return null;
|
|
75
96
|
}
|
|
76
|
-
function
|
|
77
|
-
const
|
|
78
|
-
|
|
97
|
+
function availableItems(config, app, definitions) {
|
|
98
|
+
const builtIns = definitions
|
|
99
|
+
.filter((definition) => definition.kind === "fragment")
|
|
100
|
+
.map((definition) => ({ item: { source: "shell", fragmentId: definition.id }, label: `Shell · ${definition.title}` }));
|
|
101
|
+
const services = servicesFor(config, app);
|
|
102
|
+
const serviceItems = services.flatMap((service) => {
|
|
103
|
+
const manifest = getCachedManifestForService(config, service.id);
|
|
104
|
+
return Object.values(manifest?.viewIndex ?? {}).flatMap((view) => view.fragments
|
|
105
|
+
.filter((fragment) => app.routes.some((route) => route.enabled !== false
|
|
106
|
+
&& route.serviceId === service.id
|
|
107
|
+
&& (route.resolvedServicePath ?? route.targetPath) === fragment.targetPath))
|
|
108
|
+
.map((fragment) => ({
|
|
109
|
+
item: { source: "service", serviceId: service.id, fragmentId: fragment.fragmentId, targetPath: fragment.targetPath },
|
|
110
|
+
label: `${serviceLabel(config, app, service.id)} · ${fragment.fragmentId} (${fragment.targetPath})`
|
|
111
|
+
})));
|
|
112
|
+
});
|
|
113
|
+
return [...builtIns, ...serviceItems];
|
|
79
114
|
}
|
|
80
|
-
function
|
|
81
|
-
|
|
82
|
-
appDef.fragments[location] = frags;
|
|
115
|
+
function hidden(appId, shellServiceId, fragmentId) {
|
|
116
|
+
return `<input type="hidden" name="appId" value="${escapeHtml(appId)}"><input type="hidden" name="shellServiceId" value="${escapeHtml(shellServiceId)}"><input type="hidden" name="fragmentId" value="${escapeHtml(fragmentId)}">`;
|
|
83
117
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
{
|
|
87
|
-
];
|
|
88
|
-
function renderFragmentRow(frag, idx, location, appId, config) {
|
|
89
|
-
const serviceTitle = getServiceTitle(config, frag.serviceId);
|
|
90
|
-
const btn = (action, label, btnClass, title) => `<form hx-post="${API_BASE}/fragments-editor/${action}" hx-target="#bp-fragments-editor" hx-swap="outerHTML" class="d-inline m-0">
|
|
91
|
-
<input type="hidden" name="appId" value="${escapeHtml(appId)}" />
|
|
92
|
-
<input type="hidden" name="location" value="${escapeHtml(location)}" />
|
|
93
|
-
<input type="hidden" name="index" value="${idx}" />
|
|
94
|
-
<button type="submit" class="btn btn-sm ${btnClass}" title="${escapeHtml(title)}">${label}</button>
|
|
95
|
-
</form>`;
|
|
96
|
-
return `<li class="list-group-item d-flex justify-content-between align-items-center">
|
|
97
|
-
<div class="d-flex flex-column gap-1 flex-grow-1 min-width-0">
|
|
98
|
-
<div class="d-flex align-items-center gap-2">
|
|
99
|
-
<span class="badge text-bg-secondary">${escapeHtml(serviceTitle)}</span>
|
|
100
|
-
<span class="font-monospace">${escapeHtml(frag.fragmentId)}</span>
|
|
101
|
-
<span class="text-secondary">-></span>
|
|
102
|
-
<span class="font-monospace text-secondary small">${escapeHtml(frag.targetPath)}</span>
|
|
103
|
-
</div>
|
|
104
|
-
</div>
|
|
105
|
-
<div class="btn-group btn-group-sm" role="group">
|
|
106
|
-
${btn("move-up", "^", "btn-outline-secondary", "Move up")}
|
|
107
|
-
${btn("move-down", "v", "btn-outline-secondary", "Move down")}
|
|
108
|
-
${btn("toggle", frag.enabled ? "on" : "off", frag.enabled ? "btn-success" : "btn-outline-secondary", frag.enabled ? "Disable" : "Enable")}
|
|
109
|
-
${btn("remove", "x", "btn-outline-danger", "Remove")}
|
|
110
|
-
</div>
|
|
111
|
-
</li>`;
|
|
118
|
+
function modeButtons(appId, shellServiceId, definition) {
|
|
119
|
+
const button = (mode, label, style) => `<form class="d-inline" hx-post="${API_BASE}/set-mode" hx-target="#bp-fragments-editor" hx-swap="outerHTML">${hidden(appId, shellServiceId, definition.id)}<input type="hidden" name="mode" value="${mode}"><button class="btn btn-sm ${style}" type="submit">${label}</button></form>`;
|
|
120
|
+
return `<div class="btn-group">${button("default", "Use shell default", "btn-outline-secondary")}${button("none", "Empty", "btn-outline-danger")}</div>`;
|
|
112
121
|
}
|
|
113
|
-
function
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const schemaUrl = `${s.hostname.replace(/\/+$/, "")}/.well-known/bp/schema.json`;
|
|
118
|
-
return `<option value="${escapeHtml(s.id)}" data-schema-url="${escapeHtml(schemaUrl)}">${escapeHtml(s.title)}</option>`;
|
|
119
|
-
})
|
|
120
|
-
].join("");
|
|
121
|
-
const rows = frags.length === 0
|
|
122
|
-
? `<li class="list-group-item text-secondary">No fragments in this location.</li>`
|
|
123
|
-
: frags.map((f, i) => renderFragmentRow(f, i, location, appId, config)).join("");
|
|
124
|
-
return `<div class="card mb-3">
|
|
125
|
-
<div class="card-header"><strong>${escapeHtml(label)}</strong> <span class="small text-secondary">(${frags.length})</span></div>
|
|
126
|
-
<ul class="list-group list-group-flush">${rows}</ul>
|
|
127
|
-
<div class="card-body">
|
|
128
|
-
<form hx-post="${API_BASE}/fragments-editor/add" hx-target="#bp-fragments-editor" hx-swap="outerHTML"
|
|
129
|
-
class="row g-2 align-items-end">
|
|
130
|
-
<input type="hidden" name="appId" value="${escapeHtml(appId)}" />
|
|
131
|
-
<input type="hidden" name="location" value="${escapeHtml(location)}" />
|
|
132
|
-
<div class="col-md-3">
|
|
133
|
-
<label class="form-label small mb-0">Service</label>
|
|
134
|
-
<select name="serviceId" class="form-select form-select-sm" required
|
|
135
|
-
hx-get="${API_BASE}/fragments-editor/fragments"
|
|
136
|
-
hx-target="#bp-frag-select-${location}"
|
|
137
|
-
hx-swap="outerHTML"
|
|
138
|
-
hx-trigger="change"
|
|
139
|
-
hx-include="closest form">${serviceOpts}</select>
|
|
140
|
-
</div>
|
|
141
|
-
<div class="col-md-3">
|
|
142
|
-
<label class="form-label small mb-0">Fragment</label>
|
|
143
|
-
<select id="bp-frag-select-${location}" name="fragmentId" class="form-select form-select-sm" required
|
|
144
|
-
data-bp-frag-select
|
|
145
|
-
data-location="${escapeHtml(location)}">
|
|
146
|
-
<option value="">Pick service first...</option>
|
|
147
|
-
</select>
|
|
148
|
-
<div id="bp-frag-status-${location}" class="form-text small text-secondary"></div>
|
|
149
|
-
</div>
|
|
150
|
-
<div class="col-md-4">
|
|
151
|
-
<label class="form-label small mb-0">Target Path</label>
|
|
152
|
-
<input id="bp-frag-target-${location}" type="text" name="targetPath" class="form-control form-control-sm font-monospace" placeholder="auto-filled" required />
|
|
153
|
-
</div>
|
|
154
|
-
<div class="col-md-2">
|
|
155
|
-
<button type="submit" class="btn btn-primary btn-sm w-100">Add</button>
|
|
156
|
-
</div>
|
|
157
|
-
</form>
|
|
158
|
-
</div>
|
|
159
|
-
</div>`;
|
|
122
|
+
function itemLabel(item, config, app, definitions) {
|
|
123
|
+
if (item.source === "shell")
|
|
124
|
+
return `Shell · ${definitions.find((definition) => definition.id === item.fragmentId)?.title ?? item.fragmentId}`;
|
|
125
|
+
return `${serviceLabel(config, app, item.serviceId)} · ${item.fragmentId} (${item.targetPath})`;
|
|
160
126
|
}
|
|
161
|
-
function
|
|
162
|
-
return `<
|
|
163
|
-
(() => {
|
|
164
|
-
const root = document.getElementById("bp-fragments-editor");
|
|
165
|
-
if (!root) return;
|
|
166
|
-
|
|
167
|
-
const escapeHtml = (value) => String(value).replace(/[&<>"']/g, (ch) => ({
|
|
168
|
-
"&": "&", "<": "<", ">": ">", '"': """, "'": "'"
|
|
169
|
-
})[ch]);
|
|
170
|
-
|
|
171
|
-
const loadFragments = async (fragmentSelect) => {
|
|
172
|
-
const location = fragmentSelect.dataset.location || "";
|
|
173
|
-
const schemaUrl = fragmentSelect.dataset.schemaUrl || "";
|
|
174
|
-
const targetInput = root.querySelector("#bp-frag-target-" + CSS.escape(location));
|
|
175
|
-
const status = root.querySelector("#bp-frag-status-" + CSS.escape(location));
|
|
176
|
-
if (targetInput) targetInput.value = "";
|
|
177
|
-
try {
|
|
178
|
-
if (status) status.textContent = "Loading service schema from " + schemaUrl;
|
|
179
|
-
const response = await fetch(schemaUrl, {
|
|
180
|
-
method: "GET",
|
|
181
|
-
headers: { Accept: "application/json" },
|
|
182
|
-
cache: "no-store"
|
|
183
|
-
});
|
|
184
|
-
if (!response.ok) throw new Error(String(response.status));
|
|
185
|
-
const schema = (response.headers.get("content-type") || "").includes("application/json") ? await response.json() : null;
|
|
186
|
-
if (!schema) throw new Error("service schema returned non-JSON");
|
|
187
|
-
const fragments = [];
|
|
188
|
-
for (const route of schema.routes || []) {
|
|
189
|
-
for (const fragment of route.fragments || []) {
|
|
190
|
-
if (location && fragment.fragmentLocation !== location) continue;
|
|
191
|
-
fragments.push({
|
|
192
|
-
fragmentId: fragment.fragmentId,
|
|
193
|
-
viewPath: route.path || ""
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (fragments.length === 0) {
|
|
199
|
-
fragmentSelect.innerHTML = '<option value="">No fragments at "' + escapeHtml(location) + '"</option>';
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
fragmentSelect.innerHTML = '<option value="">Select fragment...</option>' + fragments
|
|
204
|
-
.map((fragment) => '<option value="' + escapeHtml(fragment.fragmentId) + '" data-view-path="' + escapeHtml(fragment.viewPath) + '">' + escapeHtml(fragment.fragmentId) + ' (' + escapeHtml(fragment.viewPath) + ')</option>')
|
|
205
|
-
.join("");
|
|
206
|
-
if (status) status.textContent = "Loaded " + fragments.length + " fragment" + (fragments.length === 1 ? "" : "s") + " from service.";
|
|
207
|
-
} catch {
|
|
208
|
-
fragmentSelect.innerHTML = '<option value="">Unable to load fragments from browser</option>';
|
|
209
|
-
if (status) status.textContent = "Failed to load service schema from " + schemaUrl;
|
|
210
|
-
}
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
if (root.dataset.bpFragmentsBound !== "1") {
|
|
214
|
-
root.dataset.bpFragmentsBound = "1";
|
|
215
|
-
root.addEventListener("change", (event) => {
|
|
216
|
-
const target = event.target;
|
|
217
|
-
if (!(target instanceof HTMLSelectElement)) return;
|
|
218
|
-
|
|
219
|
-
if (target.matches("[data-bp-frag-select]")) {
|
|
220
|
-
const location = target.dataset.location || "";
|
|
221
|
-
const targetInput = root.querySelector("#bp-frag-target-" + CSS.escape(location));
|
|
222
|
-
const selected = target.selectedOptions && target.selectedOptions[0];
|
|
223
|
-
if (targetInput) targetInput.value = selected ? (selected.dataset.viewPath || "") : "";
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
root.querySelectorAll("[data-bp-frag-select][data-schema-url]").forEach((select) => {
|
|
229
|
-
if (select.dataset.loaded === "1") return;
|
|
230
|
-
select.dataset.loaded = "1";
|
|
231
|
-
loadFragments(select);
|
|
232
|
-
});
|
|
233
|
-
})();
|
|
234
|
-
</script>`;
|
|
127
|
+
function sourceSelect(items) {
|
|
128
|
+
return `<select name="source" class="form-select form-select-sm" required><option value="">Select fragment...</option>${items.map(({ item, label }) => `<option value="${escapeHtml(encodeItem(item))}">${escapeHtml(label)}</option>`).join("")}</select>`;
|
|
235
129
|
}
|
|
236
|
-
function
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
130
|
+
function renderDefinition(config, app, shellServiceId, definition, definitions, choices) {
|
|
131
|
+
const setting = app.shellFragments?.[shellServiceId]?.[definition.id];
|
|
132
|
+
const status = setting?.mode ?? (app.fragments?.[definition.id]?.length ? "legacy default" : "shell default");
|
|
133
|
+
if (definition.kind === "fragment") {
|
|
134
|
+
return `<section class="card mb-3"><div class="card-header d-flex justify-content-between align-items-center"><div><strong>${escapeHtml(definition.title)}</strong><div class="small text-secondary font-monospace">_${escapeHtml(definition.id)}.tsx · ${escapeHtml(status)}</div></div>${modeButtons(app.id, shellServiceId, definition)}</div><div class="card-body"><p class="small text-secondary">${escapeHtml(definition.description)}</p><form class="row g-2" hx-post="${API_BASE}/set-override" hx-target="#bp-fragments-editor" hx-swap="outerHTML">${hidden(app.id, shellServiceId, definition.id)}<div class="col">${sourceSelect(choices.filter(({ item }) => item.source === "service"))}</div><div class="col-auto"><button class="btn btn-sm btn-primary" type="submit">Override</button></div></form></div></section>`;
|
|
135
|
+
}
|
|
136
|
+
const items = effectiveBlockItems(app, shellServiceId, definition);
|
|
137
|
+
const rows = items.length ? items.map((item, index) => `<li class="list-group-item d-flex justify-content-between align-items-center"><span>${escapeHtml(itemLabel(item, config, app, definitions))}</span><div class="btn-group">${["move-up", "move-down", "remove"].map((action) => `<form hx-post="${API_BASE}/${action}" hx-target="#bp-fragments-editor" hx-swap="outerHTML">${hidden(app.id, shellServiceId, definition.id)}<input type="hidden" name="index" value="${index}"><button class="btn btn-sm ${action === "remove" ? "btn-outline-danger" : "btn-outline-secondary"}" type="submit"${(action === "move-up" && index === 0) || (action === "move-down" && index === items.length - 1) ? " disabled" : ""}>${action === "move-up" ? "Move up" : action === "move-down" ? "Move down" : "Remove"}</button></form>`).join("")}</div></li>`).join("") : `<li class="list-group-item text-secondary">Empty block.</li>`;
|
|
138
|
+
return `<section class="card mb-3"><div class="card-header d-flex justify-content-between align-items-center"><div><strong>${escapeHtml(definition.title)}</strong><div class="small text-secondary font-monospace">_${escapeHtml(definition.id)}/index.tsx · ${escapeHtml(status)}</div></div>${modeButtons(app.id, shellServiceId, definition)}</div><ul class="list-group list-group-flush">${rows}</ul><div class="card-body"><p class="small text-secondary">${escapeHtml(definition.description)}</p><form class="row g-2" hx-post="${API_BASE}/add" hx-target="#bp-fragments-editor" hx-swap="outerHTML">${hidden(app.id, shellServiceId, definition.id)}<div class="col">${sourceSelect(choices)}</div><div class="col-auto"><button class="btn btn-sm btn-primary" type="submit">Add</button></div></form></div></section>`;
|
|
244
139
|
}
|
|
245
|
-
function renderEditor(config,
|
|
246
|
-
const
|
|
247
|
-
|
|
140
|
+
function renderEditor(config, app) {
|
|
141
|
+
const shellServiceId = app.shell?.serviceId;
|
|
142
|
+
if (!shellServiceId)
|
|
143
|
+
return `<div id="bp-fragments-editor" class="alert alert-secondary">This app has no shell, so it has no shell fragments.</div>`;
|
|
144
|
+
const manifest = getCachedManifestForService(config, shellServiceId);
|
|
145
|
+
const definitions = manifest?.shell?.fragments ?? [];
|
|
146
|
+
if (!definitions.length)
|
|
147
|
+
return `<div id="bp-fragments-editor" class="alert alert-secondary">The active shell exposes no fragments or has not synced its manifest yet.</div>`;
|
|
148
|
+
const choices = availableItems(config, app, definitions);
|
|
149
|
+
return `<div id="bp-fragments-editor"><div class="mb-3"><strong>${escapeHtml(serviceLabel(config, app, shellServiceId))}</strong><div class="small text-secondary">Shell defaults are used until an explicit override, list, or empty value is saved.</div></div>${definitions.map((definition) => renderDefinition(config, app, shellServiceId, definition, definitions, choices)).join("")}</div>`;
|
|
248
150
|
}
|
|
249
|
-
export function registerFragmentsEditorRoutes(
|
|
250
|
-
const
|
|
151
|
+
export function registerFragmentsEditorRoutes(router, store) {
|
|
152
|
+
const respond = async (appId) => {
|
|
251
153
|
const config = await store.loadConfig();
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
"HX-Trigger": "bp:fragments-changed"
|
|
257
|
-
});
|
|
154
|
+
const app = appFor(config, appId);
|
|
155
|
+
return app
|
|
156
|
+
? htmlResponse(renderEditor(config, app), 200, "text/html; mode=fragment", { "HX-Trigger": "bp:fragments-changed" })
|
|
157
|
+
: htmlResponse(`<div id="bp-fragments-editor" class="alert alert-danger">App not found.</div>`, 200, "text/html; mode=fragment");
|
|
258
158
|
};
|
|
259
|
-
|
|
260
|
-
const
|
|
261
|
-
const appId = url.searchParams.get("appId") ?? "";
|
|
262
|
-
if (!appId)
|
|
263
|
-
return htmlResponse(`<div class="alert alert-secondary">Select an app</div>`, 200, "text/html; mode=fragment");
|
|
264
|
-
return respondEditor(appId);
|
|
265
|
-
});
|
|
266
|
-
app.post(`${API_BASE}/fragments-editor/add`, async (event) => {
|
|
267
|
-
const f = await readFormBody(event);
|
|
268
|
-
const config = await store.loadConfig();
|
|
269
|
-
const appDef = getApp(config, f.appId);
|
|
270
|
-
if (!appDef)
|
|
271
|
-
return jsonResponse({ error: "App not found" }, 404);
|
|
272
|
-
if (!f.serviceId || !f.fragmentId || !f.targetPath)
|
|
273
|
-
return respondEditor(f.appId);
|
|
274
|
-
const frags = getFragments(appDef, f.location);
|
|
275
|
-
frags.push({
|
|
276
|
-
serviceId: f.serviceId,
|
|
277
|
-
fragmentId: f.fragmentId,
|
|
278
|
-
targetPath: f.targetPath,
|
|
279
|
-
enabled: true
|
|
280
|
-
});
|
|
281
|
-
setFragments(appDef, f.location, frags);
|
|
282
|
-
await store.saveConfig(config);
|
|
283
|
-
return respondEditor(f.appId);
|
|
284
|
-
});
|
|
285
|
-
app.post(`${API_BASE}/fragments-editor/remove`, async (event) => {
|
|
286
|
-
const f = await readFormBody(event);
|
|
159
|
+
const mutate = (handler) => async (event) => {
|
|
160
|
+
const form = await readForm(event);
|
|
287
161
|
const config = await store.loadConfig();
|
|
288
|
-
const
|
|
289
|
-
if (!
|
|
290
|
-
return jsonResponse({ error: "
|
|
291
|
-
const
|
|
292
|
-
const
|
|
293
|
-
if (
|
|
294
|
-
|
|
295
|
-
|
|
162
|
+
const app = appFor(config, form.appId);
|
|
163
|
+
if (!app || app.shell?.serviceId !== form.shellServiceId)
|
|
164
|
+
return jsonResponse({ error: "Active shell changed; reload the editor" }, 409);
|
|
165
|
+
const definitions = getCachedManifestForService(config, form.shellServiceId)?.shell?.fragments ?? [];
|
|
166
|
+
const definition = definitions.find((candidate) => candidate.id === form.fragmentId);
|
|
167
|
+
if (!definition)
|
|
168
|
+
return jsonResponse({ error: "Shell fragment is unavailable; reload the editor" }, 409);
|
|
169
|
+
const choices = availableItems(config, app, definitions);
|
|
170
|
+
if (!handler(form, app, settingsFor(app, form.shellServiceId), definition, choices))
|
|
171
|
+
return jsonResponse({ error: "Invalid fragment editor operation" }, 400);
|
|
296
172
|
await store.saveConfig(config);
|
|
297
|
-
return
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
const appDef = getApp(config, f.appId);
|
|
303
|
-
if (!appDef)
|
|
304
|
-
return jsonResponse({ error: "App not found" }, 404);
|
|
305
|
-
const idx = parseInt(f.index, 10);
|
|
306
|
-
const frags = getFragments(appDef, f.location);
|
|
307
|
-
if (idx >= 0 && idx < frags.length)
|
|
308
|
-
frags[idx].enabled = !frags[idx].enabled;
|
|
309
|
-
setFragments(appDef, f.location, frags);
|
|
310
|
-
await store.saveConfig(config);
|
|
311
|
-
return respondEditor(f.appId);
|
|
312
|
-
});
|
|
313
|
-
app.post(`${API_BASE}/fragments-editor/move-up`, async (event) => {
|
|
314
|
-
const f = await readFormBody(event);
|
|
315
|
-
const config = await store.loadConfig();
|
|
316
|
-
const appDef = getApp(config, f.appId);
|
|
317
|
-
if (!appDef)
|
|
318
|
-
return jsonResponse({ error: "App not found" }, 404);
|
|
319
|
-
const idx = parseInt(f.index, 10);
|
|
320
|
-
const frags = getFragments(appDef, f.location);
|
|
321
|
-
if (idx > 0 && idx < frags.length) {
|
|
322
|
-
const [it] = frags.splice(idx, 1);
|
|
323
|
-
frags.splice(idx - 1, 0, it);
|
|
324
|
-
}
|
|
325
|
-
setFragments(appDef, f.location, frags);
|
|
326
|
-
await store.saveConfig(config);
|
|
327
|
-
return respondEditor(f.appId);
|
|
328
|
-
});
|
|
329
|
-
app.get(`${API_BASE}/fragments-editor/fragments`, async (event) => {
|
|
330
|
-
const url = new URL(event.req.url ?? "", RELATIVE_URL_PARSE_BASE);
|
|
331
|
-
const serviceId = url.searchParams.get("serviceId") ?? "";
|
|
332
|
-
const location = url.searchParams.get("location") ?? "";
|
|
333
|
-
if (!serviceId)
|
|
334
|
-
return htmlResponse(`<option value="">Pick service first...</option>`, 200, "text/html; mode=fragment");
|
|
335
|
-
const config = await store.loadConfig();
|
|
336
|
-
const hostname = getServiceHostname(config, serviceId);
|
|
337
|
-
if (!hostname) {
|
|
338
|
-
return htmlResponse(`<select id="bp-frag-select-${escapeHtml(location)}" name="fragmentId" class="form-select form-select-sm" required><option value="">No service</option></select>`, 200, "text/html; mode=fragment");
|
|
339
|
-
}
|
|
340
|
-
return htmlResponse(renderFragmentSelect(location, `${hostname.replace(/\/+$/, "")}/.well-known/bp/schema.json`), 200, "text/html; mode=fragment");
|
|
341
|
-
});
|
|
342
|
-
app.get(`${API_BASE}/fragments-editor/fragment-target`, async (event) => {
|
|
343
|
-
const url = new URL(event.req.url ?? "", RELATIVE_URL_PARSE_BASE);
|
|
344
|
-
const serviceId = url.searchParams.get("serviceId") ?? "";
|
|
345
|
-
const fragmentId = url.searchParams.get("fragmentId") ?? "";
|
|
346
|
-
const location = url.searchParams.get("location") ?? "";
|
|
347
|
-
const targetPath = url.searchParams.get("targetPath") ?? "";
|
|
348
|
-
return htmlResponse(`<input id="bp-frag-target-${escapeHtml(location)}" type="text" name="targetPath" class="form-control form-control-sm font-monospace" value="${escapeHtml(targetPath)}" placeholder="auto-filled" required />`, 200, "text/html; mode=fragment");
|
|
173
|
+
return respond(form.appId);
|
|
174
|
+
};
|
|
175
|
+
router.get(API_BASE, async (event) => {
|
|
176
|
+
const appId = new URL(event.req.url ?? "", RELATIVE_URL_PARSE_BASE).searchParams.get("appId") ?? "";
|
|
177
|
+
return appId ? respond(appId) : htmlResponse(`<div id="bp-fragments-editor" class="alert alert-secondary">Select an app.</div>`, 200, "text/html; mode=fragment");
|
|
349
178
|
});
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
return
|
|
179
|
+
router.post(`${API_BASE}/set-mode`, mutate((form, _app, settings) => {
|
|
180
|
+
if (form.mode === "default")
|
|
181
|
+
delete settings[form.fragmentId];
|
|
182
|
+
else if (form.mode === "none")
|
|
183
|
+
settings[form.fragmentId] = { mode: "none" };
|
|
184
|
+
else
|
|
185
|
+
return false;
|
|
186
|
+
return true;
|
|
187
|
+
}));
|
|
188
|
+
router.post(`${API_BASE}/set-override`, mutate((form, _app, settings, definition, choices) => {
|
|
189
|
+
const item = decodeItem(form.source);
|
|
190
|
+
if (definition.kind !== "fragment" || item?.source !== "service" || !choices.some((choice) => encodeItem(choice.item) === form.source))
|
|
191
|
+
return false;
|
|
192
|
+
settings[form.fragmentId] = { mode: "override", item };
|
|
193
|
+
return true;
|
|
194
|
+
}));
|
|
195
|
+
router.post(`${API_BASE}/add`, mutate((form, app, settings, definition, choices) => {
|
|
196
|
+
const item = decodeItem(form.source);
|
|
197
|
+
if (definition.kind !== "block" || !item || !choices.some((choice) => encodeItem(choice.item) === form.source))
|
|
198
|
+
return false;
|
|
199
|
+
const current = settings[form.fragmentId]?.mode === "items" ? [...settings[form.fragmentId].items] : effectiveBlockItems(app, form.shellServiceId, definition);
|
|
200
|
+
settings[form.fragmentId] = { mode: "items", items: [...current, item] };
|
|
201
|
+
return true;
|
|
202
|
+
}));
|
|
203
|
+
const editItems = (operation) => mutate((form, app, settings, definition) => {
|
|
204
|
+
if (definition.kind !== "block")
|
|
205
|
+
return false;
|
|
206
|
+
const current = settings[form.fragmentId]?.mode === "items"
|
|
207
|
+
? [...settings[form.fragmentId].items]
|
|
208
|
+
: effectiveBlockItems(app, form.shellServiceId, definition);
|
|
209
|
+
if (!operation(current, Number(form.index)))
|
|
210
|
+
return false;
|
|
211
|
+
settings[form.fragmentId] = { mode: "items", items: current };
|
|
212
|
+
return true;
|
|
365
213
|
});
|
|
214
|
+
router.post(`${API_BASE}/remove`, editItems((items, index) => {
|
|
215
|
+
if (index < 0 || index >= items.length)
|
|
216
|
+
return false;
|
|
217
|
+
items.splice(index, 1);
|
|
218
|
+
return true;
|
|
219
|
+
}));
|
|
220
|
+
router.post(`${API_BASE}/move-up`, editItems((items, index) => {
|
|
221
|
+
if (index <= 0 || index >= items.length)
|
|
222
|
+
return false;
|
|
223
|
+
items.splice(index - 1, 0, ...items.splice(index, 1));
|
|
224
|
+
return true;
|
|
225
|
+
}));
|
|
226
|
+
router.post(`${API_BASE}/move-down`, editItems((items, index) => {
|
|
227
|
+
if (index < 0 || index >= items.length - 1)
|
|
228
|
+
return false;
|
|
229
|
+
items.splice(index + 1, 0, ...items.splice(index, 1));
|
|
230
|
+
return true;
|
|
231
|
+
}));
|
|
366
232
|
}
|
|
367
233
|
//# sourceMappingURL=fragmentsEditor.js.map
|