gps-plus-slam-app-framework 1.0.0

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 (200) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +222 -0
  3. package/dist/alignment-lerper-D9BeDwZX.d.ts +29 -0
  4. package/dist/app-selectors-DyMzxCEC.d.ts +140 -0
  5. package/dist/ar/camera-blit-capture.d.ts +2 -0
  6. package/dist/ar/camera-blit-capture.js +253 -0
  7. package/dist/ar/capture-failure-tracker.d.ts +2 -0
  8. package/dist/ar/capture-failure-tracker.js +45 -0
  9. package/dist/ar/chromium-camera-access-workaround.d.ts +2 -0
  10. package/dist/ar/chromium-camera-access-workaround.js +32 -0
  11. package/dist/ar/depth-sampler.d.ts +3 -0
  12. package/dist/ar/depth-sampler.js +147 -0
  13. package/dist/ar/image-capture.d.ts +2 -0
  14. package/dist/ar/image-capture.js +152 -0
  15. package/dist/ar/index.d.ts +12 -0
  16. package/dist/ar/index.js +12 -0
  17. package/dist/ar/replay-scene.d.ts +2 -0
  18. package/dist/ar/replay-scene.js +325 -0
  19. package/dist/ar/scene-node-names.d.ts +2 -0
  20. package/dist/ar/scene-node-names.js +16 -0
  21. package/dist/ar/tracking-state.d.ts +2 -0
  22. package/dist/ar/tracking-state.js +164 -0
  23. package/dist/ar/webxr-session.d.ts +4 -0
  24. package/dist/ar/webxr-session.js +890 -0
  25. package/dist/ar/xr-camera-texture.d.ts +2 -0
  26. package/dist/ar/xr-camera-texture.js +39 -0
  27. package/dist/ar/xr-error-handler.d.ts +2 -0
  28. package/dist/ar/xr-error-handler.js +37 -0
  29. package/dist/ar-types-B-ORgk6Z.d.ts +82 -0
  30. package/dist/camera-blit-capture-DHuyrrxr.d.ts +108 -0
  31. package/dist/camera-follower-BMqPddxx.d.ts +29 -0
  32. package/dist/capture-failure-tracker-2Q6oI2uQ.d.ts +52 -0
  33. package/dist/chromium-camera-access-workaround-CU5zSKNr.d.ts +61 -0
  34. package/dist/community-license-key-ChZLCJyO.d.ts +21 -0
  35. package/dist/concurrency-p6gB30fT.d.ts +24 -0
  36. package/dist/css3d-renderer-manager-rgLMVAvd.d.ts +23 -0
  37. package/dist/depth-sampler-BNCtnEoP.d.ts +108 -0
  38. package/dist/failure-tracker-1sQ_Cgyw.d.ts +49 -0
  39. package/dist/file-system-BsQgYCU3.d.ts +128 -0
  40. package/dist/file-system-utils-5cWO8Xyv.d.ts +24 -0
  41. package/dist/format-file-size-onGq0k1Q.d.ts +24 -0
  42. package/dist/fused-path-DtqCSqJh.d.ts +25 -0
  43. package/dist/geo-types-Xx-LVcnw.d.ts +29 -0
  44. package/dist/gps-Ddzr2KlX.d.ts +54 -0
  45. package/dist/gps-compass-cubes-C-6JEnpR.d.ts +22 -0
  46. package/dist/gps-error-handler-VPuNOuf8.d.ts +40 -0
  47. package/dist/gps-event-markers-DpEy2qRd.d.ts +67 -0
  48. package/dist/h3-ref-point-CFhayNSZ.d.ts +59 -0
  49. package/dist/image-capture-2pKjYUS6.d.ts +130 -0
  50. package/dist/index.d.ts +56 -0
  51. package/dist/index.js +61 -0
  52. package/dist/leaflet-map-overlay-CvfQXTye.d.ts +114 -0
  53. package/dist/lerp-utils-87NWjUOD.d.ts +20 -0
  54. package/dist/licensing/community-license-key.d.ts +2 -0
  55. package/dist/licensing/community-license-key.js +21 -0
  56. package/dist/licensing/index.d.ts +2 -0
  57. package/dist/licensing/index.js +2 -0
  58. package/dist/list-formatter-DKQQVLcl.d.ts +14 -0
  59. package/dist/logger-B81iwxx0.d.ts +74 -0
  60. package/dist/map-overlay-DuiZuCTx.d.ts +153 -0
  61. package/dist/null-storage-backend-CsWvQwN8.d.ts +11 -0
  62. package/dist/opfs-storage-ChOzF_Wp.d.ts +181 -0
  63. package/dist/opfs-storage-backend-CVcmfJHN.d.ts +11 -0
  64. package/dist/permission-checker-jO2ziCWC.d.ts +146 -0
  65. package/dist/persistence-middleware-DrptY02i.d.ts +31 -0
  66. package/dist/recorder-slice-fcwAXrsh.d.ts +35 -0
  67. package/dist/recording-coordinator-DzflP07_.d.ts +81 -0
  68. package/dist/recording-options-BYUFX8iU.d.ts +166 -0
  69. package/dist/recording-replayer-oOzPITrd.d.ts +32 -0
  70. package/dist/ref-point-importer-B_wn5ABk.d.ts +62 -0
  71. package/dist/ref-point-loader-Pt1smgHS.d.ts +117 -0
  72. package/dist/ref-points/h3-ref-point.d.ts +2 -0
  73. package/dist/ref-points/h3-ref-point.js +74 -0
  74. package/dist/ref-points/index.d.ts +2 -0
  75. package/dist/ref-points/index.js +2 -0
  76. package/dist/ref-points-slice-tC-Wd6XJ.d.ts +48 -0
  77. package/dist/reference-points-CFWy8_wj.d.ts +68 -0
  78. package/dist/replay-engine-DuX316ae.d.ts +103 -0
  79. package/dist/replay-scene-DtUm6bsr.d.ts +109 -0
  80. package/dist/routing-slice-BlHTM8eh.d.ts +23 -0
  81. package/dist/scene-node-names-DPsaE2z3.d.ts +14 -0
  82. package/dist/sensors/gps-error-handler.d.ts +2 -0
  83. package/dist/sensors/gps-error-handler.js +53 -0
  84. package/dist/sensors/gps.d.ts +2 -0
  85. package/dist/sensors/gps.js +93 -0
  86. package/dist/sensors/index.d.ts +4 -0
  87. package/dist/sensors/index.js +4 -0
  88. package/dist/sensors/permission-checker.d.ts +2 -0
  89. package/dist/sensors/permission-checker.js +397 -0
  90. package/dist/state/app-selectors.d.ts +2 -0
  91. package/dist/state/app-selectors.js +35 -0
  92. package/dist/state/index.d.ts +17 -0
  93. package/dist/state/index.js +13 -0
  94. package/dist/state/persistence-middleware.d.ts +2 -0
  95. package/dist/state/persistence-middleware.js +71 -0
  96. package/dist/state/recorder-slice.d.ts +2 -0
  97. package/dist/state/recorder-slice.js +47 -0
  98. package/dist/state/recording-coordinator.d.ts +2 -0
  99. package/dist/state/recording-coordinator.js +150 -0
  100. package/dist/state/recording-options.d.ts +2 -0
  101. package/dist/state/recording-options.js +192 -0
  102. package/dist/state/recording-replayer.d.ts +2 -0
  103. package/dist/state/recording-replayer.js +39 -0
  104. package/dist/state/ref-points-slice.d.ts +2 -0
  105. package/dist/state/ref-points-slice.js +44 -0
  106. package/dist/state/replay-engine.d.ts +2 -0
  107. package/dist/state/replay-engine.js +230 -0
  108. package/dist/state/routing-slice.d.ts +2 -0
  109. package/dist/state/routing-slice.js +13 -0
  110. package/dist/state/store-subscribers.d.ts +3 -0
  111. package/dist/state/store-subscribers.js +97 -0
  112. package/dist/state/store.d.ts +9 -0
  113. package/dist/state/store.js +71 -0
  114. package/dist/state/subscribe-to-selector.d.ts +2 -0
  115. package/dist/state/subscribe-to-selector.js +26 -0
  116. package/dist/storage/file-system-utils.d.ts +2 -0
  117. package/dist/storage/file-system-utils.js +31 -0
  118. package/dist/storage/file-system.d.ts +2 -0
  119. package/dist/storage/file-system.js +229 -0
  120. package/dist/storage/index.d.ts +11 -0
  121. package/dist/storage/index.js +10 -0
  122. package/dist/storage/null-storage-backend.d.ts +2 -0
  123. package/dist/storage/null-storage-backend.js +8 -0
  124. package/dist/storage/opfs-storage-backend.d.ts +2 -0
  125. package/dist/storage/opfs-storage-backend.js +15 -0
  126. package/dist/storage/opfs-storage.d.ts +2 -0
  127. package/dist/storage/opfs-storage.js +242 -0
  128. package/dist/storage/ref-point-importer.d.ts +2 -0
  129. package/dist/storage/ref-point-importer.js +163 -0
  130. package/dist/storage/ref-point-loader.d.ts +2 -0
  131. package/dist/storage/ref-point-loader.js +248 -0
  132. package/dist/storage/ref-point-recovery.d.ts +28 -0
  133. package/dist/storage/ref-point-recovery.js +159 -0
  134. package/dist/storage/storage-backend.d.ts +2 -0
  135. package/dist/storage/storage-backend.js +1 -0
  136. package/dist/storage/zip-export.d.ts +2 -0
  137. package/dist/storage/zip-export.js +214 -0
  138. package/dist/storage/zip-reader.d.ts +2 -0
  139. package/dist/storage/zip-reader.js +190 -0
  140. package/dist/storage-backend-BcEItnFr.d.ts +20 -0
  141. package/dist/store-C7PKOj8S.d.ts +96 -0
  142. package/dist/store-subscribers-CuoIzB5T.d.ts +77 -0
  143. package/dist/subscribe-to-selector-UTZ0KkAc.d.ts +26 -0
  144. package/dist/test-utils/browser-mocks.d.ts +294 -0
  145. package/dist/test-utils/browser-mocks.js +568 -0
  146. package/dist/test-utils/zip-round-trip-helpers.d.ts +60 -0
  147. package/dist/test-utils/zip-round-trip-helpers.js +207 -0
  148. package/dist/three-dispose-CCBHjE_a.d.ts +30 -0
  149. package/dist/tracking-state-B9QfuZCg.d.ts +101 -0
  150. package/dist/types/ar-types.d.ts +2 -0
  151. package/dist/types/ar-types.js +1 -0
  152. package/dist/types/geo-types.d.ts +2 -0
  153. package/dist/types/geo-types.js +1 -0
  154. package/dist/types/index.d.ts +3 -0
  155. package/dist/types/index.js +1 -0
  156. package/dist/utils/concurrency.d.ts +2 -0
  157. package/dist/utils/concurrency.js +40 -0
  158. package/dist/utils/failure-tracker.d.ts +2 -0
  159. package/dist/utils/failure-tracker.js +48 -0
  160. package/dist/utils/format-file-size.d.ts +2 -0
  161. package/dist/utils/format-file-size.js +41 -0
  162. package/dist/utils/fused-path.d.ts +2 -0
  163. package/dist/utils/fused-path.js +44 -0
  164. package/dist/utils/index.d.ts +7 -0
  165. package/dist/utils/index.js +7 -0
  166. package/dist/utils/list-formatter.d.ts +2 -0
  167. package/dist/utils/list-formatter.js +17 -0
  168. package/dist/utils/logger.d.ts +2 -0
  169. package/dist/utils/logger.js +226 -0
  170. package/dist/vis-colors-Qi2vlRGt.d.ts +63 -0
  171. package/dist/visualization/alignment-lerper.d.ts +2 -0
  172. package/dist/visualization/alignment-lerper.js +68 -0
  173. package/dist/visualization/camera-follower.d.ts +2 -0
  174. package/dist/visualization/camera-follower.js +51 -0
  175. package/dist/visualization/css3d-renderer-manager.d.ts +2 -0
  176. package/dist/visualization/css3d-renderer-manager.js +41 -0
  177. package/dist/visualization/gps-compass-cubes.d.ts +2 -0
  178. package/dist/visualization/gps-compass-cubes.js +129 -0
  179. package/dist/visualization/gps-event-markers.d.ts +2 -0
  180. package/dist/visualization/gps-event-markers.js +159 -0
  181. package/dist/visualization/index.d.ts +12 -0
  182. package/dist/visualization/index.js +12 -0
  183. package/dist/visualization/leaflet-map-overlay.d.ts +2 -0
  184. package/dist/visualization/leaflet-map-overlay.js +313 -0
  185. package/dist/visualization/lerp-utils.d.ts +2 -0
  186. package/dist/visualization/lerp-utils.js +22 -0
  187. package/dist/visualization/map-overlay.d.ts +2 -0
  188. package/dist/visualization/map-overlay.js +241 -0
  189. package/dist/visualization/reference-points.d.ts +2 -0
  190. package/dist/visualization/reference-points.js +160 -0
  191. package/dist/visualization/three-dispose.d.ts +2 -0
  192. package/dist/visualization/three-dispose.js +55 -0
  193. package/dist/visualization/vis-colors.d.ts +2 -0
  194. package/dist/visualization/vis-colors.js +74 -0
  195. package/dist/webxr-session-COlSrXdL.d.ts +316 -0
  196. package/dist/xr-camera-texture-BbukNE79.d.ts +53 -0
  197. package/dist/xr-error-handler-DTHfmvrl.d.ts +24 -0
  198. package/dist/zip-export-Rtpi75JJ.d.ts +87 -0
  199. package/dist/zip-reader-D7pkpX8K.d.ts +97 -0
  200. package/package.json +104 -0
