@kerros/viewer 0.0.0-stage → 0.1.0-rc.1
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/LICENSE +21 -0
- package/README.md +31 -2
- package/dist/adapters/basemap.js +11 -0
- package/dist/adapters/basemap.js.map +1 -0
- package/dist/adapters/persistence.js +121 -0
- package/dist/adapters/persistence.js.map +1 -0
- package/dist/adapters/status.js +18 -0
- package/dist/adapters/status.js.map +1 -0
- package/dist/components/ElevatorPanel.js +120 -0
- package/dist/components/ElevatorPanel.js.map +1 -0
- package/dist/components/Icons.js +57 -0
- package/dist/components/Icons.js.map +1 -0
- package/dist/components/Inspector.js +1184 -0
- package/dist/components/Inspector.js.map +1 -0
- package/dist/components/NavigatePanel.js +301 -0
- package/dist/components/NavigatePanel.js.map +1 -0
- package/dist/components/NavigationGraph.js +234 -0
- package/dist/components/NavigationGraph.js.map +1 -0
- package/dist/components/StructureExplorer.js +510 -0
- package/dist/components/StructureExplorer.js.map +1 -0
- package/dist/components/StructureView.js +286 -0
- package/dist/components/StructureView.js.map +1 -0
- package/dist/components/controls.js +170 -0
- package/dist/components/controls.js.map +1 -0
- package/dist/components/graphLayout.js +27 -0
- package/dist/components/graphLayout.js.map +1 -0
- package/dist/host.js +169 -0
- package/dist/host.js.map +1 -0
- package/dist/i18n.js +88 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.js +171 -0
- package/dist/index.js.map +1 -0
- package/dist/map/CabinMaterials.js +81 -0
- package/dist/map/CabinMaterials.js.map +1 -0
- package/dist/map/CabinMirror.js +43 -0
- package/dist/map/CabinMirror.js.map +1 -0
- package/dist/map/Doors.js +80 -0
- package/dist/map/Doors.js.map +1 -0
- package/dist/map/ElevatorCabin.js +93 -0
- package/dist/map/ElevatorCabin.js.map +1 -0
- package/dist/map/FixtureLights.js +117 -0
- package/dist/map/FixtureLights.js.map +1 -0
- package/dist/map/MapCanvas.js +2161 -0
- package/dist/map/MapCanvas.js.map +1 -0
- package/dist/map/Passenger.js +69 -0
- package/dist/map/Passenger.js.map +1 -0
- package/dist/map/SceneLayer.js +1534 -0
- package/dist/map/SceneLayer.js.map +1 -0
- package/dist/map/UndergroundContext.js +153 -0
- package/dist/map/UndergroundContext.js.map +1 -0
- package/dist/map/ambience.js +194 -0
- package/dist/map/ambience.js.map +1 -0
- package/dist/map/architecture.js +305 -0
- package/dist/map/architecture.js.map +1 -0
- package/dist/map/bathMusic.js +92 -0
- package/dist/map/bathMusic.js.map +1 -0
- package/dist/map/cameraAim.js +33 -0
- package/dist/map/cameraAim.js.map +1 -0
- package/dist/map/elevators.js +56 -0
- package/dist/map/elevators.js.map +1 -0
- package/dist/map/excavation.js +52 -0
- package/dist/map/excavation.js.map +1 -0
- package/dist/map/exteriors.js +25 -0
- package/dist/map/exteriors.js.map +1 -0
- package/dist/map/features.js +218 -0
- package/dist/map/features.js.map +1 -0
- package/dist/map/fences.js +29 -0
- package/dist/map/fences.js.map +1 -0
- package/dist/map/journey.js +112 -0
- package/dist/map/journey.js.map +1 -0
- package/dist/map/levels.js +6 -0
- package/dist/map/levels.js.map +1 -0
- package/dist/map/lighting.js +142 -0
- package/dist/map/lighting.js.map +1 -0
- package/dist/map/loadBasemap.js +26 -0
- package/dist/map/loadBasemap.js.map +1 -0
- package/dist/map/materials.js +183 -0
- package/dist/map/materials.js.map +1 -0
- package/dist/map/poolLighting.js +39 -0
- package/dist/map/poolLighting.js.map +1 -0
- package/dist/map/projection.js +24 -0
- package/dist/map/projection.js.map +1 -0
- package/dist/map/rampGeometry.js +38 -0
- package/dist/map/rampGeometry.js.map +1 -0
- package/dist/map/route.js +107 -0
- package/dist/map/route.js.map +1 -0
- package/dist/map/stairSurfaces.js +115 -0
- package/dist/map/stairSurfaces.js.map +1 -0
- package/dist/map/surfaces.js +136 -0
- package/dist/map/surfaces.js.map +1 -0
- package/dist/map/textures.js +95 -0
- package/dist/map/textures.js.map +1 -0
- package/dist/map/underground.js +34 -0
- package/dist/map/underground.js.map +1 -0
- package/dist/map/walk.js +433 -0
- package/dist/map/walk.js.map +1 -0
- package/dist/map/walkContext.js +46 -0
- package/dist/map/walkContext.js.map +1 -0
- package/dist/map/walkDimensions.js +7 -0
- package/dist/map/walkDimensions.js.map +1 -0
- package/dist/map/walkJourney.js +85 -0
- package/dist/map/walkJourney.js.map +1 -0
- package/dist/map/walkSurfaces.js +106 -0
- package/dist/map/walkSurfaces.js.map +1 -0
- package/dist/map/wallJoins.js +70 -0
- package/dist/map/wallJoins.js.map +1 -0
- package/dist/map/water.js +274 -0
- package/dist/map/water.js.map +1 -0
- package/dist/model/authoring.js +80 -0
- package/dist/model/authoring.js.map +1 -0
- package/dist/model/boundaries.js +548 -0
- package/dist/model/boundaries.js.map +1 -0
- package/dist/model/coverage.js +30 -0
- package/dist/model/coverage.js.map +1 -0
- package/dist/model/document.js +52 -0
- package/dist/model/document.js.map +1 -0
- package/dist/model/doors.js +47 -0
- package/dist/model/doors.js.map +1 -0
- package/dist/model/factory.js +56 -0
- package/dist/model/factory.js.map +1 -0
- package/dist/model/geometry.js +394 -0
- package/dist/model/geometry.js.map +1 -0
- package/dist/model/inference.js +182 -0
- package/dist/model/inference.js.map +1 -0
- package/dist/model/materials.js +11 -0
- package/dist/model/materials.js.map +1 -0
- package/dist/model/mutations.js +157 -0
- package/dist/model/mutations.js.map +1 -0
- package/dist/model/navigation.js +275 -0
- package/dist/model/navigation.js.map +1 -0
- package/dist/model/ontology.js +109 -0
- package/dist/model/ontology.js.map +1 -0
- package/dist/model/passages.js +5 -0
- package/dist/model/passages.js.map +1 -0
- package/dist/model/portals.js +42 -0
- package/dist/model/portals.js.map +1 -0
- package/dist/model/precision.js +11 -0
- package/dist/model/precision.js.map +1 -0
- package/dist/model/project.js +17 -0
- package/dist/model/project.js.map +1 -0
- package/dist/model/ramps.js +10 -0
- package/dist/model/ramps.js.map +1 -0
- package/dist/model/roomNavigation.js +87 -0
- package/dist/model/roomNavigation.js.map +1 -0
- package/dist/model/routeClearance.js +72 -0
- package/dist/model/routeClearance.js.map +1 -0
- package/dist/model/spaces.js +104 -0
- package/dist/model/spaces.js.map +1 -0
- package/dist/model/stairGeometry.js +40 -0
- package/dist/model/stairGeometry.js.map +1 -0
- package/dist/model/stairTraversal.js +48 -0
- package/dist/model/stairTraversal.js.map +1 -0
- package/dist/model/topology.js +63 -0
- package/dist/model/topology.js.map +1 -0
- package/dist/model/types.js +35 -0
- package/dist/model/types.js.map +1 -0
- package/dist/model/validate.js +240 -0
- package/dist/model/validate.js.map +1 -0
- package/dist/model/vertical.js +129 -0
- package/dist/model/vertical.js.map +1 -0
- package/dist/model/walls.js +71 -0
- package/dist/model/walls.js.map +1 -0
- package/dist/styles.css +4931 -0
- package/dist/theme/index.js +34 -0
- package/dist/theme/index.js.map +1 -0
- package/dist/types/adapters/basemap.d.ts +2 -0
- package/dist/types/adapters/persistence.d.ts +29 -0
- package/dist/types/adapters/status.d.ts +4 -0
- package/dist/types/components/ElevatorPanel.d.ts +18 -0
- package/dist/types/components/Icons.d.ts +8 -0
- package/dist/types/components/Inspector.d.ts +38 -0
- package/dist/types/components/NavigatePanel.d.ts +27 -0
- package/dist/types/components/NavigationGraph.d.ts +10 -0
- package/dist/types/components/StructureExplorer.d.ts +8 -0
- package/dist/types/components/StructureView.d.ts +22 -0
- package/dist/types/components/controls.d.ts +52 -0
- package/dist/types/components/graphLayout.d.ts +9 -0
- package/dist/types/i18n.d.ts +85 -0
- package/dist/types/import/analysisPreview.d.ts +27 -0
- package/dist/types/import/brief.d.ts +22 -0
- package/dist/types/import/checkpoint.d.ts +49 -0
- package/dist/types/import/instructions.d.ts +7 -0
- package/dist/types/import/usage.d.ts +10 -0
- package/dist/types/map/CabinMaterials.d.ts +18 -0
- package/dist/types/map/CabinMirror.d.ts +13 -0
- package/dist/types/map/Doors.d.ts +11 -0
- package/dist/types/map/ElevatorCabin.d.ts +20 -0
- package/dist/types/map/FixtureLights.d.ts +35 -0
- package/dist/types/map/MapCanvas.d.ts +87 -0
- package/dist/types/map/Passenger.d.ts +18 -0
- package/dist/types/map/SceneLayer.d.ts +306 -0
- package/dist/types/map/UndergroundContext.d.ts +18 -0
- package/dist/types/map/ambience.d.ts +29 -0
- package/dist/types/map/architecture.d.ts +8 -0
- package/dist/types/map/bathMusic.d.ts +14 -0
- package/dist/types/map/cameraAim.d.ts +20 -0
- package/dist/types/map/elevators.d.ts +16 -0
- package/dist/types/map/excavation.d.ts +9 -0
- package/dist/types/map/exteriors.d.ts +3 -0
- package/dist/types/map/features.d.ts +33 -0
- package/dist/types/map/fences.d.ts +4 -0
- package/dist/types/map/journey.d.ts +36 -0
- package/dist/types/map/levels.d.ts +4 -0
- package/dist/types/map/lighting.d.ts +103 -0
- package/dist/types/map/loadBasemap.d.ts +4 -0
- package/dist/types/map/materials.d.ts +38 -0
- package/dist/types/map/poolLighting.d.ts +11 -0
- package/dist/types/map/projection.d.ts +9 -0
- package/dist/types/map/rampGeometry.d.ts +8 -0
- package/dist/types/map/route.d.ts +59 -0
- package/dist/types/map/stairSurfaces.d.ts +29 -0
- package/dist/types/map/surfaces.d.ts +36 -0
- package/dist/types/map/textures.d.ts +8 -0
- package/dist/types/map/underground.d.ts +30 -0
- package/dist/types/map/walk.d.ts +232 -0
- package/dist/types/map/walkContext.d.ts +11 -0
- package/dist/types/map/walkDimensions.d.ts +5 -0
- package/dist/types/map/walkJourney.d.ts +33 -0
- package/dist/types/map/walkSurfaces.d.ts +27 -0
- package/dist/types/map/wallJoins.d.ts +6 -0
- package/dist/types/map/water.d.ts +12 -0
- package/dist/types/model/authoring.d.ts +38 -0
- package/dist/types/model/boundaries.d.ts +41 -0
- package/dist/types/model/coverage.d.ts +8 -0
- package/dist/types/model/document.d.ts +9 -0
- package/dist/types/model/doors.d.ts +19 -0
- package/dist/types/model/factory.d.ts +5 -0
- package/dist/types/model/geometry.d.ts +99 -0
- package/dist/types/model/host.d.ts +154 -0
- package/dist/types/model/inference.d.ts +46 -0
- package/dist/types/model/live.d.ts +60 -0
- package/dist/types/model/materials.d.ts +28 -0
- package/dist/types/model/mutations.d.ts +178 -0
- package/dist/types/model/navigation.d.ts +70 -0
- package/dist/types/model/ontology.d.ts +43 -0
- package/dist/types/model/passages.d.ts +2 -0
- package/dist/types/model/portals.d.ts +9 -0
- package/dist/types/model/precision.d.ts +11 -0
- package/dist/types/model/project.d.ts +8 -0
- package/dist/types/model/ramps.d.ts +7 -0
- package/dist/types/model/roomNavigation.d.ts +15 -0
- package/dist/types/model/routeClearance.d.ts +11 -0
- package/dist/types/model/spaces.d.ts +46 -0
- package/dist/types/model/stairGeometry.d.ts +27 -0
- package/dist/types/model/stairTraversal.d.ts +8 -0
- package/dist/types/model/topology.d.ts +14 -0
- package/dist/types/model/types.d.ts +369 -0
- package/dist/types/model/validate.d.ts +38 -0
- package/dist/types/model/vertical.d.ts +145 -0
- package/dist/types/model/walls.d.ts +71 -0
- package/dist/types/schema/index.d.ts +25 -0
- package/dist/types/theme/index.d.ts +55 -0
- package/dist/types/viewer/FloorViewer.d.ts +76 -0
- package/dist/types/viewer/ViewerWorkspace.d.ts +2 -0
- package/dist/types/viewer/host.d.ts +11 -0
- package/dist/types/viewer/index.d.ts +2 -0
- package/dist/types/viewer/panels.d.ts +12 -0
- package/dist/viewer/FloorViewer.js +104 -0
- package/dist/viewer/FloorViewer.js.map +1 -0
- package/dist/viewer/ViewerWorkspace.js +370 -0
- package/dist/viewer/ViewerWorkspace.js.map +1 -0
- package/dist/viewer/panels.js +35 -0
- package/dist/viewer/panels.js.map +1 -0
- package/package.json +52 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NavigatePanel.js","sources":["../../../../src/components/NavigatePanel.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react';\nimport {\n ArrowUpDown,\n ArrowUpRight,\n ChevronLeft,\n ChevronRight,\n DoorOpen,\n Flag,\n Footprints,\n Layers3,\n MapPin,\n MoveUpRight,\n Pause,\n PersonStanding,\n Play,\n Search,\n Waypoints,\n X,\n} from 'lucide-react';\nimport type { Point, ProjectDocument, SiteObject } from '../model/types';\nimport type { Route, RouteStep } from '../model/navigation';\nimport { floorPhrase, HERE, navNodes } from '../model/navigation';\nimport { EntityIcon } from './Icons';\nimport { useStrings } from '../theme';\n\n/** Object kinds offered as route endpoints — everything routeAnchors can resolve meaningfully. */\nconst NAV_KINDS = ['room', 'zone', 'door', 'gate', 'poi', 'elevator', 'stairs', 'turnstile'];\nconst STEP_ICONS: Record<RouteStep['kind'], typeof MapPin> = {\n depart: MapPin,\n walk: Footprints,\n door: DoorOpen,\n stairs: MoveUpRight,\n elevator: ArrowUpRight,\n arrive: Flag,\n};\n\nexport interface NavigatePanelProps {\n project: ProjectDocument;\n /** Where the person is — the walk's avatar — offered as \"Where you are\" and proposed as the\n * start whenever nothing else has been chosen. Null when nobody has walked yet. */\n here?: { floorId: string | null; position: Point } | null;\n from: string | null;\n to: string | null;\n route: Route | null;\n playing: boolean;\n activeStep: number | null;\n editing: boolean;\n onFrom: (id: string | null) => void;\n onTo: (id: string | null) => void;\n onSwap: () => void;\n onClear: () => void;\n onPlay: () => void;\n onPause: () => void;\n onStep: (delta: 1 | -1) => void;\n onSeek: (index: number) => void;\n onClose: () => void;\n}\nexport function NavigatePanel({\n project,\n here,\n from,\n to,\n route,\n playing,\n activeStep,\n editing,\n onFrom,\n onTo,\n onSwap,\n onClear,\n onPlay,\n onPause,\n onStep,\n onSeek,\n onClose,\n}: NavigatePanelProps) {\n const en = useStrings();\n const t = en.navigate;\n // Every place a route can start or end, in level order and alphabetical within a level, each\n // knowing its level's name and the space it sits in (a door in a room, a room in a zone), which\n // is what the browse view groups by.\n const { places, levels } = useMemo(() => {\n const options = project.objects.filter(o => NAV_KINDS.includes(o.kind));\n const byId = new Map(project.objects.map(o => [o.id, o]));\n const levels: Level[] = [\n { id: null, name: en.outdoors, count: 0 },\n ...project.floors.map(f => ({ id: f.id as string | null, name: floorPhrase(project, f.id), count: 0 })),\n ];\n const places: Place[] = [];\n for (const level of levels) {\n const items = options.filter(o => o.floorId === level.id).sort((a, b) => a.name.localeCompare(b.name));\n level.count = items.length;\n for (const o of items) {\n const parent = o.parentId ? byId.get(o.parentId) : undefined;\n places.push({ id: o.id, o, floorId: level.id, floor: level.name, group: parent?.name ?? '' });\n }\n }\n return { places, levels: levels.filter(l => l.count) };\n }, [project, en.outdoors]);\n const hereLabel = here\n ? `${t.here} · ${here.floorId === null ? en.outdoors : floorPhrase(project, here.floorId)}`\n : '';\n const herePlace: Place | null = here\n ? {\n id: HERE,\n o: { id: HERE, kind: 'poi', name: t.here, floorId: here.floorId, position: here.position } as SiteObject,\n floorId: here.floorId,\n floor: hereLabel,\n group: '',\n }\n : null;\n // Propose where the person is as the start. Once, when there is one and nothing was chosen —\n // not on every render, or clearing the start would be undone at the next frame.\n const proposed = useRef(false);\n useEffect(() => {\n if (here && !from && !to && !proposed.current) {\n proposed.current = true;\n onFrom(HERE);\n }\n }, [here, from, to, onFrom]);\n const hasGraph = useMemo(() => navNodes(project).length > 0, [project]);\n const floorsCrossed = route ? new Set(route.steps.map(s => s.floorId)).size : 0;\n const picker = (\n label: string,\n value: string | null,\n other: string | null,\n set: (id: string | null) => void,\n first: Place | null,\n ) => (\n <PlacePicker\n label={label}\n places={places}\n levels={levels}\n first={first}\n value={value}\n other={other}\n onPick={set}\n placeholder={t.choose}\n strings={t}\n />\n );\n return (\n <aside className=\"inspector navigate-panel\">\n <div className=\"inspector-top\">\n <span>{t.title}</span>\n <button className=\"icon-button\" aria-label=\"Close navigation\" onClick={onClose}>\n <X size={17} />\n </button>\n </div>\n <div className=\"inspector-scroll\">\n <div className=\"nav-endpoints\">\n <div className=\"nav-endpoint-fields\">\n {picker(t.from, from, to, onFrom, herePlace)}\n {picker(t.to, to, from, onTo, null)}\n </div>\n <div className=\"nav-endpoint-actions\">\n <button className=\"icon-button\" aria-label={t.swap} title={t.swap} disabled={!from && !to} onClick={onSwap}>\n <ArrowUpDown size={16} />\n </button>\n {(from || to) && (\n <button className=\"text-button\" onClick={onClear}>\n {t.clear}\n </button>\n )}\n </div>\n </div>\n {!hasGraph ? (\n <div className=\"nav-empty\">\n <Waypoints size={22} />\n <strong>{t.noGraph}</strong>\n <p>{editing ? t.noGraphHintEdit : t.noGraphHintView}</p>\n </div>\n ) : from && to && !route ? (\n <div className=\"nav-empty\">\n <Waypoints size={22} />\n <strong>{t.unreachable}</strong>\n <p>{t.unreachableHint}</p>\n </div>\n ) : route ? (\n <>\n <div className=\"nav-summary\">\n <div>\n <strong>{Math.max(1, Math.round(route.distance))} m</strong>\n <span>{t.walking}</span>\n </div>\n <div>\n <strong>{floorsCrossed}</strong>\n <span>{t.floors}</span>\n </div>\n <div>\n <strong>{route.steps.length}</strong>\n <span>{t.steps}</span>\n </div>\n </div>\n <ol className=\"nav-steps\">\n {route.steps.map((s, i) => {\n const Icon = STEP_ICONS[s.kind];\n return (\n <li key={i}>\n <button className={`nav-step ${i === activeStep ? 'active' : ''}`} onClick={() => onSeek(i)}>\n <span className=\"nav-step-num\">{i + 1}</span>\n <Icon size={15} />\n <span className=\"nav-step-text\">\n {s.text}\n <small>\n {floorPhrase(project, s.floorId)}\n {s.distance !== undefined ? ` · ${Math.max(1, Math.round(s.distance))} m` : ''}\n </small>\n </span>\n </button>\n </li>\n );\n })}\n </ol>\n </>\n ) : (\n <p className=\"helper\">{t.pick}</p>\n )}\n </div>\n {route && (\n <div className=\"nav-controls\">\n <button\n className=\"icon-button\"\n aria-label={t.prev}\n title={t.prev}\n disabled={(activeStep ?? 0) <= 0}\n onClick={() => onStep(-1)}\n >\n <ChevronLeft size={18} />\n </button>\n <button className=\"button primary nav-play\" onClick={playing ? onPause : onPlay}>\n {playing ? (\n <>\n <Pause size={15} />\n {t.pause}\n </>\n ) : (\n <>\n <Play size={15} />\n {t.play}\n </>\n )}\n </button>\n <button\n className=\"icon-button\"\n aria-label={t.next}\n title={t.next}\n disabled={(activeStep ?? -1) >= route.steps.length - 1}\n onClick={() => onStep(1)}\n >\n <ChevronRight size={18} />\n </button>\n </div>\n )}\n </aside>\n );\n}\n\ninterface Place {\n id: string;\n o: SiteObject;\n floorId: string | null;\n floor: string;\n /** The space this place sits in, when the document says so. */\n group: string;\n}\ninterface Level {\n id: string | null;\n name: string;\n count: number;\n}\n/** How many rows a list shows before it asks to be scrolled, and how many more each scroll adds.\n * A big floor has hundreds of rooms, and all of them are one list — laid out a page at a time so\n * the first paint is instant and the last row is still reachable. */\nconst PAGE = 60;\n\n/** A searchable place picker with two ways in. Type, and it searches every place on every level;\n * leave it empty, and it browses — the levels first, then the places on the one you open, grouped\n * by the space they sit in. Both lists scroll to the end rather than stopping at a count. */\nfunction PlacePicker({\n label,\n places,\n levels,\n first,\n value,\n other,\n onPick,\n placeholder,\n strings,\n}: {\n label: string;\n places: Place[];\n levels: Level[];\n /** A place offered ahead of everything — \"Where you are\" — or null. */\n first: Place | null;\n value: string | null;\n other: string | null;\n onPick: (id: string | null) => void;\n placeholder: string;\n strings: { here: string; hereHint: string; levels: string; allLevels: string; places: string; more: string };\n}) {\n const [query, setQuery] = useState(''),\n [open, setOpen] = useState(false),\n [cursor, setCursor] = useState(0),\n // The level being browsed; undefined is the list of levels.\n [level, setLevel] = useState<string | null | undefined>(undefined),\n [limit, setLimit] = useState(PAGE);\n const wrap = useRef<HTMLDivElement>(null);\n const list = useRef<HTMLDivElement>(null);\n const selected = value === HERE ? first : value ? places.find(p => p.id === value) : undefined;\n const q = query.trim().toLowerCase();\n const browsing = open && !q;\n // Search mode: every place whose name or level matches. Browse mode on a level: that level's\n // places. Neither is cut off; the page limit only decides how many are laid out so far.\n const matches: Place[] = !open\n ? []\n : q\n ? places.filter(p => p.id !== other && (p.o.name.toLowerCase().includes(q) || p.floor.toLowerCase().includes(q)))\n : level !== undefined\n ? places.filter(p => p.id !== other && p.floorId === level)\n : [];\n const shown = matches.slice(0, limit);\n useEffect(() => {\n const away = (e: PointerEvent) => {\n if (!wrap.current?.contains(e.target as Node)) setOpen(false);\n };\n window.addEventListener('pointerdown', away);\n return () => window.removeEventListener('pointerdown', away);\n }, []);\n useEffect(() => {\n setCursor(0);\n setLimit(PAGE);\n if (list.current) list.current.scrollTop = 0;\n }, [q, open, level]);\n const pick = (id: string) => {\n onPick(id);\n setOpen(false);\n setQuery('');\n setLevel(undefined);\n };\n const more = () => {\n const el = list.current;\n if (el && el.scrollTop + el.clientHeight >= el.scrollHeight - 80 && limit < matches.length) setLimit(n => n + PAGE);\n };\n const option = (p: Place, i: number) => (\n <button\n key={p.id}\n role=\"option\"\n aria-selected={i === cursor}\n className={`place-option ${i === cursor ? 'cursor' : ''} ${p.id === value ? 'chosen' : ''} ${p.id === HERE ? 'here' : ''}`}\n onPointerDown={e => {\n e.preventDefault();\n pick(p.id);\n }}\n onMouseEnter={() => setCursor(i)}\n >\n {p.id === HERE ? <PersonStanding size={14} /> : <EntityIcon kind={p.o.kind} size={14} symbol={p.o.symbol} />}\n <span>\n {p.o.name}\n <small>{p.id === HERE ? `${strings.hereHint} · ${p.floor.split(' · ')[1] ?? ''}` : p.floor}</small>\n </span>\n </button>\n );\n // Rows of a browsed level, with a heading wherever the space changes: a room's doors under the\n // room, the rooms of a zone under the zone.\n const grouped = (rows: Place[]) => {\n const out: ReactNode[] = [];\n let group: string | undefined;\n rows.forEach((p, i) => {\n if (p.group !== group) {\n group = p.group;\n if (group)\n out.push(\n <p key={`g-${group}-${i}`} className=\"place-group\">\n {group}\n </p>,\n );\n }\n out.push(option(p, i));\n });\n return out;\n };\n return (\n <label className=\"field place-picker\" ref={wrap as never}>\n <span>{label}</span>\n <div className=\"place-input\">\n <Search size={13} />\n <input\n aria-label={label}\n placeholder={selected ? selected.o.name : placeholder}\n value={open ? query : (selected?.o.name ?? '')}\n onFocus={() => {\n setOpen(true);\n setQuery('');\n }}\n onBlur={() => setTimeout(() => setOpen(false), 120)}\n onChange={e => {\n setQuery(e.target.value);\n setOpen(true);\n }}\n onKeyDown={e => {\n if (e.key === 'ArrowDown') {\n e.preventDefault();\n setCursor(c => Math.min(c + 1, shown.length - 1));\n } else if (e.key === 'ArrowUp') {\n e.preventDefault();\n setCursor(c => Math.max(c - 1, 0));\n } else if (e.key === 'Enter') {\n e.preventDefault();\n if (shown[cursor]) pick(shown[cursor].id);\n } else if (e.key === 'Backspace' && !query && level !== undefined) {\n setLevel(undefined);\n } else if (e.key === 'Escape') {\n e.stopPropagation();\n setOpen(false);\n (e.target as HTMLInputElement).blur();\n }\n }}\n />\n {value && !open && (\n <button\n className=\"icon-button place-clear\"\n aria-label={`Clear ${label}`}\n onClick={e => {\n e.preventDefault();\n onPick(null);\n }}\n >\n <X size={13} />\n </button>\n )}\n </div>\n {open && (\n <div className=\"place-options\" role=\"listbox\" ref={list} onScroll={more}>\n {browsing && level === undefined ? (\n <>\n {first && first.id !== other && option(first, -1)}\n <p className=\"place-group\">{strings.levels}</p>\n {levels.map(l => (\n <button\n key={l.id ?? 'out'}\n className=\"place-level\"\n onPointerDown={e => {\n e.preventDefault();\n setLevel(l.id);\n }}\n >\n <Layers3 size={14} />\n {l.name}\n <small>\n {l.count} {strings.places}\n </small>\n </button>\n ))}\n </>\n ) : (\n <>\n {browsing && (\n <button\n className=\"place-back\"\n onPointerDown={e => {\n e.preventDefault();\n setLevel(undefined);\n }}\n >\n <ChevronLeft size={14} />\n {strings.allLevels}\n </button>\n )}\n {!browsing && first && first.id !== other && first.o.name.toLowerCase().includes(q) && option(first, -1)}\n {shown.length ? (\n browsing ? (\n grouped(shown)\n ) : (\n shown.map(option)\n )\n ) : (\n <p className=\"place-none\">No places match “{query}”.</p>\n )}\n {limit < matches.length && <p className=\"place-none\">{strings.more}</p>}\n </>\n )}\n </div>\n )}\n </label>\n );\n}\n"],"names":["NAV_KINDS","STEP_ICONS","MapPin","Footprints","DoorOpen","MoveUpRight","ArrowUpRight","Flag","NavigatePanel","project","here","from","to","route","playing","activeStep","editing","onFrom","onTo","onSwap","onClear","onPlay","onPause","onStep","onSeek","onClose","en","useStrings","t","places","levels","useMemo","options","byId","f","floorPhrase","level","items","o","a","b","parent","l","hereLabel","herePlace","HERE","proposed","useRef","useEffect","hasGraph","navNodes","floorsCrossed","s","picker","label","value","other","set","first","jsx","PlacePicker","jsxs","X","ArrowUpDown","Waypoints","Fragment","i","Icon","ChevronLeft","Pause","Play","ChevronRight","PAGE","onPick","placeholder","strings","query","setQuery","useState","open","setOpen","cursor","setCursor","setLevel","limit","setLimit","wrap","list","selected","p","q","browsing","matches","shown","away","e","_a","pick","id","more","el","n","option","PersonStanding","EntityIcon","grouped","rows","out","group","Search","c","Layers3"],"mappings":"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"}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { jsxs as g, jsx as l } from "react/jsx-runtime";
|
|
2
|
+
import { useState as L, useRef as x, useMemo as he, useEffect as j } from "react";
|
|
3
|
+
import { LocateFixed as fe } from "lucide-react";
|
|
4
|
+
import { navNodes as pe, navEdges as ge } from "../model/navigation.js";
|
|
5
|
+
import { balanceGraph as me } from "./graphLayout.js";
|
|
6
|
+
const U = { walk: "#8393a9", door: "#c28332", stairs: "#2eaaa0", elevator: "#9777f1" }, se = ["#7367e6", "#2aabb3", "#d9a54b", "#ce6f91", "#5d9d6e", "#a67fc4"];
|
|
7
|
+
function we({ project: b, initialFloor: W, dark: q, onLocate: ie, onClose: K }) {
|
|
8
|
+
const [T, ce] = L(W === void 0 ? "all" : W ?? "outdoors"), [F, Q] = L(!0), [_, le] = L(""), [P, D] = L(""), [de, O] = L(!1), H = x(null), a = x({ x: 0, y: 0, scale: 1 }), M = x(() => {
|
|
9
|
+
}), J = x(() => {
|
|
10
|
+
}), w = x(0), V = x(P);
|
|
11
|
+
V.current = P;
|
|
12
|
+
const Z = x(F);
|
|
13
|
+
Z.current = F;
|
|
14
|
+
const N = x(!0), n = he(() => {
|
|
15
|
+
const e = pe(b).filter((s) => T === "all" || (s.floorId ?? "outdoors") === T), t = new Map(e.map((s, p) => [s.id, p])), r = ge(b).filter((s) => t.has(s.aId) && t.has(s.bId)), d = e.map((s, p) => {
|
|
16
|
+
const k = p * 2.399963, I = 15 * Math.sqrt(p + 1);
|
|
17
|
+
return { x: Math.cos(k) * I, y: Math.sin(k) * I, vx: 0, vy: 0 };
|
|
18
|
+
}), h = new Map(b.objects.map((s) => [s.id, s]));
|
|
19
|
+
return {
|
|
20
|
+
nodes: e,
|
|
21
|
+
edges: r,
|
|
22
|
+
points: d,
|
|
23
|
+
links: r.map((s) => [t.get(s.aId), t.get(s.bId)]),
|
|
24
|
+
objects: h
|
|
25
|
+
};
|
|
26
|
+
}, [b, T]), m = n.nodes.find((e) => e.id === P), ee = (e) => {
|
|
27
|
+
var r;
|
|
28
|
+
const t = n.nodes[e];
|
|
29
|
+
return ((r = n.objects.get(t.objectId ?? "")) == null ? void 0 : r.name) || t.id;
|
|
30
|
+
}, te = (e) => {
|
|
31
|
+
var t;
|
|
32
|
+
return e === null ? "Outdoor site" : ((t = b.floors.find((r) => r.id === e)) == null ? void 0 : t.name) ?? e;
|
|
33
|
+
}, ue = () => {
|
|
34
|
+
m && ie({
|
|
35
|
+
objectIds: m.objectId ? [m.objectId] : [],
|
|
36
|
+
floorId: m.floorId,
|
|
37
|
+
position: m.position
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
j(() => {
|
|
41
|
+
w.current = 0, O(!1), D(""), N.current = !0;
|
|
42
|
+
}, [n]), j(() => {
|
|
43
|
+
const e = H.current, t = e == null ? void 0 : e.getContext("2d");
|
|
44
|
+
if (!e || !t) return;
|
|
45
|
+
let r = 1, d = 1, h = 0, s = 0, p = !1;
|
|
46
|
+
const k = new Map(b.floors.map((o, c) => [o.id, se[c % se.length]])), I = () => {
|
|
47
|
+
const o = V.current, { x: c, y, scale: i } = a.current;
|
|
48
|
+
t.clearRect(0, 0, r, d), t.save(), t.translate(r / 2 + c, d / 2 + y), t.scale(i, i), n.edges.forEach((u, f) => {
|
|
49
|
+
const [C, S] = n.links[f], B = n.points[C], A = n.points[S], oe = u.aId === o || u.bId === o;
|
|
50
|
+
if (t.globalAlpha = o && !oe ? 0.18 : 0.65, t.strokeStyle = U[u.kind], t.lineWidth = (oe ? 2.5 : 1) / i, t.beginPath(), t.moveTo(B.x, B.y), t.lineTo(A.x, A.y), t.stroke(), u.directed) {
|
|
51
|
+
const R = Math.atan2(A.y - B.y, A.x - B.x), E = 7 / i, $ = A.x - Math.cos(R) * 7 / i, z = A.y - Math.sin(R) * 7 / i;
|
|
52
|
+
t.fillStyle = U[u.kind], t.beginPath(), t.moveTo($, z), t.lineTo($ - Math.cos(R - 0.5) * E, z - Math.sin(R - 0.5) * E), t.lineTo($ - Math.cos(R + 0.5) * E, z - Math.sin(R + 0.5) * E), t.closePath(), t.fill();
|
|
53
|
+
}
|
|
54
|
+
}), t.globalAlpha = 1, n.nodes.forEach((u, f) => {
|
|
55
|
+
const C = n.points[f], S = u.id === o;
|
|
56
|
+
t.beginPath(), t.arc(C.x, C.y, (S ? 7 : 4) / i, 0, Math.PI * 2), t.fillStyle = k.get(u.floorId ?? "") ?? "#8b94a4", t.fill(), S && (t.strokeStyle = q ? "#fff" : "#191c32", t.lineWidth = 2 / i, t.stroke()), (S || n.nodes.length < 65 && i > 0.45) && (t.font = `${12 / i}px system-ui`, t.fillStyle = q ? "#eeeefa" : "#24263a", t.fillText(ee(f).slice(0, 38), C.x + 9 / i, C.y - 7 / i));
|
|
57
|
+
}), t.restore();
|
|
58
|
+
}, Y = () => {
|
|
59
|
+
if (!n.points.length) return;
|
|
60
|
+
let o = 1 / 0, c = -1 / 0, y = 1 / 0, i = -1 / 0;
|
|
61
|
+
for (const f of n.points)
|
|
62
|
+
o = Math.min(o, f.x), c = Math.max(c, f.x), y = Math.min(y, f.y), i = Math.max(i, f.y);
|
|
63
|
+
const u = Math.max(
|
|
64
|
+
0.05,
|
|
65
|
+
Math.min(3, (r - 90) / Math.max(1, c - o), (d - 90) / Math.max(1, i - y))
|
|
66
|
+
);
|
|
67
|
+
a.current = { x: -(o + c) * u / 2, y: -(y + i) * u / 2, scale: u }, I();
|
|
68
|
+
}, G = (o) => {
|
|
69
|
+
h = 0, !p && (Z.current && w.current < 360 ? (o - s > 30 && (me(n.points, n.links, w.current++), N.current && w.current % 20 === 0 ? Y() : I(), s = o), h = requestAnimationFrame(G)) : (I(), w.current >= 360 && O(!0)));
|
|
70
|
+
}, X = () => {
|
|
71
|
+
I(), !h && !p && (h = requestAnimationFrame(G));
|
|
72
|
+
};
|
|
73
|
+
M.current = X, J.current = Y;
|
|
74
|
+
const ae = new ResizeObserver(() => {
|
|
75
|
+
const o = e.getBoundingClientRect();
|
|
76
|
+
r = o.width, d = o.height;
|
|
77
|
+
const c = Math.min(2, window.devicePixelRatio || 1);
|
|
78
|
+
e.width = Math.round(r * c), e.height = Math.round(d * c), t.setTransform(c, 0, 0, c, 0, 0), Y(), X();
|
|
79
|
+
}), re = (o) => {
|
|
80
|
+
o.preventDefault(), N.current = !1;
|
|
81
|
+
const c = e.getBoundingClientRect(), y = o.clientX - c.left - c.width / 2, i = o.clientY - c.top - c.height / 2, u = a.current.scale, f = Math.max(0.03, Math.min(8, u * Math.exp(-o.deltaY * 1e-3)));
|
|
82
|
+
a.current = {
|
|
83
|
+
scale: f,
|
|
84
|
+
x: y - (y - a.current.x) * f / u,
|
|
85
|
+
y: i - (i - a.current.y) * f / u
|
|
86
|
+
}, X();
|
|
87
|
+
};
|
|
88
|
+
return e.addEventListener("wheel", re, { passive: !1 }), ae.observe(e), h = requestAnimationFrame(G), () => {
|
|
89
|
+
p = !0, cancelAnimationFrame(h), ae.disconnect(), e.removeEventListener("wheel", re);
|
|
90
|
+
};
|
|
91
|
+
}, [n, q, b]), j(() => M.current(), [F, P]);
|
|
92
|
+
const v = x(
|
|
93
|
+
null
|
|
94
|
+
), ne = n.nodes.map((e, t) => ({ node: e, i: t, label: `${ee(t)} · ${te(e.floorId)}` })).filter((e) => e.label.toLowerCase().includes(_.toLowerCase())).slice(0, 100);
|
|
95
|
+
return /* @__PURE__ */ g("section", { className: "navigation-graph", "aria-label": "Navigation graph view", children: [
|
|
96
|
+
/* @__PURE__ */ g("div", { className: "navigation-graph-toolbar", children: [
|
|
97
|
+
/* @__PURE__ */ l("strong", { children: "Navigation graph" }),
|
|
98
|
+
/* @__PURE__ */ g("select", { "aria-label": "Graph floor", value: T, onChange: (e) => ce(e.target.value), children: [
|
|
99
|
+
/* @__PURE__ */ l("option", { value: "all", children: "All floors" }),
|
|
100
|
+
/* @__PURE__ */ l("option", { value: "outdoors", children: "Outdoor site" }),
|
|
101
|
+
b.floors.map((e) => /* @__PURE__ */ l("option", { value: e.id, children: te(e.id) }, e.id))
|
|
102
|
+
] }),
|
|
103
|
+
/* @__PURE__ */ g("label", { children: [
|
|
104
|
+
/* @__PURE__ */ l("input", { type: "checkbox", checked: F, onChange: (e) => Q(e.target.checked) }),
|
|
105
|
+
"Auto balance"
|
|
106
|
+
] }),
|
|
107
|
+
/* @__PURE__ */ l(
|
|
108
|
+
"button",
|
|
109
|
+
{
|
|
110
|
+
className: "button secondary small",
|
|
111
|
+
onClick: () => {
|
|
112
|
+
w.current = 0, O(!1), Q(!0), M.current();
|
|
113
|
+
},
|
|
114
|
+
children: "Rebalance"
|
|
115
|
+
}
|
|
116
|
+
),
|
|
117
|
+
/* @__PURE__ */ l("button", { className: "button secondary small", onClick: () => J.current(), children: "Fit graph" }),
|
|
118
|
+
/* @__PURE__ */ l("button", { className: "button secondary small", onClick: K, children: "Back to map" })
|
|
119
|
+
] }),
|
|
120
|
+
/* @__PURE__ */ g("div", { className: "navigation-graph-search", children: [
|
|
121
|
+
/* @__PURE__ */ l(
|
|
122
|
+
"input",
|
|
123
|
+
{
|
|
124
|
+
type: "search",
|
|
125
|
+
"aria-label": "Search graph nodes",
|
|
126
|
+
placeholder: "Find a room or transport node…",
|
|
127
|
+
value: _,
|
|
128
|
+
onChange: (e) => le(e.target.value)
|
|
129
|
+
}
|
|
130
|
+
),
|
|
131
|
+
/* @__PURE__ */ g(
|
|
132
|
+
"select",
|
|
133
|
+
{
|
|
134
|
+
"aria-label": "Graph node",
|
|
135
|
+
value: P,
|
|
136
|
+
onChange: (e) => {
|
|
137
|
+
N.current = !1;
|
|
138
|
+
const t = n.nodes.findIndex((r) => r.id === e.target.value);
|
|
139
|
+
if (D(e.target.value), t >= 0) {
|
|
140
|
+
const r = n.points[t];
|
|
141
|
+
a.current.x = -r.x * a.current.scale, a.current.y = -r.y * a.current.scale, M.current();
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
children: [
|
|
145
|
+
/* @__PURE__ */ g("option", { value: "", children: [
|
|
146
|
+
"Select a node (",
|
|
147
|
+
n.nodes.length,
|
|
148
|
+
")"
|
|
149
|
+
] }),
|
|
150
|
+
m && !ne.some((e) => e.node.id === m.id) && /* @__PURE__ */ l("option", { value: m.id, children: m.id }),
|
|
151
|
+
ne.map((e) => /* @__PURE__ */ l("option", { value: e.node.id, children: e.label }, e.node.id))
|
|
152
|
+
]
|
|
153
|
+
}
|
|
154
|
+
),
|
|
155
|
+
/* @__PURE__ */ l(
|
|
156
|
+
"button",
|
|
157
|
+
{
|
|
158
|
+
className: "icon-button",
|
|
159
|
+
"aria-label": "Find node on map",
|
|
160
|
+
title: "Find on map",
|
|
161
|
+
disabled: !m,
|
|
162
|
+
onClick: ue,
|
|
163
|
+
children: /* @__PURE__ */ l(fe, { size: 16 })
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
] }),
|
|
167
|
+
/* @__PURE__ */ g("div", { className: "navigation-graph-stage", children: [
|
|
168
|
+
/* @__PURE__ */ l(
|
|
169
|
+
"canvas",
|
|
170
|
+
{
|
|
171
|
+
ref: H,
|
|
172
|
+
role: "img",
|
|
173
|
+
"aria-label": `Navigation graph with ${n.nodes.length} nodes and ${n.edges.length} edges`,
|
|
174
|
+
tabIndex: 0,
|
|
175
|
+
onPointerDown: (e) => {
|
|
176
|
+
N.current = !1, e.currentTarget.setPointerCapture(e.pointerId);
|
|
177
|
+
const t = e.currentTarget.getBoundingClientRect(), r = e.clientX - t.left, d = e.clientY - t.top, h = (r - t.width / 2 - a.current.x) / a.current.scale, s = (d - t.height / 2 - a.current.y) / a.current.scale, p = n.points.findIndex((k) => Math.hypot(k.x - h, k.y - s) < 10 / a.current.scale);
|
|
178
|
+
v.current = { x: r, y: d, startX: r, startY: d, node: p, moved: !1 }, p >= 0 && (n.points[p].pinned = !0);
|
|
179
|
+
},
|
|
180
|
+
onPointerMove: (e) => {
|
|
181
|
+
const t = v.current;
|
|
182
|
+
if (!t) return;
|
|
183
|
+
const r = e.currentTarget.getBoundingClientRect(), d = e.clientX - r.left, h = e.clientY - r.top;
|
|
184
|
+
if (t.moved || (t.moved = Math.hypot(d - t.startX, h - t.startY) > 3), t.node >= 0) {
|
|
185
|
+
const s = n.points[t.node];
|
|
186
|
+
s.x += (d - t.x) / a.current.scale, s.y += (h - t.y) / a.current.scale;
|
|
187
|
+
} else
|
|
188
|
+
a.current.x += d - t.x, a.current.y += h - t.y;
|
|
189
|
+
t.x = d, t.y = h, M.current();
|
|
190
|
+
},
|
|
191
|
+
onPointerUp: () => {
|
|
192
|
+
const e = v.current;
|
|
193
|
+
v.current = null, e && e.node >= 0 && (n.points[e.node].pinned = !1, e.moved ? (w.current = 0, O(!1), M.current()) : D(n.nodes[e.node].id));
|
|
194
|
+
},
|
|
195
|
+
onPointerCancel: () => {
|
|
196
|
+
v.current && v.current.node >= 0 && (n.points[v.current.node].pinned = !1), v.current = null;
|
|
197
|
+
},
|
|
198
|
+
onKeyDown: (e) => {
|
|
199
|
+
if (e.stopPropagation(), N.current = !1, e.key === "+" || e.key === "=") a.current.scale = Math.min(8, a.current.scale * 1.2);
|
|
200
|
+
else if (e.key === "-") a.current.scale = Math.max(0.03, a.current.scale / 1.2);
|
|
201
|
+
else if (e.key === "ArrowLeft") a.current.x += 40;
|
|
202
|
+
else if (e.key === "ArrowRight") a.current.x -= 40;
|
|
203
|
+
else if (e.key === "ArrowUp") a.current.y += 40;
|
|
204
|
+
else if (e.key === "ArrowDown") a.current.y -= 40;
|
|
205
|
+
else if (e.key === "Escape") K();
|
|
206
|
+
else return;
|
|
207
|
+
e.preventDefault(), M.current();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
),
|
|
211
|
+
!n.nodes.length && /* @__PURE__ */ l("p", { className: "navigation-graph-empty", children: "No navigation nodes on this floor." })
|
|
212
|
+
] }),
|
|
213
|
+
/* @__PURE__ */ g("div", { className: "navigation-graph-legend", children: [
|
|
214
|
+
Object.entries(U).map(([e, t]) => /* @__PURE__ */ g("span", { children: [
|
|
215
|
+
/* @__PURE__ */ l("i", { style: { background: t } }),
|
|
216
|
+
e === "stairs" ? "stairs / escalators" : e
|
|
217
|
+
] }, e)),
|
|
218
|
+
/* @__PURE__ */ l("span", { children: "Arrows: one-way travel" }),
|
|
219
|
+
/* @__PURE__ */ g("span", { children: [
|
|
220
|
+
n.nodes.length,
|
|
221
|
+
" nodes · ",
|
|
222
|
+
n.edges.length,
|
|
223
|
+
" edges ·",
|
|
224
|
+
" ",
|
|
225
|
+
F ? de ? "Balanced" : "Balancing…" : "Layout paused"
|
|
226
|
+
] })
|
|
227
|
+
] }),
|
|
228
|
+
/* @__PURE__ */ l("p", { className: "navigation-graph-help", children: "Drag nodes to arrange · drag background to pan · scroll to zoom. Layout changes only this view." })
|
|
229
|
+
] });
|
|
230
|
+
}
|
|
231
|
+
export {
|
|
232
|
+
we as NavigationGraph
|
|
233
|
+
};
|
|
234
|
+
//# sourceMappingURL=NavigationGraph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NavigationGraph.js","sources":["../../../../src/components/NavigationGraph.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react';\nimport { LocateFixed } from 'lucide-react';\nimport { navEdges, navNodes } from '../model/navigation';\nimport type { ProjectDocument } from '../model/types';\nimport type { StructureTarget } from './StructureView';\nimport { balanceGraph, type GraphPoint } from './graphLayout';\n\nconst COLORS = { walk: '#8393a9', door: '#c28332', stairs: '#2eaaa0', elevator: '#9777f1' };\nconst FLOORS = ['#7367e6', '#2aabb3', '#d9a54b', '#ce6f91', '#5d9d6e', '#a67fc4'];\nexport interface NavigationGraphProps {\n project: ProjectDocument;\n initialFloor?: string | null;\n dark: boolean;\n onLocate: (target: StructureTarget) => void;\n onClose: () => void;\n}\nexport function NavigationGraph({ project, initialFloor, dark, onLocate, onClose }: NavigationGraphProps) {\n const [floor, setFloor] = useState(initialFloor === undefined ? 'all' : (initialFloor ?? 'outdoors'));\n const [auto, setAuto] = useState(true);\n const [query, setQuery] = useState('');\n const [selected, setSelected] = useState('');\n const [settled, setSettled] = useState(false);\n const canvas = useRef<HTMLCanvasElement>(null);\n const view = useRef({ x: 0, y: 0, scale: 1 });\n const redraw = useRef<() => void>(() => {});\n const fit = useRef<() => void>(() => {});\n const tick = useRef(0);\n const selectedRef = useRef(selected);\n selectedRef.current = selected;\n const autoRef = useRef(auto);\n autoRef.current = auto;\n const autoFit = useRef(true);\n const graph = useMemo(() => {\n const nodes = navNodes(project).filter(node => floor === 'all' || (node.floorId ?? 'outdoors') === floor);\n const byId = new Map(nodes.map((node, i) => [node.id, i]));\n const edges = navEdges(project).filter(edge => byId.has(edge.aId) && byId.has(edge.bId));\n const points: GraphPoint[] = nodes.map((_node, i) => {\n const angle = i * 2.399963,\n radius = 15 * Math.sqrt(i + 1);\n return { x: Math.cos(angle) * radius, y: Math.sin(angle) * radius, vx: 0, vy: 0 };\n });\n const objects = new Map(project.objects.map(object => [object.id, object]));\n return {\n nodes,\n edges,\n points,\n links: edges.map(edge => [byId.get(edge.aId)!, byId.get(edge.bId)!] as [number, number]),\n objects,\n };\n }, [project, floor]);\n const chosen = graph.nodes.find(node => node.id === selected);\n const nodeName = (index: number) => {\n const node = graph.nodes[index];\n return graph.objects.get(node.objectId ?? '')?.name || node.id;\n };\n const floorName = (id: string | null) =>\n id === null ? 'Outdoor site' : (project.floors.find(f => f.id === id)?.name ?? id);\n const locate = () => {\n if (chosen)\n onLocate({\n objectIds: chosen.objectId ? [chosen.objectId] : [],\n floorId: chosen.floorId,\n position: chosen.position,\n });\n };\n useEffect(() => {\n tick.current = 0;\n setSettled(false);\n setSelected('');\n autoFit.current = true;\n }, [graph]);\n useEffect(() => {\n const element = canvas.current,\n context = element?.getContext('2d');\n if (!element || !context) return;\n let width = 1,\n height = 1,\n frame = 0,\n lastFrame = 0,\n disposed = false;\n const colors = new Map(project.floors.map((f, i) => [f.id, FLOORS[i % FLOORS.length]]));\n const draw = () => {\n const selected = selectedRef.current;\n const { x, y, scale } = view.current;\n context.clearRect(0, 0, width, height);\n context.save();\n context.translate(width / 2 + x, height / 2 + y);\n context.scale(scale, scale);\n graph.edges.forEach((edge, i) => {\n const [a, b] = graph.links[i],\n p = graph.points[a],\n q = graph.points[b];\n const highlight = edge.aId === selected || edge.bId === selected;\n context.globalAlpha = selected && !highlight ? 0.18 : 0.65;\n context.strokeStyle = COLORS[edge.kind];\n context.lineWidth = (highlight ? 2.5 : 1) / scale;\n context.beginPath();\n context.moveTo(p.x, p.y);\n context.lineTo(q.x, q.y);\n context.stroke();\n if (edge.directed) {\n const angle = Math.atan2(q.y - p.y, q.x - p.x),\n length = 7 / scale;\n const ax = q.x - (Math.cos(angle) * 7) / scale,\n ay = q.y - (Math.sin(angle) * 7) / scale;\n context.fillStyle = COLORS[edge.kind];\n context.beginPath();\n context.moveTo(ax, ay);\n context.lineTo(ax - Math.cos(angle - 0.5) * length, ay - Math.sin(angle - 0.5) * length);\n context.lineTo(ax - Math.cos(angle + 0.5) * length, ay - Math.sin(angle + 0.5) * length);\n context.closePath();\n context.fill();\n }\n });\n context.globalAlpha = 1;\n graph.nodes.forEach((node, i) => {\n const p = graph.points[i],\n active = node.id === selected;\n context.beginPath();\n context.arc(p.x, p.y, (active ? 7 : 4) / scale, 0, Math.PI * 2);\n context.fillStyle = colors.get(node.floorId ?? '') ?? '#8b94a4';\n context.fill();\n if (active) {\n context.strokeStyle = dark ? '#fff' : '#191c32';\n context.lineWidth = 2 / scale;\n context.stroke();\n }\n if (active || (graph.nodes.length < 65 && scale > 0.45)) {\n context.font = `${12 / scale}px system-ui`;\n context.fillStyle = dark ? '#eeeefa' : '#24263a';\n context.fillText(nodeName(i).slice(0, 38), p.x + 9 / scale, p.y - 7 / scale);\n }\n });\n context.restore();\n };\n const fitGraph = () => {\n if (!graph.points.length) return;\n let left = Infinity,\n right = -Infinity,\n top = Infinity,\n bottom = -Infinity;\n for (const p of graph.points) {\n left = Math.min(left, p.x);\n right = Math.max(right, p.x);\n top = Math.min(top, p.y);\n bottom = Math.max(bottom, p.y);\n }\n const scale = Math.max(\n 0.05,\n Math.min(3, (width - 90) / Math.max(1, right - left), (height - 90) / Math.max(1, bottom - top)),\n );\n view.current = { x: (-(left + right) * scale) / 2, y: (-(top + bottom) * scale) / 2, scale };\n draw();\n };\n const animate = (time: number) => {\n frame = 0;\n if (disposed) return;\n if (autoRef.current && tick.current < 360) {\n if (time - lastFrame > 30) {\n balanceGraph(graph.points, graph.links, tick.current++);\n if (autoFit.current && tick.current % 20 === 0) fitGraph();\n else draw();\n lastFrame = time;\n }\n frame = requestAnimationFrame(animate);\n } else {\n draw();\n if (tick.current >= 360) setSettled(true);\n }\n };\n const wake = () => {\n draw();\n if (!frame && !disposed) frame = requestAnimationFrame(animate);\n };\n redraw.current = wake;\n fit.current = fitGraph;\n const resize = new ResizeObserver(() => {\n const box = element.getBoundingClientRect();\n width = box.width;\n height = box.height;\n const ratio = Math.min(2, window.devicePixelRatio || 1);\n element.width = Math.round(width * ratio);\n element.height = Math.round(height * ratio);\n context.setTransform(ratio, 0, 0, ratio, 0, 0);\n fitGraph();\n wake();\n });\n const wheel = (event: WheelEvent) => {\n event.preventDefault();\n autoFit.current = false;\n const box = element.getBoundingClientRect(),\n px = event.clientX - box.left - box.width / 2,\n py = event.clientY - box.top - box.height / 2;\n const old = view.current.scale,\n scale = Math.max(0.03, Math.min(8, old * Math.exp(-event.deltaY * 0.001)));\n view.current = {\n scale,\n x: px - ((px - view.current.x) * scale) / old,\n y: py - ((py - view.current.y) * scale) / old,\n };\n wake();\n };\n element.addEventListener('wheel', wheel, { passive: false });\n resize.observe(element);\n frame = requestAnimationFrame(animate);\n return () => {\n disposed = true;\n cancelAnimationFrame(frame);\n resize.disconnect();\n element.removeEventListener('wheel', wheel);\n };\n }, [graph, dark, project]);\n useEffect(() => redraw.current(), [auto, selected]);\n const drag = useRef<{ x: number; y: number; startX: number; startY: number; node: number; moved: boolean } | null>(\n null,\n );\n const controls = graph.nodes\n .map((node, i) => ({ node, i, label: `${nodeName(i)} · ${floorName(node.floorId)}` }))\n .filter(row => row.label.toLowerCase().includes(query.toLowerCase()))\n .slice(0, 100);\n return (\n <section className=\"navigation-graph\" aria-label=\"Navigation graph view\">\n <div className=\"navigation-graph-toolbar\">\n <strong>Navigation graph</strong>\n <select aria-label=\"Graph floor\" value={floor} onChange={event => setFloor(event.target.value)}>\n <option value=\"all\">All floors</option>\n <option value=\"outdoors\">Outdoor site</option>\n {project.floors.map(f => (\n <option key={f.id} value={f.id}>\n {floorName(f.id)}\n </option>\n ))}\n </select>\n <label>\n <input type=\"checkbox\" checked={auto} onChange={event => setAuto(event.target.checked)} />\n Auto balance\n </label>\n <button\n className=\"button secondary small\"\n onClick={() => {\n tick.current = 0;\n setSettled(false);\n setAuto(true);\n redraw.current();\n }}\n >\n Rebalance\n </button>\n <button className=\"button secondary small\" onClick={() => fit.current()}>\n Fit graph\n </button>\n <button className=\"button secondary small\" onClick={onClose}>\n Back to map\n </button>\n </div>\n <div className=\"navigation-graph-search\">\n <input\n type=\"search\"\n aria-label=\"Search graph nodes\"\n placeholder=\"Find a room or transport node…\"\n value={query}\n onChange={event => setQuery(event.target.value)}\n />\n <select\n aria-label=\"Graph node\"\n value={selected}\n onChange={event => {\n autoFit.current = false;\n const i = graph.nodes.findIndex(n => n.id === event.target.value);\n setSelected(event.target.value);\n if (i >= 0) {\n const p = graph.points[i];\n view.current.x = -p.x * view.current.scale;\n view.current.y = -p.y * view.current.scale;\n redraw.current();\n }\n }}\n >\n <option value=\"\">Select a node ({graph.nodes.length})</option>\n {chosen && !controls.some(row => row.node.id === chosen.id) && <option value={chosen.id}>{chosen.id}</option>}\n {controls.map(row => (\n <option value={row.node.id} key={row.node.id}>\n {row.label}\n </option>\n ))}\n </select>\n <button\n className=\"icon-button\"\n aria-label=\"Find node on map\"\n title=\"Find on map\"\n disabled={!chosen}\n onClick={locate}\n >\n <LocateFixed size={16} />\n </button>\n </div>\n <div className=\"navigation-graph-stage\">\n <canvas\n ref={canvas}\n role=\"img\"\n aria-label={`Navigation graph with ${graph.nodes.length} nodes and ${graph.edges.length} edges`}\n tabIndex={0}\n onPointerDown={event => {\n autoFit.current = false;\n event.currentTarget.setPointerCapture(event.pointerId);\n const box = event.currentTarget.getBoundingClientRect(),\n x = event.clientX - box.left,\n y = event.clientY - box.top;\n const gx = (x - box.width / 2 - view.current.x) / view.current.scale,\n gy = (y - box.height / 2 - view.current.y) / view.current.scale;\n const node = graph.points.findIndex(p => Math.hypot(p.x - gx, p.y - gy) < 10 / view.current.scale);\n drag.current = { x, y, startX: x, startY: y, node, moved: false };\n if (node >= 0) graph.points[node].pinned = true;\n }}\n onPointerMove={event => {\n const current = drag.current;\n if (!current) return;\n const box = event.currentTarget.getBoundingClientRect(),\n x = event.clientX - box.left,\n y = event.clientY - box.top;\n current.moved ||= Math.hypot(x - current.startX, y - current.startY) > 3;\n if (current.node >= 0) {\n const p = graph.points[current.node];\n p.x += (x - current.x) / view.current.scale;\n p.y += (y - current.y) / view.current.scale;\n } else {\n view.current.x += x - current.x;\n view.current.y += y - current.y;\n }\n current.x = x;\n current.y = y;\n redraw.current();\n }}\n onPointerUp={() => {\n const current = drag.current;\n drag.current = null;\n if (current && current.node >= 0) {\n graph.points[current.node].pinned = false;\n if (!current.moved) setSelected(graph.nodes[current.node].id);\n else {\n tick.current = 0;\n setSettled(false);\n redraw.current();\n }\n }\n }}\n onPointerCancel={() => {\n if (drag.current && drag.current.node >= 0) graph.points[drag.current.node].pinned = false;\n drag.current = null;\n }}\n onKeyDown={event => {\n event.stopPropagation();\n autoFit.current = false;\n if (event.key === '+' || event.key === '=') view.current.scale = Math.min(8, view.current.scale * 1.2);\n else if (event.key === '-') view.current.scale = Math.max(0.03, view.current.scale / 1.2);\n else if (event.key === 'ArrowLeft') view.current.x += 40;\n else if (event.key === 'ArrowRight') view.current.x -= 40;\n else if (event.key === 'ArrowUp') view.current.y += 40;\n else if (event.key === 'ArrowDown') view.current.y -= 40;\n else if (event.key === 'Escape') onClose();\n else return;\n event.preventDefault();\n redraw.current();\n }}\n />\n {!graph.nodes.length && <p className=\"navigation-graph-empty\">No navigation nodes on this floor.</p>}\n </div>\n <div className=\"navigation-graph-legend\">\n {Object.entries(COLORS).map(([kind, color]) => (\n <span key={kind}>\n <i style={{ background: color }} />\n {kind === 'stairs' ? 'stairs / escalators' : kind}\n </span>\n ))}\n <span>Arrows: one-way travel</span>\n <span>\n {graph.nodes.length} nodes · {graph.edges.length} edges ·{' '}\n {auto ? (settled ? 'Balanced' : 'Balancing…') : 'Layout paused'}\n </span>\n </div>\n <p className=\"navigation-graph-help\">\n Drag nodes to arrange · drag background to pan · scroll to zoom. Layout changes only this view.\n </p>\n </section>\n );\n}\n"],"names":["COLORS","FLOORS","NavigationGraph","project","initialFloor","dark","onLocate","onClose","floor","setFloor","useState","auto","setAuto","query","setQuery","selected","setSelected","settled","setSettled","canvas","useRef","view","redraw","fit","tick","selectedRef","autoRef","autoFit","graph","useMemo","nodes","navNodes","node","byId","i","edges","navEdges","edge","points","_node","angle","radius","objects","object","chosen","nodeName","index","_a","floorName","id","f","locate","useEffect","element","context","width","height","frame","lastFrame","disposed","colors","draw","x","scale","a","b","p","q","highlight","length","ax","ay","active","fitGraph","left","right","top","bottom","animate","time","balanceGraph","wake","resize","box","ratio","wheel","event","px","py","old","drag","controls","row","jsxs","jsx","n","LocateFixed","y","gx","gy","current","kind","color"],"mappings":"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"}
|