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,229 @@
1
+ import { createLogger } from "../utils/logger.js";
2
+ import { setFileSystemState } from "../sensors/permission-checker.js";
3
+ import { createSession, getScenarioHandle, getScenariosRootHandle, initOpfsStorage, listScenarios, resetOpfsStorage, resetSessionHandles, writeAction as writeAction$1, writeFrame as writeFrame$1, writeSessionMetadata as writeSessionMetadata$1 } from "./opfs-storage.js";
4
+ //#region ../src/storage/file-system.ts
5
+ const log = createLogger("Storage");
6
+ let storageInitialized = false;
7
+ /**
8
+ * Reset module state - exported for testing only
9
+ * @internal
10
+ */
11
+ function resetStorageState() {
12
+ storageInitialized = false;
13
+ resetOpfsStorage();
14
+ }
15
+ /**
16
+ * Reset session-level state for a new recording.
17
+ *
18
+ * Clears scenario/session names and OPFS session handles, but preserves
19
+ * `storageInitialized` and the OPFS root so `initStorage()` doesn't need
20
+ * to be called again.
21
+ *
22
+ * Used during soft reset for new recordings (Issue 4, 2026-02-06 user feedback).
23
+ */
24
+ function resetForNewSession() {
25
+ resetSessionHandles();
26
+ }
27
+ /**
28
+ * Verify that we have actual write access to a directory.
29
+ *
30
+ * This probes the directory by creating a test file, writing content,
31
+ * and cleaning up. This catches cases where showDirectoryPicker grants
32
+ * a handle but the underlying file system is read-only.
33
+ *
34
+ * User Feedback Issue #1: Even with mode: 'readwrite', Android can
35
+ * return a handle where getFileHandle({create: true}) succeeds but
36
+ * createWritable() fails with NoModificationAllowedError.
37
+ *
38
+ * @param dirHandle - Directory handle to verify
39
+ * @returns Success status and optional error message
40
+ */
41
+ async function verifyWriteAccess(dirHandle) {
42
+ const testFilename = `.write-test-${Date.now()}`;
43
+ try {
44
+ const writable = await (await dirHandle.getFileHandle(testFilename, { create: true })).createWritable();
45
+ await writable.write("write-test");
46
+ await writable.close();
47
+ try {
48
+ await dirHandle.removeEntry(testFilename);
49
+ } catch (cleanupErr) {
50
+ log.warn("Write test cleanup failed (non-critical):", cleanupErr);
51
+ }
52
+ log.info("Write access verified for:", dirHandle.name);
53
+ return { success: true };
54
+ } catch (err) {
55
+ const error = err instanceof Error ? err : new Error(String(err));
56
+ log.error("Write verification failed:", error);
57
+ if (error.name === "NoModificationAllowedError") return {
58
+ success: false,
59
+ error: "Folder is read-only. Please select a different folder with write access."
60
+ };
61
+ return {
62
+ success: false,
63
+ error: `Cannot write to folder: ${error.message}`
64
+ };
65
+ }
66
+ }
67
+ /**
68
+ * Initialize storage using OPFS.
69
+ *
70
+ * This replaces the old showDirectoryPicker approach which failed on Android Chrome.
71
+ * OPFS is automatically available and doesn't require user folder selection.
72
+ *
73
+ * @returns List of existing scenario folder names
74
+ * @throws Error if OPFS is not supported
75
+ */
76
+ async function initStorage() {
77
+ try {
78
+ await initOpfsStorage();
79
+ storageInitialized = true;
80
+ setFileSystemState({
81
+ folderSelected: true,
82
+ writeVerified: true
83
+ });
84
+ const scenarios = await listScenarios();
85
+ log.info("OPFS storage initialized, found scenarios:", scenarios);
86
+ return scenarios;
87
+ } catch (err) {
88
+ const error = err instanceof Error ? err : new Error(String(err));
89
+ log.error("OPFS initialization failed:", error);
90
+ setFileSystemState({
91
+ folderSelected: false,
92
+ writeVerified: false,
93
+ writeError: error.message
94
+ });
95
+ throw error;
96
+ }
97
+ }
98
+ /**
99
+ * Create or open a scenario folder, then create a new session folder.
100
+ *
101
+ * Now uses OPFS backend instead of showDirectoryPicker.
102
+ *
103
+ * @param scenarioName - Name of the scenario
104
+ */
105
+ async function startSession(scenarioName) {
106
+ if (!storageInitialized) throw new Error("Storage not initialized. Call initStorage first.");
107
+ const result = await createSession(scenarioName, /* @__PURE__ */ new Date());
108
+ result.scenarioName;
109
+ result.sessionName;
110
+ log.info("Session started:", `${result.scenarioName}/${result.sessionName}`);
111
+ return {
112
+ scenarioPath: result.scenarioName,
113
+ sessionPath: result.sessionName
114
+ };
115
+ }
116
+ /**
117
+ * Write a Redux action to OPFS as a JSON file.
118
+ */
119
+ async function writeAction(action, index) {
120
+ await writeAction$1(action, index);
121
+ }
122
+ /**
123
+ * Write a captured frame image to OPFS.
124
+ */
125
+ async function writeFrame(blob, index) {
126
+ await writeFrame$1(blob, index);
127
+ }
128
+ /**
129
+ * Write session metadata (session.json) to OPFS.
130
+ * Contains timing, counts, and user agent for the recording session.
131
+ */
132
+ async function writeSessionMetadata(metadata) {
133
+ await writeSessionMetadata$1(metadata);
134
+ }
135
+ /**
136
+ * Type guard for reference point actions
137
+ * Exported for testing
138
+ */
139
+ function isRefPointAction(action) {
140
+ if (typeof action !== "object" || action === null) return false;
141
+ const obj = action;
142
+ const payload = obj.payload;
143
+ return obj.type === "recorder/markRefPoint" && typeof payload === "object" && payload !== null && !Array.isArray(payload);
144
+ }
145
+ /**
146
+ * Parse action files from a session's actions directory
147
+ */
148
+ async function parseSessionRefPoints(actionsDir, sessionName) {
149
+ const refPoints = [];
150
+ for await (const actionEntry of actionsDir.values()) {
151
+ if (actionEntry.kind !== "file" || !actionEntry.name.endsWith(".json")) continue;
152
+ const text = await (await (await actionsDir.getFileHandle(actionEntry.name)).getFile()).text();
153
+ try {
154
+ const action = JSON.parse(text);
155
+ if (isRefPointAction(action)) refPoints.push({
156
+ id: action.payload.id,
157
+ sessionName,
158
+ gpsPosition: action.payload.gpsPosition,
159
+ arPose: action.payload.arPose
160
+ });
161
+ } catch {}
162
+ }
163
+ return refPoints;
164
+ }
165
+ /**
166
+ * Load existing reference points from a scenario (across all sessions).
167
+ * Now uses OPFS storage backend.
168
+ */
169
+ async function loadScenarioRefPoints(scenarioName) {
170
+ const scenariosRoot = getScenariosRootHandle();
171
+ if (!scenariosRoot) return [];
172
+ const refPoints = [];
173
+ try {
174
+ const scenarioHandle = await scenariosRoot.getDirectoryHandle(scenarioName);
175
+ for await (const sessionEntry of scenarioHandle.values()) {
176
+ if (sessionEntry.kind !== "directory") continue;
177
+ const sessionHandle = await scenarioHandle.getDirectoryHandle(sessionEntry.name);
178
+ let actionsDir;
179
+ try {
180
+ actionsDir = await sessionHandle.getDirectoryHandle("actions");
181
+ } catch {
182
+ continue;
183
+ }
184
+ const sessionRefPoints = await parseSessionRefPoints(actionsDir, sessionEntry.name);
185
+ refPoints.push(...sessionRefPoints);
186
+ }
187
+ } catch {}
188
+ return refPoints;
189
+ }
190
+ /**
191
+ * Get the current scenario handle (for loading prior sessions).
192
+ * Now delegates to OPFS storage backend.
193
+ */
194
+ function getCurrentScenarioHandle() {
195
+ return getScenarioHandle();
196
+ }
197
+ /**
198
+ * Set the current scenario handle (when user selects a scenario).
199
+ * With OPFS, we just store the name - the actual handle is retrieved when needed.
200
+ */
201
+ async function setCurrentScenario(scenarioName) {
202
+ const scenariosRoot = getScenariosRootHandle();
203
+ if (!scenariosRoot) return null;
204
+ try {
205
+ return await scenariosRoot.getDirectoryHandle(scenarioName);
206
+ } catch {
207
+ log.warn("Scenario not found:", scenarioName);
208
+ return null;
209
+ }
210
+ }
211
+ /**
212
+ * Ensure a scenario directory exists in OPFS, creating it if necessary.
213
+ * Used during OPFS recovery when the scenario directory was lost but ZIP
214
+ * data is available for restoration.
215
+ *
216
+ * Unlike setCurrentScenario() which only reads, this creates on demand.
217
+ */
218
+ async function ensureScenarioDirectory(scenarioName) {
219
+ const scenariosRoot = getScenariosRootHandle();
220
+ if (!scenariosRoot) return null;
221
+ try {
222
+ return await scenariosRoot.getDirectoryHandle(scenarioName, { create: true });
223
+ } catch (err) {
224
+ log.error("Failed to create scenario directory:", err);
225
+ return null;
226
+ }
227
+ }
228
+ //#endregion
229
+ export { ensureScenarioDirectory, getCurrentScenarioHandle, initStorage, isRefPointAction, loadScenarioRefPoints, resetForNewSession, resetStorageState, setCurrentScenario, startSession, verifyWriteAccess, writeAction, writeFrame, writeSessionMetadata };
@@ -0,0 +1,11 @@
1
+ import { a as getScenarioHandle, c as initOpfsStorage, d as resetOpfsStorage, f as resetSessionHandles, h as writeSessionMetadata$1, i as createSession, n as SessionMetadata, o as getScenariosRootHandle, r as checkStorageQuota, s as getSessionHandle, t as CreateSessionResult, u as listSessions } from "../opfs-storage-ChOzF_Wp.js";
2
+ import { t as StorageBackend } from "../storage-backend-BcEItnFr.js";
3
+ import { n as RefPointImportResult, r as importRefPointsFromFolder, t as ImportedRefPoint } from "../ref-point-importer-B_wn5ABk.js";
4
+ import { a as flattenRefPointsToMarks, c as loadRefPoint, i as averageGpsPerRefPoint, l as saveRefPointObservation, n as RefPointMark, o as listRefPointIds, r as RefPointObservation, s as loadAllRefPoints, t as RefPointDefinition } from "../ref-point-loader-Pt1smgHS.js";
5
+ import { a as ZipActionEntry, c as loadSessionMetadata, i as RecordedAction, l as loadSessionMetadataFromBlob, n as GpsPathCoord, o as loadActionsFromZip, r as MAX_ACTION_FILE_SIZE, s as loadGpsPathFromBlob, t as Entry, u as readZipEntries } from "../zip-reader-D7pkpX8K.js";
6
+ import { t as NullStorageBackend } from "../null-storage-backend-CsWvQwN8.js";
7
+ import { t as OpfsStorageBackend } from "../opfs-storage-backend-CVcmfJHN.js";
8
+ 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, s as loadScenarioRefPoints, u as setCurrentScenario } from "../file-system-BsQgYCU3.js";
9
+ import { n as formatFrameFilename, r as formatTimestamp, t as formatActionFilename } from "../file-system-utils-5cWO8Xyv.js";
10
+ import { a as syncToExternalZip, i as exportSessionAsZip, n as downloadZip, r as exportAndDownloadSession, t as ZipExportResult } from "../zip-export-Rtpi75JJ.js";
11
+ export { type CreateSessionResult, type Entry, type GpsPathCoord, type ImportedRefPoint, MAX_ACTION_FILE_SIZE, NullStorageBackend, OpfsStorageBackend, type RecordedAction, type RefPointDefinition, type RefPointImportResult, type RefPointMark, type RefPointObservation, type SessionMetadata, type StorageBackend, type WriteAccessResult, type ZipActionEntry, type ZipExportResult, averageGpsPerRefPoint, checkStorageQuota, createSession, downloadZip, exportAndDownloadSession, exportSessionAsZip, flattenRefPointsToMarks, formatActionFilename, formatFrameFilename, formatTimestamp, getCurrentScenarioHandle, getScenarioHandle, getScenariosRootHandle, getSessionHandle, importRefPointsFromFolder, initOpfsStorage, initStorage, isRefPointAction, listRefPointIds, listSessions, loadActionsFromZip, loadAllRefPoints, loadGpsPathFromBlob, loadRefPoint, loadScenarioRefPoints, loadSessionMetadataFromBlob, loadSessionMetadata as loadSessionMetadataFromZip, readZipEntries, resetForNewSession, resetOpfsStorage, resetSessionHandles, resetStorageState, saveRefPointObservation, setCurrentScenario, startSession as startStorageSession, syncToExternalZip, verifyWriteAccess, writeAction, writeFrame, writeSessionMetadata as writeSessionMeta, writeSessionMetadata$1 as writeSessionMetadata };
@@ -0,0 +1,10 @@
1
+ import { formatActionFilename, formatFrameFilename, formatTimestamp } from "./file-system-utils.js";
2
+ import { checkStorageQuota, createSession, getScenarioHandle, getScenariosRootHandle, getSessionHandle, initOpfsStorage, listSessions, resetOpfsStorage, resetSessionHandles, writeSessionMetadata as writeSessionMetadata$1 } from "./opfs-storage.js";
3
+ import { getCurrentScenarioHandle, initStorage, isRefPointAction, loadScenarioRefPoints, resetForNewSession, resetStorageState, setCurrentScenario, startSession, verifyWriteAccess, writeAction, writeFrame, writeSessionMetadata } from "./file-system.js";
4
+ import { OpfsStorageBackend } from "./opfs-storage-backend.js";
5
+ import { MAX_ACTION_FILE_SIZE, loadActionsFromZip, loadGpsPathFromBlob, loadSessionMetadata, loadSessionMetadataFromBlob, readZipEntries } from "./zip-reader.js";
6
+ import { NullStorageBackend } from "./null-storage-backend.js";
7
+ import { importRefPointsFromFolder } from "./ref-point-importer.js";
8
+ import { averageGpsPerRefPoint, flattenRefPointsToMarks, listRefPointIds, loadAllRefPoints, loadRefPoint, saveRefPointObservation } from "./ref-point-loader.js";
9
+ import { downloadZip, exportAndDownloadSession, exportSessionAsZip, syncToExternalZip } from "./zip-export.js";
10
+ export { MAX_ACTION_FILE_SIZE, NullStorageBackend, OpfsStorageBackend, averageGpsPerRefPoint, checkStorageQuota, createSession, downloadZip, exportAndDownloadSession, exportSessionAsZip, flattenRefPointsToMarks, formatActionFilename, formatFrameFilename, formatTimestamp, getCurrentScenarioHandle, getScenarioHandle, getScenariosRootHandle, getSessionHandle, importRefPointsFromFolder, initOpfsStorage, initStorage, isRefPointAction, listRefPointIds, listSessions, loadActionsFromZip, loadAllRefPoints, loadGpsPathFromBlob, loadRefPoint, loadScenarioRefPoints, loadSessionMetadataFromBlob, loadSessionMetadata as loadSessionMetadataFromZip, readZipEntries, resetForNewSession, resetOpfsStorage, resetSessionHandles, resetStorageState, saveRefPointObservation, setCurrentScenario, startSession as startStorageSession, syncToExternalZip, verifyWriteAccess, writeAction, writeFrame, writeSessionMetadata as writeSessionMeta, writeSessionMetadata$1 as writeSessionMetadata };
@@ -0,0 +1,2 @@
1
+ import { t as NullStorageBackend } from "../null-storage-backend-CsWvQwN8.js";
2
+ export { NullStorageBackend };
@@ -0,0 +1,8 @@
1
+ //#region ../src/storage/null-storage-backend.ts
2
+ var NullStorageBackend = class {
3
+ async writeAction(_action, _index) {}
4
+ async writeFrame(_blob, _index) {}
5
+ async writeSessionMetadata(_metadata) {}
6
+ };
7
+ //#endregion
8
+ export { NullStorageBackend };
@@ -0,0 +1,2 @@
1
+ import { t as OpfsStorageBackend } from "../opfs-storage-backend-CVcmfJHN.js";
2
+ export { OpfsStorageBackend };
@@ -0,0 +1,15 @@
1
+ import { writeAction, writeFrame, writeSessionMetadata } from "./file-system.js";
2
+ //#region ../src/storage/opfs-storage-backend.ts
3
+ var OpfsStorageBackend = class {
4
+ async writeAction(action, index) {
5
+ await writeAction(action, index);
6
+ }
7
+ async writeFrame(blob, index) {
8
+ await writeFrame(blob, index);
9
+ }
10
+ async writeSessionMetadata(metadata) {
11
+ await writeSessionMetadata(metadata);
12
+ }
13
+ };
14
+ //#endregion
15
+ export { OpfsStorageBackend };
@@ -0,0 +1,2 @@
1
+ import { a as getScenarioHandle, c as initOpfsStorage, d as resetOpfsStorage, f as resetSessionHandles, h as writeSessionMetadata, i as createSession, l as listScenarios, m as writeFrame, n as SessionMetadata, o as getScenariosRootHandle, p as writeAction, r as checkStorageQuota, s as getSessionHandle, t as CreateSessionResult, u as listSessions } from "../opfs-storage-ChOzF_Wp.js";
2
+ export { CreateSessionResult, SessionMetadata, checkStorageQuota, createSession, getScenarioHandle, getScenariosRootHandle, getSessionHandle, initOpfsStorage, listScenarios, listSessions, resetOpfsStorage, resetSessionHandles, writeAction, writeFrame, writeSessionMetadata };
@@ -0,0 +1,242 @@
1
+ import { createLogger } from "../utils/logger.js";
2
+ import { formatActionFilename, formatFrameFilename, formatTimestamp } from "./file-system-utils.js";
3
+ //#region ../src/storage/opfs-storage.ts
4
+ /**
5
+ * OPFS Storage Module
6
+ *
7
+ * Uses the Origin Private File System (OPFS) API to persist recording data.
8
+ * OPFS works consistently on Desktop Chrome, Android Chrome, and iOS Safari,
9
+ * unlike showDirectoryPicker which has write restrictions on Android.
10
+ *
11
+ * Directory structure:
12
+ * /gps-recorder/
13
+ * └── scenarios/
14
+ * ├── {scenario-name}/
15
+ * │ ├── recording-YYYY-MM-DD_HH-MM-SSutc/
16
+ * │ │ ├── session.json
17
+ * │ │ ├── actions/
18
+ * │ │ │ ├── 000001.json
19
+ * │ │ │ └── ...
20
+ * │ │ └── frames/
21
+ * │ │ ├── frame-000001.jpg
22
+ * │ │ └── ...
23
+ * │ └── ...
24
+ * └── ...
25
+ */
26
+ const log = createLogger("OPFS");
27
+ let opfsRoot = null;
28
+ let gpsRecorderDir = null;
29
+ let scenariosDir = null;
30
+ let currentScenarioHandle = null;
31
+ let currentSessionHandle = null;
32
+ let actionsHandle = null;
33
+ let framesHandle = null;
34
+ /**
35
+ * Reset module state - exported for testing only.
36
+ * @internal
37
+ */
38
+ function resetOpfsStorage() {
39
+ opfsRoot = null;
40
+ gpsRecorderDir = null;
41
+ scenariosDir = null;
42
+ currentScenarioHandle = null;
43
+ currentSessionHandle = null;
44
+ actionsHandle = null;
45
+ framesHandle = null;
46
+ }
47
+ /**
48
+ * Reset session-level handles only.
49
+ *
50
+ * Preserves opfsRoot/gpsRecorderDir/scenariosDir (OPFS stays initialized)
51
+ * but clears current scenario/session/actions/frames handles so a new
52
+ * session can be started fresh.
53
+ *
54
+ * Used during soft reset for new recordings (Issue 4, 2026-02-06 user feedback).
55
+ */
56
+ function resetSessionHandles() {
57
+ currentScenarioHandle = null;
58
+ currentSessionHandle = null;
59
+ actionsHandle = null;
60
+ framesHandle = null;
61
+ }
62
+ /**
63
+ * Initialize OPFS storage by creating the required directory structure.
64
+ *
65
+ * Creates:
66
+ * - /gps-recorder/
67
+ * - /gps-recorder/scenarios/
68
+ *
69
+ * This is idempotent - calling multiple times is safe.
70
+ *
71
+ * @throws Error if OPFS is not supported
72
+ */
73
+ async function initOpfsStorage() {
74
+ if (typeof navigator === "undefined" || !navigator.storage || typeof navigator.storage.getDirectory !== "function") throw new Error("OPFS (Origin Private File System) is not supported in this browser. Please use Chrome 86+, Safari 15.2+, or Firefox 111+.");
75
+ opfsRoot = await navigator.storage.getDirectory();
76
+ log.info("OPFS root obtained");
77
+ gpsRecorderDir = await opfsRoot.getDirectoryHandle("gps-recorder", { create: true });
78
+ scenariosDir = await gpsRecorderDir.getDirectoryHandle("scenarios", { create: true });
79
+ log.info("OPFS storage initialized");
80
+ }
81
+ /**
82
+ * Create a new recording session within a scenario.
83
+ *
84
+ * Creates the directory structure:
85
+ * - /gps-recorder/scenarios/{scenarioName}/recording-{timestamp}/
86
+ * - /gps-recorder/scenarios/{scenarioName}/recording-{timestamp}/actions/
87
+ * - /gps-recorder/scenarios/{scenarioName}/recording-{timestamp}/frames/
88
+ *
89
+ * @param scenarioName - Name of the scenario (creates if doesn't exist)
90
+ * @param timestamp - Session start time (used for folder naming)
91
+ * @returns Session information
92
+ * @throws Error if storage not initialized
93
+ */
94
+ async function createSession(scenarioName, timestamp) {
95
+ if (!scenariosDir) throw new Error("OPFS storage not initialized. Call initOpfsStorage first.");
96
+ currentScenarioHandle = await scenariosDir.getDirectoryHandle(scenarioName, { create: true });
97
+ const sessionName = `recording-${formatTimestamp(timestamp)}`;
98
+ currentSessionHandle = await currentScenarioHandle.getDirectoryHandle(sessionName, { create: true });
99
+ actionsHandle = await currentSessionHandle.getDirectoryHandle("actions", { create: true });
100
+ framesHandle = await currentSessionHandle.getDirectoryHandle("frames", { create: true });
101
+ log.info("Session created:", `${scenarioName}/${sessionName}`);
102
+ return {
103
+ scenarioName,
104
+ sessionName
105
+ };
106
+ }
107
+ /**
108
+ * Get the current session directory handle.
109
+ * Useful for test assertions.
110
+ * @internal
111
+ */
112
+ function getSessionHandle() {
113
+ return currentSessionHandle;
114
+ }
115
+ /**
116
+ * Get the current scenario directory handle.
117
+ */
118
+ function getScenarioHandle() {
119
+ return currentScenarioHandle;
120
+ }
121
+ /**
122
+ * Get the scenarios root directory handle.
123
+ * Used for loading reference points across scenarios.
124
+ */
125
+ function getScenariosRootHandle() {
126
+ return scenariosDir;
127
+ }
128
+ /**
129
+ * Safely write data to a file handle with proper cleanup.
130
+ *
131
+ * Uses try/finally to ensure the writable stream is always cleaned up,
132
+ * calling abort() on errors to prevent resource leaks and file locks.
133
+ *
134
+ * @param fileHandle - The file handle to write to
135
+ * @param data - The data to write (string or Blob)
136
+ */
137
+ async function safeWriteToFile(fileHandle, data) {
138
+ const writable = await fileHandle.createWritable();
139
+ let writeError = null;
140
+ try {
141
+ await writable.write(data);
142
+ await writable.close();
143
+ } catch (error) {
144
+ writeError = error;
145
+ } finally {
146
+ if (writeError !== null) try {
147
+ await writable.abort();
148
+ } catch {}
149
+ }
150
+ if (writeError !== null) {
151
+ if (writeError instanceof Error) throw writeError;
152
+ throw new Error(typeof writeError === "string" ? writeError : "File write failed");
153
+ }
154
+ }
155
+ /**
156
+ * Write a Redux action to the current session's actions directory.
157
+ *
158
+ * @param action - The action object to write
159
+ * @param index - Sequential action index (1-based)
160
+ * @throws Error if no active session
161
+ */
162
+ async function writeAction(action, index) {
163
+ if (!actionsHandle) throw new Error("No active session. Call createSession first.");
164
+ const filename = formatActionFilename(index);
165
+ await safeWriteToFile(await actionsHandle.getFileHandle(filename, { create: true }), JSON.stringify(action, null, 2));
166
+ }
167
+ /**
168
+ * Write a frame blob to the current session's frames directory.
169
+ *
170
+ * @param blob - Image blob to write
171
+ * @param index - Sequential frame index (1-based)
172
+ * @throws Error if no active session
173
+ */
174
+ async function writeFrame(blob, index) {
175
+ if (!framesHandle) throw new Error("No active session. Call createSession first.");
176
+ const filename = formatFrameFilename(index);
177
+ await safeWriteToFile(await framesHandle.getFileHandle(filename, { create: true }), blob);
178
+ }
179
+ /**
180
+ * Write session metadata to session.json in the current session directory.
181
+ *
182
+ * @param metadata - Session metadata object
183
+ * @throws Error if no active session
184
+ */
185
+ async function writeSessionMetadata(metadata) {
186
+ if (!currentSessionHandle) throw new Error("No active session. Call createSession first.");
187
+ await safeWriteToFile(await currentSessionHandle.getFileHandle("session.json", { create: true }), JSON.stringify(metadata, null, 2));
188
+ log.info("Session metadata written");
189
+ }
190
+ /**
191
+ * List all scenario names in OPFS storage.
192
+ *
193
+ * @returns Array of scenario folder names
194
+ */
195
+ async function listScenarios() {
196
+ if (!scenariosDir) return [];
197
+ const scenarios = [];
198
+ for await (const entry of scenariosDir.values()) if (entry.kind === "directory") scenarios.push(entry.name);
199
+ return scenarios;
200
+ }
201
+ /**
202
+ * List all session names within a scenario.
203
+ *
204
+ * @param scenarioName - Name of the scenario
205
+ * @returns Array of session folder names
206
+ */
207
+ async function listSessions(scenarioName) {
208
+ if (!scenariosDir) return [];
209
+ try {
210
+ const scenarioHandle = await scenariosDir.getDirectoryHandle(scenarioName);
211
+ const sessions = [];
212
+ for await (const entry of scenarioHandle.values()) if (entry.kind === "directory") sessions.push(entry.name);
213
+ return sessions;
214
+ } catch {
215
+ return [];
216
+ }
217
+ }
218
+ /**
219
+ * Check storage quota.
220
+ *
221
+ * Note: Prefer calling `initOpfsStorage()` before this function to ensure
222
+ * the Storage API is available. If the Storage Manager API is not supported,
223
+ * returns a safe default of `{ available: 0, used: 0 }`.
224
+ *
225
+ * @returns Available and used storage in bytes
226
+ */
227
+ async function checkStorageQuota() {
228
+ if (typeof navigator === "undefined" || !navigator.storage || typeof navigator.storage.estimate !== "function") {
229
+ log.warn("Storage Manager API not available, returning zero quota");
230
+ return {
231
+ available: 0,
232
+ used: 0
233
+ };
234
+ }
235
+ const estimate = await navigator.storage.estimate();
236
+ return {
237
+ available: estimate.quota ?? 0,
238
+ used: estimate.usage ?? 0
239
+ };
240
+ }
241
+ //#endregion
242
+ export { checkStorageQuota, createSession, getScenarioHandle, getScenariosRootHandle, getSessionHandle, initOpfsStorage, listScenarios, listSessions, resetOpfsStorage, resetSessionHandles, writeAction, writeFrame, writeSessionMetadata };
@@ -0,0 +1,2 @@
1
+ import { n as RefPointImportResult, r as importRefPointsFromFolder, t as ImportedRefPoint } from "../ref-point-importer-B_wn5ABk.js";
2
+ export { ImportedRefPoint, RefPointImportResult, importRefPointsFromFolder };