@weasel-js/labkit 1.0.3 → 1.0.4
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/dist/_dts/{DrawCommand-uKHt4Vul.d.ts → DrawCommand-BkZztJsW.d.ts} +3 -1
- package/dist/_dts/{useExperimentState-vrttakTt.d.ts → useExperimentState-D7EQnnwJ.d.ts} +4 -0
- package/dist/{chunk-N5KTQKQA.js → chunk-BAPZPDDA.js} +628 -588
- package/dist/chunk-BAPZPDDA.js.map +1 -0
- package/dist/{chunk-73PXCRCR.js → chunk-DEWXYFEU.js} +6 -6
- package/dist/chunk-DEWXYFEU.js.map +1 -0
- package/dist/{chunk-3WPOGKUP.js → chunk-DWV7SFKR.js} +518 -217
- package/dist/chunk-DWV7SFKR.js.map +1 -0
- package/dist/{chunk-T7OKNJTY.js → chunk-KSTEW2AF.js} +18 -2
- package/dist/chunk-KSTEW2AF.js.map +1 -0
- package/dist/index.d.ts +64 -15
- package/dist/index.js +133 -24
- package/dist/index.js.map +1 -1
- package/dist/passthrough/weasel-canvas.d.ts +1 -1
- package/dist/passthrough/weasel-canvas.js +1 -1
- package/dist/passthrough/weasel-ui.d.ts +1 -1
- package/dist/passthrough/weasel-ui.js +2 -2
- package/dist/state/index.d.ts +4 -4
- package/dist/state/index.js +2 -2
- package/dist/styles.css +163 -6
- package/dist/ui/layers/index.js +3 -3
- package/package.json +2 -1
- package/src/lab/Lab.tsx +14 -2
- package/src/lab/LabContext.ts +1 -0
- package/src/lab/WorkspaceGrid.less +37 -5
- package/src/lab/WorkspaceGrid.stories.tsx +16 -0
- package/src/lab/WorkspaceGrid.test.tsx +84 -10
- package/src/lab/WorkspaceGrid.tsx +188 -14
- package/src/lab/index.ts +0 -2
- package/src/state/helpers.ts +2 -2
- package/src/state/store.test.ts +2 -1
- package/src/state/store.ts +19 -0
- package/src/state/types.ts +3 -0
- package/src/test-setup.ts +19 -0
- package/src/workspace/Workspace.stories.tsx +1 -0
- package/src/workspace/Workspace.test.tsx +1 -0
- package/src/workspace/index.ts +1 -0
- package/src/workspace/workspaceOps.test.ts +34 -1
- package/src/workspace/workspaceOps.ts +16 -0
- package/dist/chunk-3WPOGKUP.js.map +0 -1
- package/dist/chunk-73PXCRCR.js.map +0 -1
- package/dist/chunk-N5KTQKQA.js.map +0 -1
- package/dist/chunk-T7OKNJTY.js.map +0 -1
- package/src/lab/gridDims.test.ts +0 -35
- package/src/lab/gridDims.ts +0 -13
|
@@ -183,6 +183,16 @@ function createLabStore(options) {
|
|
|
183
183
|
hydratedSnapshots = [];
|
|
184
184
|
}
|
|
185
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
|
+
}
|
|
186
196
|
const modeRaw = options.storage.read(labStorageKey(options.storageKey, "theme"));
|
|
187
197
|
const stored = modeRaw === "interstellar" ? "dark" : modeRaw;
|
|
188
198
|
let hydratedMode;
|
|
@@ -195,6 +205,7 @@ function createLabStore(options) {
|
|
|
195
205
|
workspaces: hydratedWorkspaces,
|
|
196
206
|
savedSnapshots: hydratedSnapshots,
|
|
197
207
|
mode: hydratedMode,
|
|
208
|
+
layout: hydratedLayout,
|
|
198
209
|
addWorkspace: (record) => {
|
|
199
210
|
set((s) => ({
|
|
200
211
|
workspaces: [...s.workspaces, { ...record, undoStack: emptyUndoStack() }]
|
|
@@ -304,6 +315,10 @@ function createLabStore(options) {
|
|
|
304
315
|
setMode: (mode) => {
|
|
305
316
|
set({ mode });
|
|
306
317
|
scheduleFlush();
|
|
318
|
+
},
|
|
319
|
+
setLayout: (layout) => {
|
|
320
|
+
set({ layout });
|
|
321
|
+
scheduleFlush();
|
|
307
322
|
}
|
|
308
323
|
}));
|
|
309
324
|
function scheduleFlush() {
|
|
@@ -319,6 +334,7 @@ function createLabStore(options) {
|
|
|
319
334
|
JSON.stringify(s.savedSnapshots)
|
|
320
335
|
);
|
|
321
336
|
options.storage.write(labStorageKey(options.storageKey, "theme"), s.mode);
|
|
337
|
+
options.storage.write(labStorageKey(options.storageKey, "layout"), JSON.stringify(s.layout));
|
|
322
338
|
flushTimer = null;
|
|
323
339
|
}, 300);
|
|
324
340
|
}
|
|
@@ -347,5 +363,5 @@ function useExperimentState() {
|
|
|
347
363
|
}
|
|
348
364
|
|
|
349
365
|
export { LabStoreContext, LabStoreProvider, WorkspaceIdContext, WorkspaceIdProvider, createLabStore, createMemoryAdapter, decodeUrlHash, deserializeWorkspaces, emptyUndoStack, encodeUrlHash, labStorageKey, localStorageAdapter, noneAdapter, serializeWorkspaces, sessionStorageAdapter, urlHashAdapter, useExperimentState, useLabStore, useWorkspaceId };
|
|
350
|
-
//# sourceMappingURL=chunk-
|
|
351
|
-
//# sourceMappingURL=chunk-
|
|
366
|
+
//# sourceMappingURL=chunk-KSTEW2AF.js.map
|
|
367
|
+
//# sourceMappingURL=chunk-KSTEW2AF.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-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"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -7,8 +7,8 @@ export { C as CanvasCapability, a as CanvasLayer, D as DragDropCapability, b as
|
|
|
7
7
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
8
8
|
import * as react from 'react';
|
|
9
9
|
import { ReactNode } from 'react';
|
|
10
|
-
import { S as StorageAdapter, o as LabMode, W as WorkspaceRecord, e as SavedSnapshot } from './_dts/useExperimentState-
|
|
11
|
-
export { C as CreateLabStoreOptions, E as ExperimentStateHandle, b as LabStoreContext, c as LabStoreProvider, d as LabStoreState, U as UndoStack, f as WorkspaceIdContext, g as WorkspaceIdProvider, i as createMemoryAdapter, l as localStorageAdapter, n as noneAdapter, s as sessionStorageAdapter, u as urlHashAdapter, j as useExperimentState, k as useLabStore, m as useWorkspaceId } from './_dts/useExperimentState-
|
|
10
|
+
import { S as StorageAdapter, o as LabMode, W as WorkspaceRecord, e as SavedSnapshot } from './_dts/useExperimentState-D7EQnnwJ.js';
|
|
11
|
+
export { C as CreateLabStoreOptions, E as ExperimentStateHandle, b as LabStoreContext, c as LabStoreProvider, d as LabStoreState, U as UndoStack, f as WorkspaceIdContext, g as WorkspaceIdProvider, i as createMemoryAdapter, l as localStorageAdapter, n as noneAdapter, s as sessionStorageAdapter, u as urlHashAdapter, j as useExperimentState, k as useLabStore, m as useWorkspaceId } from './_dts/useExperimentState-D7EQnnwJ.js';
|
|
12
12
|
export { LayerList, LayerListProps } from './layers/index.js';
|
|
13
13
|
export { FpsMeter, ScaleIndicator, ScaleIndicatorProps, Sidebar, SidebarProps, StatusBar, StatusBarProps, Toolbar, ToolbarProps } from './primitives/index.js';
|
|
14
14
|
export { LayerStack, LayerStackItem, LayerStackProps } from './ui/layers/index.js';
|
|
@@ -41,14 +41,6 @@ interface ValidationResult {
|
|
|
41
41
|
* fields whose own constraints do not hold. */
|
|
42
42
|
declare function validateConfigSchema(fields: ConfigField[]): ValidationResult;
|
|
43
43
|
|
|
44
|
-
/** A grid's column and row counts. */
|
|
45
|
-
interface GridDims {
|
|
46
|
-
cols: number;
|
|
47
|
-
rows: number;
|
|
48
|
-
}
|
|
49
|
-
/** The most nearly square grid that fits `count` items. */
|
|
50
|
-
declare function gridDims(count: number): GridDims;
|
|
51
|
-
|
|
52
44
|
/** Props for `<Lab>`. */
|
|
53
45
|
interface LabProps {
|
|
54
46
|
instruments: Instrument[];
|
|
@@ -80,6 +72,7 @@ interface LabContextValue {
|
|
|
80
72
|
cloneWorkspace: (id: string) => void;
|
|
81
73
|
closeWorkspace: (id: string) => void;
|
|
82
74
|
resetWorkspace: (id: string) => void;
|
|
75
|
+
reorderWorkspaces: (ids: readonly string[]) => void;
|
|
83
76
|
savedSnapshots: SavedSnapshot[];
|
|
84
77
|
saveSnapshot: (workspaceId: string, name?: string) => void;
|
|
85
78
|
loadSnapshot: (workspaceId: string, snapshotId: string) => void;
|
|
@@ -108,12 +101,62 @@ interface LabShellProps {
|
|
|
108
101
|
* the workspace runtime is wanted too. */
|
|
109
102
|
declare function LabShell({ title, children, header, footer, mode }: LabShellProps): react_jsx_runtime.JSX.Element;
|
|
110
103
|
|
|
111
|
-
/**
|
|
104
|
+
/** A tile's persisted extent, keyed by the id its caller gave it. Grid resizes
|
|
105
|
+
* write `span`; `size` is here because a strategy swap would write that. */
|
|
106
|
+
type WorkspaceLayout = Record<string, {
|
|
107
|
+
size?: {
|
|
108
|
+
w?: number;
|
|
109
|
+
h?: number;
|
|
110
|
+
};
|
|
111
|
+
span?: {
|
|
112
|
+
cols?: number;
|
|
113
|
+
rows?: number;
|
|
114
|
+
};
|
|
115
|
+
}>;
|
|
112
116
|
interface WorkspaceGridProps {
|
|
113
117
|
children: ReactNode;
|
|
118
|
+
/**
|
|
119
|
+
* Stable id per child, positionally matched. Supply these whenever a tile
|
|
120
|
+
* can be closed from the middle: without them a tile is identified by its
|
|
121
|
+
* position, so closing one shifts every id after it and the panes inherit
|
|
122
|
+
* each other's dragged extents. `layout` and `reorderable` both key off
|
|
123
|
+
* these, so neither means much without them.
|
|
124
|
+
*/
|
|
125
|
+
ids?: readonly string[];
|
|
126
|
+
/** Draggable seams between tiles. Off by default — an even tiling is the
|
|
127
|
+
* behavior every existing caller has. */
|
|
128
|
+
resizable?: boolean;
|
|
129
|
+
/** Let a tile be dragged to a new position. Off by default. The grid never
|
|
130
|
+
* reorders `children` itself: it reports the order a drop would produce and
|
|
131
|
+
* the caller commits it. */
|
|
132
|
+
reorderable?: boolean;
|
|
133
|
+
/** The full id list a drop would produce, in its new order. */
|
|
134
|
+
onReorder?: (ids: string[]) => void;
|
|
135
|
+
/** Extents from a previous session, applied to tiles as they register. */
|
|
136
|
+
layout?: WorkspaceLayout;
|
|
137
|
+
/** Fires when a tile's extent changes. Persist it and hand it back as
|
|
138
|
+
* `layout` to make a resize survive a reload. */
|
|
139
|
+
onLayoutChange?: (layout: WorkspaceLayout) => void;
|
|
140
|
+
gap?: number;
|
|
141
|
+
padding?: number;
|
|
142
|
+
/**
|
|
143
|
+
* Fixed tiling extent. Omit in an app — the grid measures its own box. Supply
|
|
144
|
+
* it where nothing measures, notably jsdom: at a zero measurement the grid
|
|
145
|
+
* renders no tiles at all.
|
|
146
|
+
*/
|
|
147
|
+
viewport?: {
|
|
148
|
+
w: number;
|
|
149
|
+
h: number;
|
|
150
|
+
};
|
|
114
151
|
}
|
|
115
|
-
/**
|
|
116
|
-
|
|
152
|
+
/**
|
|
153
|
+
* Auto-balanced tiling of workspaces, `ceil(sqrt(n))` columns wide.
|
|
154
|
+
*
|
|
155
|
+
* Tiles are absolutely positioned at the rects `gridStrategy` computes, not
|
|
156
|
+
* laid out by CSS — `windease/styles.css` (folded into
|
|
157
|
+
* `@weasel-js/labkit/styles.css`) carries the rules that positioning depends on.
|
|
158
|
+
*/
|
|
159
|
+
declare function WorkspaceGrid({ children, ids, resizable, reorderable, onReorder, layout, onLayoutChange, gap, padding, viewport, }: WorkspaceGridProps): react_jsx_runtime.JSX.Element;
|
|
117
160
|
|
|
118
161
|
type TokenValue = string | number | readonly (string | number)[];
|
|
119
162
|
/**
|
|
@@ -545,6 +588,12 @@ declare function closeWorkspace(workspaces: WorkspaceRecord[], id: string): Work
|
|
|
545
588
|
/** Return a workspace to its instrument's defaults, keeping its id and its
|
|
546
589
|
* place in the list. */
|
|
547
590
|
declare function resetWorkspace(workspaces: WorkspaceRecord[], id: string, instruments: Instrument[]): WorkspaceRecord[];
|
|
591
|
+
/**
|
|
592
|
+
* Reorder to match `ids`. Ids the list doesn't mention keep their relative
|
|
593
|
+
* order at the end, and ids it names that no longer exist are dropped — a
|
|
594
|
+
* reorder that raced a close should not resurrect the closed workspace.
|
|
595
|
+
*/
|
|
596
|
+
declare function reorderWorkspaces(workspaces: WorkspaceRecord[], ids: readonly string[]): WorkspaceRecord[];
|
|
548
597
|
|
|
549
|
-
export { CheckboxRow, ColorRow, ConfigField, CurveField, DefaultSidebar, DefaultStatusBar, DefaultToolbar, EffectCard, EffectCardList, Instrument, Lab, LabContext, LabMode, LabShell, NumberRow, PropertyGroup, PropertyList, PropertyPanel, PropertyRow, SavedSnapshot, SelectRow, SliderRow, StorageAdapter, Subpanel, TextRow, ToggleRow, Workspace, WorkspaceChrome, WorkspaceGrid, WorkspaceRecord, addWorkspace, cloneWorkspace, closeWorkspace, defineInstrument, detectCapabilities,
|
|
550
|
-
export type { CapabilityFlags, CheckboxRowProps, ColorRowProps, CurveFieldProps, CurveMark, DefaultSidebarProps, DefaultStatusBarProps, DefaultToolbarProps, EffectCardListItem, EffectCardListProps, EffectCardProps,
|
|
598
|
+
export { CheckboxRow, ColorRow, ConfigField, CurveField, DefaultSidebar, DefaultStatusBar, DefaultToolbar, EffectCard, EffectCardList, Instrument, Lab, LabContext, LabMode, LabShell, NumberRow, PropertyGroup, PropertyList, PropertyPanel, PropertyRow, SavedSnapshot, SelectRow, SliderRow, StorageAdapter, Subpanel, TextRow, ToggleRow, Workspace, WorkspaceChrome, WorkspaceGrid, WorkspaceRecord, addWorkspace, cloneWorkspace, closeWorkspace, defineInstrument, detectCapabilities, interstellarTheme, reorderWorkspaces, resetWorkspace, useLabContext, validateConfigSchema };
|
|
599
|
+
export type { CapabilityFlags, CheckboxRowProps, ColorRowProps, CurveFieldProps, CurveMark, DefaultSidebarProps, DefaultStatusBarProps, DefaultToolbarProps, EffectCardListItem, EffectCardListProps, EffectCardProps, LabContextValue, LabProps, LabShellProps, NumberRowProps, PropertyGroupProps, PropertyListPack, PropertyListProps, PropertyPanelProps, PropertyRowLayout, PropertyRowProps, PropertyRowVariant, SelectRowProps, SidebarSlot, SliderRowProps, StatusBarSlot, SubpanelProps, TextRowProps, ToggleRowProps, ToolbarSlot, ValidationResult, WorkspaceChromeProps, WorkspaceGridProps, WorkspaceProps, WorkspaceSidebarContext, WorkspaceStatusBarContext, WorkspaceToolbarContext };
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
export { LayerStack } from './chunk-
|
|
2
|
-
import { p, F } from './chunk-
|
|
3
|
-
import './chunk-
|
|
1
|
+
export { LayerStack } from './chunk-DEWXYFEU.js';
|
|
2
|
+
import { p, F } from './chunk-BAPZPDDA.js';
|
|
3
|
+
import './chunk-DWV7SFKR.js';
|
|
4
4
|
import { Sidebar, StatusBar, Toolbar } from './chunk-PMAU3SEE.js';
|
|
5
5
|
export { FpsMeter, ScaleIndicator, Sidebar, StatusBar, Toolbar } from './chunk-PMAU3SEE.js';
|
|
6
|
-
import { LabStoreContext, createLabStore, noneAdapter } from './chunk-
|
|
7
|
-
export { LabStoreContext, LabStoreProvider, WorkspaceIdContext, WorkspaceIdProvider, createMemoryAdapter, localStorageAdapter, noneAdapter, sessionStorageAdapter, urlHashAdapter, useExperimentState, useLabStore, useWorkspaceId } from './chunk-
|
|
6
|
+
import { LabStoreContext, createLabStore, noneAdapter } from './chunk-KSTEW2AF.js';
|
|
7
|
+
export { LabStoreContext, LabStoreProvider, WorkspaceIdContext, WorkspaceIdProvider, createMemoryAdapter, localStorageAdapter, noneAdapter, sessionStorageAdapter, urlHashAdapter, useExperimentState, useLabStore, useWorkspaceId } from './chunk-KSTEW2AF.js';
|
|
8
8
|
import { ControlPanel } from './chunk-5R2ATYPJ.js';
|
|
9
9
|
export { ControlPanel } from './chunk-5R2ATYPJ.js';
|
|
10
10
|
import { CanvasStack, screenToWorld } from './chunk-574LJAV4.js';
|
|
@@ -14,10 +14,12 @@ import { LayerList } from './chunk-SFL7NFKN.js';
|
|
|
14
14
|
export { LayerList } from './chunk-SFL7NFKN.js';
|
|
15
15
|
import { createEventBus, redo, undo, pushSnapshot } from './chunk-RL2LOLNI.js';
|
|
16
16
|
export { clearUndo, createEventBus, emptyStack, pushSnapshot, redo, undo } from './chunk-RL2LOLNI.js';
|
|
17
|
-
import { createContext, useContext, useMemo, useRef, useState, Children, useEffect, useCallback
|
|
17
|
+
import { createContext, useContext, useMemo, useRef, useState, Children, useLayoutEffect, useEffect, useCallback } from 'react';
|
|
18
18
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
19
19
|
import { useStore } from 'zustand/react';
|
|
20
20
|
import { createPortal } from 'react-dom';
|
|
21
|
+
import { asNodeId, Store, createNode, gridStrategy } from 'windease';
|
|
22
|
+
import { DragHandle, Provider, StrategyRegistryProvider, DragProvider, Container } from 'windease/react';
|
|
21
23
|
|
|
22
24
|
// src/instrument/capabilityDetector.ts
|
|
23
25
|
function detectCapabilities(instrument) {
|
|
@@ -88,14 +90,6 @@ function validateConfigSchema(fields) {
|
|
|
88
90
|
return { valid: errors.length === 0, errors };
|
|
89
91
|
}
|
|
90
92
|
|
|
91
|
-
// src/lab/gridDims.ts
|
|
92
|
-
function gridDims(count) {
|
|
93
|
-
if (count <= 1) return { cols: 1, rows: 1 };
|
|
94
|
-
const cols = Math.ceil(Math.sqrt(count));
|
|
95
|
-
const rows = Math.ceil(count / cols);
|
|
96
|
-
return { cols, rows };
|
|
97
|
-
}
|
|
98
|
-
|
|
99
93
|
// ../theme/dist/chunk-STFENPNX.js
|
|
100
94
|
var THEME_SOURCES = {
|
|
101
95
|
"weasel": {
|
|
@@ -1390,6 +1384,13 @@ function resetWorkspace(workspaces, id, instruments) {
|
|
|
1390
1384
|
};
|
|
1391
1385
|
return [...workspaces.slice(0, idx), reset, ...workspaces.slice(idx + 1)];
|
|
1392
1386
|
}
|
|
1387
|
+
function reorderWorkspaces(workspaces, ids) {
|
|
1388
|
+
const byId = new Map(workspaces.map((w) => [w.id, w]));
|
|
1389
|
+
const named = ids.map((id) => byId.get(id)).filter((w) => w !== void 0);
|
|
1390
|
+
const seen = new Set(named.map((w) => w.id));
|
|
1391
|
+
const rest = workspaces.filter((w) => !seen.has(w.id));
|
|
1392
|
+
return [...named, ...rest];
|
|
1393
|
+
}
|
|
1393
1394
|
function query() {
|
|
1394
1395
|
if (typeof window === "undefined" || !window.matchMedia) return null;
|
|
1395
1396
|
return window.matchMedia("(prefers-color-scheme: light)");
|
|
@@ -1421,14 +1422,107 @@ function LabShell({ title, children, header, footer, mode = "auto" }) {
|
|
|
1421
1422
|
] });
|
|
1422
1423
|
return outer ? shell : /* @__PURE__ */ jsx(ThemeProvider, { theme: interstellarTheme, mode: resolved, children: shell });
|
|
1423
1424
|
}
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
};
|
|
1431
|
-
|
|
1425
|
+
var ZONE_ID = asNodeId("lk-workspaces");
|
|
1426
|
+
var STRATEGIES = { grid: gridStrategy };
|
|
1427
|
+
var KIND = "workspace";
|
|
1428
|
+
function extentOf(store, id) {
|
|
1429
|
+
const p2 = store.getNode(id)?.membership?.placement;
|
|
1430
|
+
if (!p2) return null;
|
|
1431
|
+
const out = {};
|
|
1432
|
+
if (p2.size) out.size = p2.size;
|
|
1433
|
+
if (p2.span) out.span = p2.span;
|
|
1434
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
1435
|
+
}
|
|
1436
|
+
function WorkspaceGrid({
|
|
1437
|
+
children,
|
|
1438
|
+
ids,
|
|
1439
|
+
resizable = false,
|
|
1440
|
+
reorderable = false,
|
|
1441
|
+
onReorder,
|
|
1442
|
+
layout,
|
|
1443
|
+
onLayoutChange,
|
|
1444
|
+
gap = 12,
|
|
1445
|
+
padding = 0,
|
|
1446
|
+
viewport
|
|
1447
|
+
}) {
|
|
1448
|
+
const items = Children.toArray(children);
|
|
1449
|
+
const idKey = ids ? ids.join(",") : `#${items.length}`;
|
|
1450
|
+
const nodeIds = useMemo(() => items.map((_, i) => asNodeId(ids?.[i] ?? `lk-ws-${i}`)), [idKey]);
|
|
1451
|
+
const layoutRef = useRef(layout);
|
|
1452
|
+
layoutRef.current = layout;
|
|
1453
|
+
const onLayoutChangeRef = useRef(onLayoutChange);
|
|
1454
|
+
onLayoutChangeRef.current = onLayoutChange;
|
|
1455
|
+
const storeRef = useRef(null);
|
|
1456
|
+
if (storeRef.current === null) {
|
|
1457
|
+
const store2 = new Store();
|
|
1458
|
+
store2.registerNode(
|
|
1459
|
+
createNode({
|
|
1460
|
+
kind: "zone",
|
|
1461
|
+
id: ZONE_ID,
|
|
1462
|
+
container: { strategyId: "grid", config: { resizable, gap, padding } }
|
|
1463
|
+
})
|
|
1464
|
+
);
|
|
1465
|
+
storeRef.current = store2;
|
|
1466
|
+
}
|
|
1467
|
+
const store = storeRef.current;
|
|
1468
|
+
useLayoutEffect(() => {
|
|
1469
|
+
store.updateContainerConfig(ZONE_ID, { resizable, gap, padding });
|
|
1470
|
+
}, [store, resizable, gap, padding]);
|
|
1471
|
+
useLayoutEffect(() => {
|
|
1472
|
+
const present = new Set(store.getContainerView(ZONE_ID)?.childOrder ?? []);
|
|
1473
|
+
const wanted = new Set(nodeIds);
|
|
1474
|
+
for (const id of present) {
|
|
1475
|
+
if (!wanted.has(id)) store.unregisterNode(id);
|
|
1476
|
+
}
|
|
1477
|
+
for (const id of nodeIds) {
|
|
1478
|
+
if (present.has(id)) continue;
|
|
1479
|
+
store.registerNode(createNode({ kind: KIND, id, parentId: ZONE_ID, focus: true }));
|
|
1480
|
+
store.showNode(id);
|
|
1481
|
+
const saved = layoutRef.current?.[id];
|
|
1482
|
+
if (saved) store.patchPlacement(id, saved);
|
|
1483
|
+
}
|
|
1484
|
+
store.setChildOrder(ZONE_ID, [...nodeIds]);
|
|
1485
|
+
}, [store, nodeIds]);
|
|
1486
|
+
useEffect(() => {
|
|
1487
|
+
if (!onLayoutChange) return;
|
|
1488
|
+
return store.events.on("node.placementChanged", () => {
|
|
1489
|
+
const next = {};
|
|
1490
|
+
for (const child of store.getChildren(ZONE_ID)) {
|
|
1491
|
+
const extent = extentOf(store, child.id);
|
|
1492
|
+
if (extent) next[child.id] = extent;
|
|
1493
|
+
}
|
|
1494
|
+
onLayoutChangeRef.current?.(next);
|
|
1495
|
+
});
|
|
1496
|
+
}, [store, onLayoutChange]);
|
|
1497
|
+
const commitOrder = useCallback(
|
|
1498
|
+
(nextIds) => onReorder?.(nextIds.map(String)),
|
|
1499
|
+
[onReorder]
|
|
1500
|
+
);
|
|
1501
|
+
const chrome = useMemo(() => {
|
|
1502
|
+
const byId = new Map(nodeIds.map((id, i) => [id, items[i]]));
|
|
1503
|
+
return {
|
|
1504
|
+
[KIND]: ({ node }) => {
|
|
1505
|
+
const content = byId.get(node.id) ?? null;
|
|
1506
|
+
if (!reorderable) return content;
|
|
1507
|
+
return /* @__PURE__ */ jsxs("div", { className: "lk-workspace-tile", children: [
|
|
1508
|
+
/* @__PURE__ */ jsx(DragHandle, { nodeId: node.id, className: "lk-workspace-tile__grip", children: /* @__PURE__ */ jsx("span", { className: "lk-workspace-tile__grip-dots", "aria-hidden": "true" }) }),
|
|
1509
|
+
/* @__PURE__ */ jsx("div", { className: "lk-workspace-tile__body", children: content })
|
|
1510
|
+
] });
|
|
1511
|
+
}
|
|
1512
|
+
};
|
|
1513
|
+
}, [nodeIds, items, reorderable]);
|
|
1514
|
+
const grid = /* @__PURE__ */ jsx(
|
|
1515
|
+
Container,
|
|
1516
|
+
{
|
|
1517
|
+
parentId: ZONE_ID,
|
|
1518
|
+
chrome,
|
|
1519
|
+
className: "lk-workspace-grid windease-zone",
|
|
1520
|
+
affordances: resizable,
|
|
1521
|
+
viewport,
|
|
1522
|
+
onChildOrderChange: reorderable ? commitOrder : void 0
|
|
1523
|
+
}
|
|
1524
|
+
);
|
|
1525
|
+
return /* @__PURE__ */ jsx(Provider, { store, children: /* @__PURE__ */ jsx(StrategyRegistryProvider, { strategies: STRATEGIES, children: reorderable ? /* @__PURE__ */ jsx(DragProvider, { children: grid }) : grid }) });
|
|
1432
1526
|
}
|
|
1433
1527
|
function buildStore(instruments, defaultInstrument, storage, storageKey, initialMode) {
|
|
1434
1528
|
const store = createLabStore({ storageKey, storage, initialMode });
|
|
@@ -1484,6 +1578,7 @@ function Lab({
|
|
|
1484
1578
|
const workspaces = useStore(store, (s) => s.workspaces);
|
|
1485
1579
|
const savedSnapshots = useStore(store, (s) => s.savedSnapshots);
|
|
1486
1580
|
const modeValue = useStore(store, (s) => s.mode);
|
|
1581
|
+
const layout = useStore(store, (s) => s.layout);
|
|
1487
1582
|
const resolvedMode = useResolvedMode(modeValue);
|
|
1488
1583
|
useEffect(() => {
|
|
1489
1584
|
if (mode && mode !== store.getState().mode) {
|
|
@@ -1512,6 +1607,9 @@ function Lab({
|
|
|
1512
1607
|
const next = closeWorkspace(store.getState().workspaces, id);
|
|
1513
1608
|
replaceWorkspaces(next);
|
|
1514
1609
|
},
|
|
1610
|
+
reorderWorkspaces: (ids) => {
|
|
1611
|
+
replaceWorkspaces(reorderWorkspaces(store.getState().workspaces, ids));
|
|
1612
|
+
},
|
|
1515
1613
|
resetWorkspace: (id) => {
|
|
1516
1614
|
const next = resetWorkspace(store.getState().workspaces, id, instruments);
|
|
1517
1615
|
const record = next.find((w) => w.id === id);
|
|
@@ -1547,7 +1645,18 @@ function Lab({
|
|
|
1547
1645
|
mode: resolvedMode,
|
|
1548
1646
|
className: "lk-lab",
|
|
1549
1647
|
style: backdropStyle,
|
|
1550
|
-
children: /* @__PURE__ */ jsx(LabShell, { title: title ?? "Labkit", mode: modeValue, header: children, children: /* @__PURE__ */ jsx(
|
|
1648
|
+
children: /* @__PURE__ */ jsx(LabShell, { title: title ?? "Labkit", mode: modeValue, header: children, children: /* @__PURE__ */ jsx(
|
|
1649
|
+
WorkspaceGrid,
|
|
1650
|
+
{
|
|
1651
|
+
ids: workspaces.map((w) => w.id),
|
|
1652
|
+
resizable: true,
|
|
1653
|
+
reorderable: true,
|
|
1654
|
+
onReorder: (ids) => contextValue.reorderWorkspaces(ids),
|
|
1655
|
+
layout,
|
|
1656
|
+
onLayoutChange: (next) => store.getState().setLayout(next),
|
|
1657
|
+
children: workspaces.map((w) => /* @__PURE__ */ jsx(Workspace, { id: w.id }, w.id))
|
|
1658
|
+
}
|
|
1659
|
+
) })
|
|
1551
1660
|
}
|
|
1552
1661
|
) }) });
|
|
1553
1662
|
}
|
|
@@ -2169,6 +2278,6 @@ function ToggleRow({
|
|
|
2169
2278
|
}) }) });
|
|
2170
2279
|
}
|
|
2171
2280
|
|
|
2172
|
-
export { CheckboxRow, ColorRow, CurveField, DefaultSidebar, DefaultStatusBar, DefaultToolbar, EffectCard, EffectCardList, Lab, LabContext, LabShell, NumberRow, PropertyGroup, PropertyList, PropertyPanel, PropertyRow, SelectRow, SliderRow, Subpanel, TextRow, ToggleRow, Workspace, WorkspaceChrome, WorkspaceGrid, addWorkspace, cloneWorkspace, closeWorkspace, defineInstrument, detectCapabilities,
|
|
2281
|
+
export { CheckboxRow, ColorRow, CurveField, DefaultSidebar, DefaultStatusBar, DefaultToolbar, EffectCard, EffectCardList, Lab, LabContext, LabShell, NumberRow, PropertyGroup, PropertyList, PropertyPanel, PropertyRow, SelectRow, SliderRow, Subpanel, TextRow, ToggleRow, Workspace, WorkspaceChrome, WorkspaceGrid, addWorkspace, cloneWorkspace, closeWorkspace, defineInstrument, detectCapabilities, interstellarTheme, reorderWorkspaces, resetWorkspace, useLabContext, validateConfigSchema };
|
|
2173
2282
|
//# sourceMappingURL=index.js.map
|
|
2174
2283
|
//# sourceMappingURL=index.js.map
|