@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.
Files changed (264) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +31 -2
  3. package/dist/adapters/basemap.js +11 -0
  4. package/dist/adapters/basemap.js.map +1 -0
  5. package/dist/adapters/persistence.js +121 -0
  6. package/dist/adapters/persistence.js.map +1 -0
  7. package/dist/adapters/status.js +18 -0
  8. package/dist/adapters/status.js.map +1 -0
  9. package/dist/components/ElevatorPanel.js +120 -0
  10. package/dist/components/ElevatorPanel.js.map +1 -0
  11. package/dist/components/Icons.js +57 -0
  12. package/dist/components/Icons.js.map +1 -0
  13. package/dist/components/Inspector.js +1184 -0
  14. package/dist/components/Inspector.js.map +1 -0
  15. package/dist/components/NavigatePanel.js +301 -0
  16. package/dist/components/NavigatePanel.js.map +1 -0
  17. package/dist/components/NavigationGraph.js +234 -0
  18. package/dist/components/NavigationGraph.js.map +1 -0
  19. package/dist/components/StructureExplorer.js +510 -0
  20. package/dist/components/StructureExplorer.js.map +1 -0
  21. package/dist/components/StructureView.js +286 -0
  22. package/dist/components/StructureView.js.map +1 -0
  23. package/dist/components/controls.js +170 -0
  24. package/dist/components/controls.js.map +1 -0
  25. package/dist/components/graphLayout.js +27 -0
  26. package/dist/components/graphLayout.js.map +1 -0
  27. package/dist/host.js +169 -0
  28. package/dist/host.js.map +1 -0
  29. package/dist/i18n.js +88 -0
  30. package/dist/i18n.js.map +1 -0
  31. package/dist/index.js +171 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/map/CabinMaterials.js +81 -0
  34. package/dist/map/CabinMaterials.js.map +1 -0
  35. package/dist/map/CabinMirror.js +43 -0
  36. package/dist/map/CabinMirror.js.map +1 -0
  37. package/dist/map/Doors.js +80 -0
  38. package/dist/map/Doors.js.map +1 -0
  39. package/dist/map/ElevatorCabin.js +93 -0
  40. package/dist/map/ElevatorCabin.js.map +1 -0
  41. package/dist/map/FixtureLights.js +117 -0
  42. package/dist/map/FixtureLights.js.map +1 -0
  43. package/dist/map/MapCanvas.js +2161 -0
  44. package/dist/map/MapCanvas.js.map +1 -0
  45. package/dist/map/Passenger.js +69 -0
  46. package/dist/map/Passenger.js.map +1 -0
  47. package/dist/map/SceneLayer.js +1534 -0
  48. package/dist/map/SceneLayer.js.map +1 -0
  49. package/dist/map/UndergroundContext.js +153 -0
  50. package/dist/map/UndergroundContext.js.map +1 -0
  51. package/dist/map/ambience.js +194 -0
  52. package/dist/map/ambience.js.map +1 -0
  53. package/dist/map/architecture.js +305 -0
  54. package/dist/map/architecture.js.map +1 -0
  55. package/dist/map/bathMusic.js +92 -0
  56. package/dist/map/bathMusic.js.map +1 -0
  57. package/dist/map/cameraAim.js +33 -0
  58. package/dist/map/cameraAim.js.map +1 -0
  59. package/dist/map/elevators.js +56 -0
  60. package/dist/map/elevators.js.map +1 -0
  61. package/dist/map/excavation.js +52 -0
  62. package/dist/map/excavation.js.map +1 -0
  63. package/dist/map/exteriors.js +25 -0
  64. package/dist/map/exteriors.js.map +1 -0
  65. package/dist/map/features.js +218 -0
  66. package/dist/map/features.js.map +1 -0
  67. package/dist/map/fences.js +29 -0
  68. package/dist/map/fences.js.map +1 -0
  69. package/dist/map/journey.js +112 -0
  70. package/dist/map/journey.js.map +1 -0
  71. package/dist/map/levels.js +6 -0
  72. package/dist/map/levels.js.map +1 -0
  73. package/dist/map/lighting.js +142 -0
  74. package/dist/map/lighting.js.map +1 -0
  75. package/dist/map/loadBasemap.js +26 -0
  76. package/dist/map/loadBasemap.js.map +1 -0
  77. package/dist/map/materials.js +183 -0
  78. package/dist/map/materials.js.map +1 -0
  79. package/dist/map/poolLighting.js +39 -0
  80. package/dist/map/poolLighting.js.map +1 -0
  81. package/dist/map/projection.js +24 -0
  82. package/dist/map/projection.js.map +1 -0
  83. package/dist/map/rampGeometry.js +38 -0
  84. package/dist/map/rampGeometry.js.map +1 -0
  85. package/dist/map/route.js +107 -0
  86. package/dist/map/route.js.map +1 -0
  87. package/dist/map/stairSurfaces.js +115 -0
  88. package/dist/map/stairSurfaces.js.map +1 -0
  89. package/dist/map/surfaces.js +136 -0
  90. package/dist/map/surfaces.js.map +1 -0
  91. package/dist/map/textures.js +95 -0
  92. package/dist/map/textures.js.map +1 -0
  93. package/dist/map/underground.js +34 -0
  94. package/dist/map/underground.js.map +1 -0
  95. package/dist/map/walk.js +433 -0
  96. package/dist/map/walk.js.map +1 -0
  97. package/dist/map/walkContext.js +46 -0
  98. package/dist/map/walkContext.js.map +1 -0
  99. package/dist/map/walkDimensions.js +7 -0
  100. package/dist/map/walkDimensions.js.map +1 -0
  101. package/dist/map/walkJourney.js +85 -0
  102. package/dist/map/walkJourney.js.map +1 -0
  103. package/dist/map/walkSurfaces.js +106 -0
  104. package/dist/map/walkSurfaces.js.map +1 -0
  105. package/dist/map/wallJoins.js +70 -0
  106. package/dist/map/wallJoins.js.map +1 -0
  107. package/dist/map/water.js +274 -0
  108. package/dist/map/water.js.map +1 -0
  109. package/dist/model/authoring.js +80 -0
  110. package/dist/model/authoring.js.map +1 -0
  111. package/dist/model/boundaries.js +548 -0
  112. package/dist/model/boundaries.js.map +1 -0
  113. package/dist/model/coverage.js +30 -0
  114. package/dist/model/coverage.js.map +1 -0
  115. package/dist/model/document.js +52 -0
  116. package/dist/model/document.js.map +1 -0
  117. package/dist/model/doors.js +47 -0
  118. package/dist/model/doors.js.map +1 -0
  119. package/dist/model/factory.js +56 -0
  120. package/dist/model/factory.js.map +1 -0
  121. package/dist/model/geometry.js +394 -0
  122. package/dist/model/geometry.js.map +1 -0
  123. package/dist/model/inference.js +182 -0
  124. package/dist/model/inference.js.map +1 -0
  125. package/dist/model/materials.js +11 -0
  126. package/dist/model/materials.js.map +1 -0
  127. package/dist/model/mutations.js +157 -0
  128. package/dist/model/mutations.js.map +1 -0
  129. package/dist/model/navigation.js +275 -0
  130. package/dist/model/navigation.js.map +1 -0
  131. package/dist/model/ontology.js +109 -0
  132. package/dist/model/ontology.js.map +1 -0
  133. package/dist/model/passages.js +5 -0
  134. package/dist/model/passages.js.map +1 -0
  135. package/dist/model/portals.js +42 -0
  136. package/dist/model/portals.js.map +1 -0
  137. package/dist/model/precision.js +11 -0
  138. package/dist/model/precision.js.map +1 -0
  139. package/dist/model/project.js +17 -0
  140. package/dist/model/project.js.map +1 -0
  141. package/dist/model/ramps.js +10 -0
  142. package/dist/model/ramps.js.map +1 -0
  143. package/dist/model/roomNavigation.js +87 -0
  144. package/dist/model/roomNavigation.js.map +1 -0
  145. package/dist/model/routeClearance.js +72 -0
  146. package/dist/model/routeClearance.js.map +1 -0
  147. package/dist/model/spaces.js +104 -0
  148. package/dist/model/spaces.js.map +1 -0
  149. package/dist/model/stairGeometry.js +40 -0
  150. package/dist/model/stairGeometry.js.map +1 -0
  151. package/dist/model/stairTraversal.js +48 -0
  152. package/dist/model/stairTraversal.js.map +1 -0
  153. package/dist/model/topology.js +63 -0
  154. package/dist/model/topology.js.map +1 -0
  155. package/dist/model/types.js +35 -0
  156. package/dist/model/types.js.map +1 -0
  157. package/dist/model/validate.js +240 -0
  158. package/dist/model/validate.js.map +1 -0
  159. package/dist/model/vertical.js +129 -0
  160. package/dist/model/vertical.js.map +1 -0
  161. package/dist/model/walls.js +71 -0
  162. package/dist/model/walls.js.map +1 -0
  163. package/dist/styles.css +4931 -0
  164. package/dist/theme/index.js +34 -0
  165. package/dist/theme/index.js.map +1 -0
  166. package/dist/types/adapters/basemap.d.ts +2 -0
  167. package/dist/types/adapters/persistence.d.ts +29 -0
  168. package/dist/types/adapters/status.d.ts +4 -0
  169. package/dist/types/components/ElevatorPanel.d.ts +18 -0
  170. package/dist/types/components/Icons.d.ts +8 -0
  171. package/dist/types/components/Inspector.d.ts +38 -0
  172. package/dist/types/components/NavigatePanel.d.ts +27 -0
  173. package/dist/types/components/NavigationGraph.d.ts +10 -0
  174. package/dist/types/components/StructureExplorer.d.ts +8 -0
  175. package/dist/types/components/StructureView.d.ts +22 -0
  176. package/dist/types/components/controls.d.ts +52 -0
  177. package/dist/types/components/graphLayout.d.ts +9 -0
  178. package/dist/types/i18n.d.ts +85 -0
  179. package/dist/types/import/analysisPreview.d.ts +27 -0
  180. package/dist/types/import/brief.d.ts +22 -0
  181. package/dist/types/import/checkpoint.d.ts +49 -0
  182. package/dist/types/import/instructions.d.ts +7 -0
  183. package/dist/types/import/usage.d.ts +10 -0
  184. package/dist/types/map/CabinMaterials.d.ts +18 -0
  185. package/dist/types/map/CabinMirror.d.ts +13 -0
  186. package/dist/types/map/Doors.d.ts +11 -0
  187. package/dist/types/map/ElevatorCabin.d.ts +20 -0
  188. package/dist/types/map/FixtureLights.d.ts +35 -0
  189. package/dist/types/map/MapCanvas.d.ts +87 -0
  190. package/dist/types/map/Passenger.d.ts +18 -0
  191. package/dist/types/map/SceneLayer.d.ts +306 -0
  192. package/dist/types/map/UndergroundContext.d.ts +18 -0
  193. package/dist/types/map/ambience.d.ts +29 -0
  194. package/dist/types/map/architecture.d.ts +8 -0
  195. package/dist/types/map/bathMusic.d.ts +14 -0
  196. package/dist/types/map/cameraAim.d.ts +20 -0
  197. package/dist/types/map/elevators.d.ts +16 -0
  198. package/dist/types/map/excavation.d.ts +9 -0
  199. package/dist/types/map/exteriors.d.ts +3 -0
  200. package/dist/types/map/features.d.ts +33 -0
  201. package/dist/types/map/fences.d.ts +4 -0
  202. package/dist/types/map/journey.d.ts +36 -0
  203. package/dist/types/map/levels.d.ts +4 -0
  204. package/dist/types/map/lighting.d.ts +103 -0
  205. package/dist/types/map/loadBasemap.d.ts +4 -0
  206. package/dist/types/map/materials.d.ts +38 -0
  207. package/dist/types/map/poolLighting.d.ts +11 -0
  208. package/dist/types/map/projection.d.ts +9 -0
  209. package/dist/types/map/rampGeometry.d.ts +8 -0
  210. package/dist/types/map/route.d.ts +59 -0
  211. package/dist/types/map/stairSurfaces.d.ts +29 -0
  212. package/dist/types/map/surfaces.d.ts +36 -0
  213. package/dist/types/map/textures.d.ts +8 -0
  214. package/dist/types/map/underground.d.ts +30 -0
  215. package/dist/types/map/walk.d.ts +232 -0
  216. package/dist/types/map/walkContext.d.ts +11 -0
  217. package/dist/types/map/walkDimensions.d.ts +5 -0
  218. package/dist/types/map/walkJourney.d.ts +33 -0
  219. package/dist/types/map/walkSurfaces.d.ts +27 -0
  220. package/dist/types/map/wallJoins.d.ts +6 -0
  221. package/dist/types/map/water.d.ts +12 -0
  222. package/dist/types/model/authoring.d.ts +38 -0
  223. package/dist/types/model/boundaries.d.ts +41 -0
  224. package/dist/types/model/coverage.d.ts +8 -0
  225. package/dist/types/model/document.d.ts +9 -0
  226. package/dist/types/model/doors.d.ts +19 -0
  227. package/dist/types/model/factory.d.ts +5 -0
  228. package/dist/types/model/geometry.d.ts +99 -0
  229. package/dist/types/model/host.d.ts +154 -0
  230. package/dist/types/model/inference.d.ts +46 -0
  231. package/dist/types/model/live.d.ts +60 -0
  232. package/dist/types/model/materials.d.ts +28 -0
  233. package/dist/types/model/mutations.d.ts +178 -0
  234. package/dist/types/model/navigation.d.ts +70 -0
  235. package/dist/types/model/ontology.d.ts +43 -0
  236. package/dist/types/model/passages.d.ts +2 -0
  237. package/dist/types/model/portals.d.ts +9 -0
  238. package/dist/types/model/precision.d.ts +11 -0
  239. package/dist/types/model/project.d.ts +8 -0
  240. package/dist/types/model/ramps.d.ts +7 -0
  241. package/dist/types/model/roomNavigation.d.ts +15 -0
  242. package/dist/types/model/routeClearance.d.ts +11 -0
  243. package/dist/types/model/spaces.d.ts +46 -0
  244. package/dist/types/model/stairGeometry.d.ts +27 -0
  245. package/dist/types/model/stairTraversal.d.ts +8 -0
  246. package/dist/types/model/topology.d.ts +14 -0
  247. package/dist/types/model/types.d.ts +369 -0
  248. package/dist/types/model/validate.d.ts +38 -0
  249. package/dist/types/model/vertical.d.ts +145 -0
  250. package/dist/types/model/walls.d.ts +71 -0
  251. package/dist/types/schema/index.d.ts +25 -0
  252. package/dist/types/theme/index.d.ts +55 -0
  253. package/dist/types/viewer/FloorViewer.d.ts +76 -0
  254. package/dist/types/viewer/ViewerWorkspace.d.ts +2 -0
  255. package/dist/types/viewer/host.d.ts +11 -0
  256. package/dist/types/viewer/index.d.ts +2 -0
  257. package/dist/types/viewer/panels.d.ts +12 -0
  258. package/dist/viewer/FloorViewer.js +104 -0
  259. package/dist/viewer/FloorViewer.js.map +1 -0
  260. package/dist/viewer/ViewerWorkspace.js +370 -0
  261. package/dist/viewer/ViewerWorkspace.js.map +1 -0
  262. package/dist/viewer/panels.js +35 -0
  263. package/dist/viewer/panels.js.map +1 -0
  264. package/package.json +52 -4
