@weasel-js/labkit 1.0.4 → 1.1.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.
- package/README.md +6 -6
- package/dist/_dts/{index-CFlDPeZh.d.ts → index-iAP6XbH3.d.ts} +17 -7
- package/dist/_dts/{useExperimentState-D7EQnnwJ.d.ts → useTrialState-DYe2vUwN.d.ts} +60 -36
- package/dist/canvas/index.d.ts +1 -1
- package/dist/chunk-3TYUJR7Z.js +488 -0
- package/dist/chunk-3TYUJR7Z.js.map +1 -0
- package/dist/{chunk-BAPZPDDA.js → chunk-C6GJKPUI.js} +365 -340
- package/dist/chunk-C6GJKPUI.js.map +1 -0
- package/dist/{chunk-DWV7SFKR.js → chunk-DJLDIRFN.js} +28 -12
- package/dist/chunk-DJLDIRFN.js.map +1 -0
- package/dist/{chunk-DEWXYFEU.js → chunk-NRKWVTVT.js} +3 -3
- package/dist/{chunk-DEWXYFEU.js.map → chunk-NRKWVTVT.js.map} +1 -1
- package/dist/dragdrop/index.d.ts +1 -1
- package/dist/index.d.ts +149 -149
- package/dist/index.js +164 -159
- package/dist/index.js.map +1 -1
- package/dist/layers/index.d.ts +2 -2
- package/dist/passthrough/weasel-canvas.js +1 -1
- package/dist/passthrough/weasel-ui.d.ts +9 -2
- package/dist/passthrough/weasel-ui.js +2 -2
- package/dist/state/index.d.ts +12 -12
- package/dist/state/index.js +5 -5
- package/dist/state/index.js.map +1 -1
- package/dist/styles.css +21 -13
- package/dist/ui/layers/index.js +3 -3
- package/dist/undo/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/canvas/AGENTS.md +6 -2
- package/src/dragdrop/dragDrop.test.tsx +4 -7
- package/src/index.test.ts +52 -0
- package/src/index.ts +11 -7
- package/src/instrument/SineWave.smoke.test.tsx +1 -1
- package/src/instrument/capabilityDetector.ts +1 -1
- package/src/instrument/types.ts +18 -6
- package/src/lab/Lab.less +16 -0
- package/src/lab/Lab.stories.tsx +5 -5
- package/src/lab/Lab.test.tsx +25 -25
- package/src/lab/Lab.tsx +50 -50
- package/src/lab/LabContext.ts +12 -12
- package/src/lab/LabShell.tsx +1 -1
- package/src/lab/{WorkspaceGrid.less → Workspace.less} +2 -2
- package/src/lab/{WorkspaceGrid.stories.tsx → Workspace.stories.tsx} +13 -13
- package/src/lab/{WorkspaceGrid.test.tsx → Workspace.test.tsx} +24 -24
- package/src/lab/{WorkspaceGrid.tsx → Workspace.tsx} +18 -18
- package/src/lab/index.ts +2 -2
- package/src/layers/AGENTS.md +3 -3
- package/src/primitives/Toolbar.stories.tsx +2 -2
- package/src/state/SingletonExperiment.test.tsx +8 -8
- package/src/state/SingletonExperiment.tsx +9 -9
- package/src/state/context.tsx +12 -12
- package/src/state/document.test.ts +369 -0
- package/src/state/document.ts +194 -0
- package/src/state/helpers.test.ts +74 -46
- package/src/state/helpers.ts +21 -29
- package/src/state/index.ts +16 -8
- package/src/state/store.test.ts +440 -41
- package/src/state/store.ts +159 -102
- package/src/state/types.ts +29 -12
- package/src/state/{useExperimentState.test.tsx → useTrialState.test.tsx} +18 -18
- package/src/state/useTrialState.ts +29 -0
- package/src/styles.less +2 -2
- package/src/theme/base.less +1 -1
- package/src/{workspace → trial}/DefaultSidebar.tsx +3 -3
- package/src/{workspace → trial}/DefaultStatusBar.tsx +5 -4
- package/src/{workspace → trial}/DefaultToolbar.tsx +9 -8
- package/src/trial/Trial.canvas.test.tsx +80 -0
- package/src/{workspace/Workspace.less → trial/Trial.less} +1 -1
- package/src/{workspace/Workspace.stories.tsx → trial/Trial.stories.tsx} +13 -18
- package/src/{workspace/Workspace.test.tsx → trial/Trial.test.tsx} +26 -26
- package/src/{workspace/Workspace.tsx → trial/Trial.tsx} +48 -43
- package/src/{workspace/WorkspaceChrome.tsx → trial/TrialChrome.tsx} +42 -42
- package/src/{workspace → trial}/index.ts +13 -13
- package/src/{workspace → trial}/slotTypes.ts +16 -16
- package/src/{workspace/workspaceOps.test.ts → trial/trialOps.test.ts} +38 -44
- package/src/trial/trialOps.ts +99 -0
- package/src/ui/format.test.ts +33 -0
- package/src/ui/format.ts +11 -0
- package/src/ui/properties/CurveField.tsx +6 -6
- package/src/ui/properties/SpeechBalloonPanels.stories.tsx +3 -3
- package/dist/chunk-BAPZPDDA.js.map +0 -1
- package/dist/chunk-DWV7SFKR.js.map +0 -1
- package/dist/chunk-KSTEW2AF.js +0 -367
- package/dist/chunk-KSTEW2AF.js.map +0 -1
- package/src/state/useExperimentState.ts +0 -31
- package/src/workspace/workspaceOps.ts +0 -102
package/dist/chunk-KSTEW2AF.js
DELETED
|
@@ -1,367 +0,0 @@
|
|
|
1
|
-
import { createContext, useContext } from 'react';
|
|
2
|
-
import { useStore } from 'zustand/react';
|
|
3
|
-
import { jsx } from 'react/jsx-runtime';
|
|
4
|
-
import { createStore } from 'zustand/vanilla';
|
|
5
|
-
|
|
6
|
-
// src/state/helpers.ts
|
|
7
|
-
function labStorageKey(storageKey, bucket) {
|
|
8
|
-
return `lk:${storageKey}:${bucket}`;
|
|
9
|
-
}
|
|
10
|
-
function encodeUrlHash(value) {
|
|
11
|
-
return btoa(encodeURIComponent(value));
|
|
12
|
-
}
|
|
13
|
-
function decodeUrlHash(hash) {
|
|
14
|
-
if (!hash) return null;
|
|
15
|
-
try {
|
|
16
|
-
return decodeURIComponent(atob(hash));
|
|
17
|
-
} catch {
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
function emptyUndoStack() {
|
|
22
|
-
return { past: [], future: [] };
|
|
23
|
-
}
|
|
24
|
-
function serializeWorkspaces(workspaces, serializers) {
|
|
25
|
-
const records = workspaces.map(({ undoStack: _undo, ...w }) => {
|
|
26
|
-
const s = serializers[w.instrumentName];
|
|
27
|
-
return {
|
|
28
|
-
...w,
|
|
29
|
-
state: s?.serialize ? s.serialize(w.state) : w.state
|
|
30
|
-
};
|
|
31
|
-
});
|
|
32
|
-
return JSON.stringify(records);
|
|
33
|
-
}
|
|
34
|
-
function deserializeWorkspaces(raw, deserializers) {
|
|
35
|
-
try {
|
|
36
|
-
const records = JSON.parse(raw);
|
|
37
|
-
return records.map((r) => {
|
|
38
|
-
const d = deserializers[r.instrumentName];
|
|
39
|
-
return {
|
|
40
|
-
...r,
|
|
41
|
-
state: d?.deserialize ? d.deserialize(r.state) : r.state,
|
|
42
|
-
undoStack: emptyUndoStack()
|
|
43
|
-
};
|
|
44
|
-
});
|
|
45
|
-
} catch {
|
|
46
|
-
console.warn("[labkit] deserializeWorkspaces: failed to parse, returning empty list");
|
|
47
|
-
return [];
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// src/state/adapters.ts
|
|
52
|
-
var localStorageAdapter = {
|
|
53
|
-
read: (key) => {
|
|
54
|
-
try {
|
|
55
|
-
return localStorage.getItem(key);
|
|
56
|
-
} catch {
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
write: (key, value) => {
|
|
61
|
-
try {
|
|
62
|
-
localStorage.setItem(key, value);
|
|
63
|
-
} catch (e) {
|
|
64
|
-
console.warn("[labkit] localStorage write failed:", e);
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
delete: (key) => {
|
|
68
|
-
try {
|
|
69
|
-
localStorage.removeItem(key);
|
|
70
|
-
} catch {
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
var sessionStorageAdapter = {
|
|
75
|
-
read: (key) => {
|
|
76
|
-
try {
|
|
77
|
-
return sessionStorage.getItem(key);
|
|
78
|
-
} catch {
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
},
|
|
82
|
-
write: (key, value) => {
|
|
83
|
-
try {
|
|
84
|
-
sessionStorage.setItem(key, value);
|
|
85
|
-
} catch (e) {
|
|
86
|
-
console.warn("[labkit] sessionStorage write failed:", e);
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
|
-
delete: (key) => {
|
|
90
|
-
try {
|
|
91
|
-
sessionStorage.removeItem(key);
|
|
92
|
-
} catch {
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
var URL_HASH_GUARD = typeof window !== "undefined";
|
|
97
|
-
function readHashMap() {
|
|
98
|
-
if (!URL_HASH_GUARD) return {};
|
|
99
|
-
const raw = decodeUrlHash(window.location.hash.replace(/^#/, ""));
|
|
100
|
-
if (!raw) return {};
|
|
101
|
-
try {
|
|
102
|
-
return JSON.parse(raw);
|
|
103
|
-
} catch {
|
|
104
|
-
return {};
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
function writeHashMap(map) {
|
|
108
|
-
if (!URL_HASH_GUARD) return;
|
|
109
|
-
const encoded = encodeUrlHash(JSON.stringify(map));
|
|
110
|
-
window.history.replaceState(null, "", `#${encoded}`);
|
|
111
|
-
}
|
|
112
|
-
var urlHashAdapter = {
|
|
113
|
-
read: (key) => readHashMap()[key] ?? null,
|
|
114
|
-
write: (key, value) => {
|
|
115
|
-
const map = readHashMap();
|
|
116
|
-
map[key] = value;
|
|
117
|
-
writeHashMap(map);
|
|
118
|
-
},
|
|
119
|
-
delete: (key) => {
|
|
120
|
-
const map = readHashMap();
|
|
121
|
-
delete map[key];
|
|
122
|
-
writeHashMap(map);
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
function createMemoryAdapter() {
|
|
126
|
-
const store = /* @__PURE__ */ new Map();
|
|
127
|
-
return {
|
|
128
|
-
read: (key) => store.get(key) ?? null,
|
|
129
|
-
write: (key, value) => {
|
|
130
|
-
store.set(key, value);
|
|
131
|
-
},
|
|
132
|
-
delete: (key) => {
|
|
133
|
-
store.delete(key);
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
var noneAdapter = {
|
|
138
|
-
read: () => null,
|
|
139
|
-
write: () => {
|
|
140
|
-
},
|
|
141
|
-
delete: () => {
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
var LabStoreContext = createContext(null);
|
|
145
|
-
function LabStoreProvider({
|
|
146
|
-
store,
|
|
147
|
-
children
|
|
148
|
-
}) {
|
|
149
|
-
return /* @__PURE__ */ jsx(LabStoreContext.Provider, { value: { store }, children });
|
|
150
|
-
}
|
|
151
|
-
function useLabStore() {
|
|
152
|
-
const ctx = useContext(LabStoreContext);
|
|
153
|
-
if (!ctx) throw new Error("[labkit] useLabStore must be used inside <LabStoreProvider>");
|
|
154
|
-
return useStore(ctx.store);
|
|
155
|
-
}
|
|
156
|
-
var WorkspaceIdContext = createContext(null);
|
|
157
|
-
function WorkspaceIdProvider({
|
|
158
|
-
workspaceId,
|
|
159
|
-
children
|
|
160
|
-
}) {
|
|
161
|
-
return /* @__PURE__ */ jsx(WorkspaceIdContext.Provider, { value: workspaceId, children });
|
|
162
|
-
}
|
|
163
|
-
function useWorkspaceId() {
|
|
164
|
-
const id = useContext(WorkspaceIdContext);
|
|
165
|
-
if (!id) throw new Error("[labkit] useWorkspaceId must be used inside <WorkspaceIdProvider>");
|
|
166
|
-
return id;
|
|
167
|
-
}
|
|
168
|
-
function createLabStore(options) {
|
|
169
|
-
let serializers = {};
|
|
170
|
-
let flushTimer = null;
|
|
171
|
-
const workspacesRaw = options.storage.read(labStorageKey(options.storageKey, "workspaces"));
|
|
172
|
-
let hydratedWorkspaces = [];
|
|
173
|
-
if (workspacesRaw) {
|
|
174
|
-
hydratedWorkspaces = deserializeWorkspaces(workspacesRaw, serializers);
|
|
175
|
-
}
|
|
176
|
-
const savesRaw = options.storage.read(labStorageKey(options.storageKey, "saves"));
|
|
177
|
-
let hydratedSnapshots = [];
|
|
178
|
-
if (savesRaw) {
|
|
179
|
-
try {
|
|
180
|
-
hydratedSnapshots = JSON.parse(savesRaw);
|
|
181
|
-
} catch {
|
|
182
|
-
console.warn("[labkit] failed to parse saved snapshots, starting empty");
|
|
183
|
-
hydratedSnapshots = [];
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
const layoutRaw = options.storage.read(labStorageKey(options.storageKey, "layout"));
|
|
187
|
-
let hydratedLayout = {};
|
|
188
|
-
if (layoutRaw) {
|
|
189
|
-
try {
|
|
190
|
-
hydratedLayout = JSON.parse(layoutRaw);
|
|
191
|
-
} catch {
|
|
192
|
-
console.warn("[labkit] failed to parse saved layout, starting empty");
|
|
193
|
-
hydratedLayout = {};
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
const modeRaw = options.storage.read(labStorageKey(options.storageKey, "theme"));
|
|
197
|
-
const stored = modeRaw === "interstellar" ? "dark" : modeRaw;
|
|
198
|
-
let hydratedMode;
|
|
199
|
-
if (stored === "light" || stored === "dark" || stored === "auto") {
|
|
200
|
-
hydratedMode = stored;
|
|
201
|
-
} else {
|
|
202
|
-
hydratedMode = options.initialMode ?? "auto";
|
|
203
|
-
}
|
|
204
|
-
const store = createStore()((set, get) => ({
|
|
205
|
-
workspaces: hydratedWorkspaces,
|
|
206
|
-
savedSnapshots: hydratedSnapshots,
|
|
207
|
-
mode: hydratedMode,
|
|
208
|
-
layout: hydratedLayout,
|
|
209
|
-
addWorkspace: (record) => {
|
|
210
|
-
set((s) => ({
|
|
211
|
-
workspaces: [...s.workspaces, { ...record, undoStack: emptyUndoStack() }]
|
|
212
|
-
}));
|
|
213
|
-
scheduleFlush();
|
|
214
|
-
},
|
|
215
|
-
removeWorkspace: (id) => {
|
|
216
|
-
set((s) => ({ workspaces: s.workspaces.filter((w) => w.id !== id) }));
|
|
217
|
-
scheduleFlush();
|
|
218
|
-
},
|
|
219
|
-
updateWorkspaceState: (id, next) => {
|
|
220
|
-
set((s) => ({
|
|
221
|
-
workspaces: s.workspaces.map((w) => {
|
|
222
|
-
if (w.id !== id) return w;
|
|
223
|
-
const nextState = typeof next === "function" ? next(w.state) : next;
|
|
224
|
-
return { ...w, state: nextState };
|
|
225
|
-
})
|
|
226
|
-
}));
|
|
227
|
-
scheduleFlush();
|
|
228
|
-
},
|
|
229
|
-
updateWorkspaceConfig: (id, key, value) => {
|
|
230
|
-
set((s) => ({
|
|
231
|
-
workspaces: s.workspaces.map((w) => {
|
|
232
|
-
if (w.id !== id) return w;
|
|
233
|
-
return {
|
|
234
|
-
...w,
|
|
235
|
-
config: { ...w.config, [key]: value }
|
|
236
|
-
};
|
|
237
|
-
})
|
|
238
|
-
}));
|
|
239
|
-
scheduleFlush();
|
|
240
|
-
},
|
|
241
|
-
updateWorkspaceView: (id, view) => {
|
|
242
|
-
set((s) => ({
|
|
243
|
-
workspaces: s.workspaces.map((w) => w.id === id ? { ...w, view } : w)
|
|
244
|
-
}));
|
|
245
|
-
scheduleFlush();
|
|
246
|
-
},
|
|
247
|
-
updateWorkspaceUndoStack: (id, next) => {
|
|
248
|
-
set((s) => ({
|
|
249
|
-
workspaces: s.workspaces.map((w) => {
|
|
250
|
-
if (w.id !== id) return w;
|
|
251
|
-
const undoStack = typeof next === "function" ? next(
|
|
252
|
-
w.undoStack
|
|
253
|
-
) : next;
|
|
254
|
-
return { ...w, undoStack };
|
|
255
|
-
})
|
|
256
|
-
}));
|
|
257
|
-
},
|
|
258
|
-
setWorkspaceInstrument: (id, instrumentName) => {
|
|
259
|
-
set((s) => ({
|
|
260
|
-
workspaces: s.workspaces.map((w) => w.id === id ? { ...w, instrumentName } : w)
|
|
261
|
-
}));
|
|
262
|
-
scheduleFlush();
|
|
263
|
-
},
|
|
264
|
-
saveSnapshot: (workspaceId, name) => {
|
|
265
|
-
const workspace = get().workspaces.find((w) => w.id === workspaceId);
|
|
266
|
-
if (!workspace) return;
|
|
267
|
-
const reg = serializers[workspace.instrumentName];
|
|
268
|
-
const serializedState = reg?.serialize ? reg.serialize(workspace.state) : structuredClone(workspace.state);
|
|
269
|
-
const clonedConfig = structuredClone(workspace.config);
|
|
270
|
-
const lastAt = get().savedSnapshots.reduce((m, sn) => sn.savedAt > m ? sn.savedAt : m, 0);
|
|
271
|
-
const savedAt = Math.max(Date.now(), lastAt + 1);
|
|
272
|
-
const snapshot = {
|
|
273
|
-
id: crypto.randomUUID(),
|
|
274
|
-
name,
|
|
275
|
-
workspaceId,
|
|
276
|
-
instrumentName: workspace.instrumentName,
|
|
277
|
-
config: clonedConfig,
|
|
278
|
-
state: serializedState,
|
|
279
|
-
savedAt
|
|
280
|
-
};
|
|
281
|
-
set((s) => ({ savedSnapshots: [...s.savedSnapshots, snapshot] }));
|
|
282
|
-
scheduleFlush();
|
|
283
|
-
},
|
|
284
|
-
loadSnapshot: (snapshotId, workspaceId) => {
|
|
285
|
-
const snapshot = get().savedSnapshots.find((sn) => sn.id === snapshotId);
|
|
286
|
-
if (!snapshot) return;
|
|
287
|
-
const workspace = get().workspaces.find((w) => w.id === workspaceId);
|
|
288
|
-
if (!workspace) return;
|
|
289
|
-
if (snapshot.instrumentName !== workspace.instrumentName) {
|
|
290
|
-
console.warn(
|
|
291
|
-
`[labkit] loadSnapshot: instrument mismatch (snapshot=${snapshot.instrumentName}, workspace=${workspace.instrumentName}); refusing to load`
|
|
292
|
-
);
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
const reg = serializers[snapshot.instrumentName];
|
|
296
|
-
const restoredState = reg?.deserialize ? reg.deserialize(snapshot.state) : snapshot.state;
|
|
297
|
-
set((s) => ({
|
|
298
|
-
workspaces: s.workspaces.map(
|
|
299
|
-
(w) => w.id === workspaceId ? { ...w, state: restoredState, config: snapshot.config } : w
|
|
300
|
-
)
|
|
301
|
-
}));
|
|
302
|
-
scheduleFlush();
|
|
303
|
-
},
|
|
304
|
-
deleteSnapshot: (snapshotId) => {
|
|
305
|
-
set((s) => ({
|
|
306
|
-
savedSnapshots: s.savedSnapshots.filter((sn) => sn.id !== snapshotId)
|
|
307
|
-
}));
|
|
308
|
-
scheduleFlush();
|
|
309
|
-
},
|
|
310
|
-
listSnapshots: (workspaceId) => {
|
|
311
|
-
const all = get().savedSnapshots;
|
|
312
|
-
const filtered = workspaceId ? all.filter((sn) => sn.workspaceId === workspaceId) : all;
|
|
313
|
-
return [...filtered].sort((a, b) => b.savedAt - a.savedAt);
|
|
314
|
-
},
|
|
315
|
-
setMode: (mode) => {
|
|
316
|
-
set({ mode });
|
|
317
|
-
scheduleFlush();
|
|
318
|
-
},
|
|
319
|
-
setLayout: (layout) => {
|
|
320
|
-
set({ layout });
|
|
321
|
-
scheduleFlush();
|
|
322
|
-
}
|
|
323
|
-
}));
|
|
324
|
-
function scheduleFlush() {
|
|
325
|
-
if (flushTimer) clearTimeout(flushTimer);
|
|
326
|
-
flushTimer = setTimeout(() => {
|
|
327
|
-
const s = store.getState();
|
|
328
|
-
options.storage.write(
|
|
329
|
-
labStorageKey(options.storageKey, "workspaces"),
|
|
330
|
-
serializeWorkspaces(s.workspaces, serializers)
|
|
331
|
-
);
|
|
332
|
-
options.storage.write(
|
|
333
|
-
labStorageKey(options.storageKey, "saves"),
|
|
334
|
-
JSON.stringify(s.savedSnapshots)
|
|
335
|
-
);
|
|
336
|
-
options.storage.write(labStorageKey(options.storageKey, "theme"), s.mode);
|
|
337
|
-
options.storage.write(labStorageKey(options.storageKey, "layout"), JSON.stringify(s.layout));
|
|
338
|
-
flushTimer = null;
|
|
339
|
-
}, 300);
|
|
340
|
-
}
|
|
341
|
-
return Object.assign(store, {
|
|
342
|
-
registerSerializers(s) {
|
|
343
|
-
serializers = s;
|
|
344
|
-
}
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
function useExperimentState() {
|
|
348
|
-
const ctx = useContext(LabStoreContext);
|
|
349
|
-
if (!ctx) throw new Error("[labkit] useExperimentState must be used inside <LabStoreProvider>");
|
|
350
|
-
const workspaceId = useContext(WorkspaceIdContext);
|
|
351
|
-
if (!workspaceId)
|
|
352
|
-
throw new Error("[labkit] useExperimentState must be used inside <WorkspaceIdProvider>");
|
|
353
|
-
const record = useStore(ctx.store, (s) => s.workspaces.find((w) => w.id === workspaceId));
|
|
354
|
-
if (!record) throw new Error(`[labkit] No workspace found with id "${workspaceId}"`);
|
|
355
|
-
const updateWorkspaceState = useStore(ctx.store, (s) => s.updateWorkspaceState);
|
|
356
|
-
const updateWorkspaceConfig = useStore(ctx.store, (s) => s.updateWorkspaceConfig);
|
|
357
|
-
return {
|
|
358
|
-
state: record.state,
|
|
359
|
-
config: record.config,
|
|
360
|
-
setState: (next) => updateWorkspaceState(workspaceId, next),
|
|
361
|
-
setConfig: (key, value) => updateWorkspaceConfig(workspaceId, key, value)
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
export { LabStoreContext, LabStoreProvider, WorkspaceIdContext, WorkspaceIdProvider, createLabStore, createMemoryAdapter, decodeUrlHash, deserializeWorkspaces, emptyUndoStack, encodeUrlHash, labStorageKey, localStorageAdapter, noneAdapter, serializeWorkspaces, sessionStorageAdapter, urlHashAdapter, useExperimentState, useLabStore, useWorkspaceId };
|
|
366
|
-
//# sourceMappingURL=chunk-KSTEW2AF.js.map
|
|
367
|
-
//# sourceMappingURL=chunk-KSTEW2AF.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/state/helpers.ts","../src/state/adapters.ts","../src/state/context.tsx","../src/state/store.ts","../src/state/useExperimentState.ts"],"names":["useContext","useStore"],"mappings":"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","file":"chunk-KSTEW2AF.js","sourcesContent":["import type { InstrumentSerializers, UndoStack, WorkspaceRecord } from './types';\n\n/** The storage key a lab writes one of its buckets under. Namespaced by\n * `storageKey` so two labs sharing an origin do not collide. */\nexport function labStorageKey(\n storageKey: string,\n bucket: 'workspaces' | 'saves' | 'theme' | 'layout',\n): string {\n return `lk:${storageKey}:${bucket}`;\n}\n\n/** Encode a string for the URL fragment. */\nexport function encodeUrlHash(value: string): string {\n return btoa(encodeURIComponent(value));\n}\n\n/** Decode a URL fragment written by `encodeUrlHash`, or `null` if it is\n * malformed. */\nexport function decodeUrlHash(hash: string): string | null {\n if (!hash) return null;\n try {\n return decodeURIComponent(atob(hash));\n } catch {\n return null;\n }\n}\n\n/** A fresh, empty undo history. */\nexport function emptyUndoStack(): UndoStack {\n return { past: [], future: [] };\n}\n\ntype SerializedRecord = Omit<WorkspaceRecord, 'undoStack'>;\n\n/** Serialize workspaces for storage, running each instrument's own serializer\n * over its state. Undo history is deliberately dropped — it does not survive\n * a reload. */\nexport function serializeWorkspaces(\n workspaces: WorkspaceRecord[],\n serializers: InstrumentSerializers,\n): string {\n const records: SerializedRecord[] = workspaces.map(({ undoStack: _undo, ...w }) => {\n const s = serializers[w.instrumentName];\n return {\n ...w,\n state: s?.serialize ? s.serialize(w.state) : w.state,\n };\n });\n return JSON.stringify(records);\n}\n\n/** Rebuild workspaces from storage, running each instrument's deserializer\n * over its state and starting each with an empty undo history. Returns an\n * empty list rather than throwing on malformed input. */\nexport function deserializeWorkspaces(\n raw: string,\n deserializers: InstrumentSerializers,\n): WorkspaceRecord[] {\n try {\n const records = JSON.parse(raw) as SerializedRecord[];\n return records.map((r) => {\n const d = deserializers[r.instrumentName];\n return {\n ...r,\n state: d?.deserialize ? d.deserialize(r.state) : r.state,\n undoStack: emptyUndoStack(),\n };\n });\n } catch {\n console.warn('[labkit] deserializeWorkspaces: failed to parse, returning empty list');\n return [];\n }\n}\n","import { decodeUrlHash, encodeUrlHash } from './helpers';\nimport type { StorageAdapter } from './types';\n\n/** Persist to `localStorage` — state survives a reload and a new tab. Falls\n * back to no-ops when storage is unavailable. */\nexport const localStorageAdapter: StorageAdapter = {\n read: (key) => {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n },\n write: (key, value) => {\n try {\n localStorage.setItem(key, value);\n } catch (e) {\n console.warn('[labkit] localStorage write failed:', e);\n }\n },\n delete: (key) => {\n try {\n localStorage.removeItem(key);\n } catch {\n /* ignore */\n }\n },\n};\n\n/** Persist to `sessionStorage` — state survives a reload but not a new tab. */\nexport const sessionStorageAdapter: StorageAdapter = {\n read: (key) => {\n try {\n return sessionStorage.getItem(key);\n } catch {\n return null;\n }\n },\n write: (key, value) => {\n try {\n sessionStorage.setItem(key, value);\n } catch (e) {\n console.warn('[labkit] sessionStorage write failed:', e);\n }\n },\n delete: (key) => {\n try {\n sessionStorage.removeItem(key);\n } catch {\n /* ignore */\n }\n },\n};\n\nconst URL_HASH_GUARD = typeof window !== 'undefined';\n\nfunction readHashMap(): Record<string, string> {\n if (!URL_HASH_GUARD) return {};\n const raw = decodeUrlHash(window.location.hash.replace(/^#/, ''));\n if (!raw) return {};\n try {\n return JSON.parse(raw) as Record<string, string>;\n } catch {\n return {};\n }\n}\n\nfunction writeHashMap(map: Record<string, string>): void {\n if (!URL_HASH_GUARD) return;\n const encoded = encodeUrlHash(JSON.stringify(map));\n window.history.replaceState(null, '', `#${encoded}`);\n}\n\n/** Persist into the URL fragment, so the page's link carries its state and\n * can be shared or bookmarked. */\nexport const urlHashAdapter: StorageAdapter = {\n read: (key) => readHashMap()[key] ?? null,\n write: (key, value) => {\n const map = readHashMap();\n map[key] = value;\n writeHashMap(map);\n },\n delete: (key) => {\n const map = readHashMap();\n delete map[key];\n writeHashMap(map);\n },\n};\n\n/** An in-memory store, discarded on reload. For tests, and for labs that\n * should start fresh every time. */\nexport function createMemoryAdapter(): StorageAdapter {\n const store = new Map<string, string>();\n return {\n read: (key) => store.get(key) ?? null,\n write: (key, value) => {\n store.set(key, value);\n },\n delete: (key) => {\n store.delete(key);\n },\n };\n}\n\n/** Persists nothing and reads back nothing. */\nexport const noneAdapter: StorageAdapter = {\n read: () => null,\n write: () => {},\n delete: () => {},\n};\n","import { createContext, type ReactElement, type ReactNode, useContext } from 'react';\nimport { useStore } from 'zustand/react';\nimport type { LabStore } from './store';\nimport type { LabStoreState } from './types';\n\ntype LabStoreCtx = { store: LabStore } | null;\n\n/** Context carrying the lab store. Prefer `useLabStore`; this is exported for\n * code that needs to read the context without subscribing. */\nexport const LabStoreContext = createContext<LabStoreCtx>(null);\n\n/** Provides a lab store to its subtree. */\nexport function LabStoreProvider({\n store,\n children,\n}: {\n store: LabStore;\n children: ReactNode;\n}): ReactElement {\n return <LabStoreContext.Provider value={{ store }}>{children}</LabStoreContext.Provider>;\n}\n\n/** Subscribe to the whole lab store. Throws outside a `<LabStoreProvider>`. */\nexport function useLabStore(): LabStoreState & ReturnType<LabStore['getState']> {\n const ctx = useContext(LabStoreContext);\n if (!ctx) throw new Error('[labkit] useLabStore must be used inside <LabStoreProvider>');\n return useStore(ctx.store);\n}\n\n/** Context carrying which workspace the subtree belongs to. */\nexport const WorkspaceIdContext = createContext<string | null>(null);\n\n/** Names the workspace its subtree belongs to, so an instrument's hooks can\n * find their own record in the store without being passed an id. */\nexport function WorkspaceIdProvider({\n workspaceId,\n children,\n}: {\n workspaceId: string;\n children: ReactNode;\n}): ReactElement {\n return <WorkspaceIdContext.Provider value={workspaceId}>{children}</WorkspaceIdContext.Provider>;\n}\n\n/** The id of the workspace this component is inside. Throws outside a\n * `<WorkspaceIdProvider>`. */\nexport function useWorkspaceId(): string {\n const id = useContext(WorkspaceIdContext);\n if (!id) throw new Error('[labkit] useWorkspaceId must be used inside <WorkspaceIdProvider>');\n return id;\n}\n","import { createStore, type StoreApi } from 'zustand/vanilla';\nimport {\n deserializeWorkspaces,\n emptyUndoStack,\n labStorageKey,\n serializeWorkspaces,\n} from './helpers';\nimport type {\n CreateLabStoreOptions,\n InstrumentSerializers,\n LabMode,\n LabStoreState,\n SavedSnapshot,\n WorkspaceRecord,\n} from './types';\n\n/** Every mutation a lab store supports: managing workspaces, saving and\n * restoring snapshots, and setting the color mode. */\nexport interface LabStoreActions {\n addWorkspace: (record: Omit<WorkspaceRecord, 'undoStack'>) => void;\n removeWorkspace: (id: string) => void;\n updateWorkspaceState: <TS>(id: string, next: TS | ((prev: TS) => TS)) => void;\n updateWorkspaceConfig: <TC>(id: string, key: keyof TC, value: TC[keyof TC]) => void;\n updateWorkspaceView: (id: string, view: WorkspaceRecord['view']) => void;\n updateWorkspaceUndoStack: (\n id: string,\n next:\n | WorkspaceRecord['undoStack']\n | ((prev: WorkspaceRecord['undoStack']) => WorkspaceRecord['undoStack']),\n ) => void;\n setWorkspaceInstrument: (id: string, instrumentName: string) => void;\n saveSnapshot: (workspaceId: string, name: string) => void;\n loadSnapshot: (snapshotId: string, workspaceId: string) => void;\n deleteSnapshot: (snapshotId: string) => void;\n listSnapshots: (workspaceId?: string) => SavedSnapshot[];\n setMode: (mode: LabMode) => void;\n setLayout: (layout: Record<string, unknown>) => void;\n}\n\n/** A lab's store: its state and actions, plus the hook instruments use to\n * register how their state is serialized. */\nexport type LabStore = StoreApi<LabStoreState & LabStoreActions> & {\n registerSerializers: (s: InstrumentSerializers) => void;\n};\n\n/** Build a lab store, hydrating from storage if anything was saved under the\n * same key. Writes back are debounced. */\nexport function createLabStore(options: CreateLabStoreOptions): LabStore {\n let serializers: InstrumentSerializers = {};\n let flushTimer: ReturnType<typeof setTimeout> | null = null;\n\n const workspacesRaw = options.storage.read(labStorageKey(options.storageKey, 'workspaces'));\n let hydratedWorkspaces: WorkspaceRecord[] = [];\n if (workspacesRaw) {\n hydratedWorkspaces = deserializeWorkspaces(workspacesRaw, serializers);\n }\n\n const savesRaw = options.storage.read(labStorageKey(options.storageKey, 'saves'));\n let hydratedSnapshots: SavedSnapshot[] = [];\n if (savesRaw) {\n try {\n hydratedSnapshots = JSON.parse(savesRaw) as SavedSnapshot[];\n } catch {\n console.warn('[labkit] failed to parse saved snapshots, starting empty');\n hydratedSnapshots = [];\n }\n }\n\n const layoutRaw = options.storage.read(labStorageKey(options.storageKey, 'layout'));\n let hydratedLayout: Record<string, unknown> = {};\n if (layoutRaw) {\n try {\n hydratedLayout = JSON.parse(layoutRaw) as Record<string, unknown>;\n } catch {\n console.warn('[labkit] failed to parse saved layout, starting empty');\n hydratedLayout = {};\n }\n }\n\n const modeRaw = options.storage.read(labStorageKey(options.storageKey, 'theme'));\n // `interstellar` was the dark mode's name back when it was a theme.\n const stored = modeRaw === 'interstellar' ? 'dark' : modeRaw;\n let hydratedMode: LabMode;\n if (stored === 'light' || stored === 'dark' || stored === 'auto') {\n hydratedMode = stored;\n } else {\n hydratedMode = options.initialMode ?? 'auto';\n }\n\n const store = createStore<LabStoreState & LabStoreActions>()((set, get) => ({\n workspaces: hydratedWorkspaces,\n savedSnapshots: hydratedSnapshots,\n mode: hydratedMode,\n layout: hydratedLayout,\n\n addWorkspace: (record) => {\n set((s) => ({\n workspaces: [...s.workspaces, { ...record, undoStack: emptyUndoStack() }],\n }));\n scheduleFlush();\n },\n\n removeWorkspace: (id) => {\n set((s) => ({ workspaces: s.workspaces.filter((w) => w.id !== id) }));\n scheduleFlush();\n },\n\n updateWorkspaceState: (id, next) => {\n set((s) => ({\n workspaces: s.workspaces.map((w) => {\n if (w.id !== id) return w;\n const nextState =\n typeof next === 'function' ? (next as (prev: unknown) => unknown)(w.state) : next;\n return { ...w, state: nextState };\n }),\n }));\n scheduleFlush();\n },\n\n updateWorkspaceConfig: (id, key, value) => {\n set((s) => ({\n workspaces: s.workspaces.map((w) => {\n if (w.id !== id) return w;\n return {\n ...w,\n config: { ...(w.config as Record<string, unknown>), [key as string]: value },\n };\n }),\n }));\n scheduleFlush();\n },\n\n updateWorkspaceView: (id, view) => {\n set((s) => ({\n workspaces: s.workspaces.map((w) => (w.id === id ? { ...w, view } : w)),\n }));\n scheduleFlush();\n },\n\n updateWorkspaceUndoStack: (id, next) => {\n set((s) => ({\n workspaces: s.workspaces.map((w) => {\n if (w.id !== id) return w;\n const undoStack =\n typeof next === 'function'\n ? (next as (prev: WorkspaceRecord['undoStack']) => WorkspaceRecord['undoStack'])(\n w.undoStack,\n )\n : next;\n return { ...w, undoStack };\n }),\n }));\n },\n\n setWorkspaceInstrument: (id, instrumentName) => {\n set((s) => ({\n workspaces: s.workspaces.map((w) => (w.id === id ? { ...w, instrumentName } : w)),\n }));\n scheduleFlush();\n },\n\n saveSnapshot: (workspaceId, name) => {\n const workspace = get().workspaces.find((w) => w.id === workspaceId);\n if (!workspace) return;\n const reg = serializers[workspace.instrumentName];\n const serializedState = reg?.serialize\n ? reg.serialize(workspace.state)\n : structuredClone(workspace.state);\n const clonedConfig = structuredClone(workspace.config);\n const lastAt = get().savedSnapshots.reduce((m, sn) => (sn.savedAt > m ? sn.savedAt : m), 0);\n const savedAt = Math.max(Date.now(), lastAt + 1);\n const snapshot: SavedSnapshot = {\n id: crypto.randomUUID(),\n name,\n workspaceId,\n instrumentName: workspace.instrumentName,\n config: clonedConfig,\n state: serializedState,\n savedAt,\n };\n set((s) => ({ savedSnapshots: [...s.savedSnapshots, snapshot] }));\n scheduleFlush();\n },\n\n loadSnapshot: (snapshotId, workspaceId) => {\n const snapshot = get().savedSnapshots.find((sn) => sn.id === snapshotId);\n if (!snapshot) return;\n const workspace = get().workspaces.find((w) => w.id === workspaceId);\n if (!workspace) return;\n if (snapshot.instrumentName !== workspace.instrumentName) {\n console.warn(\n `[labkit] loadSnapshot: instrument mismatch (snapshot=${snapshot.instrumentName}, workspace=${workspace.instrumentName}); refusing to load`,\n );\n return;\n }\n const reg = serializers[snapshot.instrumentName];\n const restoredState = reg?.deserialize ? reg.deserialize(snapshot.state) : snapshot.state;\n set((s) => ({\n workspaces: s.workspaces.map((w) =>\n w.id === workspaceId ? { ...w, state: restoredState, config: snapshot.config } : w,\n ),\n }));\n scheduleFlush();\n },\n\n deleteSnapshot: (snapshotId) => {\n set((s) => ({\n savedSnapshots: s.savedSnapshots.filter((sn) => sn.id !== snapshotId),\n }));\n scheduleFlush();\n },\n\n listSnapshots: (workspaceId) => {\n const all = get().savedSnapshots;\n const filtered = workspaceId ? all.filter((sn) => sn.workspaceId === workspaceId) : all;\n return [...filtered].sort((a, b) => b.savedAt - a.savedAt);\n },\n\n setMode: (mode) => {\n set({ mode });\n scheduleFlush();\n },\n\n setLayout: (layout) => {\n set({ layout });\n scheduleFlush();\n },\n }));\n\n function scheduleFlush(): void {\n if (flushTimer) clearTimeout(flushTimer);\n flushTimer = setTimeout(() => {\n const s = store.getState();\n options.storage.write(\n labStorageKey(options.storageKey, 'workspaces'),\n serializeWorkspaces(s.workspaces, serializers),\n );\n options.storage.write(\n labStorageKey(options.storageKey, 'saves'),\n JSON.stringify(s.savedSnapshots),\n );\n options.storage.write(labStorageKey(options.storageKey, 'theme'), s.mode);\n options.storage.write(labStorageKey(options.storageKey, 'layout'), JSON.stringify(s.layout));\n flushTimer = null;\n }, 300);\n }\n\n return Object.assign(store, {\n registerSerializers(s: InstrumentSerializers) {\n serializers = s;\n },\n });\n}\n","import { useContext } from 'react';\nimport { useStore } from 'zustand/react';\nimport { LabStoreContext, WorkspaceIdContext } from './context';\nimport type { ExperimentStateHandle } from './types';\n\n/** An instrument's own state and config, plus setters. Reads the surrounding\n * workspace id, so an instrument never has to know which workspace it is\n * running in. Throws outside a lab store and workspace. */\nexport function useExperimentState<TS = unknown, TC = unknown>(): ExperimentStateHandle<TS, TC> {\n const ctx = useContext(LabStoreContext);\n if (!ctx) throw new Error('[labkit] useExperimentState must be used inside <LabStoreProvider>');\n\n const workspaceId = useContext(WorkspaceIdContext);\n if (!workspaceId)\n throw new Error('[labkit] useExperimentState must be used inside <WorkspaceIdProvider>');\n\n const record = useStore(ctx.store, (s) => s.workspaces.find((w) => w.id === workspaceId));\n\n if (!record) throw new Error(`[labkit] No workspace found with id \"${workspaceId}\"`);\n\n const updateWorkspaceState = useStore(ctx.store, (s) => s.updateWorkspaceState);\n const updateWorkspaceConfig = useStore(ctx.store, (s) => s.updateWorkspaceConfig);\n\n return {\n state: record.state as TS,\n config: record.config as TC,\n setState: (next) =>\n updateWorkspaceState(workspaceId, next as Parameters<typeof updateWorkspaceState>[1]),\n setConfig: (key, value) => updateWorkspaceConfig(workspaceId, key as never, value as never),\n };\n}\n"]}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { useContext } from 'react';
|
|
2
|
-
import { useStore } from 'zustand/react';
|
|
3
|
-
import { LabStoreContext, WorkspaceIdContext } from './context';
|
|
4
|
-
import type { ExperimentStateHandle } from './types';
|
|
5
|
-
|
|
6
|
-
/** An instrument's own state and config, plus setters. Reads the surrounding
|
|
7
|
-
* workspace id, so an instrument never has to know which workspace it is
|
|
8
|
-
* running in. Throws outside a lab store and workspace. */
|
|
9
|
-
export function useExperimentState<TS = unknown, TC = unknown>(): ExperimentStateHandle<TS, TC> {
|
|
10
|
-
const ctx = useContext(LabStoreContext);
|
|
11
|
-
if (!ctx) throw new Error('[labkit] useExperimentState must be used inside <LabStoreProvider>');
|
|
12
|
-
|
|
13
|
-
const workspaceId = useContext(WorkspaceIdContext);
|
|
14
|
-
if (!workspaceId)
|
|
15
|
-
throw new Error('[labkit] useExperimentState must be used inside <WorkspaceIdProvider>');
|
|
16
|
-
|
|
17
|
-
const record = useStore(ctx.store, (s) => s.workspaces.find((w) => w.id === workspaceId));
|
|
18
|
-
|
|
19
|
-
if (!record) throw new Error(`[labkit] No workspace found with id "${workspaceId}"`);
|
|
20
|
-
|
|
21
|
-
const updateWorkspaceState = useStore(ctx.store, (s) => s.updateWorkspaceState);
|
|
22
|
-
const updateWorkspaceConfig = useStore(ctx.store, (s) => s.updateWorkspaceConfig);
|
|
23
|
-
|
|
24
|
-
return {
|
|
25
|
-
state: record.state as TS,
|
|
26
|
-
config: record.config as TC,
|
|
27
|
-
setState: (next) =>
|
|
28
|
-
updateWorkspaceState(workspaceId, next as Parameters<typeof updateWorkspaceState>[1]),
|
|
29
|
-
setConfig: (key, value) => updateWorkspaceConfig(workspaceId, key as never, value as never),
|
|
30
|
-
};
|
|
31
|
-
}
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import type { Instrument } from '../instrument/types';
|
|
2
|
-
import type { WorkspaceRecord } from '../state/types';
|
|
3
|
-
|
|
4
|
-
const DEFAULT_VIEW = { zoom: 1, pan: { x: 0, y: 0 } } as const;
|
|
5
|
-
|
|
6
|
-
function findInstrument(instruments: Instrument[], name: string): Instrument {
|
|
7
|
-
const found = instruments.find((i) => i.name === name);
|
|
8
|
-
if (!found) {
|
|
9
|
-
throw new Error(`[labkit] Unknown instrument: "${name}"`);
|
|
10
|
-
}
|
|
11
|
-
return found;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function initialView(instrument: Instrument): WorkspaceRecord['view'] {
|
|
15
|
-
return instrument.canvas?.initialView
|
|
16
|
-
? structuredClone(instrument.canvas.initialView)
|
|
17
|
-
: { ...DEFAULT_VIEW, pan: { ...DEFAULT_VIEW.pan } };
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** Append a new workspace running `instrumentName`, at that instrument's
|
|
21
|
-
* default config and initial state. */
|
|
22
|
-
export function addWorkspace(
|
|
23
|
-
workspaces: WorkspaceRecord[],
|
|
24
|
-
instruments: Instrument[],
|
|
25
|
-
instrumentName: string,
|
|
26
|
-
): WorkspaceRecord[] {
|
|
27
|
-
const instrument = findInstrument(instruments, instrumentName);
|
|
28
|
-
const config = instrument.defaultConfig();
|
|
29
|
-
const state = instrument.initialState(config);
|
|
30
|
-
const record: WorkspaceRecord = {
|
|
31
|
-
id: crypto.randomUUID(),
|
|
32
|
-
instrumentName,
|
|
33
|
-
config,
|
|
34
|
-
state,
|
|
35
|
-
view: initialView(instrument),
|
|
36
|
-
undoStack: { past: [], future: [] },
|
|
37
|
-
};
|
|
38
|
-
return [...workspaces, record];
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** Insert a deep copy of a workspace directly after it. The copy starts with
|
|
42
|
-
* an empty undo history — the original's is not shared. */
|
|
43
|
-
export function cloneWorkspace(workspaces: WorkspaceRecord[], id: string): WorkspaceRecord[] {
|
|
44
|
-
const sourceIdx = workspaces.findIndex((w) => w.id === id);
|
|
45
|
-
const source = workspaces[sourceIdx];
|
|
46
|
-
if (!source) return workspaces;
|
|
47
|
-
const clone: WorkspaceRecord = {
|
|
48
|
-
...source,
|
|
49
|
-
id: crypto.randomUUID(),
|
|
50
|
-
config: structuredClone(source.config),
|
|
51
|
-
state: structuredClone(source.state),
|
|
52
|
-
view: structuredClone(source.view),
|
|
53
|
-
undoStack: { past: [], future: [] },
|
|
54
|
-
};
|
|
55
|
-
return [...workspaces.slice(0, sourceIdx + 1), clone, ...workspaces.slice(sourceIdx + 1)];
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** Remove a workspace, unless it is the last one — a lab always has at least
|
|
59
|
-
* one. */
|
|
60
|
-
export function closeWorkspace(workspaces: WorkspaceRecord[], id: string): WorkspaceRecord[] {
|
|
61
|
-
if (workspaces.length <= 1) return workspaces;
|
|
62
|
-
const next = workspaces.filter((w) => w.id !== id);
|
|
63
|
-
return next.length === workspaces.length ? workspaces : next;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** Return a workspace to its instrument's defaults, keeping its id and its
|
|
67
|
-
* place in the list. */
|
|
68
|
-
export function resetWorkspace(
|
|
69
|
-
workspaces: WorkspaceRecord[],
|
|
70
|
-
id: string,
|
|
71
|
-
instruments: Instrument[],
|
|
72
|
-
): WorkspaceRecord[] {
|
|
73
|
-
const idx = workspaces.findIndex((w) => w.id === id);
|
|
74
|
-
const current = workspaces[idx];
|
|
75
|
-
if (!current) return workspaces;
|
|
76
|
-
const instrument = findInstrument(instruments, current.instrumentName);
|
|
77
|
-
const config = instrument.defaultConfig();
|
|
78
|
-
const state = instrument.initialState(config);
|
|
79
|
-
const reset: WorkspaceRecord = {
|
|
80
|
-
...current,
|
|
81
|
-
config,
|
|
82
|
-
state,
|
|
83
|
-
view: initialView(instrument),
|
|
84
|
-
};
|
|
85
|
-
return [...workspaces.slice(0, idx), reset, ...workspaces.slice(idx + 1)];
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Reorder to match `ids`. Ids the list doesn't mention keep their relative
|
|
90
|
-
* order at the end, and ids it names that no longer exist are dropped — a
|
|
91
|
-
* reorder that raced a close should not resurrect the closed workspace.
|
|
92
|
-
*/
|
|
93
|
-
export function reorderWorkspaces(
|
|
94
|
-
workspaces: WorkspaceRecord[],
|
|
95
|
-
ids: readonly string[],
|
|
96
|
-
): WorkspaceRecord[] {
|
|
97
|
-
const byId = new Map(workspaces.map((w) => [w.id, w]));
|
|
98
|
-
const named = ids.map((id) => byId.get(id)).filter((w): w is WorkspaceRecord => w !== undefined);
|
|
99
|
-
const seen = new Set(named.map((w) => w.id));
|
|
100
|
-
const rest = workspaces.filter((w) => !seen.has(w.id));
|
|
101
|
-
return [...named, ...rest];
|
|
102
|
-
}
|