@weasel-js/labkit 0.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 +72 -0
- package/dist/_dts/fitViewToBounds-rWrBI4a6.d.ts +870 -0
- package/dist/_dts/index-C6Yze7sQ.d.ts +93 -0
- package/dist/_dts/types-Si4Fw-1F.d.ts +48 -0
- package/dist/_dts/useExperimentState-eSvpwV_P.d.ts +102 -0
- package/dist/canvas/index.d.ts +34 -0
- package/dist/canvas/index.js +4 -0
- package/dist/canvas/index.js.map +1 -0
- package/dist/chunk-2QNYYL3V.js +86 -0
- package/dist/chunk-2QNYYL3V.js.map +1 -0
- package/dist/chunk-54IQ2DX7.js +222 -0
- package/dist/chunk-54IQ2DX7.js.map +1 -0
- package/dist/chunk-6FO7G5BB.js +6095 -0
- package/dist/chunk-6FO7G5BB.js.map +1 -0
- package/dist/chunk-7BKDG73Z.js +54 -0
- package/dist/chunk-7BKDG73Z.js.map +1 -0
- package/dist/chunk-B5ZUYRKN.js +23330 -0
- package/dist/chunk-B5ZUYRKN.js.map +1 -0
- package/dist/chunk-HXZHVU4G.js +126 -0
- package/dist/chunk-HXZHVU4G.js.map +1 -0
- package/dist/chunk-PWC7AQZM.js +8 -0
- package/dist/chunk-PWC7AQZM.js.map +1 -0
- package/dist/chunk-SD3HDXR7.js +161 -0
- package/dist/chunk-SD3HDXR7.js.map +1 -0
- package/dist/chunk-SSMHPMDV.js +350 -0
- package/dist/chunk-SSMHPMDV.js.map +1 -0
- package/dist/chunk-VLAHRJOC.js +201 -0
- package/dist/chunk-VLAHRJOC.js.map +1 -0
- package/dist/controls/index.d.ts +13 -0
- package/dist/controls/index.js +3 -0
- package/dist/controls/index.js.map +1 -0
- package/dist/dragdrop/index.d.ts +3 -0
- package/dist/dragdrop/index.js +3 -0
- package/dist/dragdrop/index.js.map +1 -0
- package/dist/fonts/oswald-latin-variable.woff2 +0 -0
- package/dist/index.d.ts +410 -0
- package/dist/index.js +1425 -0
- package/dist/index.js.map +1 -0
- package/dist/layers/index.d.ts +17 -0
- package/dist/layers/index.js +3 -0
- package/dist/layers/index.js.map +1 -0
- package/dist/passthrough/weasel-canvas.d.ts +258 -0
- package/dist/passthrough/weasel-canvas.js +3 -0
- package/dist/passthrough/weasel-canvas.js.map +1 -0
- package/dist/passthrough/weasel-ui.d.ts +2826 -0
- package/dist/passthrough/weasel-ui.js +4 -0
- package/dist/passthrough/weasel-ui.js.map +1 -0
- package/dist/primitives/index.d.ts +57 -0
- package/dist/primitives/index.js +4 -0
- package/dist/primitives/index.js.map +1 -0
- package/dist/state/index.d.ts +33 -0
- package/dist/state/index.js +34 -0
- package/dist/state/index.js.map +1 -0
- package/dist/styles.css +1224 -0
- package/dist/theme-interstellar.css +25 -0
- package/dist/theme-light.css +13 -0
- package/dist/ui/layers/index.d.ts +42 -0
- package/dist/ui/layers/index.js +5 -0
- package/dist/ui/layers/index.js.map +1 -0
- package/dist/undo/index.d.ts +30 -0
- package/dist/undo/index.js +3 -0
- package/dist/undo/index.js.map +1 -0
- package/package.json +99 -0
- package/src/canvas/AGENTS.md +72 -0
- package/src/canvas/CanvasStack.less +22 -0
- package/src/canvas/CanvasStack.stories.tsx +63 -0
- package/src/canvas/CanvasStack.test.tsx +82 -0
- package/src/canvas/CanvasStack.tsx +108 -0
- package/src/canvas/CanvasStackContext.ts +8 -0
- package/src/canvas/canvasCoords.test.ts +30 -0
- package/src/canvas/canvasCoords.ts +15 -0
- package/src/canvas/index.ts +6 -0
- package/src/canvas/useLayerScheduler.ts +72 -0
- package/src/canvas/usePanZoom.ts +98 -0
- package/src/controls/ControlPanel.less +61 -0
- package/src/controls/ControlPanel.stories.tsx +77 -0
- package/src/controls/ControlPanel.test.tsx +149 -0
- package/src/controls/ControlPanel.tsx +249 -0
- package/src/controls/index.ts +12 -0
- package/src/controls/types.ts +61 -0
- package/src/dragdrop/DragDropRuntime.tsx +135 -0
- package/src/dragdrop/DragGhost.less +17 -0
- package/src/dragdrop/DragGhost.tsx +24 -0
- package/src/dragdrop/Palette.less +34 -0
- package/src/dragdrop/Palette.tsx +27 -0
- package/src/dragdrop/dragDrop.test.tsx +145 -0
- package/src/dragdrop/index.ts +1 -0
- package/src/fonts/oswald-latin-variable.woff2 +0 -0
- package/src/index.ts +70 -0
- package/src/instrument/SineWave.smoke.test.tsx +94 -0
- package/src/instrument/capabilityDetector.test.ts +64 -0
- package/src/instrument/capabilityDetector.ts +17 -0
- package/src/instrument/defineInstrument.test.ts +27 -0
- package/src/instrument/defineInstrument.ts +5 -0
- package/src/instrument/index.ts +19 -0
- package/src/instrument/types.ts +65 -0
- package/src/instrument/validateConfigSchema.test.ts +125 -0
- package/src/instrument/validateConfigSchema.ts +65 -0
- package/src/lab/Lab.less +3 -0
- package/src/lab/Lab.stories.tsx +51 -0
- package/src/lab/Lab.test.tsx +126 -0
- package/src/lab/Lab.tsx +196 -0
- package/src/lab/LabContext.test.ts +11 -0
- package/src/lab/LabContext.ts +28 -0
- package/src/lab/LabShell.less +45 -0
- package/src/lab/LabShell.stories.tsx +39 -0
- package/src/lab/LabShell.test.tsx +59 -0
- package/src/lab/LabShell.tsx +33 -0
- package/src/lab/WorkspaceGrid.less +8 -0
- package/src/lab/WorkspaceGrid.stories.tsx +61 -0
- package/src/lab/WorkspaceGrid.test.tsx +40 -0
- package/src/lab/WorkspaceGrid.tsx +20 -0
- package/src/lab/gridDims.test.ts +35 -0
- package/src/lab/gridDims.ts +11 -0
- package/src/lab/index.ts +10 -0
- package/src/layers/AGENTS.md +65 -0
- package/src/layers/LayerList.less +53 -0
- package/src/layers/LayerList.test.tsx +49 -0
- package/src/layers/LayerList.tsx +105 -0
- package/src/layers/index.ts +3 -0
- package/src/passthrough/weasel-canvas.test.ts +30 -0
- package/src/passthrough/weasel-canvas.ts +23 -0
- package/src/passthrough/weasel-ui.test.ts +79 -0
- package/src/passthrough/weasel-ui.ts +150 -0
- package/src/primitives/FpsMeter.less +14 -0
- package/src/primitives/FpsMeter.stories.tsx +14 -0
- package/src/primitives/FpsMeter.test.tsx +38 -0
- package/src/primitives/FpsMeter.tsx +36 -0
- package/src/primitives/ScaleIndicator.less +25 -0
- package/src/primitives/ScaleIndicator.stories.tsx +16 -0
- package/src/primitives/ScaleIndicator.test.tsx +46 -0
- package/src/primitives/ScaleIndicator.tsx +48 -0
- package/src/primitives/Sidebar.less +57 -0
- package/src/primitives/Sidebar.stories.tsx +38 -0
- package/src/primitives/Sidebar.test.tsx +74 -0
- package/src/primitives/Sidebar.tsx +32 -0
- package/src/primitives/StatusBar.less +21 -0
- package/src/primitives/StatusBar.stories.tsx +24 -0
- package/src/primitives/StatusBar.test.tsx +25 -0
- package/src/primitives/StatusBar.tsx +18 -0
- package/src/primitives/Toolbar.less +43 -0
- package/src/primitives/Toolbar.stories.tsx +36 -0
- package/src/primitives/Toolbar.test.tsx +65 -0
- package/src/primitives/Toolbar.tsx +44 -0
- package/src/primitives/fpsAverage.test.ts +21 -0
- package/src/primitives/fpsAverage.ts +5 -0
- package/src/primitives/index.ts +9 -0
- package/src/state/SingletonExperiment.test.tsx +101 -0
- package/src/state/SingletonExperiment.tsx +52 -0
- package/src/state/adapters.test.ts +94 -0
- package/src/state/adapters.ts +102 -0
- package/src/state/context.tsx +42 -0
- package/src/state/helpers.test.ts +89 -0
- package/src/state/helpers.ts +61 -0
- package/src/state/index.ts +40 -0
- package/src/state/store.test.ts +243 -0
- package/src/state/store.ts +225 -0
- package/src/state/types.ts +53 -0
- package/src/state/useExperimentState.test.tsx +102 -0
- package/src/state/useExperimentState.ts +28 -0
- package/src/styles.less +20 -0
- package/src/test-setup.ts +17 -0
- package/src/theme/Interstellar.stories.less +92 -0
- package/src/theme/Interstellar.stories.tsx +243 -0
- package/src/theme/base.less +122 -0
- package/src/theme/interstellar.less +34 -0
- package/src/theme/light.less +14 -0
- package/src/theme/tokens.less +85 -0
- package/src/ui/format.ts +19 -0
- package/src/ui/layers/LayerStack.less +143 -0
- package/src/ui/layers/LayerStack.stories.tsx +45 -0
- package/src/ui/layers/LayerStack.test.tsx +170 -0
- package/src/ui/layers/LayerStack.tsx +215 -0
- package/src/ui/layers/index.ts +1 -0
- package/src/ui/properties/CheckboxRow.stories.tsx +27 -0
- package/src/ui/properties/ColorRow.stories.tsx +106 -0
- package/src/ui/properties/CurveField.less +48 -0
- package/src/ui/properties/CurveField.stories.tsx +20 -0
- package/src/ui/properties/CurveField.test.tsx +127 -0
- package/src/ui/properties/CurveField.tsx +181 -0
- package/src/ui/properties/EffectCard.tsx +312 -0
- package/src/ui/properties/Gallery.stories.tsx +178 -0
- package/src/ui/properties/NumberRow.stories.tsx +65 -0
- package/src/ui/properties/PropertyGroup.less +50 -0
- package/src/ui/properties/PropertyGroup.stories.tsx +34 -0
- package/src/ui/properties/PropertyGroup.test.tsx +33 -0
- package/src/ui/properties/PropertyGroup.tsx +44 -0
- package/src/ui/properties/PropertyList.stories.tsx +83 -0
- package/src/ui/properties/PropertyPanel.less +592 -0
- package/src/ui/properties/PropertyPanel.stories.tsx +191 -0
- package/src/ui/properties/PropertyPanel.test.tsx +193 -0
- package/src/ui/properties/PropertyPanel.tsx +441 -0
- package/src/ui/properties/PropertyRow.stories.tsx +53 -0
- package/src/ui/properties/SelectRow.stories.tsx +58 -0
- package/src/ui/properties/SliderRow.stories.tsx +74 -0
- package/src/ui/properties/SpeechBalloonPanels.stories.tsx +396 -0
- package/src/ui/properties/TextRow.stories.tsx +62 -0
- package/src/ui/properties/ToggleRow.stories.tsx +57 -0
- package/src/ui/properties/index.ts +39 -0
- package/src/ui/properties/storyLayouts.tsx +36 -0
- package/src/undo/eventBus.test.ts +49 -0
- package/src/undo/eventBus.ts +32 -0
- package/src/undo/index.ts +5 -0
- package/src/undo/undoStack.test.ts +70 -0
- package/src/undo/undoStack.ts +38 -0
- package/src/workspace/DefaultSidebar.tsx +26 -0
- package/src/workspace/DefaultStatusBar.tsx +15 -0
- package/src/workspace/DefaultToolbar.tsx +88 -0
- package/src/workspace/Workspace.less +58 -0
- package/src/workspace/Workspace.stories.tsx +78 -0
- package/src/workspace/Workspace.test.tsx +186 -0
- package/src/workspace/Workspace.tsx +255 -0
- package/src/workspace/WorkspaceChrome.tsx +150 -0
- package/src/workspace/index.ts +24 -0
- package/src/workspace/slotTypes.ts +44 -0
- package/src/workspace/workspaceOps.test.ts +142 -0
- package/src/workspace/workspaceOps.ts +78 -0
|
@@ -0,0 +1,350 @@
|
|
|
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 themeRaw = options.storage.read(labStorageKey(options.storageKey, "theme"));
|
|
187
|
+
let hydratedTheme;
|
|
188
|
+
if (themeRaw === "light" || themeRaw === "interstellar" || themeRaw === "auto") {
|
|
189
|
+
hydratedTheme = themeRaw;
|
|
190
|
+
} else {
|
|
191
|
+
hydratedTheme = options.initialTheme ?? "auto";
|
|
192
|
+
}
|
|
193
|
+
const store = createStore()((set, get) => ({
|
|
194
|
+
workspaces: hydratedWorkspaces,
|
|
195
|
+
savedSnapshots: hydratedSnapshots,
|
|
196
|
+
theme: hydratedTheme,
|
|
197
|
+
addWorkspace: (record) => {
|
|
198
|
+
set((s) => ({
|
|
199
|
+
workspaces: [...s.workspaces, { ...record, undoStack: emptyUndoStack() }]
|
|
200
|
+
}));
|
|
201
|
+
scheduleFlush();
|
|
202
|
+
},
|
|
203
|
+
removeWorkspace: (id) => {
|
|
204
|
+
set((s) => ({ workspaces: s.workspaces.filter((w) => w.id !== id) }));
|
|
205
|
+
scheduleFlush();
|
|
206
|
+
},
|
|
207
|
+
updateWorkspaceState: (id, next) => {
|
|
208
|
+
set((s) => ({
|
|
209
|
+
workspaces: s.workspaces.map((w) => {
|
|
210
|
+
if (w.id !== id) return w;
|
|
211
|
+
const nextState = typeof next === "function" ? next(w.state) : next;
|
|
212
|
+
return { ...w, state: nextState };
|
|
213
|
+
})
|
|
214
|
+
}));
|
|
215
|
+
scheduleFlush();
|
|
216
|
+
},
|
|
217
|
+
updateWorkspaceConfig: (id, key, value) => {
|
|
218
|
+
set((s) => ({
|
|
219
|
+
workspaces: s.workspaces.map((w) => {
|
|
220
|
+
if (w.id !== id) return w;
|
|
221
|
+
return {
|
|
222
|
+
...w,
|
|
223
|
+
config: { ...w.config, [key]: value }
|
|
224
|
+
};
|
|
225
|
+
})
|
|
226
|
+
}));
|
|
227
|
+
scheduleFlush();
|
|
228
|
+
},
|
|
229
|
+
updateWorkspaceView: (id, view) => {
|
|
230
|
+
set((s) => ({
|
|
231
|
+
workspaces: s.workspaces.map((w) => w.id === id ? { ...w, view } : w)
|
|
232
|
+
}));
|
|
233
|
+
scheduleFlush();
|
|
234
|
+
},
|
|
235
|
+
updateWorkspaceUndoStack: (id, next) => {
|
|
236
|
+
set((s) => ({
|
|
237
|
+
workspaces: s.workspaces.map((w) => {
|
|
238
|
+
if (w.id !== id) return w;
|
|
239
|
+
const undoStack = typeof next === "function" ? next(
|
|
240
|
+
w.undoStack
|
|
241
|
+
) : next;
|
|
242
|
+
return { ...w, undoStack };
|
|
243
|
+
})
|
|
244
|
+
}));
|
|
245
|
+
},
|
|
246
|
+
setWorkspaceInstrument: (id, instrumentName) => {
|
|
247
|
+
set((s) => ({
|
|
248
|
+
workspaces: s.workspaces.map((w) => w.id === id ? { ...w, instrumentName } : w)
|
|
249
|
+
}));
|
|
250
|
+
scheduleFlush();
|
|
251
|
+
},
|
|
252
|
+
saveSnapshot: (workspaceId, name) => {
|
|
253
|
+
const workspace = get().workspaces.find((w) => w.id === workspaceId);
|
|
254
|
+
if (!workspace) return;
|
|
255
|
+
const reg = serializers[workspace.instrumentName];
|
|
256
|
+
const serializedState = reg?.serialize ? reg.serialize(workspace.state) : structuredClone(workspace.state);
|
|
257
|
+
const clonedConfig = structuredClone(workspace.config);
|
|
258
|
+
const lastAt = get().savedSnapshots.reduce((m, sn) => sn.savedAt > m ? sn.savedAt : m, 0);
|
|
259
|
+
const savedAt = Math.max(Date.now(), lastAt + 1);
|
|
260
|
+
const snapshot = {
|
|
261
|
+
id: crypto.randomUUID(),
|
|
262
|
+
name,
|
|
263
|
+
workspaceId,
|
|
264
|
+
instrumentName: workspace.instrumentName,
|
|
265
|
+
config: clonedConfig,
|
|
266
|
+
state: serializedState,
|
|
267
|
+
savedAt
|
|
268
|
+
};
|
|
269
|
+
set((s) => ({ savedSnapshots: [...s.savedSnapshots, snapshot] }));
|
|
270
|
+
scheduleFlush();
|
|
271
|
+
},
|
|
272
|
+
loadSnapshot: (snapshotId, workspaceId) => {
|
|
273
|
+
const snapshot = get().savedSnapshots.find((sn) => sn.id === snapshotId);
|
|
274
|
+
if (!snapshot) return;
|
|
275
|
+
const workspace = get().workspaces.find((w) => w.id === workspaceId);
|
|
276
|
+
if (!workspace) return;
|
|
277
|
+
if (snapshot.instrumentName !== workspace.instrumentName) {
|
|
278
|
+
console.warn(
|
|
279
|
+
`[labkit] loadSnapshot: instrument mismatch (snapshot=${snapshot.instrumentName}, workspace=${workspace.instrumentName}); refusing to load`
|
|
280
|
+
);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const reg = serializers[snapshot.instrumentName];
|
|
284
|
+
const restoredState = reg?.deserialize ? reg.deserialize(snapshot.state) : snapshot.state;
|
|
285
|
+
set((s) => ({
|
|
286
|
+
workspaces: s.workspaces.map(
|
|
287
|
+
(w) => w.id === workspaceId ? { ...w, state: restoredState, config: snapshot.config } : w
|
|
288
|
+
)
|
|
289
|
+
}));
|
|
290
|
+
scheduleFlush();
|
|
291
|
+
},
|
|
292
|
+
deleteSnapshot: (snapshotId) => {
|
|
293
|
+
set((s) => ({
|
|
294
|
+
savedSnapshots: s.savedSnapshots.filter((sn) => sn.id !== snapshotId)
|
|
295
|
+
}));
|
|
296
|
+
scheduleFlush();
|
|
297
|
+
},
|
|
298
|
+
listSnapshots: (workspaceId) => {
|
|
299
|
+
const all = get().savedSnapshots;
|
|
300
|
+
const filtered = workspaceId ? all.filter((sn) => sn.workspaceId === workspaceId) : all;
|
|
301
|
+
return [...filtered].sort((a, b) => b.savedAt - a.savedAt);
|
|
302
|
+
},
|
|
303
|
+
setTheme: (theme) => {
|
|
304
|
+
set({ theme });
|
|
305
|
+
scheduleFlush();
|
|
306
|
+
}
|
|
307
|
+
}));
|
|
308
|
+
function scheduleFlush() {
|
|
309
|
+
if (flushTimer) clearTimeout(flushTimer);
|
|
310
|
+
flushTimer = setTimeout(() => {
|
|
311
|
+
const s = store.getState();
|
|
312
|
+
options.storage.write(
|
|
313
|
+
labStorageKey(options.storageKey, "workspaces"),
|
|
314
|
+
serializeWorkspaces(s.workspaces, serializers)
|
|
315
|
+
);
|
|
316
|
+
options.storage.write(
|
|
317
|
+
labStorageKey(options.storageKey, "saves"),
|
|
318
|
+
JSON.stringify(s.savedSnapshots)
|
|
319
|
+
);
|
|
320
|
+
options.storage.write(labStorageKey(options.storageKey, "theme"), s.theme);
|
|
321
|
+
flushTimer = null;
|
|
322
|
+
}, 300);
|
|
323
|
+
}
|
|
324
|
+
return Object.assign(store, {
|
|
325
|
+
registerSerializers(s) {
|
|
326
|
+
serializers = s;
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
function useExperimentState() {
|
|
331
|
+
const ctx = useContext(LabStoreContext);
|
|
332
|
+
if (!ctx) throw new Error("[labkit] useExperimentState must be used inside <LabStoreProvider>");
|
|
333
|
+
const workspaceId = useContext(WorkspaceIdContext);
|
|
334
|
+
if (!workspaceId)
|
|
335
|
+
throw new Error("[labkit] useExperimentState must be used inside <WorkspaceIdProvider>");
|
|
336
|
+
const record = useStore(ctx.store, (s) => s.workspaces.find((w) => w.id === workspaceId));
|
|
337
|
+
if (!record) throw new Error(`[labkit] No workspace found with id "${workspaceId}"`);
|
|
338
|
+
const updateWorkspaceState = useStore(ctx.store, (s) => s.updateWorkspaceState);
|
|
339
|
+
const updateWorkspaceConfig = useStore(ctx.store, (s) => s.updateWorkspaceConfig);
|
|
340
|
+
return {
|
|
341
|
+
state: record.state,
|
|
342
|
+
config: record.config,
|
|
343
|
+
setState: (next) => updateWorkspaceState(workspaceId, next),
|
|
344
|
+
setConfig: (key, value) => updateWorkspaceConfig(workspaceId, key, value)
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export { LabStoreContext, LabStoreProvider, WorkspaceIdContext, WorkspaceIdProvider, createLabStore, createMemoryAdapter, decodeUrlHash, deserializeWorkspaces, emptyUndoStack, encodeUrlHash, labStorageKey, localStorageAdapter, noneAdapter, serializeWorkspaces, sessionStorageAdapter, urlHashAdapter, useExperimentState, useLabStore, useWorkspaceId };
|
|
349
|
+
//# sourceMappingURL=chunk-SSMHPMDV.js.map
|
|
350
|
+
//# sourceMappingURL=chunk-SSMHPMDV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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-SSMHPMDV.js","sourcesContent":["import type { InstrumentSerializers, UndoStack, WorkspaceRecord } from './types';\n\nexport function labStorageKey(\n storageKey: string,\n bucket: 'workspaces' | 'saves' | 'theme',\n): string {\n return `lk:${storageKey}:${bucket}`;\n}\n\nexport function encodeUrlHash(value: string): string {\n return btoa(encodeURIComponent(value));\n}\n\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\nexport function emptyUndoStack(): UndoStack {\n return { past: [], future: [] };\n}\n\ntype SerializedRecord = Omit<WorkspaceRecord, 'undoStack'>;\n\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\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\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\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\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\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\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\nexport const LabStoreContext = createContext<LabStoreCtx>(null);\n\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\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\nexport const WorkspaceIdContext = createContext<string | null>(null);\n\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\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 LabStoreState,\n SavedSnapshot,\n WorkspaceRecord,\n} from './types';\n\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 setTheme: (theme: 'light' | 'interstellar' | 'auto') => void;\n}\n\nexport type LabStore = StoreApi<LabStoreState & LabStoreActions> & {\n registerSerializers: (s: InstrumentSerializers) => void;\n};\n\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 themeRaw = options.storage.read(labStorageKey(options.storageKey, 'theme'));\n let hydratedTheme: 'light' | 'interstellar' | 'auto';\n if (themeRaw === 'light' || themeRaw === 'interstellar' || themeRaw === 'auto') {\n hydratedTheme = themeRaw;\n } else {\n hydratedTheme = options.initialTheme ?? 'auto';\n }\n\n const store = createStore<LabStoreState & LabStoreActions>()((set, get) => ({\n workspaces: hydratedWorkspaces,\n savedSnapshots: hydratedSnapshots,\n theme: hydratedTheme,\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 setTheme: (theme) => {\n set({ theme });\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.theme);\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\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"]}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { useState, useRef, useEffect } from 'react';
|
|
2
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
// src/controls/ControlPanel.tsx
|
|
5
|
+
function ControlPanel({
|
|
6
|
+
fields,
|
|
7
|
+
config,
|
|
8
|
+
setConfig,
|
|
9
|
+
className
|
|
10
|
+
}) {
|
|
11
|
+
return /* @__PURE__ */ jsx("div", { className: className ? `lk-control-panel ${className}` : "lk-control-panel", children: fields.map((field) => /* @__PURE__ */ jsx(ControlRow, { field, config, setConfig }, field.key)) });
|
|
12
|
+
}
|
|
13
|
+
function ControlRow({
|
|
14
|
+
field,
|
|
15
|
+
config,
|
|
16
|
+
setConfig
|
|
17
|
+
}) {
|
|
18
|
+
switch (field.type) {
|
|
19
|
+
case "slider":
|
|
20
|
+
return /* @__PURE__ */ jsx(SliderRow, { field, config, setConfig });
|
|
21
|
+
case "checkbox":
|
|
22
|
+
return /* @__PURE__ */ jsx(CheckboxRow, { field, config, setConfig });
|
|
23
|
+
case "select":
|
|
24
|
+
return /* @__PURE__ */ jsx(SelectRow, { field, config, setConfig });
|
|
25
|
+
case "number":
|
|
26
|
+
return /* @__PURE__ */ jsx(NumberRow, { field, config, setConfig });
|
|
27
|
+
case "text":
|
|
28
|
+
return /* @__PURE__ */ jsx(TextRow, { field, config, setConfig });
|
|
29
|
+
case "color":
|
|
30
|
+
return /* @__PURE__ */ jsx(ColorRow, { field, config, setConfig });
|
|
31
|
+
default:
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function SliderRow({
|
|
36
|
+
field,
|
|
37
|
+
config,
|
|
38
|
+
setConfig
|
|
39
|
+
}) {
|
|
40
|
+
const value = config[field.key] ?? field.default;
|
|
41
|
+
return /* @__PURE__ */ jsxs("div", { className: "lk-control-row", children: [
|
|
42
|
+
/* @__PURE__ */ jsx("label", { className: "lk-control-label", htmlFor: `lk-ctrl-${field.key}`, children: field.label }),
|
|
43
|
+
/* @__PURE__ */ jsxs("div", { className: "lk-control-slider-row", children: [
|
|
44
|
+
/* @__PURE__ */ jsx(
|
|
45
|
+
"input",
|
|
46
|
+
{
|
|
47
|
+
id: `lk-ctrl-${field.key}`,
|
|
48
|
+
className: "lk-control-input lk-control-input--range",
|
|
49
|
+
type: "range",
|
|
50
|
+
min: field.min,
|
|
51
|
+
max: field.max,
|
|
52
|
+
step: field.step ?? 1,
|
|
53
|
+
value,
|
|
54
|
+
onChange: (e) => setConfig(field.key, Number(e.target.value))
|
|
55
|
+
}
|
|
56
|
+
),
|
|
57
|
+
/* @__PURE__ */ jsx("span", { className: "lk-control-slider-value", children: value })
|
|
58
|
+
] })
|
|
59
|
+
] });
|
|
60
|
+
}
|
|
61
|
+
function CheckboxRow({
|
|
62
|
+
field,
|
|
63
|
+
config,
|
|
64
|
+
setConfig
|
|
65
|
+
}) {
|
|
66
|
+
const value = config[field.key] ?? field.default;
|
|
67
|
+
return /* @__PURE__ */ jsxs("div", { className: "lk-control-row lk-control-row--inline", children: [
|
|
68
|
+
/* @__PURE__ */ jsx(
|
|
69
|
+
"input",
|
|
70
|
+
{
|
|
71
|
+
id: `lk-ctrl-${field.key}`,
|
|
72
|
+
className: "lk-control-input lk-control-input--checkbox",
|
|
73
|
+
type: "checkbox",
|
|
74
|
+
checked: value,
|
|
75
|
+
onChange: (e) => setConfig(field.key, e.target.checked)
|
|
76
|
+
}
|
|
77
|
+
),
|
|
78
|
+
/* @__PURE__ */ jsx("label", { className: "lk-control-label", htmlFor: `lk-ctrl-${field.key}`, children: field.label })
|
|
79
|
+
] });
|
|
80
|
+
}
|
|
81
|
+
function SelectRow({
|
|
82
|
+
field,
|
|
83
|
+
config,
|
|
84
|
+
setConfig
|
|
85
|
+
}) {
|
|
86
|
+
const value = config[field.key] ?? field.default;
|
|
87
|
+
return /* @__PURE__ */ jsxs("div", { className: "lk-control-row", children: [
|
|
88
|
+
/* @__PURE__ */ jsx("label", { className: "lk-control-label", htmlFor: `lk-ctrl-${field.key}`, children: field.label }),
|
|
89
|
+
/* @__PURE__ */ jsx(
|
|
90
|
+
"select",
|
|
91
|
+
{
|
|
92
|
+
id: `lk-ctrl-${field.key}`,
|
|
93
|
+
className: "lk-control-input",
|
|
94
|
+
value,
|
|
95
|
+
onChange: (e) => setConfig(field.key, e.target.value),
|
|
96
|
+
children: field.options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
|
|
97
|
+
}
|
|
98
|
+
)
|
|
99
|
+
] });
|
|
100
|
+
}
|
|
101
|
+
function NumberRow({
|
|
102
|
+
field,
|
|
103
|
+
config,
|
|
104
|
+
setConfig
|
|
105
|
+
}) {
|
|
106
|
+
const value = config[field.key] ?? field.default;
|
|
107
|
+
return /* @__PURE__ */ jsxs("div", { className: "lk-control-row", children: [
|
|
108
|
+
/* @__PURE__ */ jsx("label", { className: "lk-control-label", htmlFor: `lk-ctrl-${field.key}`, children: field.label }),
|
|
109
|
+
/* @__PURE__ */ jsx(
|
|
110
|
+
"input",
|
|
111
|
+
{
|
|
112
|
+
id: `lk-ctrl-${field.key}`,
|
|
113
|
+
className: "lk-control-input",
|
|
114
|
+
type: "number",
|
|
115
|
+
value,
|
|
116
|
+
min: field.min,
|
|
117
|
+
max: field.max,
|
|
118
|
+
step: field.step,
|
|
119
|
+
onChange: (e) => setConfig(field.key, Number(e.target.value)),
|
|
120
|
+
onBlur: (e) => {
|
|
121
|
+
const n = Number(e.target.value);
|
|
122
|
+
if (!Number.isFinite(n)) return;
|
|
123
|
+
let clamped = n;
|
|
124
|
+
if (field.min !== void 0 && clamped < field.min) clamped = field.min;
|
|
125
|
+
if (field.max !== void 0 && clamped > field.max) clamped = field.max;
|
|
126
|
+
if (clamped !== n) setConfig(field.key, clamped);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
)
|
|
130
|
+
] });
|
|
131
|
+
}
|
|
132
|
+
function TextRow({
|
|
133
|
+
field,
|
|
134
|
+
config,
|
|
135
|
+
setConfig
|
|
136
|
+
}) {
|
|
137
|
+
const debounceMs = field.debounceMs ?? 150;
|
|
138
|
+
const initial = config[field.key] ?? field.default;
|
|
139
|
+
const [local, setLocal] = useState(initial);
|
|
140
|
+
const timer = useRef(null);
|
|
141
|
+
const lastExternal = useRef(initial);
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
const ext = config[field.key] ?? field.default;
|
|
144
|
+
if (ext !== lastExternal.current) {
|
|
145
|
+
lastExternal.current = ext;
|
|
146
|
+
setLocal(ext);
|
|
147
|
+
}
|
|
148
|
+
}, [config, field.key, field.default]);
|
|
149
|
+
return /* @__PURE__ */ jsxs("div", { className: "lk-control-row", children: [
|
|
150
|
+
/* @__PURE__ */ jsx("label", { className: "lk-control-label", htmlFor: `lk-ctrl-${field.key}`, children: field.label }),
|
|
151
|
+
/* @__PURE__ */ jsx(
|
|
152
|
+
"input",
|
|
153
|
+
{
|
|
154
|
+
id: `lk-ctrl-${field.key}`,
|
|
155
|
+
className: "lk-control-input",
|
|
156
|
+
type: "text",
|
|
157
|
+
value: local,
|
|
158
|
+
placeholder: field.placeholder,
|
|
159
|
+
maxLength: field.maxLength,
|
|
160
|
+
onChange: (e) => {
|
|
161
|
+
const next = e.target.value;
|
|
162
|
+
setLocal(next);
|
|
163
|
+
if (timer.current) clearTimeout(timer.current);
|
|
164
|
+
if (debounceMs === 0) {
|
|
165
|
+
lastExternal.current = next;
|
|
166
|
+
setConfig(field.key, next);
|
|
167
|
+
} else {
|
|
168
|
+
timer.current = setTimeout(() => {
|
|
169
|
+
lastExternal.current = next;
|
|
170
|
+
setConfig(field.key, next);
|
|
171
|
+
}, debounceMs);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
] });
|
|
177
|
+
}
|
|
178
|
+
function ColorRow({
|
|
179
|
+
field,
|
|
180
|
+
config,
|
|
181
|
+
setConfig
|
|
182
|
+
}) {
|
|
183
|
+
const value = config[field.key] ?? field.default;
|
|
184
|
+
return /* @__PURE__ */ jsxs("div", { className: "lk-control-row lk-control-row--inline", children: [
|
|
185
|
+
/* @__PURE__ */ jsx("label", { className: "lk-control-label", htmlFor: `lk-ctrl-${field.key}`, children: field.label }),
|
|
186
|
+
/* @__PURE__ */ jsx(
|
|
187
|
+
"input",
|
|
188
|
+
{
|
|
189
|
+
id: `lk-ctrl-${field.key}`,
|
|
190
|
+
className: "lk-control-input lk-control-input--color",
|
|
191
|
+
type: "color",
|
|
192
|
+
value,
|
|
193
|
+
onChange: (e) => setConfig(field.key, e.target.value)
|
|
194
|
+
}
|
|
195
|
+
)
|
|
196
|
+
] });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export { ControlPanel };
|
|
200
|
+
//# sourceMappingURL=chunk-VLAHRJOC.js.map
|
|
201
|
+
//# sourceMappingURL=chunk-VLAHRJOC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/controls/ControlPanel.tsx"],"names":[],"mappings":"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","file":"chunk-VLAHRJOC.js","sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport type {\n CheckboxField,\n ColorField,\n ConfigField,\n NumberField,\n SelectField,\n SliderField,\n TextField,\n} from './types';\n\nexport interface ControlPanelProps<TC extends Record<string, unknown>> {\n fields: ConfigField[];\n config: TC;\n setConfig: (key: keyof TC, value: unknown) => void;\n className?: string;\n}\n\nexport function ControlPanel<TC extends Record<string, unknown>>({\n fields,\n config,\n setConfig,\n className,\n}: ControlPanelProps<TC>) {\n return (\n <div className={className ? `lk-control-panel ${className}` : 'lk-control-panel'}>\n {fields.map((field) => (\n <ControlRow key={field.key} field={field} config={config} setConfig={setConfig} />\n ))}\n </div>\n );\n}\n\ninterface ControlRowProps<TC extends Record<string, unknown>> {\n field: ConfigField;\n config: TC;\n setConfig: (key: keyof TC, value: unknown) => void;\n}\n\nfunction ControlRow<TC extends Record<string, unknown>>({\n field,\n config,\n setConfig,\n}: ControlRowProps<TC>) {\n switch (field.type) {\n case 'slider':\n return <SliderRow field={field} config={config} setConfig={setConfig} />;\n case 'checkbox':\n return <CheckboxRow field={field} config={config} setConfig={setConfig} />;\n case 'select':\n return <SelectRow field={field} config={config} setConfig={setConfig} />;\n case 'number':\n return <NumberRow field={field} config={config} setConfig={setConfig} />;\n case 'text':\n return <TextRow field={field} config={config} setConfig={setConfig} />;\n case 'color':\n return <ColorRow field={field} config={config} setConfig={setConfig} />;\n default:\n return null;\n }\n}\n\ntype RowProps<F extends ConfigField, TC extends Record<string, unknown>> = {\n field: F;\n config: TC;\n setConfig: (key: keyof TC, value: unknown) => void;\n};\n\nfunction SliderRow<TC extends Record<string, unknown>>({\n field,\n config,\n setConfig,\n}: RowProps<SliderField, TC>) {\n const value = (config[field.key] as number | undefined) ?? field.default;\n return (\n <div className=\"lk-control-row\">\n <label className=\"lk-control-label\" htmlFor={`lk-ctrl-${field.key}`}>\n {field.label}\n </label>\n <div className=\"lk-control-slider-row\">\n <input\n id={`lk-ctrl-${field.key}`}\n className=\"lk-control-input lk-control-input--range\"\n type=\"range\"\n min={field.min}\n max={field.max}\n step={field.step ?? 1}\n value={value}\n onChange={(e) => setConfig(field.key as keyof TC, Number(e.target.value))}\n />\n <span className=\"lk-control-slider-value\">{value}</span>\n </div>\n </div>\n );\n}\n\nfunction CheckboxRow<TC extends Record<string, unknown>>({\n field,\n config,\n setConfig,\n}: RowProps<CheckboxField, TC>) {\n const value = (config[field.key] as boolean | undefined) ?? field.default;\n return (\n <div className=\"lk-control-row lk-control-row--inline\">\n <input\n id={`lk-ctrl-${field.key}`}\n className=\"lk-control-input lk-control-input--checkbox\"\n type=\"checkbox\"\n checked={value}\n onChange={(e) => setConfig(field.key as keyof TC, e.target.checked)}\n />\n <label className=\"lk-control-label\" htmlFor={`lk-ctrl-${field.key}`}>\n {field.label}\n </label>\n </div>\n );\n}\n\nfunction SelectRow<TC extends Record<string, unknown>>({\n field,\n config,\n setConfig,\n}: RowProps<SelectField, TC>) {\n const value = (config[field.key] as string | undefined) ?? field.default;\n return (\n <div className=\"lk-control-row\">\n <label className=\"lk-control-label\" htmlFor={`lk-ctrl-${field.key}`}>\n {field.label}\n </label>\n <select\n id={`lk-ctrl-${field.key}`}\n className=\"lk-control-input\"\n value={value}\n onChange={(e) => setConfig(field.key as keyof TC, e.target.value)}\n >\n {field.options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n </div>\n );\n}\n\nfunction NumberRow<TC extends Record<string, unknown>>({\n field,\n config,\n setConfig,\n}: RowProps<NumberField, TC>) {\n const value = (config[field.key] as number | undefined) ?? field.default;\n return (\n <div className=\"lk-control-row\">\n <label className=\"lk-control-label\" htmlFor={`lk-ctrl-${field.key}`}>\n {field.label}\n </label>\n <input\n id={`lk-ctrl-${field.key}`}\n className=\"lk-control-input\"\n type=\"number\"\n value={value}\n min={field.min}\n max={field.max}\n step={field.step}\n onChange={(e) => setConfig(field.key as keyof TC, Number(e.target.value))}\n onBlur={(e) => {\n const n = Number(e.target.value);\n if (!Number.isFinite(n)) return;\n let clamped = n;\n if (field.min !== undefined && clamped < field.min) clamped = field.min;\n if (field.max !== undefined && clamped > field.max) clamped = field.max;\n if (clamped !== n) setConfig(field.key as keyof TC, clamped);\n }}\n />\n </div>\n );\n}\n\nfunction TextRow<TC extends Record<string, unknown>>({\n field,\n config,\n setConfig,\n}: RowProps<TextField, TC>) {\n const debounceMs = field.debounceMs ?? 150;\n const initial = (config[field.key] as string | undefined) ?? field.default;\n const [local, setLocal] = useState(initial);\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const lastExternal = useRef(initial);\n\n useEffect(() => {\n const ext = (config[field.key] as string | undefined) ?? field.default;\n if (ext !== lastExternal.current) {\n lastExternal.current = ext;\n setLocal(ext);\n }\n }, [config, field.key, field.default]);\n\n return (\n <div className=\"lk-control-row\">\n <label className=\"lk-control-label\" htmlFor={`lk-ctrl-${field.key}`}>\n {field.label}\n </label>\n <input\n id={`lk-ctrl-${field.key}`}\n className=\"lk-control-input\"\n type=\"text\"\n value={local}\n placeholder={field.placeholder}\n maxLength={field.maxLength}\n onChange={(e) => {\n const next = e.target.value;\n setLocal(next);\n if (timer.current) clearTimeout(timer.current);\n if (debounceMs === 0) {\n lastExternal.current = next;\n setConfig(field.key as keyof TC, next);\n } else {\n timer.current = setTimeout(() => {\n lastExternal.current = next;\n setConfig(field.key as keyof TC, next);\n }, debounceMs);\n }\n }}\n />\n </div>\n );\n}\n\nfunction ColorRow<TC extends Record<string, unknown>>({\n field,\n config,\n setConfig,\n}: RowProps<ColorField, TC>) {\n const value = (config[field.key] as string | undefined) ?? field.default;\n return (\n <div className=\"lk-control-row lk-control-row--inline\">\n <label className=\"lk-control-label\" htmlFor={`lk-ctrl-${field.key}`}>\n {field.label}\n </label>\n <input\n id={`lk-ctrl-${field.key}`}\n className=\"lk-control-input lk-control-input--color\"\n type=\"color\"\n value={value}\n onChange={(e) => setConfig(field.key as keyof TC, e.target.value)}\n />\n </div>\n );\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { C as ConfigField } from '../_dts/types-Si4Fw-1F.js';
|
|
3
|
+
export { a as CheckboxField, b as ColorField, d as ConfigFieldType, N as NumberField, S as SelectField, e as SelectOption, f as SliderField, T as TextField } from '../_dts/types-Si4Fw-1F.js';
|
|
4
|
+
|
|
5
|
+
interface ControlPanelProps<TC extends Record<string, unknown>> {
|
|
6
|
+
fields: ConfigField[];
|
|
7
|
+
config: TC;
|
|
8
|
+
setConfig: (key: keyof TC, value: unknown) => void;
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
declare function ControlPanel<TC extends Record<string, unknown>>({ fields, config, setConfig, className, }: ControlPanelProps<TC>): react_jsx_runtime.JSX.Element;
|
|
12
|
+
|
|
13
|
+
export { ConfigField, ControlPanel };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|