@@ -0,0 +1,286 @@
1
+ import { jsx as e, jsxs as a, Fragment as A } from "react/jsx-runtime";
2
+ import { lazy as T, Suspense as B, useState as H, useMemo as V } from "react";
3
+ import { isSpace as W } from "../model/types.js";
4
+ import { zoneSpaces as D, addZone as j, perimeter as G, captive as J, setZoneMembers as K, removeZone as Q, entryInto as L } from "../model/ontology.js";
5
+ import { refreshPortals as X } from "../model/inference.js";
6
+ import { Plus as Y, RefreshCw as _, Shapes as q, ChevronDown as E, ChevronRight as ee, LocateFixed as R, Trash2 as ne, DoorOpen as te, MoveHorizontal as se, Layers as ae } from "lucide-react";
7
+ const ie = T(
8
+ () => import("./StructureExplorer.js").then((s) => ({ default: s.StructureExplorer }))
9
+ ), le = "Outdoor site";
10
+ function re(s, p) {
11
+ const g = L(s, p) !== null, N = L(s, s.a === p ? s.b : s.a) !== null;
12
+ return g && N ? "both ways" : g ? "in only" : N ? "out only" : "sealed";
13
+ }
14
+ function be(s) {
15
+ return /* @__PURE__ */ e(
16
+ B,
17
+ {
18
+ fallback: /* @__PURE__ */ e("p", { className: "structure-empty", role: "status", children: "Loading structure…" }),
19
+ children: /* @__PURE__ */ e(ie, { ...s, renderZones: (p) => /* @__PURE__ */ e(ce, { ...s, ...p }) })
20
+ }
21
+ );
22
+ }
23
+ function ce({
24
+ project: s,
25
+ selected: p,
26
+ onSelect: g,
27
+ onFloorChange: N,
28
+ onEdit: h,
29
+ onLocate: y,
30
+ query: $,
31
+ floor: S
32
+ }) {
33
+ var F;
34
+ const [P, U] = H(null), c = p && s.objects.find((r) => r.id === p && W(r.kind)), I = V(() => {
35
+ const r = new Map(s.objects.map((i) => [i.id, i])), u = new Map(s.floors.map((i) => [i.id, i.name])), t = s.zones ?? [], m = /* @__PURE__ */ new Map();
36
+ for (const i of t) {
37
+ const b = D(s, i).map((o) => r.get(o)).filter(Boolean), z = [i.name, i.id, i.purpose, ...b.map((o) => o == null ? void 0 : o.name)].join(" ").toLowerCase();
38
+ if ($.trim() && !z.includes($.trim().toLowerCase()) || S !== "all" && !b.some((o) => {
39
+ var n;
40
+ return ((o == null ? void 0 : o.floorId) ?? "outdoors") === S || ((n = o == null ? void 0 : o.servedFloorIds) == null ? void 0 : n.includes(S));
41
+ }))
42
+ continue;
43
+ const f = i.purpose ?? "other", k = m.get(f);
44
+ k ? k.push(i) : m.set(f, [i]);
45
+ }
46
+ return { byId: r, floors: u, groups: m };
47
+ }, [s, $, S]), w = (r) => {
48
+ var u;
49
+ return ((u = I.byId.get(r)) == null ? void 0 : u.name) ?? "Unknown";
50
+ }, x = (r) => {
51
+ var t;
52
+ const u = ((t = I.byId.get(r)) == null ? void 0 : t.floorId) ?? null;
53
+ return { floorId: u, label: u === null ? le : I.floors.get(u) ?? "Unknown level" };
54
+ }, Z = (r) => {
55
+ g == null || g(r), N == null || N(x(r).floorId);
56
+ }, M = h ? /* @__PURE__ */ a("div", { className: "structure-actions", children: [
57
+ /* @__PURE__ */ a(
58
+ "button",
59
+ {
60
+ type: "button",
61
+ className: "text-button",
62
+ disabled: !c,
63
+ title: c ? `New zone containing ${c.name}` : "Select an area on the plan first",
64
+ onClick: () => {
65
+ c && h((r) => j(r, `${c.name} zone`, [c.id], "security"));
66
+ },
67
+ children: [
68
+ /* @__PURE__ */ e(Y, { size: 13 }),
69
+ " New zone"
70
+ ]
71
+ }
72
+ ),
73
+ /* @__PURE__ */ a(
74
+ "button",
75
+ {
76
+ type: "button",
77
+ className: "text-button",
78
+ title: "Re-read portals from the plan, keeping any you have edited",
79
+ onClick: () => h((r) => X(r)),
80
+ children: [
81
+ /* @__PURE__ */ e(_, { size: 13 }),
82
+ " Re-read portals"
83
+ ]
84
+ }
85
+ )
86
+ ] }) : null;
87
+ return I.groups.size ? /* @__PURE__ */ a("div", { className: "structure-zone-list", children: [
88
+ M,
89
+ /* @__PURE__ */ e("div", { className: "inspector-scroll structure-scroll", children: [...I.groups].map(([r, u]) => /* @__PURE__ */ a("section", { className: "structure-group", children: [
90
+ /* @__PURE__ */ e("h3", { children: r }),
91
+ u.map((t) => {
92
+ var k, o;
93
+ const m = P === t.id, i = D(s, t), b = G(s, t), z = m ? J(s, t) : [], f = new Set(i);
94
+ return /* @__PURE__ */ a("div", { className: `structure-zone ${m ? "open" : ""}`, children: [
95
+ /* @__PURE__ */ a("div", { className: "structure-zone-toolbar", children: [
96
+ /* @__PURE__ */ a(
97
+ "button",
98
+ {
99
+ type: "button",
100
+ className: "structure-zone-head",
101
+ "aria-expanded": m,
102
+ onClick: () => U(m ? null : t.id),
103
+ children: [
104
+ m ? /* @__PURE__ */ e(E, { size: 14 }) : /* @__PURE__ */ e(ee, { size: 14 }),
105
+ /* @__PURE__ */ e("strong", { children: t.name }),
106
+ /* @__PURE__ */ a("span", { className: "structure-counts", children: [
107
+ i.length,
108
+ " ",
109
+ i.length === 1 ? "space" : "spaces",
110
+ b.length > 0 && ` · ${b.length} in`,
111
+ t.connects && ` · ${{ all: "lift", adjacent: "stairs", up: "escalator up", down: "escalator down" }[t.connects]}`
112
+ ] })
113
+ ]
114
+ }
115
+ ),
116
+ /* @__PURE__ */ e(
117
+ "button",
118
+ {
119
+ className: "structure-locate",
120
+ "aria-label": `Find ${t.name} on map`,
121
+ title: "Find on map",
122
+ disabled: !i.length,
123
+ onClick: () => y ? y({ objectIds: i }) : Z(i[0]),
124
+ children: /* @__PURE__ */ e(R, { size: 13 })
125
+ }
126
+ )
127
+ ] }),
128
+ m && /* @__PURE__ */ a("div", { className: "structure-detail", children: [
129
+ h && /* @__PURE__ */ a("div", { className: "structure-edit", children: [
130
+ /* @__PURE__ */ e(
131
+ "input",
132
+ {
133
+ "aria-label": "Zone name",
134
+ value: t.name,
135
+ onChange: (n) => {
136
+ const l = n.target.value;
137
+ h((d) => {
138
+ var C;
139
+ const v = (C = d.zones) == null ? void 0 : C.find((O) => O.id === t.id);
140
+ v && (v.name = l);
141
+ });
142
+ }
143
+ }
144
+ ),
145
+ /* @__PURE__ */ a(
146
+ "select",
147
+ {
148
+ "aria-label": "Connects",
149
+ value: t.connects ?? "",
150
+ onChange: (n) => {
151
+ const l = n.target.value;
152
+ h((d) => {
153
+ var C;
154
+ const v = (C = d.zones) == null ? void 0 : C.find((O) => O.id === t.id);
155
+ v && (v.connects = l || void 0);
156
+ });
157
+ },
158
+ children: [
159
+ /* @__PURE__ */ e("option", { value: "", children: "No implied route" }),
160
+ /* @__PURE__ */ e("option", { value: "all", children: "Lift · any landing to any" }),
161
+ /* @__PURE__ */ e("option", { value: "adjacent", children: "Stair · level by level" }),
162
+ /* @__PURE__ */ e("option", { value: "up", children: "Escalator · up only" }),
163
+ /* @__PURE__ */ e("option", { value: "down", children: "Escalator · down only" })
164
+ ]
165
+ }
166
+ ),
167
+ /* @__PURE__ */ e(
168
+ "button",
169
+ {
170
+ type: "button",
171
+ className: "text-button",
172
+ disabled: !c,
173
+ title: c ? f.has(c.id) ? `Remove ${c.name}` : `Add ${c.name}` : "Select an area on the plan first",
174
+ onClick: () => {
175
+ if (!c) return;
176
+ const n = !f.has(c.id);
177
+ h((l) => K(l, t.id, [c.id], n));
178
+ },
179
+ children: c && f.has(c.id) ? "Remove selected" : "Add selected"
180
+ }
181
+ ),
182
+ /* @__PURE__ */ e(
183
+ "button",
184
+ {
185
+ type: "button",
186
+ className: "icon-button",
187
+ "aria-label": `Delete ${t.name}`,
188
+ onClick: () => h((n) => Q(n, t.id)),
189
+ children: /* @__PURE__ */ e(ne, { size: 14 })
190
+ }
191
+ )
192
+ ] }),
193
+ b.length > 0 && /* @__PURE__ */ a(A, { children: [
194
+ /* @__PURE__ */ a("h4", { children: [
195
+ /* @__PURE__ */ e(te, { size: 12 }),
196
+ " Ways in"
197
+ ] }),
198
+ /* @__PURE__ */ e("ul", { children: b.map((n) => {
199
+ const l = f.has(n.a) ? n.a : n.b, d = l === n.a ? n.b : n.a;
200
+ return /* @__PURE__ */ a("li", { className: "structure-member", children: [
201
+ /* @__PURE__ */ a("span", { className: "structure-member-title", children: [
202
+ /* @__PURE__ */ e("span", { className: "structure-from", children: w(d) }),
203
+ /* @__PURE__ */ e(se, { size: 11 }),
204
+ /* @__PURE__ */ e("span", { children: w(l) }),
205
+ /* @__PURE__ */ e("em", { children: re(n, l) })
206
+ ] }),
207
+ /* @__PURE__ */ e(
208
+ "button",
209
+ {
210
+ type: "button",
211
+ className: "structure-locate",
212
+ title: "Find on map",
213
+ "aria-label": `Find passage from ${w(d)} to ${w(l)} on map`,
214
+ onClick: () => y ? y({
215
+ objectIds: n.openingId ? [n.openingId] : [l, d],
216
+ floorId: x(d).floorId
217
+ }) : Z(n.openingId ?? l),
218
+ children: /* @__PURE__ */ e(R, { size: 13 })
219
+ }
220
+ )
221
+ ] }, n.id);
222
+ }) })
223
+ ] }),
224
+ !!((k = t.childZoneIds) != null && k.length) && /* @__PURE__ */ a("div", { className: "structure-note", children: [
225
+ "Contains zones:",
226
+ " ",
227
+ t.childZoneIds.map((n) => {
228
+ var l, d;
229
+ return ((d = (l = s.zones) == null ? void 0 : l.find((v) => v.id === n)) == null ? void 0 : d.name) ?? n;
230
+ }).join(", ")
231
+ ] }),
232
+ ((o = s.zones) == null ? void 0 : o.some((n) => {
233
+ var l;
234
+ return (l = n.childZoneIds) == null ? void 0 : l.includes(t.id);
235
+ })) && /* @__PURE__ */ a("div", { className: "structure-note", children: [
236
+ "Part of:",
237
+ " ",
238
+ s.zones.filter((n) => {
239
+ var l;
240
+ return (l = n.childZoneIds) == null ? void 0 : l.includes(t.id);
241
+ }).map((n) => n.name).join(", ")
242
+ ] }),
243
+ /* @__PURE__ */ a("h4", { children: [
244
+ /* @__PURE__ */ e(ae, { size: 12 }),
245
+ " Spaces"
246
+ ] }),
247
+ /* @__PURE__ */ e("ul", { children: i.map((n) => /* @__PURE__ */ a("li", { className: "structure-member", children: [
248
+ /* @__PURE__ */ a("span", { className: "structure-member-title", children: [
249
+ /* @__PURE__ */ e("span", { children: w(n) }),
250
+ /* @__PURE__ */ e("em", { children: x(n).label })
251
+ ] }),
252
+ /* @__PURE__ */ e(
253
+ "button",
254
+ {
255
+ type: "button",
256
+ className: "structure-locate",
257
+ title: "Find on map",
258
+ "aria-label": `Find ${w(n)} on map`,
259
+ onClick: () => y ? y({ objectIds: [n] }) : Z(n),
260
+ children: /* @__PURE__ */ e(R, { size: 13 })
261
+ }
262
+ )
263
+ ] }, n)) }),
264
+ z.length > 0 && /* @__PURE__ */ a("p", { className: "structure-note", children: [
265
+ z.length,
266
+ " portal",
267
+ z.length > 1 ? "s" : "",
268
+ " inside this zone — crossing one does not leave it."
269
+ ] })
270
+ ] })
271
+ ] }, t.id);
272
+ })
273
+ ] }, r)) })
274
+ ] }) : /* @__PURE__ */ a("div", { className: "structure-zone-list", children: [
275
+ M,
276
+ /* @__PURE__ */ a("div", { className: "structure-empty", children: [
277
+ /* @__PURE__ */ e(q, { size: 22 }),
278
+ /* @__PURE__ */ e("strong", { children: (F = s.zones) != null && F.length ? "No zones match these filters" : "No semantic zones yet" }),
279
+ /* @__PURE__ */ e("p", { children: "Spaces and portals are available in their own tabs. Select a space on the plan to create a named zone." })
280
+ ] })
281
+ ] });
282
+ }
283
+ export {
284
+ be as StructureView
285
+ };
286
+ //# sourceMappingURL=StructureView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StructureView.js","sources":["../../../../src/components/StructureView.tsx"],"sourcesContent":["// Browsing the building as a structure rather than a picture.\n//\n// A map is the wrong instrument for most of what the ontology says. A zone spanning eleven floors, a\n// lift shaft, \"everywhere a contractor may go\" — none of those have a shape, and drawing them on a\n// plan either lies about them or hides them. This view reads the same document as a list of what\n// groups with what and what opens onto what, which is the form those questions actually take.\nimport { lazy, Suspense, useMemo, useState } from 'react';\nimport { isSpace, type Point, type Portal, type ProjectDocument, type SiteObject, type Zone } from '../model/types';\nimport { addZone, captive, entryInto, perimeter, removeZone, setZoneMembers, zoneSpaces } from '../model/ontology';\nimport { refreshPortals } from '../model/inference';\nconst StructureExplorer = lazy(() =>\n import('./StructureExplorer').then(module => ({ default: module.StructureExplorer })),\n);\nimport {\n ChevronDown,\n ChevronRight,\n DoorOpen,\n Layers,\n LocateFixed,\n MoveHorizontal,\n Plus,\n RefreshCw,\n Shapes,\n Trash2,\n} from 'lucide-react';\n\nexport interface StructureViewProps {\n project: ProjectDocument;\n selected?: string | null;\n /** Fires when a space or opening is picked, so a host can follow along on the plan. */\n onSelect?: (id: string) => void;\n /** Fires alongside onSelect when the pick lives on a particular floor. */\n onFloorChange?: (floorId: string | null) => void;\n onLocate?: (target: StructureTarget) => void;\n onGraphView?: (floorId?: string | null) => void;\n /** Omit to keep the panel read-only. Given, the panel can author zones: the host applies the\n * mutation to a draft and owns undo, exactly as the map tools do. */\n onEdit?: (change: (draft: ProjectDocument) => void) => void;\n}\nexport interface StructureTarget {\n objectIds?: string[];\n floorId?: string | null;\n position?: Point;\n}\n\nconst OUTDOORS = 'Outdoor site';\n/** Direction, said the way a person would: which way this portal lets you go, relative to a zone. */\nfunction passageLabel(portal: Portal, insideId: string): string {\n const inward = entryInto(portal, insideId) !== null;\n const outward = entryInto(portal, portal.a === insideId ? portal.b : portal.a) !== null;\n if (inward && outward) return 'both ways';\n if (inward) return 'in only';\n if (outward) return 'out only';\n return 'sealed';\n}\n\nexport function StructureView(props: StructureViewProps) {\n return (\n <Suspense\n fallback={\n <p className=\"structure-empty\" role=\"status\">\n Loading structure…\n </p>\n }\n >\n <StructureExplorer {...props} renderZones={filter => <ZoneList {...props} {...filter} />} />\n </Suspense>\n );\n}\n\nfunction ZoneList({\n project,\n selected,\n onSelect,\n onFloorChange,\n onEdit,\n onLocate,\n query,\n floor,\n}: StructureViewProps & { query: string; floor: string }) {\n const [open, setOpen] = useState<string | null>(null);\n const selectedSpace = selected && project.objects.find(o => o.id === selected && isSpace(o.kind));\n const model = useMemo(() => {\n const byId = new Map(project.objects.map(o => [o.id, o]));\n const floors = new Map(project.floors.map(f => [f.id, f.name]));\n const zones = project.zones ?? [];\n const groups = new Map<string, Zone[]>();\n for (const z of zones) {\n const members = zoneSpaces(project, z)\n .map(id => byId.get(id))\n .filter(Boolean);\n const text = [z.name, z.id, z.purpose, ...members.map(o => o?.name)].join(' ').toLowerCase();\n if (query.trim() && !text.includes(query.trim().toLowerCase())) continue;\n if (\n floor !== 'all' &&\n !members.some(o => (o?.floorId ?? 'outdoors') === floor || o?.servedFloorIds?.includes(floor))\n )\n continue;\n const key = z.purpose ?? 'other';\n const list = groups.get(key);\n if (list) list.push(z);\n else groups.set(key, [z]);\n }\n return { byId, floors, groups };\n }, [project, query, floor]);\n\n const name = (id: string) => model.byId.get(id)?.name ?? 'Unknown';\n const floorOf = (id: string) => {\n const floorId = model.byId.get(id)?.floorId ?? null;\n return { floorId, label: floorId === null ? OUTDOORS : (model.floors.get(floorId) ?? 'Unknown level') };\n };\n const pick = (id: string) => {\n onSelect?.(id);\n onFloorChange?.(floorOf(id).floorId);\n };\n\n const actions = onEdit ? (\n <div className=\"structure-actions\">\n <button\n type=\"button\"\n className=\"text-button\"\n disabled={!selectedSpace}\n title={selectedSpace ? `New zone containing ${selectedSpace.name}` : 'Select an area on the plan first'}\n onClick={() => {\n if (!selectedSpace) return;\n onEdit(d => addZone(d, `${selectedSpace.name} zone`, [selectedSpace.id], 'security'));\n }}\n >\n <Plus size={13} /> New zone\n </button>\n <button\n type=\"button\"\n className=\"text-button\"\n title=\"Re-read portals from the plan, keeping any you have edited\"\n onClick={() => onEdit(d => refreshPortals(d))}\n >\n <RefreshCw size={13} /> Re-read portals\n </button>\n </div>\n ) : null;\n\n if (!model.groups.size)\n return (\n <div className=\"structure-zone-list\">\n {actions}\n <div className=\"structure-empty\">\n <Shapes size={22} />\n <strong>{project.zones?.length ? 'No zones match these filters' : 'No semantic zones yet'}</strong>\n <p>Spaces and portals are available in their own tabs. Select a space on the plan to create a named zone.</p>\n </div>\n </div>\n );\n\n return (\n <div className=\"structure-zone-list\">\n {actions}\n <div className=\"inspector-scroll structure-scroll\">\n {[...model.groups].map(([purpose, zones]) => (\n <section key={purpose} className=\"structure-group\">\n <h3>{purpose}</h3>\n {zones.map(zone => {\n const isOpen = open === zone.id;\n const members = zoneSpaces(project, zone);\n const ways = perimeter(project, zone);\n const inner = isOpen ? captive(project, zone) : [];\n const inside = new Set(members);\n return (\n <div key={zone.id} className={`structure-zone ${isOpen ? 'open' : ''}`}>\n <div className=\"structure-zone-toolbar\">\n <button\n type=\"button\"\n className=\"structure-zone-head\"\n aria-expanded={isOpen}\n onClick={() => setOpen(isOpen ? null : zone.id)}\n >\n {isOpen ? <ChevronDown size={14} /> : <ChevronRight size={14} />}\n <strong>{zone.name}</strong>\n <span className=\"structure-counts\">\n {members.length} {members.length === 1 ? 'space' : 'spaces'}\n {ways.length > 0 && ` · ${ways.length} in`}\n {zone.connects &&\n ` · ${{ all: 'lift', adjacent: 'stairs', up: 'escalator up', down: 'escalator down' }[zone.connects]}`}\n </span>\n </button>\n <button\n className=\"structure-locate\"\n aria-label={`Find ${zone.name} on map`}\n title=\"Find on map\"\n disabled={!members.length}\n onClick={() => (onLocate ? onLocate({ objectIds: members }) : pick(members[0]))}\n >\n <LocateFixed size={13} />\n </button>\n </div>\n {isOpen && (\n <div className=\"structure-detail\">\n {onEdit && (\n <div className=\"structure-edit\">\n <input\n aria-label=\"Zone name\"\n value={zone.name}\n onChange={e => {\n const name = e.target.value;\n onEdit(d => {\n const z = d.zones?.find(x => x.id === zone.id);\n if (z) z.name = name;\n });\n }}\n />\n <select\n aria-label=\"Connects\"\n value={zone.connects ?? ''}\n onChange={e => {\n const v = e.target.value;\n onEdit(d => {\n const z = d.zones?.find(x => x.id === zone.id);\n if (z) z.connects = (v || undefined) as Zone['connects'];\n });\n }}\n >\n <option value=\"\">No implied route</option>\n <option value=\"all\">Lift · any landing to any</option>\n <option value=\"adjacent\">Stair · level by level</option>\n <option value=\"up\">Escalator · up only</option>\n <option value=\"down\">Escalator · down only</option>\n </select>\n <button\n type=\"button\"\n className=\"text-button\"\n disabled={!selectedSpace}\n title={\n !selectedSpace\n ? 'Select an area on the plan first'\n : inside.has(selectedSpace.id)\n ? `Remove ${selectedSpace.name}`\n : `Add ${selectedSpace.name}`\n }\n onClick={() => {\n if (!selectedSpace) return;\n const add = !inside.has(selectedSpace.id);\n onEdit(d => setZoneMembers(d, zone.id, [selectedSpace.id], add));\n }}\n >\n {selectedSpace && inside.has(selectedSpace.id) ? 'Remove selected' : 'Add selected'}\n </button>\n <button\n type=\"button\"\n className=\"icon-button\"\n aria-label={`Delete ${zone.name}`}\n onClick={() => onEdit(d => removeZone(d, zone.id))}\n >\n <Trash2 size={14} />\n </button>\n </div>\n )}\n {ways.length > 0 && (\n <>\n <h4>\n <DoorOpen size={12} /> Ways in\n </h4>\n <ul>\n {ways.map(portal => {\n const insideId = inside.has(portal.a) ? portal.a : portal.b;\n const outsideId = insideId === portal.a ? portal.b : portal.a;\n return (\n <li key={portal.id} className=\"structure-member\">\n <span className=\"structure-member-title\">\n <span className=\"structure-from\">{name(outsideId)}</span>\n <MoveHorizontal size={11} />\n <span>{name(insideId)}</span>\n <em>{passageLabel(portal, insideId)}</em>\n </span>\n <button\n type=\"button\"\n className=\"structure-locate\"\n title=\"Find on map\"\n aria-label={`Find passage from ${name(outsideId)} to ${name(insideId)} on map`}\n onClick={() =>\n onLocate\n ? onLocate({\n objectIds: portal.openingId ? [portal.openingId] : [insideId, outsideId],\n floorId: floorOf(outsideId).floorId,\n })\n : pick(portal.openingId ?? insideId)\n }\n >\n <LocateFixed size={13} />\n </button>\n </li>\n );\n })}\n </ul>\n </>\n )}\n {!!zone.childZoneIds?.length && (\n <div className=\"structure-note\">\n Contains zones:{' '}\n {zone.childZoneIds.map(id => project.zones?.find(z => z.id === id)?.name ?? id).join(', ')}\n </div>\n )}\n {project.zones?.some(z => z.childZoneIds?.includes(zone.id)) && (\n <div className=\"structure-note\">\n Part of:{' '}\n {project.zones\n .filter(z => z.childZoneIds?.includes(zone.id))\n .map(z => z.name)\n .join(', ')}\n </div>\n )}\n <h4>\n <Layers size={12} /> Spaces\n </h4>\n <ul>\n {members.map(id => (\n <li key={id} className=\"structure-member\">\n <span className=\"structure-member-title\">\n <span>{name(id)}</span>\n <em>{floorOf(id).label}</em>\n </span>\n <button\n type=\"button\"\n className=\"structure-locate\"\n title=\"Find on map\"\n aria-label={`Find ${name(id)} on map`}\n onClick={() => (onLocate ? onLocate({ objectIds: [id] }) : pick(id))}\n >\n <LocateFixed size={13} />\n </button>\n </li>\n ))}\n </ul>\n {inner.length > 0 && (\n <p className=\"structure-note\">\n {inner.length} portal{inner.length > 1 ? 's' : ''} inside this zone — crossing one does not\n leave it.\n </p>\n )}\n </div>\n )}\n </div>\n );\n })}\n </section>\n ))}\n </div>\n </div>\n );\n}\n\n/** Exported for hosts that want the same grouping without the chrome. */\nexport const zoneMembers = (project: ProjectDocument, zone: Zone): SiteObject[] =>\n zoneSpaces(project, zone)\n .map(id => project.objects.find(o => o.id === id))\n .filter((o): o is SiteObject => !!o);\n"],"names":["StructureExplorer","lazy","module","OUTDOORS","passageLabel","portal","insideId","inward","entryInto","outward","StructureView","props","jsx","Suspense","filter","ZoneList","project","selected","onSelect","onFloorChange","onEdit","onLocate","query","floor","open","setOpen","useState","selectedSpace","o","isSpace","model","useMemo","byId","floors","f","zones","groups","z","members","zoneSpaces","id","text","_a","key","list","name","floorOf","floorId","pick","actions","jsxs","d","addZone","Plus","refreshPortals","RefreshCw","purpose","zone","isOpen","ways","perimeter","inner","captive","inside","ChevronDown","ChevronRight","LocateFixed","e","x","v","add","setZoneMembers","removeZone","Trash2","Fragment","DoorOpen","outsideId","MoveHorizontal","_b","Layers","Shapes"],"mappings":"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"}
@@ -0,0 +1,170 @@
1
+ import { jsxs as o, jsx as n, Fragment as D } from "react/jsx-runtime";
2
+ import { ChevronDown as E, Search as S } from "lucide-react";
3
+ import { useState as v, useRef as y, useEffect as N } from "react";
4
+ function F({
5
+ label: a,
6
+ value: r,
7
+ onChange: d,
8
+ type: i = "text",
9
+ suffix: t,
10
+ min: l,
11
+ max: f,
12
+ step: u
13
+ }) {
14
+ return /* @__PURE__ */ o("label", { className: "field", children: [
15
+ /* @__PURE__ */ n("span", { children: a }),
16
+ /* @__PURE__ */ o("div", { className: "input-wrap", children: [
17
+ /* @__PURE__ */ n(
18
+ "input",
19
+ {
20
+ "aria-label": a,
21
+ type: i,
22
+ defaultValue: r,
23
+ min: l,
24
+ max: f,
25
+ step: u ?? (i === "number" ? 0.1 : void 0),
26
+ onBlur: (c) => {
27
+ String(r) !== c.currentTarget.value && d(c.currentTarget.value);
28
+ },
29
+ onKeyDown: (c) => {
30
+ c.key === "Enter" && c.currentTarget.blur();
31
+ }
32
+ },
33
+ String(r)
34
+ ),
35
+ t && /* @__PURE__ */ n("em", { children: t })
36
+ ] })
37
+ ] });
38
+ }
39
+ function M({
40
+ label: a,
41
+ value: r,
42
+ onChange: d,
43
+ description: i
44
+ }) {
45
+ return /* @__PURE__ */ o("button", { className: "toggle-row", role: "switch", "aria-checked": r, onClick: d, children: [
46
+ /* @__PURE__ */ o("span", { children: [
47
+ a,
48
+ i && /* @__PURE__ */ n("small", { children: i })
49
+ ] }),
50
+ /* @__PURE__ */ n("i", { className: `toggle ${r ? "on" : ""}` })
51
+ ] });
52
+ }
53
+ function $({
54
+ label: a,
55
+ value: r,
56
+ options: d,
57
+ onChange: i,
58
+ description: t
59
+ }) {
60
+ return /* @__PURE__ */ o("div", { className: "toggle-row choice-row", children: [
61
+ /* @__PURE__ */ o("div", { className: "choice-head", children: [
62
+ /* @__PURE__ */ n("span", { children: a }),
63
+ /* @__PURE__ */ n("div", { className: "choice", role: "radiogroup", "aria-label": a, children: d.map((l) => /* @__PURE__ */ n(
64
+ "button",
65
+ {
66
+ role: "radio",
67
+ "aria-checked": r === l.value,
68
+ className: r === l.value ? "active" : "",
69
+ title: l.title,
70
+ onClick: () => i(l.value),
71
+ children: l.label
72
+ },
73
+ l.value
74
+ )) })
75
+ ] }),
76
+ t && /* @__PURE__ */ n("small", { children: t })
77
+ ] });
78
+ }
79
+ function j({
80
+ entries: a,
81
+ value: r,
82
+ onPick: d,
83
+ ariaLabel: i
84
+ }) {
85
+ const [t, l] = v(!1), [f, u] = v(""), [c, m] = v(0), b = y(null), k = y(null), g = a.find((e) => e.id === r), h = f.trim().toLowerCase(), p = t ? a.filter(
86
+ (e) => !h || e.name.toLowerCase().includes(h) || e.code.toLowerCase().includes(h) || (e.hint ?? "").toLowerCase().includes(h)
87
+ ).slice(0, 60) : [];
88
+ N(() => {
89
+ const e = (s) => {
90
+ var w;
91
+ (w = b.current) != null && w.contains(s.target) || l(!1);
92
+ };
93
+ return window.addEventListener("pointerdown", e), () => window.removeEventListener("pointerdown", e);
94
+ }, []), N(() => {
95
+ m(0);
96
+ }, [h, t]), N(() => {
97
+ var e;
98
+ t && ((e = k.current) == null || e.focus());
99
+ }, [t]);
100
+ const C = (e) => {
101
+ d(e), l(!1), u("");
102
+ };
103
+ return /* @__PURE__ */ o("div", { className: "floor-select", ref: b, children: [
104
+ /* @__PURE__ */ o(
105
+ "button",
106
+ {
107
+ "aria-label": i,
108
+ "data-value": r,
109
+ className: "floor-select-value",
110
+ onClick: () => {
111
+ l(!t), u("");
112
+ },
113
+ children: [
114
+ g ? /* @__PURE__ */ o(D, { children: [
115
+ /* @__PURE__ */ n("b", { children: g.code }),
116
+ /* @__PURE__ */ n("span", { children: g.name })
117
+ ] }) : i,
118
+ /* @__PURE__ */ n(E, { size: 14 })
119
+ ]
120
+ }
121
+ ),
122
+ t && /* @__PURE__ */ o("div", { className: "place-options floor-select-options", role: "listbox", children: [
123
+ /* @__PURE__ */ o("div", { className: "place-input", children: [
124
+ /* @__PURE__ */ n(S, { size: 13 }),
125
+ /* @__PURE__ */ n(
126
+ "input",
127
+ {
128
+ ref: k,
129
+ "aria-label": "Search floors",
130
+ placeholder: "Search floors…",
131
+ value: f,
132
+ onChange: (e) => u(e.target.value),
133
+ onBlur: () => setTimeout(() => l(!1), 120),
134
+ onKeyDown: (e) => {
135
+ e.key === "ArrowDown" ? (e.preventDefault(), m((s) => Math.min(s + 1, p.length - 1))) : e.key === "ArrowUp" ? (e.preventDefault(), m((s) => Math.max(s - 1, 0))) : e.key === "Enter" ? (e.preventDefault(), p[c] && C(p[c].id)) : e.key === "Escape" && (e.stopPropagation(), l(!1));
136
+ }
137
+ }
138
+ )
139
+ ] }),
140
+ p.map((e, s) => /* @__PURE__ */ o(
141
+ "button",
142
+ {
143
+ role: "option",
144
+ "aria-selected": e.id === r,
145
+ className: `place-option ${s === c ? "cursor" : ""} ${e.id === r ? "chosen" : ""}`,
146
+ onPointerDown: (w) => {
147
+ w.preventDefault(), C(e.id);
148
+ },
149
+ onMouseEnter: () => m(s),
150
+ children: [
151
+ /* @__PURE__ */ n("span", { className: "floor-number", children: e.code }),
152
+ /* @__PURE__ */ o("span", { children: [
153
+ e.name,
154
+ e.hint && /* @__PURE__ */ n("small", { children: e.hint })
155
+ ] })
156
+ ]
157
+ },
158
+ e.id
159
+ )),
160
+ !p.length && /* @__PURE__ */ n("p", { className: "place-none", children: "No floors match." })
161
+ ] })
162
+ ] });
163
+ }
164
+ export {
165
+ $ as Choice,
166
+ F as Field,
167
+ j as FloorSelect,
168
+ M as Toggle
169
+ };
170
+ //# sourceMappingURL=controls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controls.js","sources":["../../../../src/components/controls.tsx"],"sourcesContent":["import { ChevronDown, Search, X } from 'lucide-react';\nimport { useEffect, useRef, useState, type ReactNode } from 'react';\nexport function Modal({\n title,\n subtitle,\n onClose,\n children,\n wide = false,\n fullscreen = false,\n}: {\n title: string;\n subtitle?: string;\n onClose: () => void;\n children: ReactNode;\n wide?: boolean;\n fullscreen?: boolean;\n}) {\n const dialog = useRef<HTMLDialogElement>(null);\n useEffect(() => {\n const el = dialog.current;\n el?.showModal();\n // Put the keyboard where the dialog is asking you to act.\n //\n // Left alone, a dialog focuses its first focusable child, which here is the X in the corner: the\n // one control that throws the dialog away. Enter then dismisses the question instead of\n // answering it, which is the opposite of what pressing Enter on a confirmation should do.\n //\n // A dialog that wants typing wants its first field — opening \"Add a floor\" and having to reach\n // for the mouse to click into the name is the wrong first move. Anything else wants the last\n // button in its footer, which is where the action sits.\n const field = el?.querySelector<HTMLElement>('input, select, textarea');\n const actions = el?.querySelectorAll<HTMLElement>('footer button');\n (field ?? actions?.[actions.length - 1])?.focus();\n return () => el?.close();\n }, []);\n return (\n <dialog\n className={`modal ${wide ? 'wide' : ''} ${fullscreen ? 'fullscreen' : ''}`}\n aria-label={title}\n ref={dialog}\n onCancel={onClose}\n onClick={e => {\n if (e.target === e.currentTarget) onClose();\n }}\n >\n <header>\n <div>\n <h2>{title}</h2>\n {subtitle && <p>{subtitle}</p>}\n </div>\n <button className=\"icon-button\" aria-label=\"Close dialog\" onClick={onClose}>\n <X size={20} />\n </button>\n </header>\n {children}\n </dialog>\n );\n}\nexport function Field({\n label,\n value,\n onChange,\n type = 'text',\n suffix,\n min,\n max,\n step,\n}: {\n label: string;\n value: string | number;\n onChange: (value: string) => void;\n type?: string;\n suffix?: string;\n min?: number;\n max?: number;\n step?: number;\n}) {\n return (\n <label className=\"field\">\n <span>{label}</span>\n <div className=\"input-wrap\">\n <input\n key={String(value)}\n aria-label={label}\n type={type}\n defaultValue={value}\n min={min}\n max={max}\n step={step ?? (type === 'number' ? 0.1 : undefined)}\n onBlur={e => {\n if (String(value) !== e.currentTarget.value) onChange(e.currentTarget.value);\n }}\n onKeyDown={e => {\n if (e.key === 'Enter') e.currentTarget.blur();\n }}\n />\n {suffix && <em>{suffix}</em>}\n </div>\n </label>\n );\n}\nexport function Toggle({\n label,\n value,\n onChange,\n description,\n}: {\n label: string;\n value: boolean;\n onChange: () => void;\n description?: string;\n}) {\n return (\n <button className=\"toggle-row\" role=\"switch\" aria-checked={value} onClick={onChange}>\n <span>\n {label}\n {description && <small>{description}</small>}\n </span>\n <i className={`toggle ${value ? 'on' : ''}`} />\n </button>\n );\n}\n\n/** A row of mutually exclusive choices, for a setting that has three answers rather than two.\n * Same shape as `Toggle` — label, optional description, the control on the right — so a panel of\n * switches does not acquire a second visual language for the one setting that needs a third state. */\nexport function Choice<T extends string>({\n label,\n value,\n options,\n onChange,\n description,\n}: {\n label: string;\n value: T;\n options: { value: T; label: string; title?: string }[];\n onChange: (value: T) => void;\n description?: string;\n}) {\n return (\n <div className=\"toggle-row choice-row\">\n <div className=\"choice-head\">\n <span>{label}</span>\n <div className=\"choice\" role=\"radiogroup\" aria-label={label}>\n {options.map(o => (\n <button\n key={o.value}\n role=\"radio\"\n aria-checked={value === o.value}\n className={value === o.value ? 'active' : ''}\n title={o.title}\n onClick={() => onChange(o.value)}\n >\n {o.label}\n </button>\n ))}\n </div>\n </div>\n {description && <small>{description}</small>}\n </div>\n );\n}\n\n/** Searchable dropdown for level selection: projects like the Silo carry a hundred floors. */\nexport interface FloorEntry {\n id: string;\n code: string;\n name: string;\n hint?: string;\n}\nexport function FloorSelect({\n entries,\n value,\n onPick,\n ariaLabel,\n}: {\n entries: FloorEntry[];\n value: string;\n onPick: (id: string) => void;\n ariaLabel: string;\n}) {\n const [open, setOpen] = useState(false);\n const [query, setQuery] = useState('');\n const [cursor, setCursor] = useState(0);\n const wrap = useRef<HTMLDivElement>(null);\n const input = useRef<HTMLInputElement>(null);\n const current = entries.find(e => e.id === value);\n const q = query.trim().toLowerCase();\n const matches = open\n ? entries\n .filter(\n e =>\n !q ||\n e.name.toLowerCase().includes(q) ||\n e.code.toLowerCase().includes(q) ||\n (e.hint ?? '').toLowerCase().includes(q),\n )\n .slice(0, 60)\n : [];\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 }, [q, open]);\n useEffect(() => {\n if (open) input.current?.focus();\n }, [open]);\n const pick = (id: string) => {\n onPick(id);\n setOpen(false);\n setQuery('');\n };\n return (\n <div className=\"floor-select\" ref={wrap}>\n <button\n aria-label={ariaLabel}\n data-value={value}\n className=\"floor-select-value\"\n onClick={() => {\n setOpen(!open);\n setQuery('');\n }}\n >\n {current ? (\n <>\n <b>{current.code}</b>\n <span>{current.name}</span>\n </>\n ) : (\n ariaLabel\n )}\n <ChevronDown size={14} />\n </button>\n {open && (\n <div className=\"place-options floor-select-options\" role=\"listbox\">\n <div className=\"place-input\">\n <Search size={13} />\n <input\n ref={input}\n aria-label=\"Search floors\"\n placeholder=\"Search floors…\"\n value={query}\n onChange={e => setQuery(e.target.value)}\n onBlur={() => setTimeout(() => setOpen(false), 120)}\n onKeyDown={e => {\n if (e.key === 'ArrowDown') {\n e.preventDefault();\n setCursor(v => Math.min(v + 1, matches.length - 1));\n } else if (e.key === 'ArrowUp') {\n e.preventDefault();\n setCursor(v => Math.max(v - 1, 0));\n } else if (e.key === 'Enter') {\n e.preventDefault();\n if (matches[cursor]) pick(matches[cursor].id);\n } else if (e.key === 'Escape') {\n e.stopPropagation();\n setOpen(false);\n }\n }}\n />\n </div>\n {matches.map((e, i) => (\n <button\n key={e.id}\n role=\"option\"\n aria-selected={e.id === value}\n className={`place-option ${i === cursor ? 'cursor' : ''} ${e.id === value ? 'chosen' : ''}`}\n onPointerDown={ev => {\n ev.preventDefault();\n pick(e.id);\n }}\n onMouseEnter={() => setCursor(i)}\n >\n <span className=\"floor-number\">{e.code}</span>\n <span>\n {e.name}\n {e.hint && <small>{e.hint}</small>}\n </span>\n </button>\n ))}\n {!matches.length && <p className=\"place-none\">No floors match.</p>}\n </div>\n )}\n </div>\n );\n}\n"],"names":["Field","label","value","onChange","type","suffix","min","max","step","jsxs","jsx","e","Toggle","description","Choice","options","o","FloorSelect","entries","onPick","ariaLabel","open","setOpen","useState","query","setQuery","cursor","setCursor","wrap","useRef","input","current","q","matches","useEffect","away","_a","pick","id","Fragment","ChevronDown","Search","v","i","ev"],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ function r(n, l, M) {
2
+ const v = Math.max(1, Math.ceil(n.length / 80)), y = Math.max(0.08, 1 - M / 360);
3
+ for (let t = 0; t < n.length; t++) {
4
+ const a = n[t];
5
+ a.vx -= a.x * 1e-3 * y, a.vy -= a.y * 1e-3 * y;
6
+ for (let x = (t + M) % v; x < n.length; x += v) {
7
+ if (x === t) continue;
8
+ const c = n[x];
9
+ let e = a.x - c.x, h = a.y - c.y;
10
+ Math.abs(e) + Math.abs(h) < 0.01 && (e = t < x ? -0.1 : 0.1, h = 0.1);
11
+ const o = 90 * v * y / Math.max(25, e * e + h * h);
12
+ a.vx += e * o, a.vy += h * o;
13
+ }
14
+ }
15
+ for (const [t, a] of l) {
16
+ const x = n[t], c = n[a];
17
+ if (!x || !c || t === a) continue;
18
+ const e = c.x - x.x, h = c.y - x.y, o = Math.hypot(e, h) || 1, f = (o - 50) / o * 0.018 * y;
19
+ x.vx += e * f, x.vy += h * f, c.vx -= e * f, c.vy -= h * f;
20
+ }
21
+ for (const t of n)
22
+ t.pinned || (t.x += Math.max(-6, Math.min(6, t.vx)), t.y += Math.max(-6, Math.min(6, t.vy))), t.vx *= 0.7, t.vy *= 0.7;
23
+ }
24
+ export {
25
+ r as balanceGraph
26
+ };
27
+ //# sourceMappingURL=graphLayout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graphLayout.js","sources":["../../../../src/components/graphLayout.ts"],"sourcesContent":["export interface GraphPoint {\n x: number;\n y: number;\n vx: number;\n vy: number;\n pinned?: boolean;\n}\n/** Bounded sampled repulsion keeps large navigation graphs out of quadratic per-frame work. */\nexport function balanceGraph(points: GraphPoint[], links: [number, number][], tick: number): void {\n const stride = Math.max(1, Math.ceil(points.length / 80));\n const alpha = Math.max(0.08, 1 - tick / 360);\n for (let i = 0; i < points.length; i++) {\n const p = points[i];\n p.vx -= p.x * 0.001 * alpha;\n p.vy -= p.y * 0.001 * alpha;\n for (let j = (i + tick) % stride; j < points.length; j += stride) {\n if (j === i) continue;\n const q = points[j];\n let dx = p.x - q.x,\n dy = p.y - q.y;\n if (Math.abs(dx) + Math.abs(dy) < 0.01) {\n dx = i < j ? -0.1 : 0.1;\n dy = 0.1;\n }\n const force = (90 * stride * alpha) / Math.max(25, dx * dx + dy * dy);\n p.vx += dx * force;\n p.vy += dy * force;\n }\n }\n for (const [a, b] of links) {\n const p = points[a],\n q = points[b];\n if (!p || !q || a === b) continue;\n const dx = q.x - p.x,\n dy = q.y - p.y,\n length = Math.hypot(dx, dy) || 1;\n const force = ((length - 50) / length) * 0.018 * alpha;\n p.vx += dx * force;\n p.vy += dy * force;\n q.vx -= dx * force;\n q.vy -= dy * force;\n }\n for (const p of points) {\n if (!p.pinned) {\n p.x += Math.max(-6, Math.min(6, p.vx));\n p.y += Math.max(-6, Math.min(6, p.vy));\n }\n p.vx *= 0.7;\n p.vy *= 0.7;\n }\n}\n"],"names":["balanceGraph","points","links","tick","stride","alpha","i","p","j","q","dx","dy","force","a","b","length"],"mappings":"AAQO,SAASA,EAAaC,GAAsBC,GAA2BC,GAAoB;AAChG,QAAMC,IAAS,KAAK,IAAI,GAAG,KAAK,KAAKH,EAAO,SAAS,EAAE,CAAC,GAClDI,IAAQ,KAAK,IAAI,MAAM,IAAIF,IAAO,GAAG;AAC3C,WAASG,IAAI,GAAGA,IAAIL,EAAO,QAAQK,KAAK;AACtC,UAAMC,IAAIN,EAAOK,CAAC;AAClB,IAAAC,EAAE,MAAMA,EAAE,IAAI,OAAQF,GACtBE,EAAE,MAAMA,EAAE,IAAI,OAAQF;AACtB,aAASG,KAAKF,IAAIH,KAAQC,GAAQI,IAAIP,EAAO,QAAQO,KAAKJ,GAAQ;AAChE,UAAII,MAAMF,EAAG;AACb,YAAMG,IAAIR,EAAOO,CAAC;AAClB,UAAIE,IAAKH,EAAE,IAAIE,EAAE,GACfE,IAAKJ,EAAE,IAAIE,EAAE;AACf,MAAI,KAAK,IAAIC,CAAE,IAAI,KAAK,IAAIC,CAAE,IAAI,SAChCD,IAAKJ,IAAIE,IAAI,OAAO,KACpBG,IAAK;AAEP,YAAMC,IAAS,KAAKR,IAASC,IAAS,KAAK,IAAI,IAAIK,IAAKA,IAAKC,IAAKA,CAAE;AACpE,MAAAJ,EAAE,MAAMG,IAAKE,GACbL,EAAE,MAAMI,IAAKC;AAAA,IACf;AAAA,EACF;AACA,aAAW,CAACC,GAAGC,CAAC,KAAKZ,GAAO;AAC1B,UAAMK,IAAIN,EAAOY,CAAC,GAChBJ,IAAIR,EAAOa,CAAC;AACd,QAAI,CAACP,KAAK,CAACE,KAAKI,MAAMC,EAAG;AACzB,UAAMJ,IAAKD,EAAE,IAAIF,EAAE,GACjBI,IAAKF,EAAE,IAAIF,EAAE,GACbQ,IAAS,KAAK,MAAML,GAAIC,CAAE,KAAK,GAC3BC,KAAUG,IAAS,MAAMA,IAAU,QAAQV;AACjD,IAAAE,EAAE,MAAMG,IAAKE,GACbL,EAAE,MAAMI,IAAKC,GACbH,EAAE,MAAMC,IAAKE,GACbH,EAAE,MAAME,IAAKC;AAAA,EACf;AACA,aAAWL,KAAKN;AACd,IAAKM,EAAE,WACLA,EAAE,KAAK,KAAK,IAAI,IAAI,KAAK,IAAI,GAAGA,EAAE,EAAE,CAAC,GACrCA,EAAE,KAAK,KAAK,IAAI,IAAI,KAAK,IAAI,GAAGA,EAAE,EAAE,CAAC,IAEvCA,EAAE,MAAM,KACRA,EAAE,MAAM;AAEZ;"}