fragment-tools 0.2.3 → 0.2.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/package.json +1 -1
- package/src/client/app/inputs/MIDI.js +0 -1
- package/src/client/app/modules/MidiPanel.svelte +23 -13
- package/src/client/app/modules/Params.svelte +28 -16
- package/src/client/app/state/Sketch.svelte.js +65 -38
- package/src/client/app/state/rendering.svelte.js +18 -3
- package/src/client/app/state/utils.svelte.js +13 -0
- package/src/client/app/ui/Field.svelte +10 -10
- package/src/client/app/ui/fields/ProgressInput.svelte +41 -14
- package/src/client/app/ui/fields/VectorInput.svelte +63 -18
- package/src/client/app/utils/fields.utils.js +69 -48
- package/src/client/app/utils/math.utils.js +6 -0
package/package.json
CHANGED
|
@@ -6,17 +6,19 @@
|
|
|
6
6
|
|
|
7
7
|
let { mID, headless = false, ...restProps } = $props();
|
|
8
8
|
|
|
9
|
-
let input = $state(
|
|
10
|
-
let output = $state(
|
|
11
|
-
let inputs = $state(
|
|
12
|
-
let outputs = $state(
|
|
9
|
+
let input = $state(undefined);
|
|
10
|
+
let output = $state(undefined);
|
|
11
|
+
let inputs = $state(MIDI.inputs);
|
|
12
|
+
let outputs = $state(MIDI.outputs);
|
|
13
|
+
let inputOptions = $derived.by(() => createDeviceOptions(inputs));
|
|
14
|
+
let outputOptions = $derived.by(() => createDeviceOptions(outputs));
|
|
13
15
|
let messages = $state([]);
|
|
14
16
|
|
|
15
17
|
function createDeviceOptions(deviceMap = new Map()) {
|
|
16
18
|
let options = [];
|
|
17
19
|
|
|
18
20
|
if (deviceMap.size !== 0) {
|
|
19
|
-
options.push({ value:
|
|
21
|
+
options.push({ value: undefined, label: 'No device selected.' });
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
for (let entry of deviceMap) {
|
|
@@ -30,7 +32,7 @@
|
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
if (options.length === 0) {
|
|
33
|
-
options
|
|
35
|
+
options.push({ value: undefined, label: 'No device detected.' });
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
return options;
|
|
@@ -45,15 +47,23 @@
|
|
|
45
47
|
await MIDI.request();
|
|
46
48
|
|
|
47
49
|
function refresh() {
|
|
48
|
-
inputs =
|
|
49
|
-
outputs =
|
|
50
|
+
inputs = MIDI.inputs;
|
|
51
|
+
outputs = MIDI.outputs;
|
|
50
52
|
|
|
51
53
|
// if a single device is connected, select it by default
|
|
52
|
-
input =
|
|
53
|
-
|
|
54
|
+
input =
|
|
55
|
+
inputs.size === 1
|
|
56
|
+
? MIDI.inputs.values().next().value.id
|
|
57
|
+
: undefined;
|
|
58
|
+
output =
|
|
59
|
+
outputs.size === 1
|
|
60
|
+
? MIDI.outputs.values().next().value.id
|
|
61
|
+
: undefined;
|
|
54
62
|
}
|
|
55
63
|
|
|
56
|
-
MIDI.addEventListener('connected',
|
|
64
|
+
MIDI.addEventListener('connected', () => {
|
|
65
|
+
refresh();
|
|
66
|
+
});
|
|
57
67
|
MIDI.addEventListener('disconnected', () => {
|
|
58
68
|
refresh();
|
|
59
69
|
});
|
|
@@ -83,7 +93,7 @@
|
|
|
83
93
|
value={input}
|
|
84
94
|
onchange={(value) => (input = value)}
|
|
85
95
|
params={{
|
|
86
|
-
options:
|
|
96
|
+
options: inputOptions,
|
|
87
97
|
}}
|
|
88
98
|
/>
|
|
89
99
|
<Field
|
|
@@ -91,7 +101,7 @@
|
|
|
91
101
|
value={output}
|
|
92
102
|
onchange={(value) => (output = value)}
|
|
93
103
|
params={{
|
|
94
|
-
options:
|
|
104
|
+
options: outputOptions,
|
|
95
105
|
}}
|
|
96
106
|
/>
|
|
97
107
|
<Field key="messages" value={messages} type="list" disabled />
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { layout } from '../state/layout.svelte';
|
|
8
8
|
import FieldGroup from '../ui/FieldGroup.svelte';
|
|
9
9
|
import { sketchesManager } from '../state/sketches.svelte';
|
|
10
|
+
import { parseFolder } from '../utils/fields.utils';
|
|
10
11
|
|
|
11
12
|
let {
|
|
12
13
|
id = layout.getID(),
|
|
@@ -52,16 +53,25 @@
|
|
|
52
53
|
|
|
53
54
|
if (!sketchPropGroup || group === sketchPropGroup) {
|
|
54
55
|
if (folder) {
|
|
56
|
+
const parsed = parseFolder(folder);
|
|
57
|
+
const current = parsed.find((m) => m.isCurrent);
|
|
58
|
+
|
|
55
59
|
const fieldgroup = sketch?.propsFolders.find(
|
|
56
|
-
(f) => f.id ===
|
|
60
|
+
(f) => f.id === current.id,
|
|
57
61
|
);
|
|
58
62
|
|
|
59
63
|
if (fieldgroup) {
|
|
60
|
-
const { depth,
|
|
64
|
+
const { depth, rootId } = fieldgroup;
|
|
65
|
+
const root =
|
|
66
|
+
rootId &&
|
|
67
|
+
sketch?.propsFolders.find((f) => f.id === rootId);
|
|
61
68
|
|
|
62
69
|
if (depth === 0 && !tree.includes(fieldgroup)) {
|
|
63
70
|
tree.push(fieldgroup);
|
|
64
|
-
} else if (
|
|
71
|
+
} else if (
|
|
72
|
+
root &&
|
|
73
|
+
!tree.some((fieldgroup) => fieldgroup.id === rootId)
|
|
74
|
+
) {
|
|
65
75
|
tree.push(root);
|
|
66
76
|
}
|
|
67
77
|
} else {
|
|
@@ -164,19 +174,21 @@
|
|
|
164
174
|
sketchProps[item.key],
|
|
165
175
|
)}
|
|
166
176
|
{:else if item.type === 'fieldgroup'}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
{#
|
|
176
|
-
{
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
177
|
+
{#if !item.hidden}
|
|
178
|
+
<FieldGroup
|
|
179
|
+
name={item.displayName}
|
|
180
|
+
collapsed={item.collapsed}
|
|
181
|
+
onchange={(collapsed) => {
|
|
182
|
+
sketch.updateFolder(item, collapsed);
|
|
183
|
+
}}
|
|
184
|
+
>
|
|
185
|
+
{#if item.children.length > 0}
|
|
186
|
+
{#each item.children as child, childIndex}
|
|
187
|
+
{@render sketchPropItem(childIndex, child)}
|
|
188
|
+
{/each}
|
|
189
|
+
{/if}
|
|
190
|
+
</FieldGroup>
|
|
191
|
+
{/if}
|
|
180
192
|
{/if}
|
|
181
193
|
{/snippet}
|
|
182
194
|
{#each sketchPropsTree as sketchPropsTreeItem, index}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { parseFolder } from '../utils/fields.utils';
|
|
1
2
|
import { rendering } from './rendering.svelte';
|
|
2
3
|
import {
|
|
3
4
|
deepAssign,
|
|
5
|
+
deepClone,
|
|
4
6
|
deepEqual,
|
|
5
7
|
hydrate,
|
|
6
8
|
isFunction,
|
|
@@ -46,7 +48,10 @@ class Sketch {
|
|
|
46
48
|
|
|
47
49
|
reset() {
|
|
48
50
|
Object.keys(this.props).forEach((key) => {
|
|
49
|
-
this.updateProp(
|
|
51
|
+
this.updateProp(
|
|
52
|
+
key,
|
|
53
|
+
$state.snapshot(this.props[key].__initialValue),
|
|
54
|
+
);
|
|
50
55
|
});
|
|
51
56
|
|
|
52
57
|
this.propsFolders.forEach((fieldgroup) => {
|
|
@@ -89,12 +94,16 @@ class Sketch {
|
|
|
89
94
|
) {
|
|
90
95
|
deepAssign(newProp.value, prevProp.value);
|
|
91
96
|
deepAssign(instanceProp.value, prevProp.value);
|
|
92
|
-
newProp.__currentValue =
|
|
97
|
+
newProp.__currentValue = deepClone(
|
|
98
|
+
$state.snapshot(newProp.value),
|
|
99
|
+
);
|
|
93
100
|
} else if (
|
|
94
101
|
newProp.__initialValue === prevProp.__initialValue
|
|
95
102
|
) {
|
|
96
103
|
newProp.value = prevProp.value;
|
|
97
|
-
newProp.__currentValue =
|
|
104
|
+
newProp.__currentValue = deepClone(
|
|
105
|
+
$state.snapshot(newProp.value),
|
|
106
|
+
);
|
|
98
107
|
instanceProp.value = prevProp.value;
|
|
99
108
|
}
|
|
100
109
|
|
|
@@ -164,18 +173,6 @@ class Sketch {
|
|
|
164
173
|
propsFoldersCollection,
|
|
165
174
|
propsGroupsCollection,
|
|
166
175
|
) {
|
|
167
|
-
const duplicateInitialValue = (value) => {
|
|
168
|
-
if (isFunction(value)) {
|
|
169
|
-
return value;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (isObject(value)) {
|
|
173
|
-
return structuredClone(value);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return value;
|
|
177
|
-
};
|
|
178
|
-
|
|
179
176
|
let {
|
|
180
177
|
value,
|
|
181
178
|
params = {},
|
|
@@ -203,7 +200,7 @@ class Sketch {
|
|
|
203
200
|
? instanceProp.hidden
|
|
204
201
|
: () => instanceProp.hidden;
|
|
205
202
|
|
|
206
|
-
let initialValue =
|
|
203
|
+
let initialValue = deepClone(value);
|
|
207
204
|
|
|
208
205
|
if (group && !propsGroupsCollection.includes(group)) {
|
|
209
206
|
propsGroupsCollection.push(group);
|
|
@@ -216,7 +213,7 @@ class Sketch {
|
|
|
216
213
|
let prop = {
|
|
217
214
|
value,
|
|
218
215
|
__initialValue: initialValue,
|
|
219
|
-
__currentValue: value,
|
|
216
|
+
__currentValue: deepClone(value),
|
|
220
217
|
__hidden,
|
|
221
218
|
type,
|
|
222
219
|
params: structuredClone(params),
|
|
@@ -237,12 +234,16 @@ class Sketch {
|
|
|
237
234
|
|
|
238
235
|
if (prop) {
|
|
239
236
|
prop.value = newValue;
|
|
240
|
-
prop.__currentValue = newValue;
|
|
237
|
+
prop.__currentValue = deepClone(newValue);
|
|
241
238
|
}
|
|
242
239
|
|
|
243
240
|
if (instanceProp) {
|
|
244
241
|
if (!deepEqual(instanceProp.value, newValue)) {
|
|
245
|
-
instanceProp.value
|
|
242
|
+
if (isObject(instanceProp.value) && isObject(newValue)) {
|
|
243
|
+
deepAssign(instanceProp.value, newValue);
|
|
244
|
+
} else {
|
|
245
|
+
instanceProp.value = newValue;
|
|
246
|
+
}
|
|
246
247
|
}
|
|
247
248
|
|
|
248
249
|
instanceProp.onChange?.(instanceProp, {
|
|
@@ -264,21 +265,24 @@ class Sketch {
|
|
|
264
265
|
if (!folder) return undefined;
|
|
265
266
|
|
|
266
267
|
let propFolder;
|
|
267
|
-
let names = folder.split('.');
|
|
268
268
|
|
|
269
|
-
|
|
270
|
-
let root;
|
|
271
|
-
let collapsedRegex = /^(.*?)(?:\[collapsed=(true|false)\])?$/;
|
|
269
|
+
const parsed = parseFolder(folder);
|
|
272
270
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
let match =
|
|
276
|
-
let
|
|
277
|
-
|
|
271
|
+
if (parsed.length > 0) {
|
|
272
|
+
for (let i = 0; i < parsed.length; i++) {
|
|
273
|
+
let match = parsed[i];
|
|
274
|
+
let {
|
|
275
|
+
depth,
|
|
276
|
+
id,
|
|
277
|
+
parentId,
|
|
278
|
+
rootId,
|
|
279
|
+
isCurrent,
|
|
280
|
+
name,
|
|
281
|
+
attributes = {},
|
|
282
|
+
} = match;
|
|
283
|
+
let { collapsed = false } = attributes;
|
|
284
|
+
let displayName = name;
|
|
278
285
|
|
|
279
|
-
let depth = i;
|
|
280
|
-
let id = [...names].slice(0, i + 1).join('.');
|
|
281
|
-
let parentId = [...names].slice(0, i).join('.');
|
|
282
286
|
let parent =
|
|
283
287
|
depth > 0
|
|
284
288
|
? collection.find((f) => f.id === parentId)
|
|
@@ -296,7 +300,8 @@ class Sketch {
|
|
|
296
300
|
children: [],
|
|
297
301
|
parent,
|
|
298
302
|
depth,
|
|
299
|
-
|
|
303
|
+
rootId,
|
|
304
|
+
hidden: false,
|
|
300
305
|
};
|
|
301
306
|
|
|
302
307
|
if (parent) {
|
|
@@ -306,11 +311,7 @@ class Sketch {
|
|
|
306
311
|
collection.push(fieldgroup);
|
|
307
312
|
}
|
|
308
313
|
|
|
309
|
-
if (
|
|
310
|
-
root = fieldgroup;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (i === names.length - 1) {
|
|
314
|
+
if (isCurrent) {
|
|
314
315
|
fieldgroup.children.push({
|
|
315
316
|
type: 'field',
|
|
316
317
|
key,
|
|
@@ -326,7 +327,7 @@ class Sketch {
|
|
|
326
327
|
|
|
327
328
|
updateFolder(folder, collapsed) {
|
|
328
329
|
this.propsFolders.forEach((f, index) => {
|
|
329
|
-
if (f === folder) {
|
|
330
|
+
if (f.id === folder.id) {
|
|
330
331
|
this.propsFolders[index].collapsed = collapsed;
|
|
331
332
|
}
|
|
332
333
|
});
|
|
@@ -366,6 +367,9 @@ class Sketch {
|
|
|
366
367
|
!deepEqual(instanceProp.value, prop.__currentValue)
|
|
367
368
|
) {
|
|
368
369
|
this.updateProp(key, instanceProp.value);
|
|
370
|
+
prop.__initialValue = deepClone(
|
|
371
|
+
$state.snapshot(prop.value),
|
|
372
|
+
);
|
|
369
373
|
}
|
|
370
374
|
|
|
371
375
|
// sync displayName
|
|
@@ -462,6 +466,29 @@ class Sketch {
|
|
|
462
466
|
delete this.props[key];
|
|
463
467
|
}
|
|
464
468
|
});
|
|
469
|
+
|
|
470
|
+
const fieldgroups = [...this.propsFolders].sort(
|
|
471
|
+
(a, b) => b.depth - a.depth,
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
fieldgroups.forEach((fieldgroup) => {
|
|
475
|
+
const hasAllFieldsHidden = fieldgroup.children
|
|
476
|
+
.filter((child) => child.type === 'field')
|
|
477
|
+
.every((child) => this.props[child.key].__hidden());
|
|
478
|
+
const hasAllFieldgroupsHidden = fieldgroup.children
|
|
479
|
+
.filter((child) => child.type === 'fieldgroup')
|
|
480
|
+
.every((child) => child.hidden);
|
|
481
|
+
|
|
482
|
+
if (hasAllFieldsHidden && hasAllFieldgroupsHidden) {
|
|
483
|
+
if (!fieldgroup.hidden) {
|
|
484
|
+
fieldgroup.hidden = true;
|
|
485
|
+
}
|
|
486
|
+
} else {
|
|
487
|
+
if (fieldgroup.hidden) {
|
|
488
|
+
fieldgroup.hidden = false;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
});
|
|
465
492
|
}
|
|
466
493
|
|
|
467
494
|
onBeforeCapture(fn) {
|
|
@@ -320,12 +320,27 @@ export class Render {
|
|
|
320
320
|
this.time = 0;
|
|
321
321
|
this.recording = false;
|
|
322
322
|
|
|
323
|
-
|
|
323
|
+
let resizeTimeout;
|
|
324
|
+
|
|
325
|
+
$effect.pre(() => {
|
|
324
326
|
const { width, height, pixelRatio } = rendering;
|
|
325
327
|
|
|
326
328
|
if (this.loaded) {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
+
if (resizeTimeout) clearTimeout(resizeTimeout);
|
|
330
|
+
|
|
331
|
+
resizeTimeout = setTimeout(() => {
|
|
332
|
+
clearTimeout(resizeTimeout);
|
|
333
|
+
resizeTimeout = null;
|
|
334
|
+
|
|
335
|
+
this.resize(width, height, pixelRatio);
|
|
336
|
+
}, 0);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
$effect(() => {
|
|
341
|
+
const { width, height, pixelRatio } = rendering;
|
|
342
|
+
|
|
343
|
+
if (!this.loaded) {
|
|
329
344
|
this.width = width;
|
|
330
345
|
this.height = height;
|
|
331
346
|
this.pixelRatio = pixelRatio;
|
|
@@ -74,3 +74,16 @@ export function deepEqual(target, source) {
|
|
|
74
74
|
|
|
75
75
|
return target === source;
|
|
76
76
|
}
|
|
77
|
+
|
|
78
|
+
export function deepClone(value) {
|
|
79
|
+
if (isFunction(value)) {
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (isObject(value)) {
|
|
84
|
+
const clone = structuredClone(value);
|
|
85
|
+
return clone;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import ButtonInput from './fields/ButtonInput.svelte';
|
|
10
10
|
import ImageInput from './fields/ImageInput.svelte';
|
|
11
11
|
import IntervalInput from './fields/IntervalInput.svelte';
|
|
12
|
-
import { fieldTypes
|
|
12
|
+
import { fieldTypes } from '../utils/fields.utils.js';
|
|
13
13
|
|
|
14
14
|
const fields = {
|
|
15
15
|
[`${fieldTypes.SELECT}`]: Select,
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
import IconTriggers from '../components/IconTriggers.svelte';
|
|
38
38
|
import IconLocked from '../components/IconLocked.svelte';
|
|
39
39
|
import ImportInput from './fields/ImportInput.svelte';
|
|
40
|
+
import { deepEqual } from '../state/utils.svelte';
|
|
40
41
|
|
|
41
42
|
let {
|
|
42
43
|
key,
|
|
@@ -91,7 +92,7 @@
|
|
|
91
92
|
let fieldType = $derived(inferFieldType({ type, value, params, key }));
|
|
92
93
|
let fieldProps = $derived(composeFieldProps(params, disabled));
|
|
93
94
|
let onTrigger = $derived(frameDebounce(onTriggers[fieldType]));
|
|
94
|
-
let
|
|
95
|
+
let Component = $derived(fields[fieldType]);
|
|
95
96
|
let triggerable = $derived(
|
|
96
97
|
params.triggerable !== false &&
|
|
97
98
|
((fieldType === fieldTypes.NUMBER &&
|
|
@@ -117,12 +118,17 @@
|
|
|
117
118
|
context,
|
|
118
119
|
};
|
|
119
120
|
}
|
|
121
|
+
|
|
122
|
+
function hasChanged(current, next) {
|
|
123
|
+
const changed = !deepEqual(current, next);
|
|
124
|
+
return changed;
|
|
125
|
+
}
|
|
120
126
|
</script>
|
|
121
127
|
|
|
122
128
|
<div
|
|
123
129
|
class="field"
|
|
124
130
|
class:disabled
|
|
125
|
-
class:changed={!disabled && hasChanged(
|
|
131
|
+
class:changed={!disabled && hasChanged(value, initialValue)}
|
|
126
132
|
style="--index: {index};"
|
|
127
133
|
>
|
|
128
134
|
<FieldSection
|
|
@@ -153,13 +159,7 @@
|
|
|
153
159
|
{/if}
|
|
154
160
|
</div>
|
|
155
161
|
{/snippet}
|
|
156
|
-
<
|
|
157
|
-
this={input}
|
|
158
|
-
{value}
|
|
159
|
-
{...fieldProps}
|
|
160
|
-
{onchange}
|
|
161
|
-
onclick={onTrigger}
|
|
162
|
-
/>
|
|
162
|
+
<Component {value} {...fieldProps} {onchange} onclick={onTrigger} />
|
|
163
163
|
{@render children?.()}
|
|
164
164
|
</FieldSection>
|
|
165
165
|
{#if triggerable}
|
|
@@ -1,22 +1,14 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import
|
|
2
|
+
import Keyboard from '../../inputs/Keyboard.js';
|
|
3
3
|
import { map, clamp, roundToStep } from '../../utils/math.utils.js';
|
|
4
4
|
|
|
5
|
-
let {
|
|
6
|
-
value,
|
|
7
|
-
min,
|
|
8
|
-
max,
|
|
9
|
-
step,
|
|
10
|
-
context = null,
|
|
11
|
-
key = '',
|
|
12
|
-
disabled = false,
|
|
13
|
-
onchange,
|
|
14
|
-
} = $props();
|
|
5
|
+
let { value, min, max, step, disabled = false, onchange } = $props();
|
|
15
6
|
|
|
16
7
|
let node;
|
|
17
8
|
let rect;
|
|
18
9
|
|
|
19
10
|
let isDragging = $state(false);
|
|
11
|
+
let steppedValue = $derived(roundToStep(value, step));
|
|
20
12
|
|
|
21
13
|
// handlers
|
|
22
14
|
function handleMouseDown(event) {
|
|
@@ -48,6 +40,28 @@
|
|
|
48
40
|
}
|
|
49
41
|
}
|
|
50
42
|
|
|
43
|
+
function handleKeyDown(event) {
|
|
44
|
+
const direction = ['ArrowUp', 'ArrowRight'].includes(event.key)
|
|
45
|
+
? 1
|
|
46
|
+
: ['ArrowDown', 'ArrowLeft'].includes(event.key)
|
|
47
|
+
? -1
|
|
48
|
+
: 0;
|
|
49
|
+
|
|
50
|
+
const diff = Keyboard.getStepFromEvent(event) * step;
|
|
51
|
+
|
|
52
|
+
if (direction !== 0) {
|
|
53
|
+
const newValue = clamp(
|
|
54
|
+
roundToStep(value + direction * diff, step),
|
|
55
|
+
min,
|
|
56
|
+
max,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
if (newValue !== value) {
|
|
60
|
+
onchange(newValue);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
51
65
|
function handleMouseUp() {
|
|
52
66
|
document.body.classList.remove('fragment-dragging');
|
|
53
67
|
document.removeEventListener('mousemove', handleMouseMove);
|
|
@@ -56,7 +70,9 @@
|
|
|
56
70
|
isDragging = false;
|
|
57
71
|
}
|
|
58
72
|
|
|
59
|
-
let progress = $derived(
|
|
73
|
+
let progress = $derived(
|
|
74
|
+
clamp(map(steppedValue, min, max, 0, 1), 0.0001, 1),
|
|
75
|
+
);
|
|
60
76
|
let opacity = $derived(progress > 0 ? 1 : 0);
|
|
61
77
|
</script>
|
|
62
78
|
|
|
@@ -64,10 +80,19 @@
|
|
|
64
80
|
class="progress"
|
|
65
81
|
bind:this={node}
|
|
66
82
|
onmousedown={handleMouseDown}
|
|
83
|
+
onkeydown={handleKeyDown}
|
|
67
84
|
class:disabled
|
|
68
85
|
class:dragging={isDragging}
|
|
86
|
+
role="slider"
|
|
87
|
+
aria-valuemin={min}
|
|
88
|
+
aria-valuemax={max}
|
|
89
|
+
aria-valuenow={value}
|
|
90
|
+
tabindex="0"
|
|
69
91
|
>
|
|
70
|
-
<div
|
|
92
|
+
<div
|
|
93
|
+
class="fill"
|
|
94
|
+
style="--progress: {progress}; --opacity: {opacity};"
|
|
95
|
+
></div>
|
|
71
96
|
</div>
|
|
72
97
|
|
|
73
98
|
<style>
|
|
@@ -81,13 +106,15 @@
|
|
|
81
106
|
background: var(--color-background-input);
|
|
82
107
|
cursor: ew-resize;
|
|
83
108
|
container-type: size;
|
|
109
|
+
outline: 0;
|
|
84
110
|
}
|
|
85
111
|
|
|
86
112
|
:global(body:not(.fragment-dragging)) .progress:hover {
|
|
87
113
|
box-shadow: inset 0 0 0 1px var(--color-active);
|
|
88
114
|
}
|
|
89
115
|
|
|
90
|
-
.progress.dragging
|
|
116
|
+
.progress.dragging,
|
|
117
|
+
:global(body:not(.fragment-dragging)) .progress:focus-visible {
|
|
91
118
|
box-shadow: 0 0 0 2px var(--color-active);
|
|
92
119
|
}
|
|
93
120
|
|
|
@@ -8,27 +8,71 @@
|
|
|
8
8
|
min = -Infinity,
|
|
9
9
|
max = Infinity,
|
|
10
10
|
step = 0.1,
|
|
11
|
+
key,
|
|
11
12
|
locked = false,
|
|
12
13
|
disabled = false,
|
|
13
14
|
context = null,
|
|
14
|
-
key = '',
|
|
15
15
|
onchange,
|
|
16
16
|
} = $props();
|
|
17
17
|
|
|
18
|
+
const keysChecks = ['x', 'y', 'z', 'w'];
|
|
18
19
|
|
|
19
20
|
let isArray = $derived(Array.isArray(value));
|
|
20
21
|
let isObject = $derived(!isArray && typeof value === 'object');
|
|
21
|
-
let
|
|
22
|
-
|
|
22
|
+
let keys = $derived.by(() => {
|
|
23
|
+
let keys = [];
|
|
24
|
+
|
|
25
|
+
if (isArray) {
|
|
26
|
+
return value.map((_, index) => index);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!isArray && !isObject) return [0];
|
|
30
|
+
|
|
31
|
+
for (let i = 0; i < keysChecks; i++) {
|
|
32
|
+
let keyCheck = keysChecks[i];
|
|
33
|
+
|
|
34
|
+
if (keyCheck in value) {
|
|
35
|
+
keys.push(keyCheck);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (value.isVector2) {
|
|
40
|
+
return ['x', 'y'];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (value.isVector3) {
|
|
44
|
+
return ['x', 'y', 'z'];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (value.isVector4 || value.isQuaternion) {
|
|
48
|
+
return ['x', 'y', 'z', 'w'];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (isObject) {
|
|
52
|
+
return Object.keys(value);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return keys;
|
|
56
|
+
});
|
|
57
|
+
let components = $derived.by(() => {
|
|
58
|
+
if (!isObject && !isArray) {
|
|
59
|
+
return [value];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return keys.map((key) => value[key]);
|
|
63
|
+
});
|
|
64
|
+
let mins = $derived(keys.map((key) => min[key]));
|
|
65
|
+
let maxs = $derived(keys.map((key) => max[key]));
|
|
66
|
+
let steps = $derived(keys.map((key) => step[key]));
|
|
23
67
|
|
|
24
68
|
function dispatchChange() {
|
|
25
|
-
let
|
|
26
|
-
all[key] = components[index];
|
|
69
|
+
let clone = isArray ? [] : {};
|
|
27
70
|
|
|
28
|
-
|
|
29
|
-
|
|
71
|
+
keys.forEach((key, index) => {
|
|
72
|
+
clone[key] = components[index];
|
|
73
|
+
});
|
|
30
74
|
|
|
31
|
-
onchange(
|
|
75
|
+
onchange(clone);
|
|
32
76
|
}
|
|
33
77
|
|
|
34
78
|
function handleComponentChange(newValue, componentIndex) {
|
|
@@ -39,11 +83,13 @@
|
|
|
39
83
|
}
|
|
40
84
|
|
|
41
85
|
components.forEach((component, index) => {
|
|
42
|
-
components[index] =
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
86
|
+
components[index] =
|
|
87
|
+
index === componentIndex
|
|
88
|
+
? newValue
|
|
89
|
+
: locked
|
|
90
|
+
? Math.round(component * ratio * (1 / step)) /
|
|
91
|
+
(1 / step)
|
|
92
|
+
: component;
|
|
47
93
|
});
|
|
48
94
|
|
|
49
95
|
dispatchChange();
|
|
@@ -56,17 +102,16 @@
|
|
|
56
102
|
>
|
|
57
103
|
{#each components as component, index}
|
|
58
104
|
<NumberInput
|
|
59
|
-
{
|
|
60
|
-
{
|
|
61
|
-
{
|
|
105
|
+
min={mins[index]}
|
|
106
|
+
max={maxs[index]}
|
|
107
|
+
step={steps[index]}
|
|
62
108
|
{suffix}
|
|
63
109
|
{disabled}
|
|
64
110
|
{context}
|
|
65
111
|
{key}
|
|
66
112
|
label={keys[index]}
|
|
67
113
|
value={component}
|
|
68
|
-
onchange={(value) =>
|
|
69
|
-
handleComponentChange(value, index)}
|
|
114
|
+
onchange={(value) => handleComponentChange(value, index)}
|
|
70
115
|
/>
|
|
71
116
|
{/each}
|
|
72
117
|
</FieldInputRow>
|
|
@@ -43,11 +43,40 @@ export function inferFieldType({ type, value, params, key }) {
|
|
|
43
43
|
|
|
44
44
|
const isArray = Array.isArray(value);
|
|
45
45
|
const isObject = !isArray && typeof value === 'object';
|
|
46
|
-
const
|
|
46
|
+
const getKeys = (value) => {
|
|
47
|
+
if (isArray) {
|
|
48
|
+
return value.map((_, index) => index);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!isArray && !isObject) return [0];
|
|
52
|
+
|
|
53
|
+
if (value.isVector3) {
|
|
54
|
+
return ['x', 'y', 'z'];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (value.isVector4 || value.isQuaternion) {
|
|
58
|
+
return ['x', 'y', 'z', 'w'];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (isObject) {
|
|
62
|
+
return Object.keys(value);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const getValues = (value, keys) => {
|
|
67
|
+
if (!isObject && !isArray) {
|
|
68
|
+
value = [value];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return keys.map((key) => value[key]);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const keys = getKeys(value);
|
|
75
|
+
const values = getValues(value, keys);
|
|
47
76
|
|
|
48
77
|
if (
|
|
49
78
|
isArray &&
|
|
50
|
-
|
|
79
|
+
values.length === 2 &&
|
|
51
80
|
typeof params.min === 'number' &&
|
|
52
81
|
typeof params.max === 'number'
|
|
53
82
|
) {
|
|
@@ -80,53 +109,45 @@ export function inferFieldType({ type, value, params, key }) {
|
|
|
80
109
|
console.warn(`Field: cannot find field type for ${key}`);
|
|
81
110
|
}
|
|
82
111
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
112
|
+
/**
|
|
113
|
+
*
|
|
114
|
+
* @param {string} folder
|
|
115
|
+
*/
|
|
116
|
+
export function parseFolder(folder) {
|
|
117
|
+
const regex = /(?<name>\w+)(?:\[(?<attributes>[^\]]+)\])?/g;
|
|
118
|
+
const matches = [...folder.matchAll(regex)];
|
|
119
|
+
|
|
120
|
+
const results = matches.map((match) => {
|
|
121
|
+
return {
|
|
122
|
+
name: match.groups.name,
|
|
123
|
+
attributes: match.groups.attributes
|
|
124
|
+
? Object.fromEntries(
|
|
125
|
+
match.groups.attributes
|
|
126
|
+
.split(', ')
|
|
127
|
+
.map((attr) => attr.split('=')),
|
|
128
|
+
)
|
|
129
|
+
: {},
|
|
130
|
+
};
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
let names = results.map((match) => match.name);
|
|
134
|
+
|
|
135
|
+
let rootId;
|
|
136
|
+
|
|
137
|
+
results.forEach((match, index) => {
|
|
138
|
+
let id = [...names].slice(0, index + 1).join('.');
|
|
139
|
+
let parentId = [...names].slice(0, index).join('.');
|
|
140
|
+
|
|
141
|
+
if (index === 0) {
|
|
142
|
+
rootId = id;
|
|
92
143
|
}
|
|
93
144
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (initialType === 'object') {
|
|
103
|
-
const keys1 = Object.keys(initialValue);
|
|
104
|
-
const keys2 = Object.keys(currentValue);
|
|
105
|
-
|
|
106
|
-
if (
|
|
107
|
-
keys1.length !== keys2.length ||
|
|
108
|
-
!keys1.every((key) => keys2.includes(key))
|
|
109
|
-
) {
|
|
110
|
-
return true;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
for (const key of keys1) {
|
|
114
|
-
const value1 = initialValue[key];
|
|
115
|
-
const value2 = currentValue[key];
|
|
116
|
-
|
|
117
|
-
if (typeof value1 === 'object' && typeof value2 === 'object') {
|
|
118
|
-
// If both values are objects, recursively compare them
|
|
119
|
-
if (hasChanged(value1, value2)) {
|
|
120
|
-
return true;
|
|
121
|
-
}
|
|
122
|
-
} else if (value1 !== value2) {
|
|
123
|
-
// If values are not objects, directly compare them
|
|
124
|
-
return true;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return false;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
145
|
+
match.id = id;
|
|
146
|
+
match.parentId = parentId;
|
|
147
|
+
match.depth = index;
|
|
148
|
+
match.isCurrent = index === results.length - 1;
|
|
149
|
+
match.rootId = rootId;
|
|
150
|
+
});
|
|
130
151
|
|
|
131
|
-
return
|
|
152
|
+
return results;
|
|
132
153
|
}
|
|
@@ -22,6 +22,12 @@ export function clamp(value, min, max) {
|
|
|
22
22
|
return Math.max(min, Math.min(value, max));
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
*
|
|
27
|
+
* @param {number} value
|
|
28
|
+
* @param {number} step
|
|
29
|
+
* @returns {number}
|
|
30
|
+
*/
|
|
25
31
|
export function roundToStep(value, step) {
|
|
26
32
|
return Math.round(value * (1 / step)) / (1 / step);
|
|
27
33
|
}
|