@weasel-js/labkit 0.7.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +11 -3
- package/dist/_dts/{DrawCommand-CEvS5AdV.d.ts → DrawCommand-BwRCZYuo.d.ts} +106 -52
- package/dist/_dts/{useExperimentState-eSvpwV_P.d.ts → useExperimentState-CJn2hHzd.d.ts} +6 -4
- package/dist/{chunk-SSMHPMDV.js → chunk-53XSBIUK.js} +12 -11
- package/dist/chunk-53XSBIUK.js.map +1 -0
- package/dist/{chunk-BG6NLMOL.js → chunk-FP5LYDVX.js} +1229 -1227
- package/dist/chunk-FP5LYDVX.js.map +1 -0
- package/dist/{chunk-ERZQAU4N.js → chunk-HNXVLKBG.js} +3 -3
- package/dist/{chunk-ERZQAU4N.js.map → chunk-HNXVLKBG.js.map} +1 -1
- package/dist/{chunk-5XDFILHZ.js → chunk-XVBXYKFJ.js} +1675 -670
- package/dist/chunk-XVBXYKFJ.js.map +1 -0
- package/dist/fonts/inter-latin.woff2 +0 -0
- package/dist/fonts/oswald-latin-variable.woff2 +0 -0
- package/dist/index.d.ts +37 -13
- package/dist/index.js +776 -27
- 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 +332 -189
- package/dist/passthrough/weasel-ui.js +2 -2
- package/dist/state/index.d.ts +2 -2
- package/dist/state/index.js +2 -2
- package/dist/styles.css +229 -288
- package/dist/ui/layers/index.js +3 -3
- package/package.json +5 -6
- package/src/canvas/CanvasStack.less +1 -1
- package/src/controls/ControlPanel.less +12 -12
- package/src/dragdrop/DragGhost.less +2 -2
- package/src/dragdrop/Palette.less +6 -6
- package/src/index.ts +2 -0
- package/src/lab/Lab.test.tsx +20 -15
- package/src/lab/Lab.tsx +33 -32
- package/src/lab/LabContext.ts +3 -3
- package/src/lab/LabShell.less +14 -14
- package/src/lab/LabShell.test.tsx +7 -20
- package/src/lab/LabShell.tsx +22 -13
- package/src/lab/WorkspaceGrid.less +1 -1
- package/src/lab/WorkspaceGrid.stories.tsx +4 -4
- package/src/lab/index.ts +1 -1
- package/src/lab/useSystemMode.ts +30 -0
- package/src/layers/LayerList.less +9 -9
- package/src/primitives/FpsMeter.less +5 -5
- package/src/primitives/ScaleIndicator.less +7 -7
- package/src/primitives/Sidebar.less +12 -12
- package/src/primitives/StatusBar.less +10 -10
- package/src/primitives/Toolbar.less +16 -16
- package/src/state/store.test.ts +17 -9
- package/src/state/store.ts +13 -10
- package/src/state/types.ts +5 -2
- package/src/theme/Interstellar.stories.less +20 -20
- package/src/theme/Interstellar.stories.tsx +44 -59
- package/src/theme/base.less +29 -28
- package/src/theme/interstellar.test.ts +30 -0
- package/src/theme/interstellar.tokens.json +57 -0
- package/src/theme/interstellar.ts +9 -0
- package/src/ui/layers/LayerStack.less +21 -21
- package/src/ui/properties/CurveField.less +5 -5
- package/src/ui/properties/EffectCard.tsx +2 -2
- package/src/ui/properties/PropertyGroup.less +6 -6
- package/src/ui/properties/PropertyPanel.less +70 -70
- package/src/ui/properties/SpeechBalloonPanels.stories.tsx +1 -1
- package/src/ui/properties/storyLayouts.tsx +1 -1
- package/src/workspace/Workspace.less +14 -14
- package/src/workspace/Workspace.stories.tsx +2 -2
- package/src/workspace/Workspace.test.tsx +2 -2
- package/dist/chunk-5XDFILHZ.js.map +0 -1
- package/dist/chunk-BG6NLMOL.js.map +0 -1
- package/dist/chunk-SSMHPMDV.js.map +0 -1
- package/dist/theme-interstellar.css +0 -25
- package/dist/theme-light.css +0 -13
- package/src/fonts/oswald-latin-variable.woff2 +0 -0
- package/src/theme/interstellar.less +0 -34
- package/src/theme/light.less +0 -14
- package/src/theme/tokens.less +0 -85
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 orochi235
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -43,14 +43,22 @@ function MyLab() {
|
|
|
43
43
|
}
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
##
|
|
46
|
+
## Theming
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
labkit ships one theme, `interstellar` — a cosmic dark and a warm parchment
|
|
49
|
+
light — as a value, not a stylesheet:
|
|
49
50
|
|
|
50
51
|
```tsx
|
|
51
|
-
|
|
52
|
+
import { interstellarTheme } from '@weasel-js/labkit';
|
|
53
|
+
import { ThemeProvider } from '@weasel-js/theme/react';
|
|
54
|
+
|
|
55
|
+
<ThemeProvider theme={interstellarTheme} mode="dark">…</ThemeProvider>
|
|
52
56
|
```
|
|
53
57
|
|
|
58
|
+
`<Lab>` and `<LabShell>` do this for you; `mode` is `"auto"` (follow the OS),
|
|
59
|
+
`"light"` or `"dark"`. Only `styles.css` needs importing — the token values
|
|
60
|
+
arrive through the provider.
|
|
61
|
+
|
|
54
62
|
## Development
|
|
55
63
|
|
|
56
64
|
```bash
|
|
@@ -172,51 +172,6 @@ interface History {
|
|
|
172
172
|
resumeJournal(journal: Journal): void;
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
/**
|
|
176
|
-
* Pose composition for hierarchical scene graphs.
|
|
177
|
-
*
|
|
178
|
-
* As of the nesting change, `getPose(id)` on adapters returns the
|
|
179
|
-
* **local** pose — relative to the object's direct parent. Anything in the
|
|
180
|
-
* kit that needs to draw, hit-test, snap, or otherwise reason about world
|
|
181
|
-
* coordinates routes through `composeWorldPose`, which walks the parent
|
|
182
|
-
* chain and folds local poses together via a consumer-supplied `compose`.
|
|
183
|
-
*
|
|
184
|
-
* Pose shape is generic, so the compose strategy is too. For the common
|
|
185
|
-
* `{x, y, width, height}` axis-aligned rect, use `composeRectPose` —
|
|
186
|
-
* translation only, child dimensions preserved. Custom pose shapes (paths,
|
|
187
|
-
* matrix transforms) supply their own.
|
|
188
|
-
*
|
|
189
|
-
* The inverse — `rebaseLocalPose` — converts a world-space pose into a
|
|
190
|
-
* local pose under a target parent. Used when reparenting so the visual
|
|
191
|
-
* world position of a child is preserved across the parent change.
|
|
192
|
-
*/
|
|
193
|
-
/**
|
|
194
|
-
* Axis-aligned rectangle pose with optional rotation. The canonical pose
|
|
195
|
-
* shape used by `composeRectPose` and the `unionBounds` helper. Rotation is
|
|
196
|
-
* in radians, pivoted on the unrotated AABB center; absent === 0. Kit-side
|
|
197
|
-
* code that consumes rotation already reads `pose.rotation ?? 0`
|
|
198
|
-
* (`SceneCanvas.defaultDrawOne`, `rotate/handle.ts`, `pathInWorld.ts`), so
|
|
199
|
-
* the slot exists on every default scene whether or not the consumer
|
|
200
|
-
* populates it. Defined here in core/transforms so the compose helpers
|
|
201
|
-
* don't depend on features.
|
|
202
|
-
*/
|
|
203
|
-
interface RectPose {
|
|
204
|
-
x: number;
|
|
205
|
-
y: number;
|
|
206
|
-
width: number;
|
|
207
|
-
height: number;
|
|
208
|
-
/** Rotation in radians around the unrotated AABB center. Absent === 0. */
|
|
209
|
-
rotation?: number;
|
|
210
|
-
}
|
|
211
|
-
/** Consumer's pose-composition strategy for hierarchical scenes. `compose`
|
|
212
|
-
* folds a child's pose (in parent's frame) up to the next frame; `decompose`
|
|
213
|
-
* is its inverse. Default is IDENTITY — an absolute-pose scene where every
|
|
214
|
-
* node already stores world coords (parent is grouping-only, no transform). */
|
|
215
|
-
interface PoseComposition<TPose> {
|
|
216
|
-
compose: (parent: TPose, child: TPose) => TPose;
|
|
217
|
-
decompose: (parent: TPose, world: TPose) => TPose;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
175
|
/** Fill rule used by polygon path hit-testing and `ctx.fill()`. */
|
|
221
176
|
type PathFillRule = 'nonzero' | 'evenodd';
|
|
222
177
|
/**
|
|
@@ -246,6 +201,23 @@ interface RectPath {
|
|
|
246
201
|
/** Canonical path shape — either an axis-aligned rect (fast path) or a polygon command stream. */
|
|
247
202
|
type Path = PolygonPath | RectPath;
|
|
248
203
|
|
|
204
|
+
/**
|
|
205
|
+
* Axis-aligned rectangle pose with optional rotation. The canonical pose
|
|
206
|
+
* shape used by `composeRectPose` and the `unionBounds` helper. Rotation is
|
|
207
|
+
* in radians, pivoted on the unrotated AABB center; absent === 0. Kit-side
|
|
208
|
+
* code that consumes rotation already reads `pose.rotation ?? 0`
|
|
209
|
+
* (`SceneCanvas.defaultDrawOne`, `rotate/handle.ts`, `pathInWorld.ts`), so
|
|
210
|
+
* the slot exists on every default scene whether or not the consumer
|
|
211
|
+
* populates it.
|
|
212
|
+
*/
|
|
213
|
+
interface RectPose {
|
|
214
|
+
x: number;
|
|
215
|
+
y: number;
|
|
216
|
+
width: number;
|
|
217
|
+
height: number;
|
|
218
|
+
/** Rotation in radians around the unrotated AABB center. Absent === 0. */
|
|
219
|
+
rotation?: number;
|
|
220
|
+
}
|
|
249
221
|
/**
|
|
250
222
|
* # SceneNode — the thing in the scene
|
|
251
223
|
*
|
|
@@ -631,11 +603,18 @@ interface TextureHandle {
|
|
|
631
603
|
* equivalent to `{ fill: 'solid', color: '#abc' }`. Pattern paints must set
|
|
632
604
|
* `fill: 'pattern'` explicitly.
|
|
633
605
|
*
|
|
634
|
-
* The `'pattern'` variant's payload is a `
|
|
635
|
-
*
|
|
636
|
-
* `
|
|
637
|
-
*
|
|
638
|
-
*
|
|
606
|
+
* The `'pattern'` variant's payload is either a `TilePatternSpec` — plain
|
|
607
|
+
* data naming one of the built-in tiles, which survives serialization — or a
|
|
608
|
+
* `TextureHandle` for a tile the consumer built itself via
|
|
609
|
+
* `createTilePattern()`. A handle is a session-scoped registry key, so a
|
|
610
|
+
* paint carrying one cannot be persisted or exported; prefer the spec.
|
|
611
|
+
*
|
|
612
|
+
* `units` on a pattern names the space the tile's origin and scale live in,
|
|
613
|
+
* not the space of any geometry (a pattern has none). Under `'bounds'` the
|
|
614
|
+
* tile anchors to the painted node's box, so the pattern travels with the
|
|
615
|
+
* node and a resize reveals more tiles rather than stretching them;
|
|
616
|
+
* `fillInPoseFrame` resolves that to a `'local'` paint with an explicit
|
|
617
|
+
* `origin` before the renderer sees it.
|
|
639
618
|
*/
|
|
640
619
|
type FillStyle = {
|
|
641
620
|
fill?: 'solid';
|
|
@@ -643,7 +622,12 @@ type FillStyle = {
|
|
|
643
622
|
opacity?: number;
|
|
644
623
|
} | {
|
|
645
624
|
fill: 'pattern';
|
|
646
|
-
pattern: TextureHandle;
|
|
625
|
+
pattern: TextureHandle | TilePatternSpec;
|
|
626
|
+
units?: GradientUnits;
|
|
627
|
+
origin?: {
|
|
628
|
+
x: number;
|
|
629
|
+
y: number;
|
|
630
|
+
};
|
|
647
631
|
opacity?: number;
|
|
648
632
|
} | {
|
|
649
633
|
fill: 'linear-gradient';
|
|
@@ -656,6 +640,7 @@ type FillStyle = {
|
|
|
656
640
|
y: number;
|
|
657
641
|
};
|
|
658
642
|
stops: GradStop[];
|
|
643
|
+
units?: GradientUnits;
|
|
659
644
|
opacity?: number;
|
|
660
645
|
} | {
|
|
661
646
|
fill: 'radial-gradient';
|
|
@@ -665,6 +650,7 @@ type FillStyle = {
|
|
|
665
650
|
};
|
|
666
651
|
radius: number;
|
|
667
652
|
stops: GradStop[];
|
|
653
|
+
units?: GradientUnits;
|
|
668
654
|
opacity?: number;
|
|
669
655
|
} | {
|
|
670
656
|
fill: 'conic-gradient';
|
|
@@ -674,8 +660,56 @@ type FillStyle = {
|
|
|
674
660
|
};
|
|
675
661
|
angle: number;
|
|
676
662
|
stops: GradStop[];
|
|
663
|
+
units?: GradientUnits;
|
|
677
664
|
opacity?: number;
|
|
678
665
|
};
|
|
666
|
+
/**
|
|
667
|
+
* Which coordinate space a gradient's geometry (`from`/`to`, `center`,
|
|
668
|
+
* `radius`) is expressed in. SVG's `gradientUnits`, plus an option for
|
|
669
|
+
* paints that are themselves viewport furniture.
|
|
670
|
+
*
|
|
671
|
+
* - `'bounds'`: fractions of the painted node's bounding box, `0..1` on each
|
|
672
|
+
* axis — SVG `objectBoundingBox`. Resolved by the node painter, before the
|
|
673
|
+
* renderer sees it, so the paint follows the node through moves, resizes
|
|
674
|
+
* and rotation. What a gradient on a scene node wants.
|
|
675
|
+
* - `'local'`: the coordinate space the geometry was handed to the renderer
|
|
676
|
+
* in — the enclosing group's frame. For draw commands a consumer builds
|
|
677
|
+
* itself, where "the coordinates I just wrote" is the useful frame.
|
|
678
|
+
* - `'world'`: scene coordinates. The paint stays put under pan and zoom
|
|
679
|
+
* while the geometry moves through it. SVG `userSpaceOnUse`. Requires the
|
|
680
|
+
* renderer to have been handed a view matrix; falls back to `'screen'`
|
|
681
|
+
* when it has not.
|
|
682
|
+
* - `'screen'`: CSS pixels of the drawing surface. For overlays and
|
|
683
|
+
* viewport-fixed washes that should not move with the content at all.
|
|
684
|
+
*
|
|
685
|
+
* Defaults to `'screen'`, which is the behavior every gradient had before
|
|
686
|
+
* this field existed.
|
|
687
|
+
*/
|
|
688
|
+
type GradientUnits = 'bounds' | 'local' | 'world' | 'screen';
|
|
689
|
+
/**
|
|
690
|
+
* A built-in tile, described as plain data. The serializable half of the
|
|
691
|
+
* pattern paint: `resolvePatternSpec()` turns one into a `TextureHandle`
|
|
692
|
+
* at paint time, memoized so identical specs share a texture.
|
|
693
|
+
*
|
|
694
|
+
* `size` is the tile's edge length, and doubles as its extent in paint
|
|
695
|
+
* space — a bigger `size` rasterizes a bigger tile rather than magnifying
|
|
696
|
+
* a small one, which is why there is no separate scale field.
|
|
697
|
+
*/
|
|
698
|
+
interface TilePatternSpec {
|
|
699
|
+
tile: 'hatch' | 'crosshatch' | 'dots' | 'chunks';
|
|
700
|
+
color: string;
|
|
701
|
+
/** `chunks` only — omit for a transparent tile background. */
|
|
702
|
+
bg?: string;
|
|
703
|
+
size?: number;
|
|
704
|
+
/** `hatch` / `crosshatch`. */
|
|
705
|
+
lineWidth?: number;
|
|
706
|
+
/** `dots`. */
|
|
707
|
+
radius?: number;
|
|
708
|
+
/** `chunks`. */
|
|
709
|
+
density?: number;
|
|
710
|
+
chunkSize?: number;
|
|
711
|
+
seed?: number;
|
|
712
|
+
}
|
|
679
713
|
/** A single color stop within a gradient. `offset` is in 0..1. */
|
|
680
714
|
interface GradStop {
|
|
681
715
|
offset: number;
|
|
@@ -791,6 +825,19 @@ interface TextStyle {
|
|
|
791
825
|
underline?: boolean;
|
|
792
826
|
/** Default `false`. */
|
|
793
827
|
strikethrough?: boolean;
|
|
828
|
+
/**
|
|
829
|
+
* Outline painted over the glyph fill. Omitted (the default) means no
|
|
830
|
+
* outline — there is no such thing as a default text stroke.
|
|
831
|
+
*
|
|
832
|
+
* Only glyphs on the outline tier are stroked: above
|
|
833
|
+
* `textOutlineMinScreenSize` a glyph is a real `PolygonPath`, so it gets
|
|
834
|
+
* the ordinary tessellated ribbon with real joins, caps and miters, in any
|
|
835
|
+
* paint. Below it a glyph is a sampled distance field with no geometry to
|
|
836
|
+
* stroke, and it renders unstroked rather than approximated. `width` is in
|
|
837
|
+
* world units, like every other stroke in the kit — it does not scale with
|
|
838
|
+
* `fontSize`.
|
|
839
|
+
*/
|
|
840
|
+
stroke?: Stroke;
|
|
794
841
|
}
|
|
795
842
|
|
|
796
843
|
/**
|
|
@@ -818,6 +865,9 @@ interface ResolvedRun {
|
|
|
818
865
|
fontWeight: number;
|
|
819
866
|
fontStyle: 'normal' | 'italic';
|
|
820
867
|
fill: FillStyle;
|
|
868
|
+
/** Outline over this run's glyphs, or absent for none. Painted only on the
|
|
869
|
+
* outline tier — a distance field has no geometry to stroke. */
|
|
870
|
+
stroke?: Stroke;
|
|
821
871
|
/** Extra advance added after each glyph of this run, in world units. */
|
|
822
872
|
letterSpacing: number;
|
|
823
873
|
/** Draw a rule below this run's baseline. Additive over the node style. */
|
|
@@ -944,6 +994,10 @@ interface ImageDrawCommand {
|
|
|
944
994
|
w: number;
|
|
945
995
|
h: number;
|
|
946
996
|
opacity?: number;
|
|
997
|
+
/** Magnification filter. `'linear'` (default) smooths; `'nearest'` shows
|
|
998
|
+
* device pixels as hard squares — required by anything magnifying a
|
|
999
|
+
* framebuffer readback, where blur destroys the point of the readback. */
|
|
1000
|
+
sampling?: 'linear' | 'nearest';
|
|
947
1001
|
}
|
|
948
1002
|
/**
|
|
949
1003
|
* Custom shader draw command. The renderer generates a quad over `bounds`
|
|
@@ -968,4 +1022,4 @@ interface ShaderDrawCommand {
|
|
|
968
1022
|
};
|
|
969
1023
|
}
|
|
970
1024
|
|
|
971
|
-
export type { Bounds as B, DrawCommand as D, History as H, Node as N, Op as O, PathDrawCommand as P, Scene as S, View as V, ViewportDims as a, NodeId as b, Path as c
|
|
1025
|
+
export type { Bounds as B, DrawCommand as D, History as H, Node as N, Op as O, PathDrawCommand as P, Scene as S, View as V, ViewportDims as a, NodeId as b, Path as c };
|
|
@@ -29,10 +29,12 @@ interface SavedSnapshot {
|
|
|
29
29
|
state: unknown;
|
|
30
30
|
savedAt: number;
|
|
31
31
|
}
|
|
32
|
+
/** `auto` follows the OS; the other two are an explicit choice. */
|
|
33
|
+
type LabMode = 'auto' | 'light' | 'dark';
|
|
32
34
|
interface LabStoreState {
|
|
33
35
|
workspaces: WorkspaceRecord[];
|
|
34
36
|
savedSnapshots: SavedSnapshot[];
|
|
35
|
-
|
|
37
|
+
mode: LabMode;
|
|
36
38
|
}
|
|
37
39
|
interface StorageAdapter {
|
|
38
40
|
read(key: string): string | null;
|
|
@@ -48,7 +50,7 @@ interface ExperimentStateHandle<TS, TC> {
|
|
|
48
50
|
interface CreateLabStoreOptions {
|
|
49
51
|
storageKey: string;
|
|
50
52
|
storage: StorageAdapter;
|
|
51
|
-
|
|
53
|
+
initialMode?: LabMode;
|
|
52
54
|
}
|
|
53
55
|
type InstrumentSerializers = Record<string, {
|
|
54
56
|
serialize?: (state: unknown) => unknown;
|
|
@@ -73,7 +75,7 @@ interface LabStoreActions {
|
|
|
73
75
|
loadSnapshot: (snapshotId: string, workspaceId: string) => void;
|
|
74
76
|
deleteSnapshot: (snapshotId: string) => void;
|
|
75
77
|
listSnapshots: (workspaceId?: string) => SavedSnapshot[];
|
|
76
|
-
|
|
78
|
+
setMode: (mode: LabMode) => void;
|
|
77
79
|
}
|
|
78
80
|
type LabStore = StoreApi<LabStoreState & LabStoreActions> & {
|
|
79
81
|
registerSerializers: (s: InstrumentSerializers) => void;
|
|
@@ -99,4 +101,4 @@ declare function useWorkspaceId(): string;
|
|
|
99
101
|
declare function useExperimentState<TS = unknown, TC = unknown>(): ExperimentStateHandle<TS, TC>;
|
|
100
102
|
|
|
101
103
|
export { LabStoreContext as b, LabStoreProvider as c, WorkspaceIdContext as f, WorkspaceIdProvider as g, createLabStore as h, createMemoryAdapter as i, useExperimentState as j, useLabStore as k, localStorageAdapter as l, useWorkspaceId as m, noneAdapter as n, sessionStorageAdapter as s, urlHashAdapter as u };
|
|
102
|
-
export type { CreateLabStoreOptions as C, ExperimentStateHandle as E, InstrumentSerializers as I, LabStore as L, StorageAdapter as S, UndoStack as U, WorkspaceRecord as W, LabStoreActions as a, LabStoreState as d, SavedSnapshot as e };
|
|
104
|
+
export type { CreateLabStoreOptions as C, ExperimentStateHandle as E, InstrumentSerializers as I, LabStore as L, StorageAdapter as S, UndoStack as U, WorkspaceRecord as W, LabStoreActions as a, LabStoreState as d, SavedSnapshot as e, LabMode as o };
|
|
@@ -183,17 +183,18 @@ function createLabStore(options) {
|
|
|
183
183
|
hydratedSnapshots = [];
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
186
|
+
const modeRaw = options.storage.read(labStorageKey(options.storageKey, "theme"));
|
|
187
|
+
const stored = modeRaw === "interstellar" ? "dark" : modeRaw;
|
|
188
|
+
let hydratedMode;
|
|
189
|
+
if (stored === "light" || stored === "dark" || stored === "auto") {
|
|
190
|
+
hydratedMode = stored;
|
|
190
191
|
} else {
|
|
191
|
-
|
|
192
|
+
hydratedMode = options.initialMode ?? "auto";
|
|
192
193
|
}
|
|
193
194
|
const store = createStore()((set, get) => ({
|
|
194
195
|
workspaces: hydratedWorkspaces,
|
|
195
196
|
savedSnapshots: hydratedSnapshots,
|
|
196
|
-
|
|
197
|
+
mode: hydratedMode,
|
|
197
198
|
addWorkspace: (record) => {
|
|
198
199
|
set((s) => ({
|
|
199
200
|
workspaces: [...s.workspaces, { ...record, undoStack: emptyUndoStack() }]
|
|
@@ -300,8 +301,8 @@ function createLabStore(options) {
|
|
|
300
301
|
const filtered = workspaceId ? all.filter((sn) => sn.workspaceId === workspaceId) : all;
|
|
301
302
|
return [...filtered].sort((a, b) => b.savedAt - a.savedAt);
|
|
302
303
|
},
|
|
303
|
-
|
|
304
|
-
set({
|
|
304
|
+
setMode: (mode) => {
|
|
305
|
+
set({ mode });
|
|
305
306
|
scheduleFlush();
|
|
306
307
|
}
|
|
307
308
|
}));
|
|
@@ -317,7 +318,7 @@ function createLabStore(options) {
|
|
|
317
318
|
labStorageKey(options.storageKey, "saves"),
|
|
318
319
|
JSON.stringify(s.savedSnapshots)
|
|
319
320
|
);
|
|
320
|
-
options.storage.write(labStorageKey(options.storageKey, "theme"), s.
|
|
321
|
+
options.storage.write(labStorageKey(options.storageKey, "theme"), s.mode);
|
|
321
322
|
flushTimer = null;
|
|
322
323
|
}, 300);
|
|
323
324
|
}
|
|
@@ -346,5 +347,5 @@ function useExperimentState() {
|
|
|
346
347
|
}
|
|
347
348
|
|
|
348
349
|
export { LabStoreContext, LabStoreProvider, WorkspaceIdContext, WorkspaceIdProvider, createLabStore, createMemoryAdapter, decodeUrlHash, deserializeWorkspaces, emptyUndoStack, encodeUrlHash, labStorageKey, localStorageAdapter, noneAdapter, serializeWorkspaces, sessionStorageAdapter, urlHashAdapter, useExperimentState, useLabStore, useWorkspaceId };
|
|
349
|
-
//# sourceMappingURL=chunk-
|
|
350
|
-
//# sourceMappingURL=chunk-
|
|
350
|
+
//# sourceMappingURL=chunk-53XSBIUK.js.map
|
|
351
|
+
//# sourceMappingURL=chunk-53XSBIUK.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-53XSBIUK.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 LabMode,\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 setMode: (mode: LabMode) => 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 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\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\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 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"]}
|