@pixerate/editor 0.28.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -1
- package/dist/HistoryManager-DWSLd6Tf.d.ts +150 -0
- package/dist/HistoryManager-DeQQMBCj.d.cts +150 -0
- package/dist/agent/index.cjs +20 -427
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +4 -342
- package/dist/agent/index.d.ts +4 -342
- package/dist/agent/index.js +14 -421
- package/dist/agent/index.js.map +1 -1
- package/dist/animation/index.cjs +6 -2
- package/dist/animation/index.cjs.map +1 -1
- package/dist/animation/index.d.cts +3 -73
- package/dist/animation/index.d.ts +3 -73
- package/dist/animation/index.js +17 -13
- package/dist/{builtins-nMXh9sT3.d.cts → builtins-DxC3aUR2.d.cts} +2 -2
- package/dist/{builtins-haN82vjT.d.ts → builtins-NGxNxQfJ.d.ts} +2 -2
- package/dist/canvas/index.cjs +338 -28
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +109 -4
- package/dist/canvas/index.d.ts +109 -4
- package/dist/canvas/index.js +323 -13
- package/dist/canvas/index.js.map +1 -1
- package/dist/chunk-36BLROHV.js +438 -0
- package/dist/chunk-36BLROHV.js.map +1 -0
- package/dist/{chunk-OYEFRO3D.cjs → chunk-6MDQKHRH.cjs} +4 -4
- package/dist/{chunk-OYEFRO3D.cjs.map → chunk-6MDQKHRH.cjs.map} +1 -1
- package/dist/{chunk-ARRTP33I.js → chunk-D6S5M7HV.js} +2 -2
- package/dist/{chunk-ARRTP33I.js.map → chunk-D6S5M7HV.js.map} +1 -1
- package/dist/{chunk-MXZJ4PKN.js → chunk-DDADOHJW.js} +8 -8
- package/dist/{chunk-CPOKFNBG.cjs → chunk-I6BZB5ZH.cjs} +2 -2
- package/dist/{chunk-CPOKFNBG.cjs.map → chunk-I6BZB5ZH.cjs.map} +1 -1
- package/dist/chunk-PIIAZV3L.cjs +244 -0
- package/dist/chunk-PIIAZV3L.cjs.map +1 -0
- package/dist/{chunk-G2I4QJOT.cjs → chunk-QKLRAA4T.cjs} +3 -244
- package/dist/chunk-QKLRAA4T.cjs.map +1 -0
- package/dist/{chunk-2H7ZHPEJ.js → chunk-QVZZGT4X.js} +1 -242
- package/dist/chunk-QVZZGT4X.js.map +1 -0
- package/dist/chunk-UB2OY7FD.js +262 -0
- package/dist/chunk-UB2OY7FD.js.map +1 -0
- package/dist/chunk-V7CC7RZ6.cjs +438 -0
- package/dist/chunk-V7CC7RZ6.cjs.map +1 -0
- package/dist/{chunk-PWJARCI4.js → chunk-W7BOBWZD.js} +5 -196
- package/dist/chunk-W7BOBWZD.js.map +1 -0
- package/dist/{chunk-TTYCN6ZH.cjs → chunk-XHRZSB77.cjs} +8 -199
- package/dist/chunk-XHRZSB77.cjs.map +1 -0
- package/dist/chunk-XZ4L3R4K.cjs +262 -0
- package/dist/chunk-XZ4L3R4K.cjs.map +1 -0
- package/dist/chunk-YK57D7PW.js +244 -0
- package/dist/chunk-YK57D7PW.js.map +1 -0
- package/dist/commands/index.cjs +2 -2
- package/dist/commands/index.cjs.map +1 -1
- package/dist/commands/index.d.cts +2 -2
- package/dist/commands/index.d.ts +2 -2
- package/dist/commands/index.js +1 -1
- package/dist/commands/index.js.map +1 -1
- package/dist/easing-DTmX99QG.d.cts +88 -0
- package/dist/easing-DTmX99QG.d.ts +88 -0
- package/dist/extensions/index.cjs +5 -5
- package/dist/extensions/index.cjs.map +1 -1
- package/dist/extensions/index.js +9 -9
- package/dist/generation/index.cjs +2 -2
- package/dist/generation/index.cjs.map +1 -1
- package/dist/generation/index.js +1 -1
- package/dist/generation/index.js.map +1 -1
- package/dist/history/index.cjs +4 -2
- package/dist/history/index.cjs.map +1 -1
- package/dist/history/index.d.cts +3 -149
- package/dist/history/index.d.ts +3 -149
- package/dist/history/index.js +5 -3
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.js.map +1 -1
- package/dist/index.cjs +15 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +22 -20
- package/dist/index.js.map +1 -1
- package/dist/parameters/index.cjs +1320 -0
- package/dist/parameters/index.cjs.map +1 -0
- package/dist/parameters/index.d.cts +625 -0
- package/dist/parameters/index.d.ts +625 -0
- package/dist/parameters/index.js +1320 -0
- package/dist/parameters/index.js.map +1 -0
- package/dist/tools/index.cjs +31 -0
- package/dist/tools/index.cjs.map +1 -0
- package/dist/tools/index.d.cts +343 -0
- package/dist/tools/index.d.ts +343 -0
- package/dist/tools/index.js +31 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/{types-GT5a5YAX.d.cts → types-DsiXGmjo.d.cts} +1 -1
- package/dist/{types-GT5a5YAX.d.ts → types-DsiXGmjo.d.ts} +1 -1
- package/package.json +11 -1
- package/dist/chunk-2H7ZHPEJ.js.map +0 -1
- package/dist/chunk-G2I4QJOT.cjs.map +0 -1
- package/dist/chunk-PWJARCI4.js.map +0 -1
- package/dist/chunk-TTYCN6ZH.cjs.map +0 -1
- /package/dist/{chunk-MXZJ4PKN.js.map → chunk-DDADOHJW.js.map} +0 -0
|
@@ -0,0 +1,1320 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;
|
|
2
|
+
|
|
3
|
+
var _chunkQKLRAA4Tcjs = require('../chunk-QKLRAA4T.cjs');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
var _chunkXZ4L3R4Kcjs = require('../chunk-XZ4L3R4K.cjs');
|
|
7
|
+
|
|
8
|
+
// src/parameters/schema.ts
|
|
9
|
+
function humanizeParameterId(id) {
|
|
10
|
+
const words = id.replace(/^u(?=[A-Z])/, "").replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").trim().toLowerCase();
|
|
11
|
+
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
12
|
+
}
|
|
13
|
+
function parameterLabel(item) {
|
|
14
|
+
return _nullishCoalesce(item.label, () => ( humanizeParameterId(item.id)));
|
|
15
|
+
}
|
|
16
|
+
var HEX = /^#?([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
|
|
17
|
+
function normalizeHexColor(input, alpha = false) {
|
|
18
|
+
const m = HEX.exec(input.trim());
|
|
19
|
+
if (!m) return null;
|
|
20
|
+
let digits = m[1].toLowerCase();
|
|
21
|
+
if (digits.length <= 4) digits = [...digits].map((c) => c + c).join("");
|
|
22
|
+
const rgb = digits.slice(0, 6);
|
|
23
|
+
const a = digits.length === 8 ? digits.slice(6) : "ff";
|
|
24
|
+
return alpha ? `#${rgb}${a}` : `#${rgb}`;
|
|
25
|
+
}
|
|
26
|
+
function hexToRgba01(hex, alpha = false) {
|
|
27
|
+
const n = normalizeHexColor(hex, true);
|
|
28
|
+
if (!n) return null;
|
|
29
|
+
const parts = [1, 3, 5, 7].map((i) => parseInt(n.slice(i, i + 2), 16) / 255);
|
|
30
|
+
return alpha ? parts : parts.slice(0, 3);
|
|
31
|
+
}
|
|
32
|
+
function rgba01ToHex(components, alpha = false) {
|
|
33
|
+
const byte = (c) => Math.round(Math.min(1, Math.max(0, Number.isFinite(c) ? c : 0)) * 255).toString(16).padStart(2, "0");
|
|
34
|
+
const rgb = [0, 1, 2].map((i) => byte(_nullishCoalesce(components[i], () => ( 0)))).join("");
|
|
35
|
+
return alpha ? `#${rgb}${byte(_nullishCoalesce(components[3], () => ( 1)))}` : `#${rgb}`;
|
|
36
|
+
}
|
|
37
|
+
var isParameterGroup = (item) => item.type === "group";
|
|
38
|
+
function getParameterFields(schema) {
|
|
39
|
+
const out = [];
|
|
40
|
+
const walk = (items) => {
|
|
41
|
+
for (const item of items) {
|
|
42
|
+
if (isParameterGroup(item)) walk(item.fields);
|
|
43
|
+
else out.push(item);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
walk(Array.isArray(schema) ? schema : schema.fields);
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
function getParameterGroups(schema) {
|
|
50
|
+
const out = [];
|
|
51
|
+
const walk = (items) => {
|
|
52
|
+
for (const item of items) {
|
|
53
|
+
if (isParameterGroup(item)) {
|
|
54
|
+
out.push(item);
|
|
55
|
+
walk(item.fields);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
walk(schema.fields);
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
var ParameterSchemaError = class extends Error {
|
|
63
|
+
constructor(message) {
|
|
64
|
+
super(message);
|
|
65
|
+
this.name = "ParameterSchemaError";
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
var FIELD_TYPES = /* @__PURE__ */ new Set(["number", "int", "boolean", "choice", "color", "vector", "image", "text"]);
|
|
69
|
+
function checkParameterSchema(schema) {
|
|
70
|
+
const problems = [];
|
|
71
|
+
const ids = /* @__PURE__ */ new Set();
|
|
72
|
+
const visit = (items) => {
|
|
73
|
+
for (const item of items) {
|
|
74
|
+
if (!item || typeof item.id !== "string" || item.id === "") {
|
|
75
|
+
problems.push("Every field and group needs an id.");
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (ids.has(item.id)) problems.push(`The id "${item.id}" is used twice.`);
|
|
79
|
+
ids.add(item.id);
|
|
80
|
+
if (isParameterGroup(item)) {
|
|
81
|
+
visit(_nullishCoalesce(item.fields, () => ( [])));
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (!FIELD_TYPES.has(item.type)) {
|
|
85
|
+
problems.push(`Field "${item.id}" has an unknown type "${item.type}".`);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (item.type === "choice" && (!item.options || item.options.length === 0)) {
|
|
89
|
+
problems.push(`Field "${item.id}" needs at least one option.`);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (item.type === "vector") {
|
|
93
|
+
const size = vectorSize(item);
|
|
94
|
+
if (size < 2 || size > 4) {
|
|
95
|
+
problems.push(`Field "${item.id}" must have 2 to 4 components.`);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const error = validateParameterValue(item, item.default);
|
|
100
|
+
if (error) problems.push(`Field "${item.id}" has an invalid default: ${error.message}`);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
visit(_nullishCoalesce(schema.fields, () => ( [])));
|
|
104
|
+
const presetNames = /* @__PURE__ */ new Set();
|
|
105
|
+
for (const preset of _nullishCoalesce(schema.presets, () => ( []))) {
|
|
106
|
+
if (presetNames.has(preset.name)) problems.push(`The preset name "${preset.name}" is used twice.`);
|
|
107
|
+
presetNames.add(preset.name);
|
|
108
|
+
}
|
|
109
|
+
return problems;
|
|
110
|
+
}
|
|
111
|
+
function getParameterDefaults(schema) {
|
|
112
|
+
const values = {};
|
|
113
|
+
for (const field of getParameterFields(schema)) {
|
|
114
|
+
const result = coerceParameterValue(field, field.default);
|
|
115
|
+
values[field.id] = result.ok ? result.value : cloneValue(field.default);
|
|
116
|
+
}
|
|
117
|
+
return values;
|
|
118
|
+
}
|
|
119
|
+
var cloneValue = (value) => Array.isArray(value) ? [...value] : value;
|
|
120
|
+
function parameterValuesEqual(a, b) {
|
|
121
|
+
if (a === b) return true;
|
|
122
|
+
if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((v, i) => v === b[i]);
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
var truthy = (v) => Array.isArray(v) ? v.some((c) => c !== 0) : v !== void 0 && v !== false && v !== 0 && v !== "";
|
|
126
|
+
function testCondition(value, test) {
|
|
127
|
+
if (Array.isArray(test)) return test.some((option) => parameterValuesEqual(value, option));
|
|
128
|
+
if (typeof test !== "object" || test === null) return parameterValuesEqual(value, test);
|
|
129
|
+
const t = test;
|
|
130
|
+
if (t.eq !== void 0 && !parameterValuesEqual(value, t.eq)) return false;
|
|
131
|
+
if (t.ne !== void 0 && parameterValuesEqual(value, t.ne)) return false;
|
|
132
|
+
if (t.in && !t.in.some((option) => parameterValuesEqual(value, option))) return false;
|
|
133
|
+
if (t.notIn && t.notIn.some((option) => parameterValuesEqual(value, option))) return false;
|
|
134
|
+
if (t.truthy !== void 0 && truthy(value) !== t.truthy) return false;
|
|
135
|
+
const n = typeof value === "number" ? value : NaN;
|
|
136
|
+
if (t.gt !== void 0 && !(n > t.gt)) return false;
|
|
137
|
+
if (t.gte !== void 0 && !(n >= t.gte)) return false;
|
|
138
|
+
if (t.lt !== void 0 && !(n < t.lt)) return false;
|
|
139
|
+
if (t.lte !== void 0 && !(n <= t.lte)) return false;
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
function evaluateParameterCondition(condition, values) {
|
|
143
|
+
if (!condition) return true;
|
|
144
|
+
if (typeof condition === "function") {
|
|
145
|
+
try {
|
|
146
|
+
return condition(values) !== false;
|
|
147
|
+
} catch (e2) {
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return Object.entries(condition).every(([id, test]) => testCondition(values[id], test));
|
|
152
|
+
}
|
|
153
|
+
function getParameterVisibility(schema, values) {
|
|
154
|
+
const visible = {};
|
|
155
|
+
const walk = (items, parentVisible) => {
|
|
156
|
+
for (const item of items) {
|
|
157
|
+
const shown = parentVisible && evaluateParameterCondition(item.showIf, values);
|
|
158
|
+
visible[item.id] = shown;
|
|
159
|
+
if (isParameterGroup(item)) walk(item.fields, shown);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
walk(schema.fields, true);
|
|
163
|
+
return visible;
|
|
164
|
+
}
|
|
165
|
+
function getChoiceOptions(field) {
|
|
166
|
+
return field.options.map((o) => typeof o === "object" ? o : { value: o });
|
|
167
|
+
}
|
|
168
|
+
var choiceOptionLabel = (option) => _nullishCoalesce(option.label, () => ( (typeof option.value === "string" ? humanizeParameterId(option.value) : String(option.value))));
|
|
169
|
+
function vectorSize(field) {
|
|
170
|
+
return _nullishCoalesce(field.size, () => ( (Array.isArray(field.default) ? field.default.length : 2)));
|
|
171
|
+
}
|
|
172
|
+
var pick = (bound, i) => typeof bound === "number" ? bound : _optionalChain([bound, 'optionalAccess', _2 => _2[i]]);
|
|
173
|
+
function vectorComponentField(field, index) {
|
|
174
|
+
const labels = _nullishCoalesce(field.labels, () => ( ["x", "y", "z", "w"]));
|
|
175
|
+
return {
|
|
176
|
+
id: `${field.id}.${index}`,
|
|
177
|
+
type: "number",
|
|
178
|
+
label: _nullishCoalesce(labels[index], () => ( String(index))),
|
|
179
|
+
default: _nullishCoalesce(field.default[index], () => ( 0)),
|
|
180
|
+
min: pick(field.min, index),
|
|
181
|
+
max: pick(field.max, index),
|
|
182
|
+
step: field.step,
|
|
183
|
+
unit: field.unit,
|
|
184
|
+
decimals: field.decimals,
|
|
185
|
+
control: "input"
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function parameterStep(field) {
|
|
189
|
+
if (field.step !== void 0 && field.step > 0) return field.type === "int" ? Math.max(1, Math.round(field.step)) : field.step;
|
|
190
|
+
if (field.type === "int") return 1;
|
|
191
|
+
if (field.min !== void 0 && field.max !== void 0 && field.max > field.min) return (field.max - field.min) / 100;
|
|
192
|
+
return 0.01;
|
|
193
|
+
}
|
|
194
|
+
function parameterDecimals(field) {
|
|
195
|
+
if (field.decimals !== void 0) return field.decimals;
|
|
196
|
+
if (field.type === "int") return 0;
|
|
197
|
+
const step = parameterStep(field);
|
|
198
|
+
for (let d = 0; d <= 4; d++) if (Math.abs(Math.round(step * 10 ** d) - step * 10 ** d) < 1e-9) return d;
|
|
199
|
+
return 4;
|
|
200
|
+
}
|
|
201
|
+
var isSliderField = (field) => field.control ? field.control === "slider" : field.min !== void 0 && field.max !== void 0;
|
|
202
|
+
function formatParameterNumber(field, value) {
|
|
203
|
+
const fixed = (Number.isFinite(value) ? value : 0).toFixed(parameterDecimals(field));
|
|
204
|
+
const trimmed = fixed.includes(".") ? fixed.replace(/\.?0+$/, "") : fixed;
|
|
205
|
+
return `${trimmed === "-0" ? "0" : trimmed}${_nullishCoalesce(field.unit, () => ( ""))}`;
|
|
206
|
+
}
|
|
207
|
+
function clampParameterNumber(field, value) {
|
|
208
|
+
let v = Number.isFinite(value) ? value : field.default;
|
|
209
|
+
if (field.min !== void 0) v = Math.max(field.min, v);
|
|
210
|
+
if (field.max !== void 0) v = Math.min(field.max, v);
|
|
211
|
+
if (field.type === "int") return Math.round(v);
|
|
212
|
+
return Math.round(v * 1e6) / 1e6;
|
|
213
|
+
}
|
|
214
|
+
function parseParameterNumberInput(field, text) {
|
|
215
|
+
let t = text.trim();
|
|
216
|
+
if (field.unit && t.endsWith(field.unit)) t = t.slice(0, -field.unit.length).trim();
|
|
217
|
+
if (t === "") return null;
|
|
218
|
+
const n = Number(t.replace(",", "."));
|
|
219
|
+
return Number.isFinite(n) ? clampParameterNumber(field, n) : null;
|
|
220
|
+
}
|
|
221
|
+
function formatParameterValue(field, value) {
|
|
222
|
+
if (value === void 0) return "";
|
|
223
|
+
switch (field.type) {
|
|
224
|
+
case "number":
|
|
225
|
+
case "int":
|
|
226
|
+
return formatParameterNumber(field, value);
|
|
227
|
+
case "boolean":
|
|
228
|
+
return value ? "On" : "Off";
|
|
229
|
+
case "choice": {
|
|
230
|
+
const option = getChoiceOptions(field).find((o) => o.value === value);
|
|
231
|
+
return option ? choiceOptionLabel(option) : String(value);
|
|
232
|
+
}
|
|
233
|
+
case "vector":
|
|
234
|
+
return value.map((v, i) => formatParameterNumber(vectorComponentField(field, i), v)).join(", ");
|
|
235
|
+
default:
|
|
236
|
+
return String(value);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
var describe = (v) => v === null ? "null" : Array.isArray(v) ? "a list" : typeof v === "string" ? `"${v.length > 40 ? `${v.slice(0, 40)}\u2026` : v}"` : String(v);
|
|
240
|
+
function err(field, message, index) {
|
|
241
|
+
return { id: field.id, path: index === void 0 ? `/${field.id}` : `/${field.id}/${index}`, message };
|
|
242
|
+
}
|
|
243
|
+
function numberError(field, name, v, opts, index) {
|
|
244
|
+
if (typeof v !== "number" || !Number.isFinite(v)) return err(field, `${name} must be a number, not ${describe(v)}.`, index);
|
|
245
|
+
if (opts.int && !Number.isInteger(v)) return err(field, `${name} must be a whole number.`, index);
|
|
246
|
+
if (opts.min !== void 0 && v < opts.min) return err(field, `${name} must be at least ${opts.min}.`, index);
|
|
247
|
+
if (opts.max !== void 0 && v > opts.max) return err(field, `${name} must be at most ${opts.max}.`, index);
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
function validateParameterValue(field, value) {
|
|
251
|
+
const name = parameterLabel(field);
|
|
252
|
+
switch (field.type) {
|
|
253
|
+
case "number":
|
|
254
|
+
case "int":
|
|
255
|
+
return numberError(field, name, value, { int: field.type === "int", min: field.min, max: field.max });
|
|
256
|
+
case "boolean":
|
|
257
|
+
return typeof value === "boolean" ? null : err(field, `${name} must be true or false.`);
|
|
258
|
+
case "choice": {
|
|
259
|
+
const options = getChoiceOptions(field);
|
|
260
|
+
if (options.some((o) => o.value === value)) return null;
|
|
261
|
+
return err(field, `${name} must be one of ${options.map((o) => JSON.stringify(o.value)).join(", ")}, not ${describe(value)}.`);
|
|
262
|
+
}
|
|
263
|
+
case "color": {
|
|
264
|
+
const example = field.alpha ? '"#77c16aff"' : '"#77c16a"';
|
|
265
|
+
if (typeof value !== "string" || normalizeHexColor(value, !!field.alpha) === null) {
|
|
266
|
+
return err(field, `${name} must be a hex colour like ${example}.`);
|
|
267
|
+
}
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
case "vector": {
|
|
271
|
+
const size = vectorSize(field);
|
|
272
|
+
if (!Array.isArray(value) || value.length !== size) return err(field, `${name} must be a list of ${size} numbers.`);
|
|
273
|
+
for (let i = 0; i < size; i++) {
|
|
274
|
+
const component = `${name} ${_nullishCoalesce((_nullishCoalesce(field.labels, () => ( ["x", "y", "z", "w"])))[i], () => ( i))}`;
|
|
275
|
+
const e = numberError(field, component, value[i], { int: false, min: pick(field.min, i), max: pick(field.max, i) }, i);
|
|
276
|
+
if (e) return e;
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
case "image":
|
|
281
|
+
return typeof value === "string" ? null : err(field, `${name} must be an image URL (or "" for none).`);
|
|
282
|
+
case "text": {
|
|
283
|
+
if (typeof value !== "string") return err(field, `${name} must be text.`);
|
|
284
|
+
if (field.minLength !== void 0 && value.length < field.minLength) {
|
|
285
|
+
return err(field, `${name} must be at least ${field.minLength} characters.`);
|
|
286
|
+
}
|
|
287
|
+
if (field.maxLength !== void 0 && value.length > field.maxLength) {
|
|
288
|
+
return err(field, `${name} must be at most ${field.maxLength} characters.`);
|
|
289
|
+
}
|
|
290
|
+
if (field.pattern !== void 0) {
|
|
291
|
+
let regex = null;
|
|
292
|
+
try {
|
|
293
|
+
regex = new RegExp(`^(?:${field.pattern})$`, "u");
|
|
294
|
+
} catch (e3) {
|
|
295
|
+
}
|
|
296
|
+
if (regex && !regex.test(value)) return err(field, `${name} must match the pattern ${field.pattern}.`);
|
|
297
|
+
}
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
default:
|
|
301
|
+
return err(field, `${name} has an unknown type.`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function coerceParameterValue(field, raw) {
|
|
305
|
+
let value = raw;
|
|
306
|
+
if (field.type === "int" && typeof raw === "number" && Number.isFinite(raw)) value = Math.round(raw);
|
|
307
|
+
if (field.type === "color" && typeof raw === "string") value = _nullishCoalesce(normalizeHexColor(raw, !!field.alpha), () => ( raw));
|
|
308
|
+
if (field.type === "vector" && Array.isArray(raw)) value = [...raw];
|
|
309
|
+
const error = validateParameterValue(field, value);
|
|
310
|
+
return error ? { ok: false, error } : { ok: true, value };
|
|
311
|
+
}
|
|
312
|
+
function validateParameterValues(schema, values) {
|
|
313
|
+
const errors = [];
|
|
314
|
+
for (const field of getParameterFields(schema)) {
|
|
315
|
+
const value = values[field.id];
|
|
316
|
+
if (value === void 0) {
|
|
317
|
+
errors.push(err(field, `${parameterLabel(field)} is missing.`));
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
const error = validateParameterValue(field, value);
|
|
321
|
+
if (error) errors.push(error);
|
|
322
|
+
}
|
|
323
|
+
return errors;
|
|
324
|
+
}
|
|
325
|
+
function parameterFieldToJsonSchema(field) {
|
|
326
|
+
const description = [parameterLabel(field), field.description].filter(Boolean).join(": ");
|
|
327
|
+
const base = { title: parameterLabel(field), description, default: field.default };
|
|
328
|
+
switch (field.type) {
|
|
329
|
+
case "number":
|
|
330
|
+
case "int": {
|
|
331
|
+
const unit = field.unit ? ` (${field.unit})` : "";
|
|
332
|
+
return {
|
|
333
|
+
...base,
|
|
334
|
+
description: description + unit,
|
|
335
|
+
type: field.type === "int" ? "integer" : "number",
|
|
336
|
+
...field.min !== void 0 ? { minimum: field.min } : {},
|
|
337
|
+
...field.max !== void 0 ? { maximum: field.max } : {}
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
case "boolean":
|
|
341
|
+
return { ...base, type: "boolean" };
|
|
342
|
+
case "choice": {
|
|
343
|
+
const options = getChoiceOptions(field);
|
|
344
|
+
return { ...base, enum: options.map((o) => o.value) };
|
|
345
|
+
}
|
|
346
|
+
case "color":
|
|
347
|
+
return {
|
|
348
|
+
...base,
|
|
349
|
+
type: "string",
|
|
350
|
+
description: `${description} (hex colour${field.alpha ? " with alpha, #rrggbbaa" : ", #rrggbb"})`,
|
|
351
|
+
pattern: field.alpha ? "^#?([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$" : "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
|
|
352
|
+
};
|
|
353
|
+
case "vector": {
|
|
354
|
+
const size = vectorSize(field);
|
|
355
|
+
const items = { type: "number" };
|
|
356
|
+
if (typeof field.min === "number") items.minimum = field.min;
|
|
357
|
+
if (typeof field.max === "number") items.maximum = field.max;
|
|
358
|
+
return { ...base, type: "array", items, minItems: size, maxItems: size };
|
|
359
|
+
}
|
|
360
|
+
case "image":
|
|
361
|
+
return { ...base, type: "string", description: `${description} (image URL, "" for none)` };
|
|
362
|
+
case "text":
|
|
363
|
+
return {
|
|
364
|
+
...base,
|
|
365
|
+
type: "string",
|
|
366
|
+
...field.minLength !== void 0 ? { minLength: field.minLength } : {},
|
|
367
|
+
...field.maxLength !== void 0 ? { maxLength: field.maxLength } : {}
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
function parameterSchemaToJsonSchema(schema) {
|
|
372
|
+
const properties = {};
|
|
373
|
+
for (const field of getParameterFields(schema)) properties[field.id] = parameterFieldToJsonSchema(field);
|
|
374
|
+
return { type: "object", properties, additionalProperties: false };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// src/parameters/keyframes.ts
|
|
378
|
+
var PARAMETER_KEY_TOLERANCE = 0.5;
|
|
379
|
+
function findParameterKeyframe(track, time, tolerance = PARAMETER_KEY_TOLERANCE) {
|
|
380
|
+
if (!track) return -1;
|
|
381
|
+
return track.keyframes.findIndex((k) => Math.abs(k.time - time) <= tolerance);
|
|
382
|
+
}
|
|
383
|
+
function getParameterKeyState(track, time, tolerance = PARAMETER_KEY_TOLERANCE) {
|
|
384
|
+
if (!track || track.keyframes.length === 0) return "none";
|
|
385
|
+
return findParameterKeyframe(track, time, tolerance) >= 0 ? "keyed" : "animated";
|
|
386
|
+
}
|
|
387
|
+
var sortKeys = (keys) => keys.sort((a, b) => a.time - b.time);
|
|
388
|
+
function withParameterKeyframe(track, key, tolerance = PARAMETER_KEY_TOLERANCE) {
|
|
389
|
+
const keyframes = [..._nullishCoalesce(_optionalChain([track, 'optionalAccess', _3 => _3.keyframes]), () => ( []))];
|
|
390
|
+
const at = findParameterKeyframe(track, key.time, tolerance);
|
|
391
|
+
if (at >= 0) keyframes[at] = { ...keyframes[at], value: key.value, ...key.easing !== void 0 ? { easing: key.easing } : {} };
|
|
392
|
+
else keyframes.push({ ...key });
|
|
393
|
+
return { ..._nullishCoalesce(track, () => ( {})), keyframes: sortKeys(keyframes) };
|
|
394
|
+
}
|
|
395
|
+
function withoutParameterKeyframe(track, time, tolerance = PARAMETER_KEY_TOLERANCE) {
|
|
396
|
+
if (!track) return void 0;
|
|
397
|
+
const keyframes = track.keyframes.filter((k) => Math.abs(k.time - time) > tolerance);
|
|
398
|
+
return keyframes.length ? { ...track, keyframes } : void 0;
|
|
399
|
+
}
|
|
400
|
+
function createParameterKeyframeStore(initial = {}) {
|
|
401
|
+
let tracks = { ...initial };
|
|
402
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
403
|
+
const emit = () => listeners.forEach((l) => l());
|
|
404
|
+
return {
|
|
405
|
+
getTrack: (id) => tracks[id],
|
|
406
|
+
getTracks: () => tracks,
|
|
407
|
+
setTrack(id, track) {
|
|
408
|
+
const next = { ...tracks };
|
|
409
|
+
if (track && track.keyframes.length > 0) next[id] = { ...track, keyframes: sortKeys([...track.keyframes]) };
|
|
410
|
+
else delete next[id];
|
|
411
|
+
tracks = next;
|
|
412
|
+
emit();
|
|
413
|
+
},
|
|
414
|
+
setTracks(next, silent) {
|
|
415
|
+
tracks = { ...next };
|
|
416
|
+
if (!silent) emit();
|
|
417
|
+
},
|
|
418
|
+
subscribe(listener) {
|
|
419
|
+
listeners.add(listener);
|
|
420
|
+
return () => listeners.delete(listener);
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// src/parameters/controller.ts
|
|
426
|
+
function seededRandom(seed) {
|
|
427
|
+
let a = seed >>> 0;
|
|
428
|
+
return () => {
|
|
429
|
+
a = a + 1831565813 >>> 0;
|
|
430
|
+
let t = a;
|
|
431
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
432
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
433
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
var unknownMessage = (id, fields) => `There is no parameter "${id}"; expected one of ${fields.map((f) => `"${f.id}"`).join(", ")}.`;
|
|
437
|
+
var ParametersController = (_class = class {
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
__init() {this.fields = []}
|
|
441
|
+
__init2() {this.fieldMap = /* @__PURE__ */ new Map()}
|
|
442
|
+
__init3() {this.defaults = {}}
|
|
443
|
+
__init4() {this.values = {}}
|
|
444
|
+
|
|
445
|
+
__init5() {this.collapsed = {}}
|
|
446
|
+
__init6() {this.errors = {}}
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
__init7() {this.listeners = /* @__PURE__ */ new Set()}
|
|
453
|
+
__init8() {this.cleanups = []}
|
|
454
|
+
__init9() {this.gestureId = null}
|
|
455
|
+
__init10() {this.gestureCount = 0}
|
|
456
|
+
__init11() {this.revision = 0}
|
|
457
|
+
__init12() {this.applying = false}
|
|
458
|
+
|
|
459
|
+
constructor(options) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);_class.prototype.__init6.call(this);_class.prototype.__init7.call(this);_class.prototype.__init8.call(this);_class.prototype.__init9.call(this);_class.prototype.__init10.call(this);_class.prototype.__init11.call(this);_class.prototype.__init12.call(this);_class.prototype.__init13.call(this);_class.prototype.__init14.call(this);
|
|
460
|
+
this.history = _nullishCoalesce(options.history, () => ( new (0, _chunkQKLRAA4Tcjs.HistoryManager)()));
|
|
461
|
+
this.schema = options.schema;
|
|
462
|
+
this.userPresets = (_nullishCoalesce(options.presets, () => ( []))).map((p) => ({ ...p, values: { ...p.values }, builtIn: false }));
|
|
463
|
+
this.time = _nullishCoalesce(options.time, () => ( 0));
|
|
464
|
+
this.keyframes = options.keyframes;
|
|
465
|
+
this.keyTolerance = _nullishCoalesce(options.keyTolerance, () => ( PARAMETER_KEY_TOLERANCE));
|
|
466
|
+
this.random = _nullishCoalesce(options.random, () => ( Math.random));
|
|
467
|
+
this.onChange = options.onChange;
|
|
468
|
+
this.indexSchema(options.schema);
|
|
469
|
+
for (const group of getParameterGroups(this.schema)) if (group.collapsed) this.collapsed[group.id] = true;
|
|
470
|
+
this.values = this.adopt(_nullishCoalesce(options.values, () => ( {})), this.defaults);
|
|
471
|
+
this.state = this.computeState();
|
|
472
|
+
this.cleanups.push(
|
|
473
|
+
this.history.subscribe((h) => {
|
|
474
|
+
const s = this.state;
|
|
475
|
+
if (this.applying || s.canUndo === h.canUndo && s.canRedo === h.canRedo && s.undoLabel === h.undoName && s.redoLabel === h.redoName) return;
|
|
476
|
+
this.notify();
|
|
477
|
+
})
|
|
478
|
+
);
|
|
479
|
+
this.watchKeyframes();
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
watchKeyframes() {
|
|
483
|
+
_optionalChain([this, 'access', _4 => _4.unwatchKeyframes, 'optionalCall', _5 => _5()]);
|
|
484
|
+
this.unwatchKeyframes = _optionalChain([this, 'access', _6 => _6.keyframes, 'optionalAccess', _7 => _7.subscribe, 'optionalCall', _8 => _8(() => {
|
|
485
|
+
if (!this.applying) this.notify();
|
|
486
|
+
})]);
|
|
487
|
+
}
|
|
488
|
+
// -------------------------------------------------------------------------
|
|
489
|
+
// State
|
|
490
|
+
// -------------------------------------------------------------------------
|
|
491
|
+
/** The current snapshot (stable until the next change). */
|
|
492
|
+
__init13() {this.getState = () => this.state}
|
|
493
|
+
/** Calls `listener` after every change. Returns an unsubscribe function. */
|
|
494
|
+
__init14() {this.subscribe = (listener) => {
|
|
495
|
+
this.listeners.add(listener);
|
|
496
|
+
return () => {
|
|
497
|
+
this.listeners.delete(listener);
|
|
498
|
+
};
|
|
499
|
+
}}
|
|
500
|
+
getSchema() {
|
|
501
|
+
return this.schema;
|
|
502
|
+
}
|
|
503
|
+
getField(id) {
|
|
504
|
+
return this.fieldMap.get(id);
|
|
505
|
+
}
|
|
506
|
+
getValue(id) {
|
|
507
|
+
return this.values[id];
|
|
508
|
+
}
|
|
509
|
+
getValues() {
|
|
510
|
+
return this.values;
|
|
511
|
+
}
|
|
512
|
+
indexSchema(schema) {
|
|
513
|
+
const problems = checkParameterSchema(schema);
|
|
514
|
+
if (problems.length) throw new ParameterSchemaError(problems.join(" "));
|
|
515
|
+
this.fields = getParameterFields(schema);
|
|
516
|
+
this.fieldMap = new Map(this.fields.map((f) => [f.id, f]));
|
|
517
|
+
this.defaults = getParameterDefaults(schema);
|
|
518
|
+
}
|
|
519
|
+
/** Values for every field: valid entries of `values`, else `fallback`. Invalid ones are reported. */
|
|
520
|
+
adopt(values, fallback) {
|
|
521
|
+
const out = {};
|
|
522
|
+
for (const field of this.fields) {
|
|
523
|
+
const raw = values[field.id];
|
|
524
|
+
if (raw === void 0) {
|
|
525
|
+
out[field.id] = cloneValue(_nullishCoalesce(fallback[field.id], () => ( field.default)));
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
const result = coerceParameterValue(field, raw);
|
|
529
|
+
if (result.ok) out[field.id] = result.value;
|
|
530
|
+
else {
|
|
531
|
+
out[field.id] = cloneValue(_nullishCoalesce(fallback[field.id], () => ( field.default)));
|
|
532
|
+
this.errors = { ...this.errors, [field.id]: result.error.message };
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return out;
|
|
536
|
+
}
|
|
537
|
+
allPresets() {
|
|
538
|
+
const builtIn = (_nullishCoalesce(this.schema.presets, () => ( []))).map((p) => ({ ...p, builtIn: true }));
|
|
539
|
+
const names = new Set(builtIn.map((p) => p.name));
|
|
540
|
+
return [...builtIn, ...this.userPresets.filter((p) => !names.has(p.name))];
|
|
541
|
+
}
|
|
542
|
+
presetValues(preset, base) {
|
|
543
|
+
const out = { ...base };
|
|
544
|
+
for (const field of this.fields) {
|
|
545
|
+
const raw = preset.values[field.id];
|
|
546
|
+
if (raw === void 0) continue;
|
|
547
|
+
const result = coerceParameterValue(field, raw);
|
|
548
|
+
if (result.ok) out[field.id] = result.value;
|
|
549
|
+
}
|
|
550
|
+
return out;
|
|
551
|
+
}
|
|
552
|
+
computeState() {
|
|
553
|
+
const history = this.history.getState();
|
|
554
|
+
const presets = this.allPresets();
|
|
555
|
+
const keys = {};
|
|
556
|
+
if (this.keyframes) {
|
|
557
|
+
for (const field of this.fields) keys[field.id] = getParameterKeyState(this.keyframes.getTrack(field.id), this.time, this.keyTolerance);
|
|
558
|
+
}
|
|
559
|
+
const same = (a) => this.fields.every((f) => parameterValuesEqual(a[f.id], this.values[f.id]));
|
|
560
|
+
return {
|
|
561
|
+
schema: this.schema,
|
|
562
|
+
fields: this.fields,
|
|
563
|
+
values: this.values,
|
|
564
|
+
defaults: this.defaults,
|
|
565
|
+
visible: getParameterVisibility(this.schema, this.values),
|
|
566
|
+
collapsed: this.collapsed,
|
|
567
|
+
errors: this.errors,
|
|
568
|
+
presets,
|
|
569
|
+
activePreset: _nullishCoalesce(_optionalChain([presets, 'access', _9 => _9.find, 'call', _10 => _10((p) => same(this.presetValues(p, this.defaults))), 'optionalAccess', _11 => _11.name]), () => ( null)),
|
|
570
|
+
modified: !same(this.defaults),
|
|
571
|
+
time: this.time,
|
|
572
|
+
keyframesEnabled: !!this.keyframes,
|
|
573
|
+
keys,
|
|
574
|
+
gesture: this.gestureId,
|
|
575
|
+
canUndo: history.canUndo,
|
|
576
|
+
canRedo: history.canRedo,
|
|
577
|
+
undoLabel: history.undoName,
|
|
578
|
+
redoLabel: history.redoName,
|
|
579
|
+
revision: this.revision
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
notify(changed = []) {
|
|
583
|
+
this.revision++;
|
|
584
|
+
this.state = this.computeState();
|
|
585
|
+
if (changed.length) _optionalChain([this, 'access', _12 => _12.onChange, 'optionalCall', _13 => _13(this.values, changed)]);
|
|
586
|
+
const state = this.state;
|
|
587
|
+
this.listeners.forEach((listener) => {
|
|
588
|
+
try {
|
|
589
|
+
listener(state);
|
|
590
|
+
} catch (error) {
|
|
591
|
+
console.error("[ParametersController] Error in state listener:", error);
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
// -------------------------------------------------------------------------
|
|
596
|
+
// Changes (all edits go through one undoable command shape)
|
|
597
|
+
// -------------------------------------------------------------------------
|
|
598
|
+
apply(change, side) {
|
|
599
|
+
const changed = [];
|
|
600
|
+
this.applying = true;
|
|
601
|
+
try {
|
|
602
|
+
if (change.values) {
|
|
603
|
+
const patch = change.values[side];
|
|
604
|
+
const next = { ...this.values };
|
|
605
|
+
for (const [id, value] of Object.entries(patch)) {
|
|
606
|
+
if (!this.fieldMap.has(id) || value === void 0) continue;
|
|
607
|
+
next[id] = cloneValue(value);
|
|
608
|
+
changed.push(id);
|
|
609
|
+
if (id in this.errors) {
|
|
610
|
+
const { [id]: _, ...rest } = this.errors;
|
|
611
|
+
this.errors = rest;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
this.values = next;
|
|
615
|
+
}
|
|
616
|
+
if (change.presets) this.userPresets = change.presets[side].map((p) => ({ ...p }));
|
|
617
|
+
if (change.tracks && this.keyframes) {
|
|
618
|
+
for (const [id, t] of Object.entries(change.tracks)) this.keyframes.setTrack(id, t[side]);
|
|
619
|
+
}
|
|
620
|
+
} finally {
|
|
621
|
+
this.applying = false;
|
|
622
|
+
}
|
|
623
|
+
this.notify(changed);
|
|
624
|
+
}
|
|
625
|
+
command(name, change, tag) {
|
|
626
|
+
return {
|
|
627
|
+
name,
|
|
628
|
+
tag,
|
|
629
|
+
execute: () => this.apply(change, "after"),
|
|
630
|
+
undo: () => this.apply(change, "before"),
|
|
631
|
+
redo: () => this.apply(change, "after")
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
/** Track updates that keep keys at the playhead in step with new values. */
|
|
635
|
+
keyUpdates(after) {
|
|
636
|
+
if (!this.keyframes) return void 0;
|
|
637
|
+
let tracks;
|
|
638
|
+
for (const [id, value] of Object.entries(after)) {
|
|
639
|
+
const track = this.keyframes.getTrack(id);
|
|
640
|
+
if (value === void 0 || findParameterKeyframe(track, this.time, this.keyTolerance) < 0) continue;
|
|
641
|
+
(tracks ??= {})[id] = { before: track, after: withParameterKeyframe(track, { time: this.time, value }, this.keyTolerance) };
|
|
642
|
+
}
|
|
643
|
+
return tracks;
|
|
644
|
+
}
|
|
645
|
+
record(name, after, extra = {}) {
|
|
646
|
+
const before = {};
|
|
647
|
+
for (const id of Object.keys(after)) before[id] = cloneValue(this.values[id]);
|
|
648
|
+
const tracks = { ..._nullishCoalesce(this.keyUpdates(after), () => ( {})), ..._nullishCoalesce(extra.tracks, () => ( {})) };
|
|
649
|
+
const change = { values: { before, after }, ...extra, ...Object.keys(tracks).length ? { tracks } : {} };
|
|
650
|
+
this.history.execute(this.command(name, change));
|
|
651
|
+
}
|
|
652
|
+
reject(errors) {
|
|
653
|
+
const next = { ...this.errors };
|
|
654
|
+
for (const e of errors) next[e.id] = e.message;
|
|
655
|
+
this.errors = next;
|
|
656
|
+
this.notify();
|
|
657
|
+
}
|
|
658
|
+
/** Only the entries of `values` that differ from the current values. */
|
|
659
|
+
diff(values) {
|
|
660
|
+
const out = {};
|
|
661
|
+
for (const [id, value] of Object.entries(values)) {
|
|
662
|
+
if (value !== void 0 && !parameterValuesEqual(this.values[id], value)) out[id] = value;
|
|
663
|
+
}
|
|
664
|
+
return out;
|
|
665
|
+
}
|
|
666
|
+
clearErrors(ids) {
|
|
667
|
+
if (!ids.some((id) => id in this.errors)) return false;
|
|
668
|
+
const next = { ...this.errors };
|
|
669
|
+
for (const id of ids) delete next[id];
|
|
670
|
+
this.errors = next;
|
|
671
|
+
return true;
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Sets one value after validating it (`int`s round, colours normalise). Invalid values are
|
|
675
|
+
* not stored; the error is returned and shown in `state.errors`. One undo step, or part of
|
|
676
|
+
* the current gesture (see `beginGesture`).
|
|
677
|
+
*/
|
|
678
|
+
setValue(id, value, options = {}) {
|
|
679
|
+
const field = this.fieldMap.get(id);
|
|
680
|
+
if (!field) {
|
|
681
|
+
const message = unknownMessage(id, this.fields);
|
|
682
|
+
return { ok: false, error: { id, path: `/${id}`, message }, message };
|
|
683
|
+
}
|
|
684
|
+
const result = coerceParameterValue(field, value);
|
|
685
|
+
if (!result.ok) {
|
|
686
|
+
this.reject([result.error]);
|
|
687
|
+
return { ok: false, error: result.error, message: result.error.message };
|
|
688
|
+
}
|
|
689
|
+
if (parameterValuesEqual(this.values[id], result.value)) {
|
|
690
|
+
if (this.clearErrors([id])) this.notify();
|
|
691
|
+
return { ok: true, value: result.value };
|
|
692
|
+
}
|
|
693
|
+
const name = _nullishCoalesce(options.label, () => ( `Change ${parameterLabel(field)}`));
|
|
694
|
+
if (this.gestureId !== null) {
|
|
695
|
+
const after = { [id]: result.value };
|
|
696
|
+
const before = { [id]: cloneValue(this.values[id]) };
|
|
697
|
+
const tracks = this.keyUpdates(after);
|
|
698
|
+
const change = { values: { before, after }, ...tracks ? { tracks } : {} };
|
|
699
|
+
this.history.executeMerged(this.command(name, change, `parameters.set:${id}`), {
|
|
700
|
+
key: `parameters.gesture:${this.gestureId}:${this.gestureCount}`
|
|
701
|
+
});
|
|
702
|
+
} else {
|
|
703
|
+
this.record(name, { [id]: result.value });
|
|
704
|
+
}
|
|
705
|
+
return { ok: true, value: result.value };
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Sets several values as one undo step. All-or-nothing: if any id is unknown or any value
|
|
709
|
+
* invalid, nothing changes and every error is returned.
|
|
710
|
+
*/
|
|
711
|
+
setValues(values, options = {}) {
|
|
712
|
+
const errors = [];
|
|
713
|
+
const coerced = {};
|
|
714
|
+
for (const [id, raw] of Object.entries(values)) {
|
|
715
|
+
if (raw === void 0) continue;
|
|
716
|
+
const field = this.fieldMap.get(id);
|
|
717
|
+
if (!field) {
|
|
718
|
+
errors.push({ id, path: `/${id}`, message: unknownMessage(id, this.fields) });
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
721
|
+
const result = coerceParameterValue(field, raw);
|
|
722
|
+
if (result.ok) coerced[id] = result.value;
|
|
723
|
+
else errors.push(result.error);
|
|
724
|
+
}
|
|
725
|
+
if (errors.length) {
|
|
726
|
+
this.reject(errors.filter((e) => this.fieldMap.has(e.id)));
|
|
727
|
+
return { ok: false, errors, message: errors.map((e) => e.message).join(" ") };
|
|
728
|
+
}
|
|
729
|
+
const after = this.diff(coerced);
|
|
730
|
+
const changed = Object.keys(after);
|
|
731
|
+
if (changed.length) {
|
|
732
|
+
const label = _nullishCoalesce(options.label, () => ( (changed.length === 1 ? `Change ${parameterLabel(this.fieldMap.get(changed[0]))}` : "Change parameters")));
|
|
733
|
+
this.record(label, after);
|
|
734
|
+
} else if (this.clearErrors(Object.keys(coerced))) this.notify();
|
|
735
|
+
return { ok: true, values: coerced, changed };
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Starts a continuous edit of `id` (a slider drag): every `setValue` until `endGesture()`
|
|
739
|
+
* merges into one undo step.
|
|
740
|
+
*/
|
|
741
|
+
beginGesture(id) {
|
|
742
|
+
if (this.gestureId === id) return;
|
|
743
|
+
this.history.endGesture();
|
|
744
|
+
this.gestureId = id;
|
|
745
|
+
this.gestureCount++;
|
|
746
|
+
this.notify();
|
|
747
|
+
}
|
|
748
|
+
/** Ends the current gesture; the next change is a new undo step. */
|
|
749
|
+
endGesture() {
|
|
750
|
+
if (this.gestureId === null) return;
|
|
751
|
+
this.gestureId = null;
|
|
752
|
+
this.history.endGesture();
|
|
753
|
+
this.notify();
|
|
754
|
+
}
|
|
755
|
+
/** Puts fields (default: all) back to their defaults as one undo step. */
|
|
756
|
+
reset(ids) {
|
|
757
|
+
const targets = _nullishCoalesce(ids, () => ( this.fields.map((f) => f.id)));
|
|
758
|
+
const unknown = targets.find((id) => !this.fieldMap.has(id));
|
|
759
|
+
if (unknown) return { ok: false, message: unknownMessage(unknown, this.fields) };
|
|
760
|
+
const after = this.diff(Object.fromEntries(targets.map((id) => [id, cloneValue(this.defaults[id])])));
|
|
761
|
+
const changed = Object.keys(after);
|
|
762
|
+
if (changed.length) {
|
|
763
|
+
const label = ids && changed.length === 1 ? `Reset ${parameterLabel(this.fieldMap.get(changed[0]))}` : "Reset parameters";
|
|
764
|
+
this.record(label, after);
|
|
765
|
+
} else if (this.clearErrors(targets)) this.notify();
|
|
766
|
+
return { ok: true };
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Picks random values within each field's range, snapped to its step, as one undo step.
|
|
770
|
+
* Skips hidden fields, `randomize: false`, `readOnly`, image and text fields, and numbers
|
|
771
|
+
* without both `min` and `max`.
|
|
772
|
+
*/
|
|
773
|
+
randomize(options = {}) {
|
|
774
|
+
const rand = _nullishCoalesce(options.random, () => ( (options.seed !== void 0 ? seededRandom(options.seed) : this.random)));
|
|
775
|
+
const visible = this.state.visible;
|
|
776
|
+
const explicit = options.ids !== void 0;
|
|
777
|
+
const errors = [];
|
|
778
|
+
const after = {};
|
|
779
|
+
for (const id of _nullishCoalesce(options.ids, () => ( this.fields.map((f) => f.id)))) {
|
|
780
|
+
const field = this.fieldMap.get(id);
|
|
781
|
+
if (!field) {
|
|
782
|
+
errors.push({ id, path: `/${id}`, message: unknownMessage(id, this.fields) });
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
785
|
+
if (!explicit && (!visible[id] || field.randomize === false || field.readOnly)) continue;
|
|
786
|
+
const value = randomValue(field, this.values[id], rand);
|
|
787
|
+
if (value !== void 0) after[id] = value;
|
|
788
|
+
}
|
|
789
|
+
if (errors.length) return { ok: false, errors, message: errors.map((e) => e.message).join(" ") };
|
|
790
|
+
const diff = this.diff(after);
|
|
791
|
+
const changed = Object.keys(diff);
|
|
792
|
+
if (changed.length) this.record("Randomize parameters", diff);
|
|
793
|
+
return { ok: true, values: after, changed };
|
|
794
|
+
}
|
|
795
|
+
// -------------------------------------------------------------------------
|
|
796
|
+
// Presets
|
|
797
|
+
// -------------------------------------------------------------------------
|
|
798
|
+
/** Applies a preset as one undo step. Fields it leaves out return to their defaults unless `merge`. */
|
|
799
|
+
applyPreset(name, options = {}) {
|
|
800
|
+
const preset = this.allPresets().find((p) => p.name === name);
|
|
801
|
+
if (!preset) return { ok: false, message: this.unknownPreset(name) };
|
|
802
|
+
const target = this.presetValues(preset, options.merge ? this.values : this.defaults);
|
|
803
|
+
const after = this.diff(target);
|
|
804
|
+
if (Object.keys(after).length) this.record(`Apply preset "${name}"`, after);
|
|
805
|
+
return { ok: true };
|
|
806
|
+
}
|
|
807
|
+
/** Saves the current values (all fields) as a user preset, replacing one with the same name. Undoable. */
|
|
808
|
+
savePreset(name, options = {}) {
|
|
809
|
+
const trimmed = name.trim();
|
|
810
|
+
if (!trimmed) return { ok: false, message: "A preset needs a name." };
|
|
811
|
+
if ((_nullishCoalesce(this.schema.presets, () => ( []))).some((p) => p.name === trimmed)) {
|
|
812
|
+
return { ok: false, message: `"${trimmed}" is a built-in preset; save under another name.` };
|
|
813
|
+
}
|
|
814
|
+
const preset = { name: trimmed, values: Object.fromEntries(this.fields.map((f) => [f.id, cloneValue(this.values[f.id])])) };
|
|
815
|
+
if (options.description) preset.description = options.description;
|
|
816
|
+
const before = this.userPresets;
|
|
817
|
+
const at = before.findIndex((p) => p.name === trimmed);
|
|
818
|
+
const after = at >= 0 ? before.map((p, i) => i === at ? preset : p) : [...before, preset];
|
|
819
|
+
this.history.execute(this.command(`Save preset "${trimmed}"`, { presets: { before, after } }));
|
|
820
|
+
return { ok: true };
|
|
821
|
+
}
|
|
822
|
+
/** Deletes a user preset (built-in presets cannot be deleted). Undoable. */
|
|
823
|
+
deletePreset(name) {
|
|
824
|
+
if ((_nullishCoalesce(this.schema.presets, () => ( []))).some((p) => p.name === name)) {
|
|
825
|
+
return { ok: false, message: `"${name}" is a built-in preset and cannot be deleted.` };
|
|
826
|
+
}
|
|
827
|
+
const before = this.userPresets;
|
|
828
|
+
if (!before.some((p) => p.name === name)) return { ok: false, message: this.unknownPreset(name) };
|
|
829
|
+
const after = before.filter((p) => p.name !== name);
|
|
830
|
+
this.history.execute(this.command(`Delete preset "${name}"`, { presets: { before, after } }));
|
|
831
|
+
return { ok: true };
|
|
832
|
+
}
|
|
833
|
+
unknownPreset(name) {
|
|
834
|
+
const names = this.allPresets().map((p) => `"${p.name}"`);
|
|
835
|
+
return `There is no preset "${name}"; ${names.length ? `expected one of ${names.join(", ")}` : "none are saved yet"}.`;
|
|
836
|
+
}
|
|
837
|
+
// -------------------------------------------------------------------------
|
|
838
|
+
// Keyframes
|
|
839
|
+
// -------------------------------------------------------------------------
|
|
840
|
+
/** Moves the playhead. `values` (e.g. evaluated from the tracks) are shown without an undo step. */
|
|
841
|
+
setTime(time, values) {
|
|
842
|
+
const t = Number.isFinite(time) ? Math.max(0, time) : 0;
|
|
843
|
+
const changed = [];
|
|
844
|
+
if (values) {
|
|
845
|
+
const next = { ...this.values };
|
|
846
|
+
for (const [id, raw] of Object.entries(values)) {
|
|
847
|
+
const field = this.fieldMap.get(id);
|
|
848
|
+
if (!field || raw === void 0) continue;
|
|
849
|
+
const result = coerceParameterValue(field, raw);
|
|
850
|
+
if (result.ok && !parameterValuesEqual(next[id], result.value)) {
|
|
851
|
+
next[id] = result.value;
|
|
852
|
+
changed.push(id);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
this.values = next;
|
|
856
|
+
}
|
|
857
|
+
if (t === this.time && changed.length === 0) return;
|
|
858
|
+
this.time = t;
|
|
859
|
+
this.notify(changed);
|
|
860
|
+
}
|
|
861
|
+
/** `keyed`, `animated` or `none` for a field at `time` (default: the playhead). */
|
|
862
|
+
keyState(id, time = this.time) {
|
|
863
|
+
if (!this.keyframes) return "none";
|
|
864
|
+
return getParameterKeyState(this.keyframes.getTrack(id), time, this.keyTolerance);
|
|
865
|
+
}
|
|
866
|
+
/** Attaches (or with `undefined` detaches) a keyframes adapter. */
|
|
867
|
+
setKeyframes(keyframes) {
|
|
868
|
+
this.keyframes = keyframes;
|
|
869
|
+
this.watchKeyframes();
|
|
870
|
+
this.notify();
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Adds a keyframe with the field's current value at `time` (default: the playhead), or
|
|
874
|
+
* removes the one already there. One undo step.
|
|
875
|
+
*/
|
|
876
|
+
toggleKeyframe(id, time = this.time) {
|
|
877
|
+
if (!this.keyframes) return { ok: false, message: "Keyframes are not available here." };
|
|
878
|
+
const field = this.fieldMap.get(id);
|
|
879
|
+
if (!field) return { ok: false, message: unknownMessage(id, this.fields) };
|
|
880
|
+
const before = this.keyframes.getTrack(id);
|
|
881
|
+
const keyed = findParameterKeyframe(before, time, this.keyTolerance) >= 0;
|
|
882
|
+
const after = keyed ? withoutParameterKeyframe(before, time, this.keyTolerance) : withParameterKeyframe(before, { time, value: cloneValue(this.values[id]) }, this.keyTolerance);
|
|
883
|
+
const label = `${keyed ? "Remove keyframe from" : "Add keyframe to"} ${parameterLabel(field)}`;
|
|
884
|
+
this.history.execute(this.command(label, { tracks: { [id]: { before, after } } }));
|
|
885
|
+
return { ok: true, keyed: !keyed };
|
|
886
|
+
}
|
|
887
|
+
// -------------------------------------------------------------------------
|
|
888
|
+
// Groups, schema, persistence, history
|
|
889
|
+
// -------------------------------------------------------------------------
|
|
890
|
+
/** Collapses or expands a group (view state; not an undo step). */
|
|
891
|
+
setCollapsed(groupId, collapsed) {
|
|
892
|
+
if (!!this.collapsed[groupId] === collapsed) return;
|
|
893
|
+
const next = { ...this.collapsed };
|
|
894
|
+
if (collapsed) next[groupId] = true;
|
|
895
|
+
else delete next[groupId];
|
|
896
|
+
this.collapsed = next;
|
|
897
|
+
this.notify();
|
|
898
|
+
}
|
|
899
|
+
toggleCollapsed(groupId) {
|
|
900
|
+
this.setCollapsed(groupId, !this.collapsed[groupId]);
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Replaces the schema (e.g. after a shader recompiles). Values that still fit their field
|
|
904
|
+
* are kept; others reset to the new default. Not an undo step; throws a
|
|
905
|
+
* `ParameterSchemaError` for an invalid schema.
|
|
906
|
+
*/
|
|
907
|
+
setSchema(schema) {
|
|
908
|
+
const old = this.values;
|
|
909
|
+
this.indexSchema(schema);
|
|
910
|
+
this.schema = schema;
|
|
911
|
+
const kept = {};
|
|
912
|
+
for (const field of this.fields) {
|
|
913
|
+
const result = old[field.id] === void 0 ? null : coerceParameterValue(field, old[field.id]);
|
|
914
|
+
kept[field.id] = _optionalChain([result, 'optionalAccess', _14 => _14.ok]) ? result.value : cloneValue(this.defaults[field.id]);
|
|
915
|
+
}
|
|
916
|
+
const changed = this.fields.filter((f) => !parameterValuesEqual(old[f.id], kept[f.id])).map((f) => f.id);
|
|
917
|
+
this.values = kept;
|
|
918
|
+
this.errors = Object.fromEntries(Object.entries(this.errors).filter(([id]) => this.fieldMap.has(id)));
|
|
919
|
+
for (const group of getParameterGroups(schema)) if (group.collapsed && !(group.id in this.collapsed)) this.collapsed[group.id] = true;
|
|
920
|
+
this.collapsed = { ...this.collapsed };
|
|
921
|
+
this.notify(changed);
|
|
922
|
+
}
|
|
923
|
+
/** Values and user presets, for saving. */
|
|
924
|
+
toJSON() {
|
|
925
|
+
return {
|
|
926
|
+
values: Object.fromEntries(this.fields.map((f) => [f.id, cloneValue(this.values[f.id])])),
|
|
927
|
+
presets: this.userPresets.map(({ builtIn: _, ...p }) => ({ ...p, values: { ...p.values } }))
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Replaces values (and user presets, when given) without an undo step, e.g. when opening a
|
|
932
|
+
* document. Missing values use defaults; invalid ones use defaults and are reported in
|
|
933
|
+
* `state.errors`. Clears the history when `clearHistory` is true.
|
|
934
|
+
*/
|
|
935
|
+
load(snapshot, options = {}) {
|
|
936
|
+
const old = this.values;
|
|
937
|
+
this.errors = {};
|
|
938
|
+
this.values = this.adopt(_nullishCoalesce(snapshot.values, () => ( {})), this.defaults);
|
|
939
|
+
if (snapshot.presets) this.userPresets = snapshot.presets.map((p) => ({ ...p, values: { ...p.values }, builtIn: false }));
|
|
940
|
+
const changed = this.fields.filter((f) => !parameterValuesEqual(old[f.id], this.values[f.id])).map((f) => f.id);
|
|
941
|
+
if (options.clearHistory) {
|
|
942
|
+
this.applying = true;
|
|
943
|
+
try {
|
|
944
|
+
this.history.clear();
|
|
945
|
+
} finally {
|
|
946
|
+
this.applying = false;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
this.notify(changed);
|
|
950
|
+
}
|
|
951
|
+
undo() {
|
|
952
|
+
this.endGesture();
|
|
953
|
+
return this.history.undo();
|
|
954
|
+
}
|
|
955
|
+
redo() {
|
|
956
|
+
this.endGesture();
|
|
957
|
+
return this.history.redo();
|
|
958
|
+
}
|
|
959
|
+
/** Stops listening to the history and keyframes adapter. */
|
|
960
|
+
destroy() {
|
|
961
|
+
this.cleanups.splice(0).forEach((fn) => fn());
|
|
962
|
+
_optionalChain([this, 'access', _15 => _15.unwatchKeyframes, 'optionalCall', _16 => _16()]);
|
|
963
|
+
this.unwatchKeyframes = void 0;
|
|
964
|
+
this.listeners.clear();
|
|
965
|
+
}
|
|
966
|
+
}, _class);
|
|
967
|
+
function createParametersController(options) {
|
|
968
|
+
return new ParametersController(options);
|
|
969
|
+
}
|
|
970
|
+
function randomNumber(rand, min, max, step, int) {
|
|
971
|
+
const raw = min + rand() * (max - min);
|
|
972
|
+
const snapped = step > 0 ? min + Math.round((raw - min) / step) * step : raw;
|
|
973
|
+
const v = Math.min(max, Math.max(min, snapped));
|
|
974
|
+
return int ? Math.round(v) : Math.round(v * 1e6) / 1e6;
|
|
975
|
+
}
|
|
976
|
+
function randomValue(field, current, rand) {
|
|
977
|
+
switch (field.type) {
|
|
978
|
+
case "number":
|
|
979
|
+
case "int":
|
|
980
|
+
if (field.min === void 0 || field.max === void 0) return void 0;
|
|
981
|
+
return randomNumber(rand, field.min, field.max, parameterStep(field), field.type === "int");
|
|
982
|
+
case "boolean":
|
|
983
|
+
return rand() < 0.5;
|
|
984
|
+
case "choice": {
|
|
985
|
+
const options = getChoiceOptions(field);
|
|
986
|
+
return options[Math.min(options.length - 1, Math.floor(rand() * options.length))].value;
|
|
987
|
+
}
|
|
988
|
+
case "color": {
|
|
989
|
+
const rgb = Array.from({ length: 3 }, () => Math.floor(rand() * 256).toString(16).padStart(2, "0")).join("");
|
|
990
|
+
const alpha = field.alpha ? (_nullishCoalesce(normalizeHexColor(String(_nullishCoalesce(current, () => ( field.default))), true), () => ( "#000000ff"))).slice(7) : "";
|
|
991
|
+
return `#${rgb}${alpha}`;
|
|
992
|
+
}
|
|
993
|
+
case "vector": {
|
|
994
|
+
const out = [];
|
|
995
|
+
for (let i = 0; i < vectorSize(field); i++) {
|
|
996
|
+
const c = vectorComponentField(field, i);
|
|
997
|
+
const now = Array.isArray(current) ? current[i] : c.default;
|
|
998
|
+
out.push(c.min === void 0 || c.max === void 0 ? now : randomNumber(rand, c.min, c.max, parameterStep(c), false));
|
|
999
|
+
}
|
|
1000
|
+
return out;
|
|
1001
|
+
}
|
|
1002
|
+
default:
|
|
1003
|
+
return void 0;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// src/parameters/commands.ts
|
|
1008
|
+
var fail = (message) => {
|
|
1009
|
+
throw new Error(message);
|
|
1010
|
+
};
|
|
1011
|
+
var IDS = { type: "array", items: { type: "string" }, description: "Parameter ids (default: all)." };
|
|
1012
|
+
function createParameterCommands(controller, options = {}) {
|
|
1013
|
+
const prefix = _nullishCoalesce(options.prefix, () => ( "parameters."));
|
|
1014
|
+
const group = _nullishCoalesce(_nullishCoalesce(options.group, () => ( controller.getSchema().title)), () => ( "Parameters"));
|
|
1015
|
+
const base = { group, ...options.scope ? { scope: options.scope } : {} };
|
|
1016
|
+
const presetNames = () => controller.getState().presets.map((p) => p.name);
|
|
1017
|
+
const set = {
|
|
1018
|
+
...base,
|
|
1019
|
+
id: `${prefix}set`,
|
|
1020
|
+
label: "Set Parameters\u2026",
|
|
1021
|
+
keywords: ["change", "value", "control", "slider"],
|
|
1022
|
+
description: "Sets one or more parameter values as one undo step. Invalid values change nothing and return why.",
|
|
1023
|
+
// A getter so agents always see the current schema (it can change with `setSchema`).
|
|
1024
|
+
get params() {
|
|
1025
|
+
return {
|
|
1026
|
+
type: "object",
|
|
1027
|
+
properties: { values: { ...parameterSchemaToJsonSchema(controller.getSchema()), description: "Parameter id \u2192 new value." } },
|
|
1028
|
+
required: ["values"]
|
|
1029
|
+
};
|
|
1030
|
+
},
|
|
1031
|
+
run: (_ctx, params) => {
|
|
1032
|
+
const result = controller.setValues(params.values);
|
|
1033
|
+
if (!result.ok) fail(result.message);
|
|
1034
|
+
return { changed: result.ok ? result.changed : [], values: controller.getValues() };
|
|
1035
|
+
}
|
|
1036
|
+
};
|
|
1037
|
+
const commands = [
|
|
1038
|
+
{
|
|
1039
|
+
...base,
|
|
1040
|
+
id: `${prefix}list`,
|
|
1041
|
+
label: "List Parameters",
|
|
1042
|
+
readOnly: true,
|
|
1043
|
+
keywords: ["controls", "values", "schema"],
|
|
1044
|
+
description: "Lists every parameter with its type, constraints, current value and default.",
|
|
1045
|
+
run: () => {
|
|
1046
|
+
const state = controller.getState();
|
|
1047
|
+
return {
|
|
1048
|
+
parameters: state.fields.map((field) => ({
|
|
1049
|
+
id: field.id,
|
|
1050
|
+
label: parameterLabel(field),
|
|
1051
|
+
type: field.type,
|
|
1052
|
+
...field.description ? { description: field.description } : {},
|
|
1053
|
+
value: state.values[field.id],
|
|
1054
|
+
default: state.defaults[field.id],
|
|
1055
|
+
visible: state.visible[field.id] !== false,
|
|
1056
|
+
...state.keyframesEnabled ? { keyframe: state.keys[field.id] } : {},
|
|
1057
|
+
schema: {
|
|
1058
|
+
...parameterFieldToJsonSchema(field),
|
|
1059
|
+
...field.type === "choice" ? { options: getChoiceOptions(field) } : {},
|
|
1060
|
+
...field.type === "vector" ? { size: vectorSize(field) } : {}
|
|
1061
|
+
}
|
|
1062
|
+
})),
|
|
1063
|
+
presets: presetNames(),
|
|
1064
|
+
time: state.time
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
},
|
|
1068
|
+
set,
|
|
1069
|
+
{
|
|
1070
|
+
...base,
|
|
1071
|
+
id: `${prefix}reset`,
|
|
1072
|
+
label: "Reset Parameters",
|
|
1073
|
+
keywords: ["default", "revert", "clear"],
|
|
1074
|
+
description: "Puts parameters back to their defaults.",
|
|
1075
|
+
params: { type: "object", properties: { ids: IDS } },
|
|
1076
|
+
enabled: (_ctx, params) => _optionalChain([params, 'optionalAccess', _17 => _17.ids]) || controller.getState().modified ? true : "Every parameter is already at its default.",
|
|
1077
|
+
run: (_ctx, params) => {
|
|
1078
|
+
const result = controller.reset(_optionalChain([params, 'optionalAccess', _18 => _18.ids]));
|
|
1079
|
+
if (!result.ok) fail(result.message);
|
|
1080
|
+
return { values: controller.getValues() };
|
|
1081
|
+
}
|
|
1082
|
+
},
|
|
1083
|
+
{
|
|
1084
|
+
...base,
|
|
1085
|
+
id: `${prefix}randomize`,
|
|
1086
|
+
label: "Randomize Parameters",
|
|
1087
|
+
keywords: ["random", "shuffle", "surprise", "explore"],
|
|
1088
|
+
description: "Picks random values within each parameter range (visible fields only unless `ids` is given).",
|
|
1089
|
+
params: {
|
|
1090
|
+
type: "object",
|
|
1091
|
+
properties: { ids: IDS, seed: { type: "integer", description: "Seed for a repeatable result." } }
|
|
1092
|
+
},
|
|
1093
|
+
run: (_ctx, params) => {
|
|
1094
|
+
const result = controller.randomize({ ids: _optionalChain([params, 'optionalAccess', _19 => _19.ids]), seed: _optionalChain([params, 'optionalAccess', _20 => _20.seed]) });
|
|
1095
|
+
if (!result.ok) fail(result.message);
|
|
1096
|
+
return { changed: result.ok ? result.changed : [], values: controller.getValues() };
|
|
1097
|
+
}
|
|
1098
|
+
},
|
|
1099
|
+
{
|
|
1100
|
+
...base,
|
|
1101
|
+
id: `${prefix}applyPreset`,
|
|
1102
|
+
label: "Apply Parameter Preset\u2026",
|
|
1103
|
+
keywords: ["preset", "look", "style"],
|
|
1104
|
+
description: "Applies a preset; parameters it leaves out return to their defaults unless `merge` is true.",
|
|
1105
|
+
params: {
|
|
1106
|
+
type: "object",
|
|
1107
|
+
properties: { name: { type: "string", description: "Preset name (see parameters list)." }, merge: { type: "boolean" } },
|
|
1108
|
+
required: ["name"]
|
|
1109
|
+
},
|
|
1110
|
+
enabled: () => presetNames().length ? true : "There are no presets.",
|
|
1111
|
+
run: (_ctx, params) => {
|
|
1112
|
+
const result = controller.applyPreset(params.name, { merge: params.merge });
|
|
1113
|
+
if (!result.ok) fail(result.message);
|
|
1114
|
+
return { values: controller.getValues() };
|
|
1115
|
+
}
|
|
1116
|
+
},
|
|
1117
|
+
{
|
|
1118
|
+
...base,
|
|
1119
|
+
id: `${prefix}savePreset`,
|
|
1120
|
+
label: "Save Parameter Preset\u2026",
|
|
1121
|
+
keywords: ["preset", "store", "remember"],
|
|
1122
|
+
description: "Saves the current values as a preset (replacing a user preset with the same name).",
|
|
1123
|
+
params: {
|
|
1124
|
+
type: "object",
|
|
1125
|
+
properties: { name: { type: "string", minLength: 1 }, description: { type: "string" } },
|
|
1126
|
+
required: ["name"]
|
|
1127
|
+
},
|
|
1128
|
+
run: (_ctx, params) => {
|
|
1129
|
+
const result = controller.savePreset(params.name, { description: params.description });
|
|
1130
|
+
if (!result.ok) fail(result.message);
|
|
1131
|
+
return { presets: presetNames() };
|
|
1132
|
+
}
|
|
1133
|
+
},
|
|
1134
|
+
{
|
|
1135
|
+
...base,
|
|
1136
|
+
id: `${prefix}deletePreset`,
|
|
1137
|
+
label: "Delete Parameter Preset\u2026",
|
|
1138
|
+
keywords: ["preset", "remove"],
|
|
1139
|
+
params: { type: "object", properties: { name: { type: "string" } }, required: ["name"] },
|
|
1140
|
+
enabled: () => controller.getState().presets.some((p) => !p.builtIn) ? true : "There are no saved presets.",
|
|
1141
|
+
run: (_ctx, params) => {
|
|
1142
|
+
const result = controller.deletePreset(params.name);
|
|
1143
|
+
if (!result.ok) fail(result.message);
|
|
1144
|
+
return { presets: presetNames() };
|
|
1145
|
+
}
|
|
1146
|
+
},
|
|
1147
|
+
{
|
|
1148
|
+
...base,
|
|
1149
|
+
id: `${prefix}toggleKeyframe`,
|
|
1150
|
+
label: "Toggle Parameter Keyframe\u2026",
|
|
1151
|
+
keywords: ["key", "animate", "diamond"],
|
|
1152
|
+
description: "Adds a keyframe with the current value at `time` (default: the playhead), or removes the one there.",
|
|
1153
|
+
params: {
|
|
1154
|
+
type: "object",
|
|
1155
|
+
properties: { id: { type: "string", description: "Parameter id." }, time: { type: "number", minimum: 0, description: "Time in ms." } },
|
|
1156
|
+
required: ["id"]
|
|
1157
|
+
},
|
|
1158
|
+
enabled: () => controller.getState().keyframesEnabled ? true : "Keyframes are not available here.",
|
|
1159
|
+
run: (_ctx, params) => {
|
|
1160
|
+
const result = controller.toggleKeyframe(params.id, params.time);
|
|
1161
|
+
if (!result.ok) fail(result.message);
|
|
1162
|
+
return { keyed: result.ok && result.keyed, time: _nullishCoalesce(params.time, () => ( controller.getState().time)) };
|
|
1163
|
+
}
|
|
1164
|
+
},
|
|
1165
|
+
{
|
|
1166
|
+
...base,
|
|
1167
|
+
id: `${prefix}undo`,
|
|
1168
|
+
label: "Undo Parameter Change",
|
|
1169
|
+
hidden: true,
|
|
1170
|
+
enabled: () => controller.getState().canUndo ? true : "Nothing to undo.",
|
|
1171
|
+
run: () => controller.undo()
|
|
1172
|
+
},
|
|
1173
|
+
{
|
|
1174
|
+
...base,
|
|
1175
|
+
id: `${prefix}redo`,
|
|
1176
|
+
label: "Redo Parameter Change",
|
|
1177
|
+
hidden: true,
|
|
1178
|
+
enabled: () => controller.getState().canRedo ? true : "Nothing to redo.",
|
|
1179
|
+
run: () => controller.redo()
|
|
1180
|
+
}
|
|
1181
|
+
];
|
|
1182
|
+
return commands;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
// src/parameters/shader.ts
|
|
1186
|
+
var SIZES = { vec2: 2, vec3: 3, vec4: 4 };
|
|
1187
|
+
function parameterFieldFromShaderControl(control) {
|
|
1188
|
+
const common = {
|
|
1189
|
+
id: control.name,
|
|
1190
|
+
...control.label ? { label: control.label } : {},
|
|
1191
|
+
...control.description ? { description: control.description } : {}
|
|
1192
|
+
};
|
|
1193
|
+
switch (control.type) {
|
|
1194
|
+
case "bool":
|
|
1195
|
+
return { ...common, type: "boolean", default: control.default === true };
|
|
1196
|
+
case "sampler2D":
|
|
1197
|
+
return { ...common, type: "image", default: typeof control.default === "string" ? control.default : "" };
|
|
1198
|
+
case "float":
|
|
1199
|
+
case "int":
|
|
1200
|
+
return {
|
|
1201
|
+
...common,
|
|
1202
|
+
type: control.type === "int" ? "int" : "number",
|
|
1203
|
+
default: typeof control.default === "number" ? control.default : 0,
|
|
1204
|
+
...control.min !== void 0 ? { min: control.min } : {},
|
|
1205
|
+
...control.max !== void 0 ? { max: control.max } : {},
|
|
1206
|
+
...control.step !== void 0 ? { step: control.step } : {}
|
|
1207
|
+
};
|
|
1208
|
+
default: {
|
|
1209
|
+
const size = SIZES[control.type];
|
|
1210
|
+
const components = Array.isArray(control.default) ? control.default : Array.from({ length: size }, () => 0);
|
|
1211
|
+
if (control.color && size >= 3) {
|
|
1212
|
+
return { ...common, type: "color", alpha: size === 4, default: rgba01ToHex(components, size === 4) };
|
|
1213
|
+
}
|
|
1214
|
+
return {
|
|
1215
|
+
...common,
|
|
1216
|
+
type: "vector",
|
|
1217
|
+
size,
|
|
1218
|
+
default: Array.from({ length: size }, (_, i) => _nullishCoalesce(components[i], () => ( 0))),
|
|
1219
|
+
...control.min !== void 0 ? { min: control.min } : {},
|
|
1220
|
+
...control.max !== void 0 ? { max: control.max } : {},
|
|
1221
|
+
...control.step !== void 0 ? { step: control.step } : {}
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
function parameterSchemaFromShaderControls(controls, options = {}) {
|
|
1227
|
+
return { ...options.title ? { title: options.title } : {}, fields: controls.map(parameterFieldFromShaderControl) };
|
|
1228
|
+
}
|
|
1229
|
+
function shaderControlValue(control, value) {
|
|
1230
|
+
if (control.color && typeof value === "string") {
|
|
1231
|
+
return _nullishCoalesce(hexToRgba01(value, control.type === "vec4"), () => ( control.default));
|
|
1232
|
+
}
|
|
1233
|
+
return value;
|
|
1234
|
+
}
|
|
1235
|
+
function parameterValueFromShaderControl(control, value) {
|
|
1236
|
+
if (control.color && Array.isArray(value)) return rgba01ToHex(value, control.type === "vec4");
|
|
1237
|
+
return value;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// src/parameters/rangeMapper.ts
|
|
1241
|
+
function createRangeMapper(options = {}) {
|
|
1242
|
+
const [inMin, inMax] = _nullishCoalesce(options.from, () => ( [0, 1]));
|
|
1243
|
+
const [outMin, outMax] = _nullishCoalesce(options.to, () => ( [0, 1]));
|
|
1244
|
+
const shouldClamp = _nullishCoalesce(options.clamp, () => ( true));
|
|
1245
|
+
const easing = options.easing;
|
|
1246
|
+
const inSpan = inMax - inMin;
|
|
1247
|
+
const outSpan = outMax - outMin;
|
|
1248
|
+
return function map(value) {
|
|
1249
|
+
if (!Number.isFinite(value)) return outMin;
|
|
1250
|
+
if (Math.abs(inSpan) < 1e-12) {
|
|
1251
|
+
return outMin;
|
|
1252
|
+
}
|
|
1253
|
+
let normalized = (value - inMin) / inSpan;
|
|
1254
|
+
if (shouldClamp) {
|
|
1255
|
+
normalized = Math.min(1, Math.max(0, normalized));
|
|
1256
|
+
}
|
|
1257
|
+
let progress = normalized;
|
|
1258
|
+
if (easing !== void 0 && easing !== null) {
|
|
1259
|
+
if (typeof easing === "function") {
|
|
1260
|
+
progress = easing(normalized);
|
|
1261
|
+
} else {
|
|
1262
|
+
progress = _chunkXZ4L3R4Kcjs.evaluateEasing.call(void 0, easing, normalized);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
return outMin + progress * outSpan;
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
function mapRange(value, from, to, options) {
|
|
1269
|
+
return createRangeMapper({ from, to, ...options })(value);
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
|
|
1273
|
+
|
|
1274
|
+
|
|
1275
|
+
|
|
1276
|
+
|
|
1277
|
+
|
|
1278
|
+
|
|
1279
|
+
|
|
1280
|
+
|
|
1281
|
+
|
|
1282
|
+
|
|
1283
|
+
|
|
1284
|
+
|
|
1285
|
+
|
|
1286
|
+
|
|
1287
|
+
|
|
1288
|
+
|
|
1289
|
+
|
|
1290
|
+
|
|
1291
|
+
|
|
1292
|
+
|
|
1293
|
+
|
|
1294
|
+
|
|
1295
|
+
|
|
1296
|
+
|
|
1297
|
+
|
|
1298
|
+
|
|
1299
|
+
|
|
1300
|
+
|
|
1301
|
+
|
|
1302
|
+
|
|
1303
|
+
|
|
1304
|
+
|
|
1305
|
+
|
|
1306
|
+
|
|
1307
|
+
|
|
1308
|
+
|
|
1309
|
+
|
|
1310
|
+
|
|
1311
|
+
|
|
1312
|
+
|
|
1313
|
+
|
|
1314
|
+
|
|
1315
|
+
|
|
1316
|
+
|
|
1317
|
+
|
|
1318
|
+
|
|
1319
|
+
exports.PARAMETER_KEY_TOLERANCE = PARAMETER_KEY_TOLERANCE; exports.ParameterSchemaError = ParameterSchemaError; exports.ParametersController = ParametersController; exports.checkParameterSchema = checkParameterSchema; exports.choiceOptionLabel = choiceOptionLabel; exports.clampParameterNumber = clampParameterNumber; exports.cloneValue = cloneValue; exports.coerceParameterValue = coerceParameterValue; exports.createParameterCommands = createParameterCommands; exports.createParameterKeyframeStore = createParameterKeyframeStore; exports.createParametersController = createParametersController; exports.createRangeMapper = createRangeMapper; exports.evaluateParameterCondition = evaluateParameterCondition; exports.findParameterKeyframe = findParameterKeyframe; exports.formatParameterNumber = formatParameterNumber; exports.formatParameterValue = formatParameterValue; exports.getChoiceOptions = getChoiceOptions; exports.getParameterDefaults = getParameterDefaults; exports.getParameterFields = getParameterFields; exports.getParameterGroups = getParameterGroups; exports.getParameterKeyState = getParameterKeyState; exports.getParameterVisibility = getParameterVisibility; exports.hexToRgba01 = hexToRgba01; exports.humanizeParameterId = humanizeParameterId; exports.isParameterGroup = isParameterGroup; exports.isSliderField = isSliderField; exports.mapRange = mapRange; exports.normalizeHexColor = normalizeHexColor; exports.parameterDecimals = parameterDecimals; exports.parameterFieldFromShaderControl = parameterFieldFromShaderControl; exports.parameterFieldToJsonSchema = parameterFieldToJsonSchema; exports.parameterLabel = parameterLabel; exports.parameterSchemaFromShaderControls = parameterSchemaFromShaderControls; exports.parameterSchemaToJsonSchema = parameterSchemaToJsonSchema; exports.parameterStep = parameterStep; exports.parameterValueFromShaderControl = parameterValueFromShaderControl; exports.parameterValuesEqual = parameterValuesEqual; exports.parseParameterNumberInput = parseParameterNumberInput; exports.rgba01ToHex = rgba01ToHex; exports.seededRandom = seededRandom; exports.shaderControlValue = shaderControlValue; exports.validateParameterValue = validateParameterValue; exports.validateParameterValues = validateParameterValues; exports.vectorComponentField = vectorComponentField; exports.vectorSize = vectorSize; exports.withParameterKeyframe = withParameterKeyframe; exports.withoutParameterKeyframe = withoutParameterKeyframe;
|
|
1320
|
+
//# sourceMappingURL=index.cjs.map
|