Add product presentation extension contracts
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { Activity, Bell, BookUser, BriefcaseBusiness, Building2, CalendarClock, CalendarDays, ClipboardPenLine, DatabaseZap, Folder, FolderKanban, Form, Gavel, Inbox, Landmark, LayoutDashboard, LayoutTemplate, ListChecks, ListTree, Mail, Mails, RadioTower, Shield, ShieldCheck, Users, Vote, Waypoints, Workflow as WorkflowIcon, type LucideIcon } from "lucide-react";
|
||||
import installedWebModuleLoaderSource from "virtual:govoplan-installed-modules";
|
||||
import type { AuthInfo, DashboardWidgetContribution, DashboardWidgetsUiCapability, EffectiveViewProjection, PlatformModuleInfo, PlatformNavItem, PlatformPublicModuleInfo, PlatformViewSurface, PlatformWebModule } from "../types";
|
||||
import type { AuthInfo, DashboardWidgetContribution, DashboardWidgetsUiCapability, EffectiveViewProjection, PlatformModuleInfo, PlatformNavItem, PlatformPublicModuleInfo, PlatformViewSurface, PlatformWebModule, ProductAreaContribution, QuickAccessToolMetadata } from "../types";
|
||||
import {
|
||||
hasUiCapability as hasUiCapabilityForModules,
|
||||
moduleInstalled as moduleInstalledForModules,
|
||||
@@ -168,6 +168,36 @@ function routesWithServerMetadata(
|
||||
});
|
||||
}
|
||||
|
||||
function productAreasFromMetadata(info: PlatformModuleInfo): ProductAreaContribution[] {
|
||||
return (info.frontend?.product_areas ?? []).map((area) => ({
|
||||
id: area.id,
|
||||
moduleId: area.module_id,
|
||||
label: area.label,
|
||||
description: area.description,
|
||||
iconName: area.icon,
|
||||
surfaceIds: area.surface_ids,
|
||||
order: area.order
|
||||
}));
|
||||
}
|
||||
|
||||
function quickAccessToolsFromMetadata(info: PlatformModuleInfo): QuickAccessToolMetadata[] {
|
||||
return (info.frontend?.quick_access_tools ?? []).map((tool) => ({
|
||||
id: tool.id,
|
||||
moduleId: tool.module_id,
|
||||
categoryId: tool.category_id,
|
||||
label: tool.label,
|
||||
description: tool.description,
|
||||
surfaceId: tool.surface_id,
|
||||
iconName: tool.icon,
|
||||
fullPagePath: tool.full_page_path,
|
||||
allOf: tool.required_all,
|
||||
anyOf: tool.required_any,
|
||||
order: tool.order,
|
||||
defaultEnabled: tool.default_enabled,
|
||||
modes: tool.modes
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
function runtimeUiCapabilitiesForModule(module: PlatformWebModule, info: PlatformModuleInfo) {
|
||||
const enabledNames = new Set(info.runtime_ui_capabilities ?? []);
|
||||
@@ -194,6 +224,8 @@ function applyServerMetadata(module: PlatformWebModule, info: PlatformModuleInfo
|
||||
routes: routesWithServerMetadata(module, info),
|
||||
publicRoutes: filterPublicRoutes(module, info.frontend?.public_routes),
|
||||
viewSurfaces: mergeViewSurfaces(module, info),
|
||||
productAreas: productAreasFromMetadata(info),
|
||||
quickAccessTools: quickAccessToolsFromMetadata(info),
|
||||
helpContexts: info.help_contexts ?? module.helpContexts,
|
||||
uiCapabilities: {
|
||||
...(module.uiCapabilities ?? {}),
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import type {
|
||||
PlatformNavItem,
|
||||
ProductAreaContribution,
|
||||
ViewPresentation
|
||||
} from "../types";
|
||||
|
||||
|
||||
export type NavigationGroup = {
|
||||
id: string;
|
||||
label?: string;
|
||||
areaLabel?: string;
|
||||
items: PlatformNavItem[];
|
||||
};
|
||||
|
||||
export function groupNavigationItems(
|
||||
items: PlatformNavItem[],
|
||||
contributions: ProductAreaContribution[],
|
||||
presentation?: ViewPresentation
|
||||
): NavigationGroup[] {
|
||||
if (presentation?.navigationMode === "flat" || contributions.length === 0) {
|
||||
return [{ id: "all-tools", items }];
|
||||
}
|
||||
|
||||
const areaById = new Map<
|
||||
string,
|
||||
{
|
||||
id: string;
|
||||
label: string;
|
||||
order: number;
|
||||
surfaceIds: Set<string>;
|
||||
}
|
||||
>();
|
||||
for (const contribution of contributions) {
|
||||
const existing = areaById.get(contribution.id);
|
||||
if (existing) {
|
||||
contribution.surfaceIds.forEach((id) => existing.surfaceIds.add(id));
|
||||
existing.order = Math.min(existing.order, contribution.order ?? 100);
|
||||
continue;
|
||||
}
|
||||
areaById.set(contribution.id, {
|
||||
id: contribution.id,
|
||||
label:
|
||||
presentation?.productAreaLabels?.[contribution.id] ??
|
||||
contribution.label,
|
||||
order: contribution.order ?? 100,
|
||||
surfaceIds: new Set(contribution.surfaceIds)
|
||||
});
|
||||
}
|
||||
|
||||
const configuredOrder = new Map(
|
||||
(presentation?.productAreaOrder ?? []).map((id, index) => [id, index])
|
||||
);
|
||||
const areas = [...areaById.values()].sort(
|
||||
(left, right) =>
|
||||
(configuredOrder.get(left.id) ?? 10_000) -
|
||||
(configuredOrder.get(right.id) ?? 10_000) ||
|
||||
left.order - right.order ||
|
||||
left.label.localeCompare(right.label)
|
||||
);
|
||||
const assigned = new Set<string>();
|
||||
const groups: NavigationGroup[] = [];
|
||||
const pinned = items.filter((item) => item.to === "/dashboard");
|
||||
pinned.forEach((item) => assigned.add(item.to));
|
||||
if (pinned.length) groups.push({ id: "overview", items: pinned });
|
||||
|
||||
for (const area of areas) {
|
||||
const areaItems = items.filter(
|
||||
(item) =>
|
||||
!assigned.has(item.to) &&
|
||||
Boolean(item.surfaceId && area.surfaceIds.has(item.surfaceId))
|
||||
);
|
||||
if (!areaItems.length) continue;
|
||||
areaItems.forEach((item) => assigned.add(item.to));
|
||||
groups.push({
|
||||
id: `product-area:${area.id}`,
|
||||
label: area.label,
|
||||
areaLabel: area.label,
|
||||
items: areaItems
|
||||
});
|
||||
}
|
||||
|
||||
const remaining = items.filter((item) => !assigned.has(item.to));
|
||||
if (remaining.length) {
|
||||
groups.push({
|
||||
id: "more-tools",
|
||||
label: "i18n:govoplan-core.more_tools",
|
||||
items: remaining
|
||||
});
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
Reference in New Issue
Block a user