@weasel-js/labkit 1.4.2 → 1.4.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/README.md +20 -2
- package/dist/_dts/{CanvasStackContext-kjILVnPj.d.ts → CanvasStackContext-kTEZvEgE.d.ts} +1 -1
- package/dist/_dts/{PrefsForm.d-CgxUequc.d.ts → PrefsForm.d-DDHFANkc.d.ts} +1 -1
- package/dist/_dts/{frac-A4R6v4ld.d.ts → frac-z7ker2Vx.d.ts} +11 -7
- package/dist/_dts/{index-CsU9WhjM.d.ts → index-Blv9uQzu.d.ts} +42 -9
- package/dist/_dts/types-B9_zrHmb.d.ts +166 -0
- package/dist/_dts/{types-BbXpvQa8.d.ts → types-BP2OCcpg.d.ts} +21 -3
- package/dist/_dts/types-x92Kfeme.d.ts +62 -0
- package/dist/_dts/{useTrialState-DKjqpv20.d.ts → useTrialState-D6Vb3T-g.d.ts} +7 -7
- package/dist/canvas/index.d.ts +9 -8
- package/dist/canvas/index.js +1 -2
- package/dist/chrome/index.d.ts +7 -6
- package/dist/chrome/index.js +5 -5
- package/dist/{chunk-ULDW42CR.js → chunk-2P6PP5N4.js} +33 -23
- package/dist/chunk-2P6PP5N4.js.map +1 -0
- package/dist/{chunk-W2FJR5FF.js → chunk-64ZCN3DA.js} +74 -76
- package/dist/chunk-64ZCN3DA.js.map +1 -0
- package/dist/{chunk-NSOVI3AZ.js → chunk-BKFVHKJH.js} +6 -6
- package/dist/{chunk-NSOVI3AZ.js.map → chunk-BKFVHKJH.js.map} +1 -1
- package/dist/chunk-E44SU6XS.js +50 -0
- package/dist/chunk-E44SU6XS.js.map +1 -0
- package/dist/{chunk-WS6ZRV75.js → chunk-FQMJUVHJ.js} +3 -3
- package/dist/{chunk-WS6ZRV75.js.map → chunk-FQMJUVHJ.js.map} +1 -1
- package/dist/{chunk-FJG4PHTL.js → chunk-I6JCVE24.js} +3 -3
- package/dist/chunk-I6JCVE24.js.map +1 -0
- package/dist/{chunk-UDXOYZEC.js → chunk-MOM3GOVY.js} +4 -4
- package/dist/{chunk-UDXOYZEC.js.map → chunk-MOM3GOVY.js.map} +1 -1
- package/dist/{chunk-TN7YSJVU.js → chunk-RS3HRQWU.js} +3 -3
- package/dist/{chunk-TN7YSJVU.js.map → chunk-RS3HRQWU.js.map} +1 -1
- package/dist/{chunk-UTOEDPNU.js → chunk-SMHP6XZ4.js} +7 -6
- package/dist/chunk-SMHP6XZ4.js.map +1 -0
- package/dist/{chunk-D5KQ5OY6.js → chunk-TJ7QY3OC.js} +3 -3
- package/dist/{chunk-D5KQ5OY6.js.map → chunk-TJ7QY3OC.js.map} +1 -1
- package/dist/{chunk-XKENZTNE.js → chunk-U3IYHIAE.js} +71 -55
- package/dist/chunk-U3IYHIAE.js.map +1 -0
- package/dist/chunk-W3ECWC2K.js +7335 -0
- package/dist/chunk-W3ECWC2K.js.map +1 -0
- package/dist/controls/index.d.ts +5 -4
- package/dist/controls/index.js +3 -3
- package/dist/dragdrop/index.d.ts +6 -5
- package/dist/dragdrop/index.js +1 -2
- package/dist/index.d.ts +139 -26
- package/dist/index.js +309 -144
- package/dist/index.js.map +1 -1
- package/dist/layers/index.d.ts +7 -6
- package/dist/layers/index.js +2 -3
- package/dist/loupe/index.d.ts +8 -7
- package/dist/loupe/index.js +1 -2
- package/dist/passthrough/weasel-canvas.d.ts +1 -3
- package/dist/passthrough/weasel-canvas.js +1 -1
- package/dist/passthrough/weasel-canvas.js.map +1 -1
- package/dist/passthrough/weasel-ui.d.ts +98 -20
- package/dist/passthrough/weasel-ui.js +1 -2
- package/dist/primitives/index.js +3 -4
- package/dist/state/index.d.ts +6 -3
- package/dist/state/index.js +3 -2
- package/dist/state/index.js.map +1 -1
- package/dist/styles.css +82 -15
- package/dist/surface/index.js +1 -2
- package/dist/ui/layers/index.js +1 -2
- package/dist/undo/index.d.ts +6 -6
- package/package.json +7 -7
- package/src/annotations/ExportMenu.test.tsx +16 -0
- package/src/annotations/ExportMenu.tsx +0 -5
- package/src/annotations/frac.test.ts +4 -4
- package/src/annotations/frac.ts +3 -2
- package/src/annotations/index.ts +1 -1
- package/src/annotations/store.ts +2 -2
- package/src/annotations/svgNodes.ts +1 -2
- package/src/config/builder.test.ts +56 -2
- package/src/config/builder.ts +73 -13
- package/src/config/index.ts +16 -1
- package/src/config/path.test.ts +109 -0
- package/src/config/path.ts +76 -0
- package/src/config/resolve.test.ts +93 -2
- package/src/config/resolve.ts +72 -29
- package/src/config/types.ts +99 -9
- package/src/config/visible.ts +6 -4
- package/src/controls/ControlPanel.stories.tsx +38 -4
- package/src/controls/ControlPanel.test.tsx +114 -0
- package/src/controls/ControlPanel.tsx +99 -50
- package/src/index.ts +17 -2
- package/src/instrument/serializers.ts +36 -0
- package/src/instrument/types.ts +4 -2
- package/src/lab/Lab.test.tsx +29 -1
- package/src/lab/Lab.tsx +10 -1
- package/src/lab/LabShell.test.tsx +8 -0
- package/src/lab/LabShell.tsx +17 -3
- package/src/lab/LabSwitcher.less +85 -0
- package/src/lab/LabSwitcher.test.tsx +96 -0
- package/src/lab/LabSwitcher.tsx +112 -0
- package/src/lab/index.ts +2 -0
- package/src/primitives/ZoomControl.less +6 -2
- package/src/primitives/ZoomControl.tsx +1 -0
- package/src/state/helpers.test.ts +20 -0
- package/src/state/helpers.ts +17 -1
- package/src/state/store.test.ts +155 -1
- package/src/state/store.ts +32 -26
- package/src/state/types.ts +24 -3
- package/src/state/useTrialState.ts +1 -1
- package/src/styles.less +1 -0
- package/src/theme/base.less +4 -4
- package/src/trial/Trial.config.test.tsx +44 -0
- package/src/trial/Trial.tsx +3 -3
- package/src/trial/TrialChrome.tsx +10 -5
- package/src/trial/trialOps.ts +7 -10
- package/dist/_dts/types-lg4TSCb2.d.ts +0 -164
- package/dist/_dts/weasel-canvas-FJTFi3ZZ.d.ts +0 -5041
- package/dist/chunk-54ZWZ5FQ.js +0 -29997
- package/dist/chunk-54ZWZ5FQ.js.map +0 -1
- package/dist/chunk-FJG4PHTL.js.map +0 -1
- package/dist/chunk-ISSVF5PT.js +0 -7263
- package/dist/chunk-ISSVF5PT.js.map +0 -1
- package/dist/chunk-ULDW42CR.js.map +0 -1
- package/dist/chunk-UTOEDPNU.js.map +0 -1
- package/dist/chunk-W2FJR5FF.js.map +0 -1
- package/dist/chunk-XKENZTNE.js.map +0 -1
package/src/trial/Trial.tsx
CHANGED
|
@@ -239,11 +239,11 @@ function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }: T
|
|
|
239
239
|
updateTrialState(record.id, next);
|
|
240
240
|
bus.emit('state.change');
|
|
241
241
|
},
|
|
242
|
-
setConfig: (
|
|
243
|
-
const evt = `config.change:${
|
|
242
|
+
setConfig: (path, value) => {
|
|
243
|
+
const evt = `config.change:${path}`;
|
|
244
244
|
snapshotIfNeeded('config.change');
|
|
245
245
|
snapshotIfNeeded(evt);
|
|
246
|
-
updateTrialConfig(record.id,
|
|
246
|
+
updateTrialConfig(record.id, path, value);
|
|
247
247
|
bus.emit('config.change');
|
|
248
248
|
bus.emit(evt);
|
|
249
249
|
},
|
|
@@ -9,6 +9,7 @@ import { TitleBarRegion } from '../chrome/regions/TitleBarRegion';
|
|
|
9
9
|
import { ToolbarRegion } from '../chrome/regions/ToolbarRegion';
|
|
10
10
|
import { ViewportRegion } from '../chrome/regions/ViewportRegion';
|
|
11
11
|
import type { TrialChromeContext, TrialContribution, TrialRegion } from '../chrome/types';
|
|
12
|
+
import { hasConfigPath, schemaNodeAtPath, withValueAtPath } from '../config/path';
|
|
12
13
|
import { useConfigSchema } from '../config/useConfigSchema';
|
|
13
14
|
import type { Instrument } from '../instrument/types';
|
|
14
15
|
import type { JobHandle } from '../job/types';
|
|
@@ -135,16 +136,20 @@ export function TrialChrome({
|
|
|
135
136
|
configSchema,
|
|
136
137
|
configFields: instrument.config ? [] : (instrument.configSchema?.() ?? []),
|
|
137
138
|
config: record.config,
|
|
138
|
-
setConfig: (
|
|
139
|
+
setConfig: (path, value) => {
|
|
139
140
|
const prevConfig = record.config as Record<string, unknown>;
|
|
140
|
-
if (
|
|
141
|
+
if (
|
|
142
|
+
process.env.NODE_ENV !== 'production' &&
|
|
143
|
+
!hasConfigPath(prevConfig, path) &&
|
|
144
|
+
schemaNodeAtPath(configSchema.group, path) === undefined
|
|
145
|
+
) {
|
|
141
146
|
console.warn(
|
|
142
|
-
`[labkit] setConfig: unknown
|
|
147
|
+
`[labkit] setConfig: unknown path "${path}" for instrument "${record.instrumentName}"`,
|
|
143
148
|
);
|
|
144
149
|
}
|
|
145
|
-
updateTrialConfig(trialId,
|
|
150
|
+
updateTrialConfig(trialId, path, value);
|
|
146
151
|
if (instrument.onConfigChange) {
|
|
147
|
-
const nextConfig =
|
|
152
|
+
const nextConfig = withValueAtPath(prevConfig, path, value);
|
|
148
153
|
const nextState = instrument.onConfigChange(nextConfig, prevConfig, record.state);
|
|
149
154
|
updateTrialState(trialId, nextState as never);
|
|
150
155
|
}
|
package/src/trial/trialOps.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { fillConfigDefaults } from '../config/path';
|
|
1
2
|
import type { Instrument, InstrumentList } from '../instrument/types';
|
|
2
3
|
import type { TrialRecord } from '../state/types';
|
|
4
|
+
import { newId } from '../state/helpers';
|
|
3
5
|
|
|
4
6
|
const DEFAULT_VIEW = { zoom: 1, pan: { x: 0, y: 0 } } as const;
|
|
5
7
|
|
|
@@ -27,15 +29,10 @@ export interface AddTrialOptions<TC = Record<string, unknown>> {
|
|
|
27
29
|
config?: Partial<TC>;
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
/** `defaultConfig()` with `seed` written over it
|
|
31
|
-
*
|
|
32
|
+
/** `defaultConfig()` with `seed` written over it, down the tree: a seed naming
|
|
33
|
+
* one leaf of a group leaves that group's other leaves at their defaults. */
|
|
32
34
|
function seedConfig<TC>(defaults: TC, seed: Partial<TC> | undefined): TC {
|
|
33
|
-
|
|
34
|
-
const out = { ...defaults };
|
|
35
|
-
for (const [key, value] of Object.entries(seed)) {
|
|
36
|
-
if (value !== undefined) (out as Record<string, unknown>)[key] = value;
|
|
37
|
-
}
|
|
38
|
-
return out;
|
|
35
|
+
return seed ? fillConfigDefaults(seed, defaults) : defaults;
|
|
39
36
|
}
|
|
40
37
|
|
|
41
38
|
/** Append a new trial running `instrumentName`, at that instrument's default
|
|
@@ -51,7 +48,7 @@ export function addTrial(
|
|
|
51
48
|
const config = seedConfig(instrument.defaultConfig(), options.config);
|
|
52
49
|
const state = instrument.initialState(config);
|
|
53
50
|
const record: TrialRecord = {
|
|
54
|
-
id:
|
|
51
|
+
id: newId(),
|
|
55
52
|
instrumentName,
|
|
56
53
|
config,
|
|
57
54
|
state,
|
|
@@ -70,7 +67,7 @@ export function cloneTrial(trials: TrialRecord[], id: string): TrialRecord[] {
|
|
|
70
67
|
if (!source) return trials;
|
|
71
68
|
const clone: TrialRecord = {
|
|
72
69
|
...source,
|
|
73
|
-
id:
|
|
70
|
+
id: newId(),
|
|
74
71
|
config: structuredClone(source.config),
|
|
75
72
|
state: structuredClone(source.state),
|
|
76
73
|
view: structuredClone(source.view),
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import { T as ToolPrefLeaf, a as ToolPrefGroup } from './weasel-canvas-FJTFi3ZZ.js';
|
|
2
|
-
import { P as PrefRenderer } from './PrefsForm.d-CgxUequc.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Renders the control cell for one config leaf. Identical to weasel-ui's
|
|
6
|
-
* `PrefRenderer` on purpose — aliased rather than redeclared so the two
|
|
7
|
-
* cannot drift.
|
|
8
|
-
*/
|
|
9
|
-
type ControlRenderer = PrefRenderer;
|
|
10
|
-
/** One labeled choice in an `enum` leaf. */
|
|
11
|
-
interface ConfigOption {
|
|
12
|
-
value: string;
|
|
13
|
-
label: string;
|
|
14
|
-
}
|
|
15
|
-
/** Everything a leaf can carry beyond its kind and default. A flat union of
|
|
16
|
-
* every `Pref*` leaf's extras, plus labkit's own `debounceMs`. */
|
|
17
|
-
interface Annotations {
|
|
18
|
-
name?: string;
|
|
19
|
-
description?: string;
|
|
20
|
-
hidden?: boolean;
|
|
21
|
-
block?: boolean;
|
|
22
|
-
pair?: string;
|
|
23
|
-
min?: number;
|
|
24
|
-
max?: number;
|
|
25
|
-
step?: number;
|
|
26
|
-
suffix?: string;
|
|
27
|
-
control?: string;
|
|
28
|
-
options?: readonly ConfigOption[];
|
|
29
|
-
placeholder?: string;
|
|
30
|
-
maxLength?: number;
|
|
31
|
-
/** Milliseconds to debounce a string leaf's live writes. Default 150. */
|
|
32
|
-
debounceMs?: number;
|
|
33
|
-
}
|
|
34
|
-
/** What a rule contributes. `kind` is honored only while it is still unset,
|
|
35
|
-
* which is what makes `f.value` claimable and a kinded factory final. */
|
|
36
|
-
type LeafPatch = Annotations & {
|
|
37
|
-
kind?: string;
|
|
38
|
-
};
|
|
39
|
-
/** What a rule is given for the leaf it is deciding about. */
|
|
40
|
-
interface ConfigRuleContext {
|
|
41
|
-
/** The config key this leaf writes. */
|
|
42
|
-
key: string;
|
|
43
|
-
/** Dotted path within the schema. Equal to `key` while schemas are flat. */
|
|
44
|
-
path: string;
|
|
45
|
-
/** The leaf's default value. A rule may read it but never change it. */
|
|
46
|
-
default: unknown;
|
|
47
|
-
/** What earlier rules and the author's own annotations have settled. */
|
|
48
|
-
leaf: Readonly<LeafPatch>;
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Decides part of how a leaf is presented. Returns a patch, or null to
|
|
52
|
-
* abstain. Merging is gap-filling: a property already settled is never
|
|
53
|
-
* overwritten, so the author's annotations beat every rule and a consumer's
|
|
54
|
-
* rules beat labkit's built-ins.
|
|
55
|
-
*/
|
|
56
|
-
type ConfigRule = (ctx: ConfigRuleContext) => LeafPatch | null;
|
|
57
|
-
/** A presentational bucket of leaves, rendered under one heading. */
|
|
58
|
-
interface SectionSpec {
|
|
59
|
-
label: string;
|
|
60
|
-
paths: readonly string[];
|
|
61
|
-
}
|
|
62
|
-
/** A schema resolved against a set of rules: the vocabulary weasel-ui renders,
|
|
63
|
-
* plus the three things labkit keeps on the side because `PrefLeaf` has no
|
|
64
|
-
* field for them. */
|
|
65
|
-
interface ResolvedConfig {
|
|
66
|
-
/** Flat: every leaf is a direct child, so a leaf's path is its config key. */
|
|
67
|
-
group: ToolPrefGroup;
|
|
68
|
-
sections: readonly SectionSpec[];
|
|
69
|
-
showIf: ReadonlyMap<string, (config: Record<string, unknown>) => boolean>;
|
|
70
|
-
/** Node-level `.render` overrides, keyed by path. */
|
|
71
|
-
renderers: Readonly<Record<string, ControlRenderer>>;
|
|
72
|
-
}
|
|
73
|
-
/** Per-node extras that do not belong on a `PrefLeaf`. */
|
|
74
|
-
interface NodeOptions {
|
|
75
|
-
section?: string;
|
|
76
|
-
showIf?: (config: Record<string, unknown>) => boolean;
|
|
77
|
-
render?: ControlRenderer;
|
|
78
|
-
validate?: (leaf: ToolPrefLeaf) => string[];
|
|
79
|
-
}
|
|
80
|
-
/** The builder's leaf: a kind (or null, to be decided by rules), a default,
|
|
81
|
-
* an annotation bag, and the extras above. */
|
|
82
|
-
interface ConfigNode<T = unknown> {
|
|
83
|
-
readonly kind: string | null;
|
|
84
|
-
readonly default: T;
|
|
85
|
-
readonly annotations: Readonly<Annotations>;
|
|
86
|
-
readonly options: Readonly<NodeOptions>;
|
|
87
|
-
}
|
|
88
|
-
/** The value type a node produces. */
|
|
89
|
-
type NodeValue<N> = N extends ConfigNode<infer T> ? T : never;
|
|
90
|
-
/** The config type a builder shape produces. */
|
|
91
|
-
type InferConfig<S> = {
|
|
92
|
-
[K in keyof S]: NodeValue<S[K]>;
|
|
93
|
-
};
|
|
94
|
-
/** An instrument's config, declared once. */
|
|
95
|
-
interface ConfigSchema<TC> {
|
|
96
|
-
readonly nodes: Readonly<Record<string, ConfigNode>>;
|
|
97
|
-
/** The starting config — what `defaultConfig()` would have returned. */
|
|
98
|
-
defaults(): TC;
|
|
99
|
-
}
|
|
100
|
-
/** The config type behind a schema: `ConfigOf<typeof sceneConfig>`. */
|
|
101
|
-
type ConfigOf<S> = S extends ConfigSchema<infer TC> ? TC : never;
|
|
102
|
-
|
|
103
|
-
/** Which control a config field is edited with. */
|
|
104
|
-
type ConfigFieldType = 'slider' | 'checkbox' | 'select' | 'number' | 'text' | 'color';
|
|
105
|
-
/** What every config field carries: the config key it writes, the label shown
|
|
106
|
-
* beside it, and which control renders it. */
|
|
107
|
-
interface ConfigFieldBase {
|
|
108
|
-
key: string;
|
|
109
|
-
label: string;
|
|
110
|
-
type: ConfigFieldType;
|
|
111
|
-
}
|
|
112
|
-
/** A bounded number edited by dragging. */
|
|
113
|
-
interface SliderField extends ConfigFieldBase {
|
|
114
|
-
type: 'slider';
|
|
115
|
-
default: number;
|
|
116
|
-
min: number;
|
|
117
|
-
max: number;
|
|
118
|
-
step?: number;
|
|
119
|
-
}
|
|
120
|
-
/** A boolean. */
|
|
121
|
-
interface CheckboxField extends ConfigFieldBase {
|
|
122
|
-
type: 'checkbox';
|
|
123
|
-
default: boolean;
|
|
124
|
-
}
|
|
125
|
-
/** One choice in a select field. */
|
|
126
|
-
interface SelectOption {
|
|
127
|
-
value: string;
|
|
128
|
-
label: string;
|
|
129
|
-
}
|
|
130
|
-
/** A fixed set of labeled choices. */
|
|
131
|
-
interface SelectField extends ConfigFieldBase {
|
|
132
|
-
type: 'select';
|
|
133
|
-
default: string;
|
|
134
|
-
options: SelectOption[];
|
|
135
|
-
}
|
|
136
|
-
/** A number typed directly, optionally bounded. Use a slider field instead
|
|
137
|
-
* when the range matters more than the exact value. */
|
|
138
|
-
interface NumberField extends ConfigFieldBase {
|
|
139
|
-
type: 'number';
|
|
140
|
-
default: number;
|
|
141
|
-
min?: number;
|
|
142
|
-
max?: number;
|
|
143
|
-
step?: number;
|
|
144
|
-
}
|
|
145
|
-
/** A free-text string. Writes are debounced so typing does not re-run the
|
|
146
|
-
* instrument on every keystroke. */
|
|
147
|
-
interface TextField extends ConfigFieldBase {
|
|
148
|
-
type: 'text';
|
|
149
|
-
default: string;
|
|
150
|
-
placeholder?: string;
|
|
151
|
-
maxLength?: number;
|
|
152
|
-
/** Milliseconds to debounce live setConfig calls. Default 150 ms. Set to 0 to disable. */
|
|
153
|
-
debounceMs?: number;
|
|
154
|
-
}
|
|
155
|
-
/** A color, as a CSS color string. */
|
|
156
|
-
interface ColorField extends ConfigFieldBase {
|
|
157
|
-
type: 'color';
|
|
158
|
-
default: string;
|
|
159
|
-
}
|
|
160
|
-
/** One field of an instrument's config schema. The schema is what the control
|
|
161
|
-
* panel renders, and what `validateConfigSchema` checks. */
|
|
162
|
-
type ConfigField = SliderField | CheckboxField | SelectField | NumberField | TextField | ColorField;
|
|
163
|
-
|
|
164
|
-
export type { Annotations as A, ConfigField as C, InferConfig as I, LeafPatch as L, NodeOptions as N, ResolvedConfig as R, SectionSpec as S, TextField as T, ConfigSchema as a, ConfigNode as b, ControlRenderer as c, ConfigOption as d, ConfigRule as e, ConfigRuleContext as f, CheckboxField as g, ColorField as h, ConfigFieldBase as i, ConfigFieldType as j, ConfigOf as k, NodeValue as l, NumberField as m, SelectField as n, SelectOption as o, SliderField as p };
|