import type { GlassMaterialSettings, NodedcTheme } from "@nodedc/ui-core"; import type { ToolbarPlacement } from "@nodedc/ui-react"; import type { FaviconAssetUrls } from "./favicon.js"; import type { MapPageLayout, MapPageSettings } from "./MapFixturePreview.js"; import type { MapPresentationProfile } from "./mapPresentationProfile.js"; export type MaterialDraft = { panelHex: string; panelOpacity: number; fieldHex: string; fieldOpacity: number; nestedHex: string; }; export type MapDesignProfileFragment = { schemaVersion: 1; templateId: "map"; templateVersion: string; settings: MapPageSettings; presentationProfiles: MapPresentationProfile[]; }; export type DesignProfilePageFragment = MapDesignProfileFragment; export interface StoredLayout { theme?: NodedcTheme; accentHex?: string; materialByTheme?: Record; environment?: { lightColor?: string; brightness?: number; glowDistance?: number; connectionType?: string; connectionColor?: string; usePortColors?: boolean; fillColor?: string; fillOpacity?: number; strokeColor?: string; strokeOpacity?: number; }; media?: { source?: "file" | "url"; url?: string; fileName?: string; fileSrc?: string; visible?: boolean; logoSource?: "file" | "url"; logoUrl?: string; logoFileName?: string; logoFileSrc?: string; faviconFileName?: string; faviconAssets?: FaviconAssetUrls; }; glass?: GlassMaterialSettings; toolbar?: { placement?: ToolbarPlacement; background?: string; border?: string; outline?: string; minSize?: number; maxSize?: number; lensCount?: number; autoHide?: boolean; }; /** Design fragments are keyed by a registered Page Library type/version. */ pageTypes?: Record; } export type MapDesignOverrides = { settings?: Partial; presentationProfiles?: MapPresentationProfile[]; }; export const mapDesignProfileKey = (templateVersion: string) => `map@${templateVersion}`; export function mapDesignFragmentFromLayout(layout: MapPageLayout, templateVersion: string): MapDesignProfileFragment { return { schemaVersion: 1, templateId: "map", templateVersion, settings: structuredClone(layout.settings), presentationProfiles: structuredClone(layout.presentationProfiles), }; } export function mapDesignFragmentForLayout(layout: StoredLayout | null | undefined, templateVersion: string) { const fragment = layout?.pageTypes?.[mapDesignProfileKey(templateVersion)]; return fragment?.templateId === "map" ? fragment : null; } export function resolveMapDesignLayout( base: MapPageLayout | null | undefined, fragment: MapDesignProfileFragment | null | undefined, overrides?: MapDesignOverrides, ) { if (!base) return null; if (!fragment) return base; return { ...base, settings: { ...base.settings, ...fragment.settings, ...overrides?.settings, }, presentationProfiles: structuredClone(overrides?.presentationProfiles ?? fragment.presentationProfiles), } satisfies MapPageLayout; } export function mapDesignOverridesFromResolved( resolved: MapPageLayout, fragment: MapDesignProfileFragment | null | undefined, ): MapDesignOverrides | undefined { if (!fragment) return undefined; const settings = Object.fromEntries(Object.entries(resolved.settings).filter(([key, value]) => ( fragment.settings[key as keyof MapPageSettings] !== value ))) as Partial; const presentationProfiles = JSON.stringify(resolved.presentationProfiles) === JSON.stringify(fragment.presentationProfiles) ? undefined : structuredClone(resolved.presentationProfiles); if (Object.keys(settings).length === 0 && !presentationProfiles) return undefined; return { ...(Object.keys(settings).length > 0 ? { settings } : {}), ...(presentationProfiles ? { presentationProfiles } : {}), }; }