Add governed View surface runtime
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { Activity, Bell, BookUser, Building2, CalendarClock, CalendarDays, ClipboardPenLine, DatabaseZap, Folder, Form, LayoutDashboard, LayoutTemplate, Mail, Mails, RadioTower, Shield, Users, Waypoints, Workflow as WorkflowIcon, type LucideIcon } from "lucide-react";
|
||||
import installedWebModules from "virtual:govoplan-installed-modules";
|
||||
import type { AuthInfo, DashboardWidgetContribution, DashboardWidgetsUiCapability, PlatformModuleInfo, PlatformNavItem, PlatformPublicModuleInfo, PlatformWebModule } from "../types";
|
||||
import type { AuthInfo, DashboardWidgetContribution, DashboardWidgetsUiCapability, EffectiveViewProjection, PlatformModuleInfo, PlatformNavItem, PlatformPublicModuleInfo, PlatformViewSurface, PlatformWebModule } from "../types";
|
||||
import {
|
||||
hasUiCapability as hasUiCapabilityForModules,
|
||||
moduleInstalled as moduleInstalledForModules,
|
||||
@@ -11,6 +11,12 @@ import {
|
||||
uiCapability as uiCapabilityForModules } from
|
||||
"./moduleLogic";
|
||||
import { hasAnyScope, hasScope } from "../utils/permissions";
|
||||
import {
|
||||
isViewSurfaceVisible,
|
||||
navigationViewSurfaceId,
|
||||
routeViewSurfaceId,
|
||||
viewSurfaceCatalogueForModules
|
||||
} from "./views";
|
||||
|
||||
export const fallbackDashboardNavItem: PlatformNavItem =
|
||||
{ to: "/dashboard", label: "i18n:govoplan-core.dashboard.d87f47b4", iconName: "dashboard", order: 10 };
|
||||
@@ -86,10 +92,65 @@ function navFromMetadata(item: PlatformModuleInfo["nav"][number]): PlatformNavIt
|
||||
iconName: item.icon ?? null,
|
||||
allOf: item.required_all,
|
||||
anyOf: item.required_any,
|
||||
order: item.order
|
||||
order: item.order,
|
||||
surfaceId: item.surface_id ?? undefined
|
||||
};
|
||||
}
|
||||
|
||||
function viewSurfaceFromMetadata(
|
||||
item: NonNullable<NonNullable<PlatformModuleInfo["frontend"]>["view_surfaces"]>[number]
|
||||
): PlatformViewSurface {
|
||||
return {
|
||||
id: item.id,
|
||||
moduleId: item.module_id,
|
||||
kind: item.kind,
|
||||
label: item.label,
|
||||
parentId: item.parent_id,
|
||||
description: item.description,
|
||||
order: item.order,
|
||||
defaultVisible: item.default_visible,
|
||||
required: item.required
|
||||
};
|
||||
}
|
||||
|
||||
function mergeViewSurfaces(
|
||||
module: PlatformWebModule,
|
||||
info: PlatformModuleInfo
|
||||
): PlatformViewSurface[] | undefined {
|
||||
const surfaces = new Map<string, PlatformViewSurface>();
|
||||
for (const surface of info.frontend?.view_surfaces ?? []) {
|
||||
const normalized = viewSurfaceFromMetadata(surface);
|
||||
surfaces.set(normalized.id, normalized);
|
||||
}
|
||||
for (const surface of module.viewSurfaces ?? []) {
|
||||
surfaces.set(surface.id, {
|
||||
...surfaces.get(surface.id),
|
||||
...surface,
|
||||
required: Boolean(surfaces.get(surface.id)?.required || surface.required)
|
||||
});
|
||||
}
|
||||
return surfaces.size ? [...surfaces.values()] : undefined;
|
||||
}
|
||||
|
||||
function routesWithServerMetadata(
|
||||
module: PlatformWebModule,
|
||||
info: PlatformModuleInfo
|
||||
) {
|
||||
const routesByPath = new Map(
|
||||
(info.frontend?.routes ?? []).map((route) => [route.path, route])
|
||||
);
|
||||
return (module.routes ?? []).map((route) => {
|
||||
const metadata = routesByPath.get(route.path);
|
||||
return {
|
||||
...route,
|
||||
surfaceId:
|
||||
metadata?.surface_id ??
|
||||
route.surfaceId ??
|
||||
routeViewSurfaceId(module.id, route.path)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function runtimeUiCapabilitiesForModule(module: PlatformWebModule, info: PlatformModuleInfo) {
|
||||
const enabledNames = new Set(info.runtime_ui_capabilities ?? []);
|
||||
@@ -113,7 +174,9 @@ function applyServerMetadata(module: PlatformWebModule, info: PlatformModuleInfo
|
||||
remoteAssetIntegrity: info.frontend?.asset_manifest_integrity ?? module.remoteAssetIntegrity,
|
||||
remoteAssetContractVersion: info.frontend?.asset_manifest_contract_version ?? module.remoteAssetContractVersion,
|
||||
navItems: backendNav.length ? backendNav.map(navFromMetadata) : module.navItems,
|
||||
routes: routesWithServerMetadata(module, info),
|
||||
publicRoutes: filterPublicRoutes(module, info.frontend?.public_routes),
|
||||
viewSurfaces: mergeViewSurfaces(module, info),
|
||||
uiCapabilities: {
|
||||
...(module.uiCapabilities ?? {}),
|
||||
...runtimeUiCapabilitiesForModule(module, info)
|
||||
@@ -353,26 +416,42 @@ export function publicRouteContributionsForModules(modules: PlatformWebModule[])
|
||||
return publicRouteContributionsForModuleList(modules);
|
||||
}
|
||||
|
||||
export function dashboardWidgetsForModules(modules: PlatformWebModule[] = localModules): DashboardWidgetContribution[] {
|
||||
export function dashboardWidgetsForModules(
|
||||
modules: PlatformWebModule[] = localModules,
|
||||
projection?: EffectiveViewProjection | null
|
||||
): DashboardWidgetContribution[] {
|
||||
const catalogue = viewSurfaceCatalogueForModules(modules);
|
||||
return uiCapabilitiesForModules<DashboardWidgetsUiCapability>("dashboard.widgets", modules).
|
||||
flatMap((capability) => capability.widgets ?? []).
|
||||
filter((widget) => isViewSurfaceVisible(projection, widget.surfaceId, catalogue)).
|
||||
sort((left, right) => (left.order ?? 100) - (right.order ?? 100));
|
||||
}
|
||||
|
||||
export function navItemsForModules(modules: PlatformWebModule[]): PlatformNavItem[] {
|
||||
return [...shellNavItemsForModules(modules), ...modules.flatMap((module) => module.navItems ?? [])].
|
||||
export function navItemsForModules(
|
||||
modules: PlatformWebModule[],
|
||||
projection?: EffectiveViewProjection | null
|
||||
): PlatformNavItem[] {
|
||||
const catalogue = viewSurfaceCatalogueForModules(modules);
|
||||
const moduleItems = modules.flatMap((module) =>
|
||||
(module.navItems ?? []).map((item) => ({
|
||||
...item,
|
||||
surfaceId: item.surfaceId ?? navigationViewSurfaceId(module.id, item.to)
|
||||
}))
|
||||
);
|
||||
return [...shellNavItemsForModules(modules), ...moduleItems].
|
||||
map(resolveNavItemIcon).
|
||||
filter((item) => isViewSurfaceVisible(projection, item.surfaceId, catalogue)).
|
||||
sort((left, right) => (left.order ?? 100) - (right.order ?? 100));
|
||||
}
|
||||
|
||||
export function visibleNavItems(auth: AuthInfo | null | undefined, modules: PlatformWebModule[] = localModules): PlatformNavItem[] {
|
||||
return navItemsForModules(modules).filter((item) => {
|
||||
export function visibleNavItems(auth: AuthInfo | null | undefined, modules: PlatformWebModule[] = localModules, projection?: EffectiveViewProjection | null): PlatformNavItem[] {
|
||||
return navItemsForModules(modules, projection).filter((item) => {
|
||||
if (item.allOf?.length && !item.allOf.every((scope) => hasScope(auth, scope))) return false;
|
||||
if (item.anyOf?.length && !hasAnyScope(auth, item.anyOf)) return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
export function firstAccessibleRoute(auth: AuthInfo, modules: PlatformWebModule[] = localModules): string {
|
||||
return visibleNavItems(auth, modules)[0]?.to ?? "/dashboard";
|
||||
export function firstAccessibleRoute(auth: AuthInfo, modules: PlatformWebModule[] = localModules, projection?: EffectiveViewProjection | null): string {
|
||||
return visibleNavItems(auth, modules, projection)[0]?.to ?? "/dashboard";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user