@open-mercato/ui 0.4.5-develop-6bdcebbece → 0.4.5-develop-986cfd8c37
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 +8 -0
- package/dist/backend/AppShell.js +395 -134
- package/dist/backend/AppShell.js.map +2 -2
- package/dist/backend/CrudForm.js +232 -21
- package/dist/backend/CrudForm.js.map +2 -2
- package/dist/backend/ProfileDropdown.js +214 -94
- package/dist/backend/ProfileDropdown.js.map +2 -2
- package/dist/backend/injection/InjectionSpot.js +74 -4
- package/dist/backend/injection/InjectionSpot.js.map +2 -2
- package/dist/backend/injection/SseEventIndicator.js +16 -0
- package/dist/backend/injection/SseEventIndicator.js.map +7 -0
- package/dist/backend/injection/WidgetSharedState.js +49 -0
- package/dist/backend/injection/WidgetSharedState.js.map +7 -0
- package/dist/backend/injection/eventBridge.js +105 -0
- package/dist/backend/injection/eventBridge.js.map +7 -0
- package/dist/backend/injection/mergeMenuItems.js +43 -0
- package/dist/backend/injection/mergeMenuItems.js.map +7 -0
- package/dist/backend/injection/resolveInjectedIcon.js +23 -0
- package/dist/backend/injection/resolveInjectedIcon.js.map +7 -0
- package/dist/backend/injection/spotIds.js +40 -1
- package/dist/backend/injection/spotIds.js.map +2 -2
- package/dist/backend/injection/useAppEvent.js +35 -0
- package/dist/backend/injection/useAppEvent.js.map +7 -0
- package/dist/backend/injection/useInjectedMenuItems.js +92 -0
- package/dist/backend/injection/useInjectedMenuItems.js.map +7 -0
- package/dist/backend/injection/useInjectionDataWidgets.js +36 -0
- package/dist/backend/injection/useInjectionDataWidgets.js.map +7 -0
- package/dist/backend/injection/useOperationProgress.js +64 -0
- package/dist/backend/injection/useOperationProgress.js.map +7 -0
- package/dist/backend/injection/useWidgetSharedState.js +26 -0
- package/dist/backend/injection/useWidgetSharedState.js.map +7 -0
- package/dist/backend/section-page/SectionNav.js +22 -2
- package/dist/backend/section-page/SectionNav.js.map +2 -2
- package/dist/backend/utils/api.js +9 -1
- package/dist/backend/utils/api.js.map +2 -2
- package/package.json +2 -2
- package/src/backend/AGENTS.md +50 -0
- package/src/backend/AppShell.tsx +317 -30
- package/src/backend/CrudForm.tsx +238 -21
- package/src/backend/ProfileDropdown.tsx +199 -78
- package/src/backend/injection/InjectionSpot.tsx +118 -16
- package/src/backend/injection/SseEventIndicator.tsx +24 -0
- package/src/backend/injection/WidgetSharedState.ts +58 -0
- package/src/backend/injection/eventBridge.ts +134 -0
- package/src/backend/injection/mergeMenuItems.ts +71 -0
- package/src/backend/injection/resolveInjectedIcon.tsx +30 -0
- package/src/backend/injection/spotIds.ts +38 -0
- package/src/backend/injection/useAppEvent.ts +76 -0
- package/src/backend/injection/useInjectedMenuItems.ts +125 -0
- package/src/backend/injection/useInjectionDataWidgets.ts +41 -0
- package/src/backend/injection/useOperationProgress.ts +105 -0
- package/src/backend/injection/useWidgetSharedState.ts +28 -0
- package/src/backend/section-page/SectionNav.tsx +22 -1
- package/src/backend/utils/api.ts +14 -5
package/dist/backend/AppShell.js
CHANGED
|
@@ -17,18 +17,176 @@ import { useLocale, useT } from "@open-mercato/shared/lib/i18n/context";
|
|
|
17
17
|
import { slugifySidebarId } from "@open-mercato/shared/modules/navigation/sidebarPreferences";
|
|
18
18
|
import { InjectionSpot } from "./injection/InjectionSpot.js";
|
|
19
19
|
import { LEGACY_GLOBAL_MUTATION_INJECTION_SPOT_ID } from "./injection/mutationEvents.js";
|
|
20
|
+
import { mergeMenuItems } from "./injection/mergeMenuItems.js";
|
|
21
|
+
import { useInjectedMenuItems } from "./injection/useInjectedMenuItems.js";
|
|
22
|
+
import { resolveInjectedIcon } from "./injection/resolveInjectedIcon.js";
|
|
23
|
+
import { useEventBridge } from "./injection/eventBridge.js";
|
|
24
|
+
import { SseEventIndicator } from "./injection/SseEventIndicator.js";
|
|
20
25
|
import {
|
|
21
26
|
BACKEND_LAYOUT_FOOTER_INJECTION_SPOT_ID,
|
|
22
27
|
BACKEND_LAYOUT_TOP_INJECTION_SPOT_ID,
|
|
23
28
|
BACKEND_RECORD_CURRENT_INJECTION_SPOT_ID,
|
|
24
29
|
BACKEND_SIDEBAR_FOOTER_INJECTION_SPOT_ID,
|
|
25
|
-
BACKEND_SIDEBAR_TOP_INJECTION_SPOT_ID
|
|
30
|
+
BACKEND_SIDEBAR_TOP_INJECTION_SPOT_ID,
|
|
31
|
+
BACKEND_SIDEBAR_NAV_FOOTER_INJECTION_SPOT_ID,
|
|
32
|
+
BACKEND_SIDEBAR_NAV_INJECTION_SPOT_ID,
|
|
33
|
+
BACKEND_TOPBAR_ACTIONS_INJECTION_SPOT_ID,
|
|
34
|
+
GLOBAL_HEADER_STATUS_INDICATORS_INJECTION_SPOT_ID,
|
|
35
|
+
GLOBAL_SIDEBAR_STATUS_BADGES_INJECTION_SPOT_ID
|
|
26
36
|
} from "./injection/spotIds.js";
|
|
37
|
+
function convertInjectedMenuItemToSidebarItem(item, title) {
|
|
38
|
+
if (!item.href) return null;
|
|
39
|
+
return {
|
|
40
|
+
id: item.id,
|
|
41
|
+
href: item.href,
|
|
42
|
+
title,
|
|
43
|
+
defaultTitle: title,
|
|
44
|
+
icon: resolveInjectedIcon(item.icon) ?? void 0,
|
|
45
|
+
enabled: true,
|
|
46
|
+
hidden: false,
|
|
47
|
+
pageContext: "main"
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function resolveInjectedMenuLabel(item, t) {
|
|
51
|
+
if (item.labelKey && item.label) return t(item.labelKey, item.label);
|
|
52
|
+
if (item.labelKey) return t(item.labelKey, item.id);
|
|
53
|
+
if (item.label && item.label.includes(".")) return t(item.label, item.id);
|
|
54
|
+
return item.label ?? item.id;
|
|
55
|
+
}
|
|
56
|
+
function mergeSidebarItemsWithInjected(items, injectedItems, t) {
|
|
57
|
+
if (injectedItems.length === 0) return items;
|
|
58
|
+
const builtInById = /* @__PURE__ */ new Map();
|
|
59
|
+
for (const item of items) {
|
|
60
|
+
builtInById.set(item.id ?? item.href, item);
|
|
61
|
+
}
|
|
62
|
+
const merged = mergeMenuItems(
|
|
63
|
+
items.map((item) => ({
|
|
64
|
+
id: item.id ?? item.href
|
|
65
|
+
})),
|
|
66
|
+
injectedItems
|
|
67
|
+
);
|
|
68
|
+
const result = [];
|
|
69
|
+
for (const entry of merged) {
|
|
70
|
+
if (entry.source === "built-in") {
|
|
71
|
+
const original = builtInById.get(entry.id);
|
|
72
|
+
if (original) result.push(original);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const translatedLabel = resolveInjectedMenuLabel(
|
|
76
|
+
{ id: entry.id, label: entry.label, labelKey: entry.labelKey },
|
|
77
|
+
t
|
|
78
|
+
);
|
|
79
|
+
const converted = convertInjectedMenuItemToSidebarItem(
|
|
80
|
+
{
|
|
81
|
+
id: entry.id,
|
|
82
|
+
label: translatedLabel,
|
|
83
|
+
icon: entry.icon,
|
|
84
|
+
href: entry.href
|
|
85
|
+
},
|
|
86
|
+
translatedLabel
|
|
87
|
+
);
|
|
88
|
+
if (converted) result.push(converted);
|
|
89
|
+
}
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
function mergeSidebarGroupsWithInjected(groups, injectedItems, t) {
|
|
93
|
+
if (injectedItems.length === 0) return groups;
|
|
94
|
+
const injectedByGroup = /* @__PURE__ */ new Map();
|
|
95
|
+
const ungrouped = [];
|
|
96
|
+
for (const item of injectedItems) {
|
|
97
|
+
if (item.groupId && item.groupId.trim().length > 0) {
|
|
98
|
+
const groupItems = injectedByGroup.get(item.groupId) ?? [];
|
|
99
|
+
groupItems.push(item);
|
|
100
|
+
injectedByGroup.set(item.groupId, groupItems);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
ungrouped.push(item);
|
|
104
|
+
}
|
|
105
|
+
const nextGroups = groups.map((group, index) => {
|
|
106
|
+
const groupId = group.id || resolveGroupKey(group);
|
|
107
|
+
const groupInjected = [
|
|
108
|
+
...injectedByGroup.get(groupId) ?? [],
|
|
109
|
+
...index === 0 ? ungrouped : []
|
|
110
|
+
];
|
|
111
|
+
return {
|
|
112
|
+
...group,
|
|
113
|
+
items: mergeSidebarItemsWithInjected(group.items, groupInjected, t)
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
const existingIds = new Set(nextGroups.map((group) => group.id || resolveGroupKey(group)));
|
|
117
|
+
for (const [groupId, items] of injectedByGroup.entries()) {
|
|
118
|
+
if (existingIds.has(groupId)) continue;
|
|
119
|
+
const first = items[0];
|
|
120
|
+
const label = first.groupLabelKey ? t(first.groupLabelKey, first.groupLabel ?? groupId) : first.groupLabel ?? groupId;
|
|
121
|
+
const groupItems = mergeSidebarItemsWithInjected([], items, t);
|
|
122
|
+
if (groupItems.length === 0) continue;
|
|
123
|
+
nextGroups.push({
|
|
124
|
+
id: groupId,
|
|
125
|
+
name: label,
|
|
126
|
+
defaultName: label,
|
|
127
|
+
items: groupItems
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return nextGroups;
|
|
131
|
+
}
|
|
132
|
+
function mergeSectionGroupsWithInjected(sections, injectedItems, t) {
|
|
133
|
+
if (injectedItems.length === 0) return sections;
|
|
134
|
+
const byGroup = /* @__PURE__ */ new Map();
|
|
135
|
+
for (const item of injectedItems) {
|
|
136
|
+
const groupId = item.groupId && item.groupId.trim().length > 0 ? item.groupId : "injected";
|
|
137
|
+
const bucket = byGroup.get(groupId) ?? [];
|
|
138
|
+
bucket.push(item);
|
|
139
|
+
byGroup.set(groupId, bucket);
|
|
140
|
+
}
|
|
141
|
+
const nextSections = sections.map((section) => {
|
|
142
|
+
const sectionItems = byGroup.get(section.id) ?? [];
|
|
143
|
+
if (sectionItems.length === 0) return section;
|
|
144
|
+
const mergedItems = mergeMenuItems(
|
|
145
|
+
section.items.map((item) => ({ id: item.id, item })),
|
|
146
|
+
sectionItems
|
|
147
|
+
).flatMap((item) => {
|
|
148
|
+
if (item.source === "built-in") {
|
|
149
|
+
const original = section.items.find((entry) => entry.id === item.id);
|
|
150
|
+
return original ? [original] : [];
|
|
151
|
+
}
|
|
152
|
+
if (!item.href) return [];
|
|
153
|
+
const label = resolveInjectedMenuLabel(item, t);
|
|
154
|
+
return [{
|
|
155
|
+
id: item.id,
|
|
156
|
+
label,
|
|
157
|
+
href: item.href
|
|
158
|
+
}];
|
|
159
|
+
});
|
|
160
|
+
return {
|
|
161
|
+
...section,
|
|
162
|
+
items: mergedItems
|
|
163
|
+
};
|
|
164
|
+
});
|
|
165
|
+
for (const [sectionId, sectionItems] of byGroup.entries()) {
|
|
166
|
+
const exists = nextSections.some((section) => section.id === sectionId);
|
|
167
|
+
if (exists) continue;
|
|
168
|
+
const first = sectionItems[0];
|
|
169
|
+
const label = first.groupLabelKey ? t(first.groupLabelKey, first.groupLabel ?? sectionId) : first.groupLabel ?? sectionId;
|
|
170
|
+
const items = sectionItems.flatMap((item) => {
|
|
171
|
+
if (!item.href) return [];
|
|
172
|
+
const itemLabel = resolveInjectedMenuLabel(item, t);
|
|
173
|
+
return [{ id: item.id, label: itemLabel, href: item.href }];
|
|
174
|
+
});
|
|
175
|
+
if (items.length === 0) continue;
|
|
176
|
+
nextSections.push({ id: sectionId, label, items });
|
|
177
|
+
}
|
|
178
|
+
return nextSections;
|
|
179
|
+
}
|
|
27
180
|
function resolveGroupKey(group) {
|
|
28
181
|
if (group.id && group.id.length) return group.id;
|
|
29
182
|
if (group.defaultName && group.defaultName.length) return slugifySidebarId(group.defaultName);
|
|
30
183
|
return slugifySidebarId(group.name);
|
|
31
184
|
}
|
|
185
|
+
function resolveItemKey(item) {
|
|
186
|
+
const candidate = item.id?.trim();
|
|
187
|
+
if (candidate && candidate.length > 0) return candidate;
|
|
188
|
+
return item.href;
|
|
189
|
+
}
|
|
32
190
|
const HeaderContext = React.createContext(null);
|
|
33
191
|
function ApplyBreadcrumb({ breadcrumb, title, titleKey }) {
|
|
34
192
|
const ctx = React.useContext(HeaderContext);
|
|
@@ -79,6 +237,11 @@ function AppShell({ productName, email, groups, rightHeaderSlot, children, sideb
|
|
|
79
237
|
const searchParams = useSearchParams();
|
|
80
238
|
const t = useT();
|
|
81
239
|
const locale = useLocale();
|
|
240
|
+
const { items: mainSidebarInjectedMenuItems } = useInjectedMenuItems("menu:sidebar:main");
|
|
241
|
+
const { items: settingsSidebarInjectedMenuItems } = useInjectedMenuItems("menu:sidebar:settings");
|
|
242
|
+
const { items: profileSidebarInjectedMenuItems } = useInjectedMenuItems("menu:sidebar:profile");
|
|
243
|
+
const { items: topbarInjectedMenuItems } = useInjectedMenuItems("menu:topbar:actions");
|
|
244
|
+
useEventBridge();
|
|
82
245
|
const resolvedProductName = productName ?? t("appShell.productName");
|
|
83
246
|
const [mobileOpen, setMobileOpen] = React.useState(false);
|
|
84
247
|
const [collapsed, setCollapsed] = React.useState(sidebarCollapsedDefault);
|
|
@@ -117,6 +280,10 @@ function AppShell({ productName, email, groups, rightHeaderSlot, children, sideb
|
|
|
117
280
|
return profilePathPrefixes.some((prefix) => pathname.startsWith(prefix));
|
|
118
281
|
}, [pathname, profilePathPrefixes]);
|
|
119
282
|
const sidebarMode = isOnSettingsPath ? "settings" : isOnProfilePath ? "profile" : "main";
|
|
283
|
+
const mainNavGroupsWithInjected = React.useMemo(
|
|
284
|
+
() => mergeSidebarGroupsWithInjected(navGroups, mainSidebarInjectedMenuItems, t),
|
|
285
|
+
[mainSidebarInjectedMenuItems, navGroups, t]
|
|
286
|
+
);
|
|
120
287
|
React.useEffect(() => {
|
|
121
288
|
if (!mobileOpen || typeof document === "undefined") return;
|
|
122
289
|
const prev = document.body.style.overflow;
|
|
@@ -181,7 +348,7 @@ function AppShell({ productName, email, groups, rightHeaderSlot, children, sideb
|
|
|
181
348
|
responseItemLabels[trimmedKey] = value;
|
|
182
349
|
}
|
|
183
350
|
}
|
|
184
|
-
const responseHiddenItems = Array.isArray(rawSettings?.hiddenItems) ? rawSettings.hiddenItems.map((
|
|
351
|
+
const responseHiddenItems = Array.isArray(rawSettings?.hiddenItems) ? rawSettings.hiddenItems.map((itemId) => typeof itemId === "string" ? itemId.trim() : "").filter((itemId) => itemId.length > 0) : [];
|
|
185
352
|
const canManageRoles = data?.canApplyToRoles === true;
|
|
186
353
|
setCanApplyToRoles(canManageRoles);
|
|
187
354
|
if (canManageRoles) {
|
|
@@ -201,9 +368,9 @@ function AppShell({ productName, email, groups, rightHeaderSlot, children, sideb
|
|
|
201
368
|
const order = mergeGroupOrder(responseOrder, currentIds);
|
|
202
369
|
const { itemDefaults } = collectSidebarDefaults(baseSnapshot);
|
|
203
370
|
const hiddenItemIds = {};
|
|
204
|
-
for (const
|
|
205
|
-
if (!itemDefaults.has(
|
|
206
|
-
hiddenItemIds[
|
|
371
|
+
for (const itemId of responseHiddenItems) {
|
|
372
|
+
if (!itemDefaults.has(itemId)) continue;
|
|
373
|
+
hiddenItemIds[itemId] = true;
|
|
207
374
|
}
|
|
208
375
|
const draft = {
|
|
209
376
|
order,
|
|
@@ -261,17 +428,17 @@ function AppShell({ productName, email, groups, rightHeaderSlot, children, sideb
|
|
|
261
428
|
if (trimmed !== base) sanitizedGroupLabels[key] = trimmed;
|
|
262
429
|
}
|
|
263
430
|
const sanitizedItemLabels = {};
|
|
264
|
-
for (const [
|
|
431
|
+
for (const [itemId, value] of Object.entries(customDraft.itemLabels)) {
|
|
265
432
|
const trimmed = value.trim();
|
|
266
|
-
const base = itemDefaults.get(
|
|
433
|
+
const base = itemDefaults.get(itemId);
|
|
267
434
|
if (!trimmed || !base) continue;
|
|
268
|
-
if (trimmed !== base) sanitizedItemLabels[
|
|
435
|
+
if (trimmed !== base) sanitizedItemLabels[itemId] = trimmed;
|
|
269
436
|
}
|
|
270
437
|
const sanitizedHiddenItems = [];
|
|
271
|
-
for (const [
|
|
438
|
+
for (const [itemId, hidden] of Object.entries(customDraft.hiddenItemIds)) {
|
|
272
439
|
if (!hidden) continue;
|
|
273
|
-
if (!itemDefaults.has(
|
|
274
|
-
sanitizedHiddenItems.push(
|
|
440
|
+
if (!itemDefaults.has(itemId)) continue;
|
|
441
|
+
sanitizedHiddenItems.push(itemId);
|
|
275
442
|
}
|
|
276
443
|
const applyToRolesPayload = canApplyToRoles ? [...selectedRoleIds] : [];
|
|
277
444
|
const clearRoleIdsPayload = canApplyToRoles ? availableRoleTargets.filter((role) => role.hasPreference && !selectedRoleIds.includes(role.id)).map((role) => role.id) : [];
|
|
@@ -342,19 +509,19 @@ function AppShell({ productName, email, groups, rightHeaderSlot, children, sideb
|
|
|
342
509
|
return { ...draft, groupLabels: next };
|
|
343
510
|
});
|
|
344
511
|
}, [updateDraft]);
|
|
345
|
-
const setItemLabel = React.useCallback((
|
|
512
|
+
const setItemLabel = React.useCallback((itemId, value) => {
|
|
346
513
|
updateDraft((draft) => {
|
|
347
514
|
const next = { ...draft.itemLabels };
|
|
348
|
-
if (value.trim().length === 0) delete next[
|
|
349
|
-
else next[
|
|
515
|
+
if (value.trim().length === 0) delete next[itemId];
|
|
516
|
+
else next[itemId] = value;
|
|
350
517
|
return { ...draft, itemLabels: next };
|
|
351
518
|
});
|
|
352
519
|
}, [updateDraft]);
|
|
353
|
-
const setItemHidden = React.useCallback((
|
|
520
|
+
const setItemHidden = React.useCallback((itemId, hidden) => {
|
|
354
521
|
updateDraft((draft) => {
|
|
355
522
|
const next = { ...draft.hiddenItemIds };
|
|
356
|
-
if (hidden) next[
|
|
357
|
-
else delete next[
|
|
523
|
+
if (hidden) next[itemId] = true;
|
|
524
|
+
else delete next[itemId];
|
|
358
525
|
return { ...draft, hiddenItemIds: next };
|
|
359
526
|
});
|
|
360
527
|
}, [updateDraft]);
|
|
@@ -565,6 +732,7 @@ function AppShell({ productName, email, groups, rightHeaderSlot, children, sideb
|
|
|
565
732
|
className: `relative text-sm rounded inline-flex items-center ${base} ${isActive ? "bg-background border shadow-sm" : "hover:bg-accent hover:text-accent-foreground"}`,
|
|
566
733
|
style: spacingStyle,
|
|
567
734
|
title: compact ? label : void 0,
|
|
735
|
+
"data-menu-item-id": item.id,
|
|
568
736
|
onClick: () => setMobileOpen(false),
|
|
569
737
|
children: [
|
|
570
738
|
isActive && /* @__PURE__ */ jsx("span", { className: "absolute left-0 top-1 bottom-1 w-0.5 rounded bg-foreground" }),
|
|
@@ -599,16 +767,26 @@ function AppShell({ productName, email, groups, rightHeaderSlot, children, sideb
|
|
|
599
767
|
}
|
|
600
768
|
function renderSidebar(compact, hideHeader) {
|
|
601
769
|
if (sidebarMode === "settings" && settingsSections && settingsSections.length > 0) {
|
|
602
|
-
|
|
770
|
+
const mergedSettingsSections = mergeSectionGroupsWithInjected(
|
|
603
771
|
settingsSections,
|
|
772
|
+
settingsSidebarInjectedMenuItems,
|
|
773
|
+
t
|
|
774
|
+
);
|
|
775
|
+
return renderSectionSidebar(
|
|
776
|
+
mergedSettingsSections,
|
|
604
777
|
settingsSectionTitle ?? t("backend.nav.settings", "Settings"),
|
|
605
778
|
compact,
|
|
606
779
|
hideHeader
|
|
607
780
|
);
|
|
608
781
|
}
|
|
609
782
|
if (sidebarMode === "profile" && profileSections && profileSections.length > 0) {
|
|
610
|
-
|
|
783
|
+
const mergedProfileSections = mergeSectionGroupsWithInjected(
|
|
611
784
|
profileSections,
|
|
785
|
+
profileSidebarInjectedMenuItems,
|
|
786
|
+
t
|
|
787
|
+
);
|
|
788
|
+
return renderSectionSidebar(
|
|
789
|
+
mergedProfileSections,
|
|
612
790
|
profileSectionTitle ?? t("backend.nav.profile", "Profile"),
|
|
613
791
|
compact,
|
|
614
792
|
hideHeader
|
|
@@ -616,13 +794,13 @@ function AppShell({ productName, email, groups, rightHeaderSlot, children, sideb
|
|
|
616
794
|
}
|
|
617
795
|
const isMobileVariant = !!hideHeader;
|
|
618
796
|
const shouldRenderSidebarInjectionSpots = !isMobileVariant;
|
|
619
|
-
const baseGroupsForDefaults = originalNavRef.current ??
|
|
797
|
+
const baseGroupsForDefaults = originalNavRef.current ?? mainNavGroupsWithInjected;
|
|
620
798
|
const baseGroupMap = /* @__PURE__ */ new Map();
|
|
621
799
|
for (const group of baseGroupsForDefaults) {
|
|
622
800
|
baseGroupMap.set(resolveGroupKey(group), group);
|
|
623
801
|
}
|
|
624
802
|
const localeLabel = (locale || "").toUpperCase();
|
|
625
|
-
const orderedGroupIds = customDraft ? mergeGroupOrder(customDraft.order, Array.from(baseGroupMap.keys())) :
|
|
803
|
+
const orderedGroupIds = customDraft ? mergeGroupOrder(customDraft.order, Array.from(baseGroupMap.keys())) : mainNavGroupsWithInjected.map((group) => resolveGroupKey(group));
|
|
626
804
|
const lastVisibleGroupIndex = (() => {
|
|
627
805
|
for (let idx = navGroups.length - 1; idx >= 0; idx -= 1) {
|
|
628
806
|
if (navGroups[idx].items.some((item) => item.hidden !== true)) return idx;
|
|
@@ -632,10 +810,11 @@ function AppShell({ productName, email, groups, rightHeaderSlot, children, sideb
|
|
|
632
810
|
const renderEditableItems = (baseItems, currentItems, depth = 0) => {
|
|
633
811
|
if (!customDraft) return null;
|
|
634
812
|
return baseItems.map((baseItem) => {
|
|
813
|
+
const itemKey = resolveItemKey(baseItem);
|
|
635
814
|
const current = currentItems.find((item) => item.href === baseItem.href) ?? baseItem;
|
|
636
815
|
const placeholder = baseItem.defaultTitle ?? baseItem.title;
|
|
637
|
-
const value = customDraft.itemLabels[
|
|
638
|
-
const hidden = customDraft.hiddenItemIds[
|
|
816
|
+
const value = customDraft.itemLabels[itemKey] ?? "";
|
|
817
|
+
const hidden = customDraft.hiddenItemIds[itemKey] === true;
|
|
639
818
|
return /* @__PURE__ */ jsxs(
|
|
640
819
|
"div",
|
|
641
820
|
{
|
|
@@ -650,7 +829,7 @@ function AppShell({ productName, email, groups, rightHeaderSlot, children, sideb
|
|
|
650
829
|
type: "checkbox",
|
|
651
830
|
className: "h-4 w-4 accent-foreground",
|
|
652
831
|
checked: !hidden,
|
|
653
|
-
onChange: (event) => setItemHidden(
|
|
832
|
+
onChange: (event) => setItemHidden(itemKey, !event.target.checked),
|
|
654
833
|
disabled: savingPreferences,
|
|
655
834
|
"aria-label": t("appShell.sidebarCustomizationShowItem"),
|
|
656
835
|
title: t("appShell.sidebarCustomizationShowItem")
|
|
@@ -660,7 +839,7 @@ function AppShell({ productName, email, groups, rightHeaderSlot, children, sideb
|
|
|
660
839
|
"input",
|
|
661
840
|
{
|
|
662
841
|
value,
|
|
663
|
-
onChange: (event) => setItemLabel(
|
|
842
|
+
onChange: (event) => setItemLabel(itemKey, event.target.value),
|
|
664
843
|
placeholder,
|
|
665
844
|
disabled: savingPreferences,
|
|
666
845
|
className: "h-8 flex-1 rounded border bg-background px-2 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-60"
|
|
@@ -670,7 +849,7 @@ function AppShell({ productName, email, groups, rightHeaderSlot, children, sideb
|
|
|
670
849
|
baseItem.children && baseItem.children.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1", children: renderEditableItems(baseItem.children, current.children ?? [], depth + 1) }) : null
|
|
671
850
|
]
|
|
672
851
|
},
|
|
673
|
-
|
|
852
|
+
itemKey
|
|
674
853
|
);
|
|
675
854
|
});
|
|
676
855
|
};
|
|
@@ -810,7 +989,7 @@ function AppShell({ productName, email, groups, rightHeaderSlot, children, sideb
|
|
|
810
989
|
if (isSettingsPath(item.href)) return false;
|
|
811
990
|
return true;
|
|
812
991
|
};
|
|
813
|
-
const mainGroups =
|
|
992
|
+
const mainGroups = mainNavGroupsWithInjected.map((g) => ({
|
|
814
993
|
...g,
|
|
815
994
|
items: g.items.filter((item) => isMainItem(item) && item.hidden !== true)
|
|
816
995
|
})).filter((g) => g.items.length > 0);
|
|
@@ -821,117 +1000,146 @@ function AppShell({ productName, email, groups, rightHeaderSlot, children, sideb
|
|
|
821
1000
|
return -1;
|
|
822
1001
|
})();
|
|
823
1002
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
824
|
-
/* @__PURE__ */
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
isParentActive ? /* @__PURE__ */ jsx("span", { className: "absolute left-0 top-1 bottom-1 w-0.5 rounded bg-foreground" }) : null,
|
|
860
|
-
/* @__PURE__ */ jsx("span", { className: `flex items-center justify-center shrink-0 ${compact ? "" : "text-muted-foreground"}`, children: i.icon ?? DefaultIcon }),
|
|
861
|
-
!compact && /* @__PURE__ */ jsx("span", { children: i.title })
|
|
862
|
-
]
|
|
863
|
-
}
|
|
864
|
-
),
|
|
865
|
-
showChildren ? /* @__PURE__ */ jsx("div", { className: `flex flex-col ${compact ? "items-center" : ""} gap-1 ${!compact ? "pl-4" : ""}`, children: childItems.map((c) => {
|
|
866
|
-
const childActive = pathname?.startsWith(c.href);
|
|
867
|
-
const childBase = compact ? "w-10 h-8 justify-center" : "px-2 py-1 gap-2";
|
|
868
|
-
return /* @__PURE__ */ jsxs(
|
|
1003
|
+
/* @__PURE__ */ jsxs("nav", { className: "flex flex-col gap-2", "data-testid": "sidebar", children: [
|
|
1004
|
+
shouldRenderSidebarInjectionSpots ? /* @__PURE__ */ jsx(
|
|
1005
|
+
InjectionSpot,
|
|
1006
|
+
{
|
|
1007
|
+
spotId: BACKEND_SIDEBAR_NAV_INJECTION_SPOT_ID,
|
|
1008
|
+
context: injectionContext
|
|
1009
|
+
}
|
|
1010
|
+
) : null,
|
|
1011
|
+
mainGroups.map((g, gi) => {
|
|
1012
|
+
const groupId = resolveGroupKey(g);
|
|
1013
|
+
const open = openGroups[groupId] !== false;
|
|
1014
|
+
const visibleItems = g.items.filter((item) => item.hidden !== true);
|
|
1015
|
+
if (visibleItems.length === 0) return null;
|
|
1016
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
1017
|
+
/* @__PURE__ */ jsxs(
|
|
1018
|
+
Button,
|
|
1019
|
+
{
|
|
1020
|
+
variant: "muted",
|
|
1021
|
+
onClick: () => toggleGroup(groupId),
|
|
1022
|
+
className: `w-full ${compact ? "px-0 justify-center" : "px-2 justify-between"} flex text-xs uppercase text-muted-foreground/90 py-2`,
|
|
1023
|
+
"aria-expanded": open,
|
|
1024
|
+
children: [
|
|
1025
|
+
!compact && /* @__PURE__ */ jsx("span", { children: g.name }),
|
|
1026
|
+
!compact && /* @__PURE__ */ jsx(Chevron, { open })
|
|
1027
|
+
]
|
|
1028
|
+
}
|
|
1029
|
+
),
|
|
1030
|
+
open && /* @__PURE__ */ jsx("div", { className: `flex flex-col ${compact ? "items-center" : ""} gap-1 ${!compact ? "pl-1" : ""}`, children: visibleItems.map((i) => {
|
|
1031
|
+
const childItems = (i.children ?? []).filter((child) => child.hidden !== true);
|
|
1032
|
+
const showChildren = !!pathname && childItems.length > 0 && pathname.startsWith(i.href);
|
|
1033
|
+
const hasActiveChild = !!(pathname && childItems.some((c) => pathname.startsWith(c.href)));
|
|
1034
|
+
const isParentActive = pathname === i.href || showChildren && !hasActiveChild;
|
|
1035
|
+
const base = compact ? "w-10 h-10 justify-center" : "px-2 py-1 gap-2";
|
|
1036
|
+
return /* @__PURE__ */ jsxs(React.Fragment, { children: [
|
|
1037
|
+
/* @__PURE__ */ jsxs(
|
|
869
1038
|
Link,
|
|
870
1039
|
{
|
|
871
|
-
href:
|
|
872
|
-
className: `relative text-sm rounded inline-flex items-center ${
|
|
873
|
-
"aria-disabled":
|
|
874
|
-
title: compact ?
|
|
1040
|
+
href: i.href,
|
|
1041
|
+
className: `relative text-sm rounded inline-flex items-center ${base} ${isParentActive ? "bg-background border shadow-sm" : "hover:bg-accent hover:text-accent-foreground"} ${i.enabled === false ? "pointer-events-none opacity-50" : ""}`,
|
|
1042
|
+
"aria-disabled": i.enabled === false,
|
|
1043
|
+
title: compact ? i.title : void 0,
|
|
1044
|
+
"data-menu-item-id": i.id ?? i.href,
|
|
875
1045
|
onClick: () => setMobileOpen(false),
|
|
876
1046
|
children: [
|
|
877
|
-
|
|
878
|
-
/* @__PURE__ */ jsx("span", { className: `flex items-center justify-center shrink-0 ${compact ? "" : "text-muted-foreground"}`, children:
|
|
879
|
-
!compact && /* @__PURE__ */ jsx("span", { children:
|
|
1047
|
+
isParentActive ? /* @__PURE__ */ jsx("span", { className: "absolute left-0 top-1 bottom-1 w-0.5 rounded bg-foreground" }) : null,
|
|
1048
|
+
/* @__PURE__ */ jsx("span", { className: `flex items-center justify-center shrink-0 ${compact ? "" : "text-muted-foreground"}`, children: i.icon ?? DefaultIcon }),
|
|
1049
|
+
!compact && /* @__PURE__ */ jsx("span", { children: i.title })
|
|
880
1050
|
]
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
)
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
1051
|
+
}
|
|
1052
|
+
),
|
|
1053
|
+
showChildren ? /* @__PURE__ */ jsx("div", { className: `flex flex-col ${compact ? "items-center" : ""} gap-1 ${!compact ? "pl-4" : ""}`, children: childItems.map((c) => {
|
|
1054
|
+
const childActive = pathname?.startsWith(c.href);
|
|
1055
|
+
const childBase = compact ? "w-10 h-8 justify-center" : "px-2 py-1 gap-2";
|
|
1056
|
+
return /* @__PURE__ */ jsxs(
|
|
1057
|
+
Link,
|
|
1058
|
+
{
|
|
1059
|
+
href: c.href,
|
|
1060
|
+
className: `relative text-sm rounded inline-flex items-center ${childBase} ${childActive ? "bg-background border shadow-sm" : "hover:bg-accent hover:text-accent-foreground"} ${c.enabled === false ? "pointer-events-none opacity-50" : ""}`,
|
|
1061
|
+
"aria-disabled": c.enabled === false,
|
|
1062
|
+
title: compact ? c.title : void 0,
|
|
1063
|
+
"data-menu-item-id": c.id ?? c.href,
|
|
1064
|
+
onClick: () => setMobileOpen(false),
|
|
1065
|
+
children: [
|
|
1066
|
+
childActive ? /* @__PURE__ */ jsx("span", { className: "absolute left-0 top-1 bottom-1 w-0.5 rounded bg-foreground" }) : null,
|
|
1067
|
+
/* @__PURE__ */ jsx("span", { className: `flex items-center justify-center shrink-0 ${compact ? "" : "text-muted-foreground"}`, children: c.icon ?? (c.href.includes("/backend/entities/user/") && c.href.endsWith("/records") ? DataTableIcon : DefaultIcon) }),
|
|
1068
|
+
!compact && /* @__PURE__ */ jsx("span", { children: c.title })
|
|
1069
|
+
]
|
|
1070
|
+
},
|
|
1071
|
+
c.href
|
|
1072
|
+
);
|
|
1073
|
+
}) }) : null
|
|
1074
|
+
] }, i.href);
|
|
1075
|
+
}) }),
|
|
1076
|
+
gi !== mainLastVisibleGroupIndex && /* @__PURE__ */ jsx("div", { className: "my-2 border-t border-dotted" })
|
|
1077
|
+
] }, groupId);
|
|
1078
|
+
})
|
|
1079
|
+
] }),
|
|
1080
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-4 pt-4 border-t", children: [
|
|
1081
|
+
shouldRenderSidebarInjectionSpots ? /* @__PURE__ */ jsx(
|
|
1082
|
+
InjectionSpot,
|
|
1083
|
+
{
|
|
1084
|
+
spotId: BACKEND_SIDEBAR_NAV_FOOTER_INJECTION_SPOT_ID,
|
|
1085
|
+
context: injectionContext
|
|
1086
|
+
}
|
|
1087
|
+
) : null,
|
|
1088
|
+
/* @__PURE__ */ jsxs(
|
|
1089
|
+
Link,
|
|
1090
|
+
{
|
|
1091
|
+
href: "/backend/settings",
|
|
1092
|
+
className: `relative text-sm rounded inline-flex items-center w-full ${compact ? "w-10 h-10 justify-center" : "px-2 py-1 gap-2"} ${pathname?.startsWith("/backend/settings") || pathname?.startsWith("/backend/config") || pathname?.startsWith("/backend/users") || pathname?.startsWith("/backend/roles") || pathname?.startsWith("/backend/api-keys") || pathname?.startsWith("/backend/entities") || pathname?.startsWith("/backend/query-indexes") || pathname?.startsWith("/backend/definitions") || pathname?.startsWith("/backend/instances") || pathname?.startsWith("/backend/tasks") || pathname?.startsWith("/backend/events") || pathname?.startsWith("/backend/rules") || pathname?.startsWith("/backend/sets") || pathname?.startsWith("/backend/logs") || pathname?.startsWith("/backend/directory") || pathname?.startsWith("/backend/feature-toggles") ? "bg-background border shadow-sm font-medium" : "hover:bg-accent hover:text-accent-foreground"}`,
|
|
1093
|
+
title: compact ? t("backend.nav.settings", "Settings") : void 0,
|
|
1094
|
+
onClick: () => setMobileOpen(false),
|
|
1095
|
+
children: [
|
|
1096
|
+
(pathname?.startsWith("/backend/settings") || pathname?.startsWith("/backend/config") || pathname?.startsWith("/backend/users") || pathname?.startsWith("/backend/roles") || pathname?.startsWith("/backend/api-keys") || pathname?.startsWith("/backend/entities") || pathname?.startsWith("/backend/query-indexes") || pathname?.startsWith("/backend/definitions") || pathname?.startsWith("/backend/instances") || pathname?.startsWith("/backend/tasks") || pathname?.startsWith("/backend/events") || pathname?.startsWith("/backend/rules") || pathname?.startsWith("/backend/sets") || pathname?.startsWith("/backend/logs") || pathname?.startsWith("/backend/directory") || pathname?.startsWith("/backend/feature-toggles")) && /* @__PURE__ */ jsx("span", { className: "absolute left-0 top-1 bottom-1 w-0.5 rounded bg-foreground" }),
|
|
1097
|
+
/* @__PURE__ */ jsx("span", { className: `flex items-center justify-center shrink-0 ${compact ? "" : "text-muted-foreground"}`, children: /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
1098
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" }),
|
|
1099
|
+
/* @__PURE__ */ jsx("path", { d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" })
|
|
1100
|
+
] }) }),
|
|
1101
|
+
!compact && /* @__PURE__ */ jsx("span", { children: t("backend.nav.settings", "Settings") })
|
|
1102
|
+
]
|
|
1103
|
+
}
|
|
1104
|
+
)
|
|
1105
|
+
] })
|
|
907
1106
|
] });
|
|
908
1107
|
})() }),
|
|
909
|
-
!customizing && /* @__PURE__ */
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
1108
|
+
!customizing && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1109
|
+
shouldRenderSidebarInjectionSpots ? /* @__PURE__ */ jsx(
|
|
1110
|
+
InjectionSpot,
|
|
1111
|
+
{
|
|
1112
|
+
spotId: GLOBAL_SIDEBAR_STATUS_BADGES_INJECTION_SPOT_ID,
|
|
1113
|
+
context: injectionContext
|
|
1114
|
+
}
|
|
1115
|
+
) : null,
|
|
1116
|
+
compact || isMobileVariant ? /* @__PURE__ */ jsx(
|
|
1117
|
+
IconButton,
|
|
1118
|
+
{
|
|
1119
|
+
variant: "outline",
|
|
1120
|
+
size: "lg",
|
|
1121
|
+
className: "mt-auto",
|
|
1122
|
+
onClick: startCustomization,
|
|
1123
|
+
disabled: loadingPreferences,
|
|
1124
|
+
"aria-label": t("appShell.customizeSidebar"),
|
|
1125
|
+
children: CustomizeIcon
|
|
1126
|
+
}
|
|
1127
|
+
) : /* @__PURE__ */ jsxs(
|
|
1128
|
+
Button,
|
|
1129
|
+
{
|
|
1130
|
+
variant: "outline",
|
|
1131
|
+
size: "default",
|
|
1132
|
+
className: "mt-auto",
|
|
1133
|
+
onClick: startCustomization,
|
|
1134
|
+
disabled: loadingPreferences,
|
|
1135
|
+
"aria-label": t("appShell.customizeSidebar"),
|
|
1136
|
+
children: [
|
|
1137
|
+
CustomizeIcon,
|
|
1138
|
+
loadingPreferences ? t("appShell.sidebarCustomizationLoading") : t("appShell.customizeSidebar")
|
|
1139
|
+
]
|
|
1140
|
+
}
|
|
1141
|
+
)
|
|
1142
|
+
] }),
|
|
935
1143
|
shouldRenderSidebarInjectionSpots ? /* @__PURE__ */ jsx(
|
|
936
1144
|
InjectionSpot,
|
|
937
1145
|
{
|
|
@@ -946,6 +1154,37 @@ function AppShell({ productName, email, groups, rightHeaderSlot, children, sideb
|
|
|
946
1154
|
setBreadcrumb: setHeaderBreadcrumb,
|
|
947
1155
|
setTitle: setHeaderTitle
|
|
948
1156
|
}), []);
|
|
1157
|
+
const renderedTopbarInjectedActions = React.useMemo(
|
|
1158
|
+
() => topbarInjectedMenuItems.map((item) => {
|
|
1159
|
+
const label = resolveInjectedMenuLabel(item, t);
|
|
1160
|
+
if (item.href) {
|
|
1161
|
+
return /* @__PURE__ */ jsx(
|
|
1162
|
+
Link,
|
|
1163
|
+
{
|
|
1164
|
+
href: item.href,
|
|
1165
|
+
className: "inline-flex items-center rounded border px-2 py-1 text-xs hover:bg-accent hover:text-accent-foreground",
|
|
1166
|
+
"data-menu-item-id": item.id,
|
|
1167
|
+
children: label
|
|
1168
|
+
},
|
|
1169
|
+
item.id
|
|
1170
|
+
);
|
|
1171
|
+
}
|
|
1172
|
+
return /* @__PURE__ */ jsx(
|
|
1173
|
+
Button,
|
|
1174
|
+
{
|
|
1175
|
+
type: "button",
|
|
1176
|
+
variant: "outline",
|
|
1177
|
+
size: "sm",
|
|
1178
|
+
className: "h-7 text-xs",
|
|
1179
|
+
"data-menu-item-id": item.id,
|
|
1180
|
+
onClick: () => item.onClick?.(),
|
|
1181
|
+
children: label
|
|
1182
|
+
},
|
|
1183
|
+
item.id
|
|
1184
|
+
);
|
|
1185
|
+
}),
|
|
1186
|
+
[t, topbarInjectedMenuItems]
|
|
1187
|
+
);
|
|
949
1188
|
return /* @__PURE__ */ jsx(HeaderContext.Provider, { value: headerCtxValue, children: /* @__PURE__ */ jsxs("div", { className: `min-h-svh lg:grid ${gridColsClass}`, children: [
|
|
950
1189
|
/* @__PURE__ */ jsx("aside", { className: `${asideClassesBase} ${effectiveCollapsed ? "px-2" : "px-3"} hidden lg:block`, style: { width: asideWidth }, children: renderSidebar(effectiveCollapsed) }),
|
|
951
1190
|
/* @__PURE__ */ jsxs("div", { className: "flex min-h-svh flex-col min-w-0", children: [
|
|
@@ -990,12 +1229,30 @@ function AppShell({ productName, email, groups, rightHeaderSlot, children, sideb
|
|
|
990
1229
|
}) });
|
|
991
1230
|
})()
|
|
992
1231
|
] }),
|
|
993
|
-
/* @__PURE__ */
|
|
1232
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 md:gap-2 text-sm shrink-0", children: [
|
|
1233
|
+
/* @__PURE__ */ jsx(
|
|
1234
|
+
InjectionSpot,
|
|
1235
|
+
{
|
|
1236
|
+
spotId: GLOBAL_HEADER_STATUS_INDICATORS_INJECTION_SPOT_ID,
|
|
1237
|
+
context: injectionContext
|
|
1238
|
+
}
|
|
1239
|
+
),
|
|
1240
|
+
/* @__PURE__ */ jsx(
|
|
1241
|
+
InjectionSpot,
|
|
1242
|
+
{
|
|
1243
|
+
spotId: BACKEND_TOPBAR_ACTIONS_INJECTION_SPOT_ID,
|
|
1244
|
+
context: injectionContext
|
|
1245
|
+
}
|
|
1246
|
+
),
|
|
1247
|
+
renderedTopbarInjectedActions,
|
|
1248
|
+
rightHeaderSlot ? rightHeaderSlot : /* @__PURE__ */ jsx("span", { className: "opacity-80", children: email || t("appShell.userFallback") })
|
|
1249
|
+
] })
|
|
994
1250
|
] }),
|
|
995
1251
|
/* @__PURE__ */ jsx(ProgressTopBar, { t, className: "sticky top-0 z-10" }),
|
|
996
1252
|
/* @__PURE__ */ jsxs("main", { className: "flex-1 p-4 lg:p-6", children: [
|
|
997
1253
|
/* @__PURE__ */ jsx(InjectionSpot, { spotId: BACKEND_LAYOUT_TOP_INJECTION_SPOT_ID, context: injectionContext }),
|
|
998
1254
|
/* @__PURE__ */ jsx(FlashMessages, {}),
|
|
1255
|
+
/* @__PURE__ */ jsx(SseEventIndicator, {}),
|
|
999
1256
|
/* @__PURE__ */ jsx(PartialIndexBanner, {}),
|
|
1000
1257
|
/* @__PURE__ */ jsx(UpgradeActionBanner, {}),
|
|
1001
1258
|
/* @__PURE__ */ jsx(LastOperationBanner, {}),
|
|
@@ -1037,6 +1294,7 @@ function AppShell({ productName, email, groups, rightHeaderSlot, children, sideb
|
|
|
1037
1294
|
}
|
|
1038
1295
|
AppShell.cloneGroups = function cloneGroups(groups) {
|
|
1039
1296
|
const cloneItem = (item) => ({
|
|
1297
|
+
id: item.id,
|
|
1040
1298
|
href: item.href,
|
|
1041
1299
|
title: item.title,
|
|
1042
1300
|
defaultTitle: item.defaultTitle,
|
|
@@ -1078,10 +1336,11 @@ function applyCustomizationDraft(baseGroups, draft) {
|
|
|
1078
1336
|
return result;
|
|
1079
1337
|
}
|
|
1080
1338
|
function applyItemDraft(item, draft) {
|
|
1339
|
+
const itemKey = resolveItemKey(item);
|
|
1081
1340
|
const baseTitle = item.defaultTitle ?? item.title;
|
|
1082
|
-
const override = draft.itemLabels[
|
|
1341
|
+
const override = draft.itemLabels[itemKey]?.trim();
|
|
1083
1342
|
const children = item.children ? item.children.map((child) => applyItemDraft(child, draft)) : void 0;
|
|
1084
|
-
const hidden = draft.hiddenItemIds[
|
|
1343
|
+
const hidden = draft.hiddenItemIds[itemKey] === true;
|
|
1085
1344
|
return {
|
|
1086
1345
|
...item,
|
|
1087
1346
|
title: override && override.length > 0 ? override : baseTitle,
|
|
@@ -1110,7 +1369,9 @@ function collectSidebarDefaults(groups) {
|
|
|
1110
1369
|
const itemDefaults = /* @__PURE__ */ new Map();
|
|
1111
1370
|
const visitItems = (items) => {
|
|
1112
1371
|
for (const item of items) {
|
|
1372
|
+
const key = resolveItemKey(item);
|
|
1113
1373
|
const baseTitle = item.defaultTitle ?? item.title;
|
|
1374
|
+
itemDefaults.set(key, baseTitle);
|
|
1114
1375
|
itemDefaults.set(item.href, baseTitle);
|
|
1115
1376
|
if (item.children && item.children.length > 0) visitItems(item.children);
|
|
1116
1377
|
}
|