@@ -0,0 +1,230 @@
1
+ //#region ../src/state/replay-engine.ts
2
+ /** Default maximum delay between actions in ms (30 seconds real-time) */
3
+ const DEFAULT_MAX_DELAY_MS = 3e4;
4
+ /** Max consecutive dispatch errors before auto-pause (Risk R7) */
5
+ const MAX_CONSECUTIVE_ERRORS = 10;
6
+ /**
7
+ * Extract an absolute epoch-ms timestamp from a Redux action, or null
8
+ * if the action type doesn't carry one.
9
+ *
10
+ * IMPORTANT: depthSample uses performance.now() (relative to page load),
11
+ * NOT epoch ms. Returning it here would mix clock domains and produce
12
+ * garbage delays. It must return null. (Risk R4)
13
+ *
14
+ * @param action - A Redux action with type and optional payload
15
+ * @returns Epoch milliseconds, or null if unavailable/unreliable
16
+ */
17
+ function extractActionTimestamp(action) {
18
+ if (!action.payload || typeof action.payload !== "object") return null;
19
+ const payload = action.payload;
20
+ switch (action.type) {
21
+ case "gpsData/recordGpsEvent": {
22
+ const rawGpsPoint = payload.rawGpsPoint;
23
+ if (rawGpsPoint && typeof rawGpsPoint.timestamp === "number") return rawGpsPoint.timestamp;
24
+ const gpsPoint = payload.gpsPoint;
25
+ if (gpsPoint && typeof gpsPoint.timestamp === "number") return gpsPoint.timestamp;
26
+ return null;
27
+ }
28
+ case "recorder/startSession":
29
+ if (typeof payload.startTime === "number") return payload.startTime;
30
+ return null;
31
+ case "gpsData/markReferencePoint": {
32
+ if (typeof payload.timestamp === "number") return payload.timestamp;
33
+ const rawGpsPoint = payload.rawGpsPoint;
34
+ if (rawGpsPoint && typeof rawGpsPoint.timestamp === "number") return rawGpsPoint.timestamp;
35
+ const gpsPoint = payload.gpsPoint;
36
+ if (gpsPoint && typeof gpsPoint.timestamp === "number") return gpsPoint.timestamp;
37
+ return null;
38
+ }
39
+ case "recorder/recordDepthSample": return null;
40
+ case "recorder/endSession": return null;
41
+ default: return null;
42
+ }
43
+ }
44
+ /**
45
+ * Compute the delay in ms between two consecutive actions for replay.
46
+ *
47
+ * @param currentTs - Epoch ms of the current (just-dispatched) action, or null
48
+ * @param nextTs - Epoch ms of the next action, or null
49
+ * @param speedFactor - Playback speed multiplier (1 = real-time)
50
+ * @param maxDelay - Maximum delay in ms (default: DEFAULT_MAX_DELAY_MS)
51
+ * @returns Delay in ms, clamped to [0, maxDelay]
52
+ */
53
+ function computeInterActionDelay(currentTs, nextTs, speedFactor, maxDelay = DEFAULT_MAX_DELAY_MS) {
54
+ if (currentTs === null || nextTs === null) return 0;
55
+ const rawDelay = (nextTs - currentTs) / speedFactor;
56
+ return Math.min(Math.max(0, rawDelay), maxDelay);
57
+ }
58
+ /**
59
+ * Async controller that dispatches recorded actions with timed delays.
60
+ *
61
+ * Uses AbortController for pause/cancel:
62
+ * - play() starts the async loop
63
+ * - pause() aborts the current controller → loop exits
64
+ * - resume() creates a new controller and restarts from current index
65
+ * - setSpeed() updates a closure variable, picked up on next delay calc
66
+ * - dispose() stops everything and resets state
67
+ */
68
+ var ReplayEngine = class {
69
+ state = "idle";
70
+ currentIndex = 0;
71
+ actions = [];
72
+ store = null;
73
+ speedFactor = 1;
74
+ abortController = null;
75
+ progressCallbacks = [];
76
+ completeCallbacks = [];
77
+ errorCallbacks = [];
78
+ currentPlayPromise = null;
79
+ /** Get the current engine state */
80
+ getState() {
81
+ return this.state;
82
+ }
83
+ /** Get the current action index (1-based after dispatch, 0 before play) */
84
+ getCurrentActionIndex() {
85
+ return this.currentIndex;
86
+ }
87
+ /** Register a progress callback: (currentIndex, totalCount) */
88
+ onProgress(callback) {
89
+ this.progressCallbacks.push(callback);
90
+ }
91
+ /** Register a completion callback */
92
+ onComplete(callback) {
93
+ this.completeCallbacks.push(callback);
94
+ }
95
+ /**
96
+ * Register an error callback: (actionIndex, error).
97
+ * Called when a dispatch throws. Replay continues past the error.
98
+ * After MAX_CONSECUTIVE_ERRORS consecutive failures, engine auto-pauses (R7).
99
+ */
100
+ onError(callback) {
101
+ this.errorCallbacks.push(callback);
102
+ }
103
+ /** Update playback speed (takes effect on next delay calculation) */
104
+ setSpeed(factor) {
105
+ if (!Number.isFinite(factor) || factor <= 0) throw new RangeError(`Speed factor must be a positive finite number, got ${factor}`);
106
+ this.speedFactor = factor;
107
+ }
108
+ /**
109
+ * Start replaying actions with timed delays.
110
+ *
111
+ * @param actions - Array of Redux actions to dispatch
112
+ * @param store - RecorderStore to dispatch into
113
+ * @param speedFactor - Initial playback speed (1 = real-time)
114
+ * @returns Promise that resolves when replay completes or is paused
115
+ */
116
+ play(actions, store, speedFactor) {
117
+ this.abortController?.abort();
118
+ this.actions = actions;
119
+ this.store = store;
120
+ this.speedFactor = speedFactor;
121
+ this.currentIndex = 0;
122
+ this.state = "playing";
123
+ this.abortController = new AbortController();
124
+ this.currentPlayPromise = this.runLoop(this.abortController.signal);
125
+ return this.currentPlayPromise;
126
+ }
127
+ /** Pause the replay. Can be resumed later with resume(). */
128
+ pause() {
129
+ if (this.state !== "playing") return;
130
+ this.state = "paused";
131
+ this.abortController?.abort();
132
+ this.abortController = null;
133
+ }
134
+ /**
135
+ * Resume from the current action index after a pause.
136
+ * @returns Promise that resolves when replay completes or is paused again
137
+ */
138
+ resume() {
139
+ if (this.state !== "paused" || !this.store) return Promise.resolve();
140
+ this.state = "playing";
141
+ this.abortController = new AbortController();
142
+ this.currentPlayPromise = this.runLoop(this.abortController.signal);
143
+ return this.currentPlayPromise;
144
+ }
145
+ /** Stop playback and reset to idle. */
146
+ dispose() {
147
+ this.abortController?.abort();
148
+ this.abortController = null;
149
+ this.state = "idle";
150
+ this.currentIndex = 0;
151
+ this.actions = [];
152
+ this.store = null;
153
+ this.currentPlayPromise = null;
154
+ this.progressCallbacks = [];
155
+ this.completeCallbacks = [];
156
+ this.errorCallbacks = [];
157
+ }
158
+ async runLoop(signal) {
159
+ const { actions, store } = this;
160
+ if (!store) return;
161
+ const total = actions.length;
162
+ if (total === 0) {
163
+ this.state = "completed";
164
+ this.notifyComplete();
165
+ return;
166
+ }
167
+ let consecutiveErrors = 0;
168
+ while (this.currentIndex < total) {
169
+ if (signal.aborted) return;
170
+ const action = actions[this.currentIndex];
171
+ try {
172
+ store.dispatch(action);
173
+ consecutiveErrors = 0;
174
+ } catch (err) {
175
+ consecutiveErrors++;
176
+ const error = err instanceof Error ? err : new Error(String(err));
177
+ this.notifyError(this.currentIndex + 1, error);
178
+ if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
179
+ this.state = "paused";
180
+ this.abortController?.abort();
181
+ this.abortController = null;
182
+ return;
183
+ }
184
+ }
185
+ this.currentIndex++;
186
+ this.notifyProgress(this.currentIndex, total);
187
+ if (this.currentIndex >= total) {
188
+ this.state = "completed";
189
+ this.notifyComplete();
190
+ return;
191
+ }
192
+ const delay = computeInterActionDelay(extractActionTimestamp(action), extractActionTimestamp(actions[this.currentIndex]), this.speedFactor);
193
+ if (delay > 0) {
194
+ if (await this.abortableDelay(delay, signal)) return;
195
+ }
196
+ }
197
+ }
198
+ /**
199
+ * Promise-based delay that resolves on timeout or rejects on abort.
200
+ * Returns true if aborted, false if delay completed normally.
201
+ */
202
+ abortableDelay(ms, signal) {
203
+ return new Promise((resolve) => {
204
+ if (signal.aborted) {
205
+ resolve(true);
206
+ return;
207
+ }
208
+ const timer = setTimeout(() => {
209
+ signal.removeEventListener("abort", onAbort);
210
+ resolve(false);
211
+ }, ms);
212
+ const onAbort = () => {
213
+ clearTimeout(timer);
214
+ resolve(true);
215
+ };
216
+ signal.addEventListener("abort", onAbort, { once: true });
217
+ });
218
+ }
219
+ notifyProgress(current, total) {
220
+ for (const cb of this.progressCallbacks) cb(current, total);
221
+ }
222
+ notifyComplete() {
223
+ for (const cb of this.completeCallbacks) cb();
224
+ }
225
+ notifyError(actionIndex, error) {
226
+ for (const cb of this.errorCallbacks) cb(actionIndex, error);
227
+ }
228
+ };
229
+ //#endregion
230
+ export { DEFAULT_MAX_DELAY_MS, ReplayEngine, computeInterActionDelay, extractActionTimestamp };
@@ -0,0 +1,2 @@
1
+ import { i as routingReducer, n as RoutingState, r as navigateTo, t as AppScreen } from "../routing-slice-BlHTM8eh.js";
2
+ export { AppScreen, RoutingState, navigateTo, routingReducer };
@@ -0,0 +1,13 @@
1
+ import { createSlice } from "@reduxjs/toolkit";
2
+ //#region ../src/state/routing-slice.ts
3
+ const routingSlice = createSlice({
4
+ name: "routing",
5
+ initialState: { currentScreen: "setup" },
6
+ reducers: { navigateTo(state, action) {
7
+ state.currentScreen = action.payload;
8
+ } }
9
+ });
10
+ const { navigateTo } = routingSlice.actions;
11
+ const routingReducer = routingSlice.reducer;
12
+ //#endregion
13
+ export { navigateTo, routingReducer };
@@ -0,0 +1,3 @@
1
+ import { t as SubscribableStore } from "../subscribe-to-selector-UTZ0KkAc.js";
2
+ import { n as wireStoreSubscribers, t as StoreSubscriberDeps } from "../store-subscribers-CuoIzB5T.js";
3
+ export { StoreSubscriberDeps, SubscribableStore, wireStoreSubscribers };
@@ -0,0 +1,97 @@
1
+ import { fusedGpsFromOdom } from "../utils/fused-path.js";
2
+ import { subscribeToSelector } from "./subscribe-to-selector.js";
3
+ import { selectAlignmentMatrix, selectGpsPositions, selectOdometryPositions, selectOdometryRotations, selectReferencePoints, selectZeroReference } from "./app-selectors.js";
4
+ import { calcGpsCoords } from "gps-plus-slam-js";
5
+ import { mat4, vec3 } from "gl-matrix";
6
+ //#region ../src/state/store-subscribers.ts
7
+ /**
8
+ * Wire store state-change subscriptions to visualization dependencies.
9
+ *
10
+ * Uses subscribeToSelector to decompose the monolithic subscriber into
11
+ * focused, change-gated callbacks:
12
+ * 1. Alignment matrix changes → apply matrix + capture snapshots
13
+ * 2. GPS position changes → incremental event visualization + map overlay
14
+ * 3. Reference point changes → forward to map overlay
15
+ *
16
+ * Each subscription uses reference equality to detect changes — callbacks
17
+ * only fire when the selector output actually differs between dispatches.
18
+ *
19
+ * Each call creates **fresh subscriptions** — no manual reset needed between
20
+ * sessions. Call the returned unsubscribe function to tear down all.
21
+ *
22
+ * @param store — any object satisfying SubscribableStore (RecorderStore, replay store, mock)
23
+ * @param deps — visualization dependencies (real singletons or test mocks)
24
+ * @returns An unsubscribe function that removes all listeners from the store.
25
+ */
26
+ function wireStoreSubscribers(store, deps) {
27
+ const _odomVec = vec3.create();
28
+ const _alignedVec = vec3.create();
29
+ const unsubs = [];
30
+ unsubs.push(subscribeToSelector(store, selectAlignmentMatrix, (matrix) => {
31
+ if (!matrix) return;
32
+ deps.applyAlignmentMatrix(matrix);
33
+ const state = store.getState();
34
+ const odomPositions = selectOdometryPositions(state);
35
+ if (odomPositions.length > 0) {
36
+ const latestOdom = odomPositions[odomPositions.length - 1];
37
+ const glMat = mat4.fromValues(...matrix);
38
+ vec3.set(_odomVec, latestOdom[0], latestOdom[1], latestOdom[2]);
39
+ vec3.transformMat4(_alignedVec, _odomVec, glMat);
40
+ const snapshotPos = [
41
+ _alignedVec[0],
42
+ _alignedVec[1],
43
+ _alignedVec[2]
44
+ ];
45
+ deps.gpsEventVisualizer.addAlignmentSnapshot(snapshotPos);
46
+ deps.onAlignmentSnapshot?.(snapshotPos);
47
+ const zeroRef = selectZeroReference(state);
48
+ if (zeroRef && deps.mapOverlay?.addAlignmentSnapshot) {
49
+ const gps = calcGpsCoords(zeroRef, _alignedVec);
50
+ deps.mapOverlay.addAlignmentSnapshot(gps.lat, gps.lon);
51
+ }
52
+ }
53
+ }));
54
+ unsubs.push(subscribeToSelector(store, selectGpsPositions, (gpsPositions, prevPositions) => {
55
+ const prevCount = prevPositions?.length ?? 0;
56
+ const state = store.getState();
57
+ const zeroRef = selectZeroReference(state);
58
+ if (zeroRef && !deps.gpsEventVisualizer.getZeroRef()) deps.gpsEventVisualizer.setZeroRef(zeroRef);
59
+ const odomPositions = selectOdometryPositions(state);
60
+ const odomRotations = selectOdometryRotations(state);
61
+ for (let i = prevCount; i < gpsPositions.length; i++) {
62
+ const gpsPoint = gpsPositions[i];
63
+ const odomPos = odomPositions[i];
64
+ if (gpsPoint && odomPos) {
65
+ deps.gpsEventVisualizer.addGpsEvent(gpsPoint.coordinates, odomPos);
66
+ deps.onNewGpsPosition?.(gpsPoint.coordinates);
67
+ deps.onNewGpsLatLng?.(gpsPoint.latitude, gpsPoint.longitude);
68
+ deps.mapOverlay?.addRawGpsPoint?.(gpsPoint.latitude, gpsPoint.longitude);
69
+ const alignmentMatrix = selectAlignmentMatrix(state);
70
+ const zero = selectZeroReference(state);
71
+ if (alignmentMatrix && zero && deps.mapOverlay?.addFusedPoint) {
72
+ const fusedGps = fusedGpsFromOdom(alignmentMatrix, odomPos, zero);
73
+ deps.mapOverlay.addFusedPoint(fusedGps.lat, fusedGps.lon);
74
+ }
75
+ const odomRot = odomRotations[i];
76
+ if (odomRot) deps.onNewOdomPose?.(odomPos, odomRot);
77
+ }
78
+ }
79
+ if (deps.mapOverlay && gpsPositions.length > 0) {
80
+ const lastGpsPoint = gpsPositions[gpsPositions.length - 1];
81
+ deps.mapOverlay.setGpsPosition(lastGpsPoint.latitude, lastGpsPoint.longitude);
82
+ }
83
+ }));
84
+ unsubs.push(subscribeToSelector(store, selectReferencePoints, (refPoints, prevPoints) => {
85
+ const prevCount = prevPoints?.length ?? 0;
86
+ if (!deps.mapOverlay?.addRefPoint || refPoints.length <= prevCount) return;
87
+ for (let i = prevCount; i < refPoints.length; i++) {
88
+ const rp = refPoints[i];
89
+ deps.mapOverlay.addRefPoint(rp.gpsPoint.latitude, rp.gpsPoint.longitude, rp.id);
90
+ }
91
+ }));
92
+ return () => {
93
+ for (const unsub of unsubs) unsub();
94
+ };
95
+ }
96
+ //#endregion
97
+ export { wireStoreSubscribers };
@@ -0,0 +1,9 @@
1
+ import { i as DepthSample, r as DepthPoint } from "../ar-types-B-ORgk6Z.js";
2
+ import { s as RecordingOptions } from "../recording-options-BYUFX8iU.js";
3
+ import { n as SessionMetadata } from "../opfs-storage-ChOzF_Wp.js";
4
+ import { t as StorageBackend } from "../storage-backend-BcEItnFr.js";
5
+ import { a as recordWriteFailure, c as startSession, i as recordDepthSample, n as SessionMetadata$1, r as endSession, s as setCurrentScenarioName, t as RecorderState } from "../recorder-slice-fcwAXrsh.js";
6
+ import { a as resetRefPointsState, n as clearSessionRefPointUsage, o as selectCachedKnownRefPoints, r as incrementRefPointUsage, s as setImportedRefPoints, t as RefPointsState } from "../ref-points-slice-tC-Wd6XJ.js";
7
+ import { n as RefPointMark } from "../ref-point-loader-Pt1smgHS.js";
8
+ import { _ as setZeroPos, a as MarkReferencePointPayload, c as RecordGpsEventPayload, d as RootState, f as add2dImage, g as recordGpsEvent, h as markReferencePoint, i as LatLong, l as RecorderStore, m as createRecorderStore, n as CombinedRootState, o as RawDeviceOrientation, p as calcRelativeCoordsInMeters, r as GpsPoint, s as RawGpsPoint, t as AppDispatch, u as RecorderStoreOptions } from "../store-C7PKOj8S.js";
9
+ export { AppDispatch, CombinedRootState, DepthPoint, DepthSample, GpsPoint, LatLong, MarkReferencePointPayload, SessionMetadata as OpfsSessionMetadata, RawDeviceOrientation, RawGpsPoint, RecordGpsEventPayload, RecorderState, RecorderStore, RecorderStoreOptions, RecordingOptions, RefPointMark, RefPointsState, RootState, SessionMetadata$1 as SessionMetadata, StorageBackend, add2dImage, calcRelativeCoordsInMeters, clearSessionRefPointUsage, createRecorderStore, endSession, incrementRefPointUsage, markReferencePoint, recordDepthSample, recordGpsEvent, recordWriteFailure, resetRefPointsState, selectCachedKnownRefPoints, setCurrentScenarioName, setImportedRefPoints, setZeroPos, startSession };
@@ -0,0 +1,71 @@
1
+ import { endSession, recordDepthSample, recordWriteFailure, recorderReducer, setCurrentScenarioName, startSession } from "./recorder-slice.js";
2
+ import { clearSessionRefPointUsage, incrementRefPointUsage, refPointsReducer, resetRefPointsState, selectCachedKnownRefPoints, setImportedRefPoints } from "./ref-points-slice.js";
3
+ import { routingReducer } from "./routing-slice.js";
4
+ import { OpfsStorageBackend } from "../storage/opfs-storage-backend.js";
5
+ import { createPersistenceMiddleware } from "./persistence-middleware.js";
6
+ import "../licensing/community-license-key.js";
7
+ import { configureStore } from "@reduxjs/toolkit";
8
+ import { add2dImage, arElementsReducer, calcRelativeCoordsInMeters, gpsDataReducer, gpsElementsReducer, markReferencePoint, recordGpsEvent, sanitizeForDevTools, setZeroPos, validateLicenseKey } from "gps-plus-slam-js";
9
+ //#region ../src/state/store.ts
10
+ /**
11
+ * Redux Store for Recorder App
12
+ *
13
+ * Uses Redux Toolkit's configureStore to combine all reducers (library +
14
+ * app-level) into a single store. Action persistence during recording is
15
+ * handled by the persistence middleware.
16
+ *
17
+ * ARCHITECTURE NOTE: See docs/architecture-ar-gps-pose-separation.md
18
+ * The library's recordGpsEvent action captures paired AR+GPS data
19
+ * that feeds directly into the alignment algorithm.
20
+ *
21
+ * Migration note (§4 — architecture-observations-consolidated.md):
22
+ * Previously the store used a manual wrapper pattern with action.type.startsWith()
23
+ * prefix matching to route actions between a library store and local state
24
+ * variables. This has been replaced with a single configureStore call that
25
+ * combines all 6 reducers declaratively.
26
+ */
27
+ /**
28
+ * Create the recorder store using Redux Toolkit's configureStore.
29
+ *
30
+ * Combines all 6 reducers (3 library: gpsData, gpsElements, arElements;
31
+ * 3 app: recorder, refPoints, routing) into a single store with the
32
+ * persistence middleware for action recording.
33
+ *
34
+ * @param options - Optional configuration including callbacks
35
+ */
36
+ function createRecorderStore(options = {}) {
37
+ const { onWriteFailure } = options;
38
+ const storageBackend = options.storageBackend ?? new OpfsStorageBackend();
39
+ const enableDevChecks = options.enableDevChecks ?? true;
40
+ validateLicenseKey(options.licenseKey ?? "eyJ0eXBlIjoiY29tbXVuaXR5IiwiZXhwIjoxODA4NzcyNDI3fQ.YYNf0IdqniZdMzRXCuvewxsQQ01XNywrKsfL5IpiJotfFJfqtrGg4u9z0QFhXeszPtUhnvgC8_oOnbOkbXPuAw");
41
+ const store = configureStore({
42
+ reducer: {
43
+ gpsData: gpsDataReducer,
44
+ gpsElements: gpsElementsReducer,
45
+ arElements: arElementsReducer,
46
+ recorder: recorderReducer,
47
+ refPoints: refPointsReducer,
48
+ routing: routingReducer
49
+ },
50
+ middleware: (getDefaultMiddleware) => getDefaultMiddleware({
51
+ serializableCheck: enableDevChecks,
52
+ immutableCheck: enableDevChecks
53
+ }).concat(createPersistenceMiddleware({
54
+ storageBackend,
55
+ onWriteFailure
56
+ })),
57
+ devTools: {
58
+ actionSanitizer: sanitizeForDevTools,
59
+ stateSanitizer: sanitizeForDevTools
60
+ }
61
+ });
62
+ return {
63
+ getState: () => store.getState(),
64
+ dispatch: store.dispatch,
65
+ subscribe: (listener) => store.subscribe(listener),
66
+ writeFrame: (blob, index) => storageBackend.writeFrame(blob, index),
67
+ writeSessionMetadata: (metadata) => storageBackend.writeSessionMetadata(metadata)
68
+ };
69
+ }
70
+ //#endregion
71
+ export { add2dImage, calcRelativeCoordsInMeters, clearSessionRefPointUsage, createRecorderStore, endSession, incrementRefPointUsage, markReferencePoint, recordDepthSample, recordGpsEvent, recordWriteFailure, resetRefPointsState, selectCachedKnownRefPoints, setCurrentScenarioName, setImportedRefPoints, setZeroPos, startSession };
@@ -0,0 +1,2 @@
1
+ import { n as subscribeToSelector, t as SubscribableStore } from "../subscribe-to-selector-UTZ0KkAc.js";
2
+ export { SubscribableStore, subscribeToSelector };
@@ -0,0 +1,26 @@
1
+ //#region ../src/state/subscribe-to-selector.ts
2
+ /**
3
+ * Subscribe to a specific derived value from the store, invoking `onChange`
4
+ * only when the selector result changes by strict reference equality (`!==`).
5
+ *
6
+ * This replaces the `let lastX = …; if (current !== lastX) { … }` pattern
7
+ * previously used in store-subscribers.ts.
8
+ *
9
+ * @param store — subscribable store instance
10
+ * @param selector — pure function extracting a value from state
11
+ * @param onChange — callback receiving (current, previous) when the value changes
12
+ * @returns unsubscribe function
13
+ */
14
+ function subscribeToSelector(store, selector, onChange) {
15
+ let previous;
16
+ return store.subscribe(() => {
17
+ const current = selector(store.getState());
18
+ if (current !== previous) {
19
+ const prev = previous;
20
+ previous = current;
21
+ onChange(current, prev);
22
+ }
23
+ });
24
+ }
25
+ //#endregion
26
+ export { subscribeToSelector };
@@ -0,0 +1,2 @@
1
+ import { n as formatFrameFilename, r as formatTimestamp, t as formatActionFilename } from "../file-system-utils-5cWO8Xyv.js";
2
+ export { formatActionFilename, formatFrameFilename, formatTimestamp };
@@ -0,0 +1,31 @@
1
+ //#region ../src/storage/file-system-utils.ts
2
+ /**
3
+ * File System Utilities - Pure Functions
4
+ *
5
+ * These are extracted from file-system.ts so they can be tested
6
+ * without mocking browser APIs.
7
+ */
8
+ /**
9
+ * Format a Date as a timestamp string for folder names
10
+ * @example "2025-02-28_14-30-11utc"
11
+ */
12
+ function formatTimestamp(date) {
13
+ const pad = (n) => String(n).padStart(2, "0");
14
+ return `${date.getUTCFullYear()}-${pad(date.getUTCMonth() + 1)}-${pad(date.getUTCDate())}_${pad(date.getUTCHours())}-${pad(date.getUTCMinutes())}-${pad(date.getUTCSeconds())}utc`;
15
+ }
16
+ /**
17
+ * Format an action file index as a zero-padded filename
18
+ * @example 42 -> "000042.json"
19
+ */
20
+ function formatActionFilename(index) {
21
+ return `${String(index).padStart(6, "0")}.json`;
22
+ }
23
+ /**
24
+ * Format a frame index as a zero-padded filename
25
+ * @example 42 -> "frame-000042.jpg"
26
+ */
27
+ function formatFrameFilename(index) {
28
+ return `frame-${String(index).padStart(6, "0")}.jpg`;
29
+ }
30
+ //#endregion
31
+ export { formatActionFilename, formatFrameFilename, formatTimestamp };
@@ -0,0 +1,2 @@
1
+ import { a as initStorage, c as resetForNewSession, d as startSession, f as verifyWriteAccess, h as writeSessionMetadata, i as getCurrentScenarioHandle, l as resetStorageState, m as writeFrame, n as WriteAccessResult, o as isRefPointAction, p as writeAction, r as ensureScenarioDirectory, s as loadScenarioRefPoints, t as RefPointRecord, u as setCurrentScenario } from "../file-system-BsQgYCU3.js";
2
+ export { RefPointRecord, WriteAccessResult, ensureScenarioDirectory, getCurrentScenarioHandle, initStorage, isRefPointAction, loadScenarioRefPoints, resetForNewSession, resetStorageState, setCurrentScenario, startSession, verifyWriteAccess, writeAction, writeFrame, writeSessionMetadata };