fragment-tools 0.2.11 → 0.2.13
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 +12 -11
- package/src/cli/build.js +1 -0
- package/src/cli/create.js +22 -4
- package/src/cli/createConfig.js +2 -2
- package/src/cli/getEntries.js +10 -1
- package/src/cli/plugins/hot-shader-replacement.js +54 -16
- package/src/cli/plugins/save.js +97 -38
- package/src/cli/prompts.js +89 -36
- package/src/cli/run.js +1 -1
- package/src/cli/templates/blank/index.ts +1 -1
- package/src/cli/templates/default/index.js +10 -2
- package/src/cli/templates/default/index.ts +5 -2
- package/src/cli/templates/fragment-gl/index.ts +1 -1
- package/src/cli/templates/p5/index.ts +1 -1
- package/src/cli/templates/p5-webgl/index.ts +1 -1
- package/src/cli/templates/three-fragment/index.js +5 -3
- package/src/cli/templates/three-fragment/index.ts +5 -4
- package/src/cli/templates/three-orthographic/index.js +6 -1
- package/src/cli/templates/three-orthographic/index.ts +6 -2
- package/src/cli/templates/three-perspective/index.js +6 -1
- package/src/cli/templates/three-perspective/index.ts +6 -2
- package/src/client/app/actions/resize.js +8 -1
- package/src/client/app/attachments/draggable.js +93 -0
- package/src/client/app/client.js +90 -18
- package/src/client/app/components/IconFlip.svelte +46 -0
- package/src/client/app/hooks.js +25 -1
- package/src/client/app/lib/canvas-recorder/CanvasRecorder.js +95 -3
- package/src/client/app/lib/canvas-recorder/FrameRecorder.js +45 -3
- package/src/client/app/lib/canvas-recorder/GIFRecorder.js +72 -13
- package/src/client/app/lib/canvas-recorder/MediaBunnyRecorder.js +43 -9
- package/src/client/app/lib/canvas-recorder/utils.js +18 -9
- package/src/client/app/modules/Params.svelte +1 -0
- package/src/client/app/renderers/2DRenderer.js +20 -16
- package/src/client/app/renderers/P5GLRenderer.js +13 -5
- package/src/client/app/renderers/P5Renderer.js +9 -1
- package/src/client/app/renderers/THREERenderer.js +63 -48
- package/src/client/app/state/Sketch.svelte.js +150 -10
- package/src/client/app/state/errors.svelte.js +19 -0
- package/src/client/app/state/exports.svelte.js +14 -1
- package/src/client/app/state/rendering.svelte.js +90 -13
- package/src/client/app/state/sketches.svelte.js +43 -7
- package/src/client/app/state/utils.svelte.js +49 -0
- package/src/client/app/ui/Field.svelte +63 -16
- package/src/client/app/ui/FieldSection.svelte +4 -4
- package/src/client/app/ui/ParamsOutput.svelte +7 -5
- package/src/client/app/ui/SketchRenderer.svelte +21 -0
- package/src/client/app/ui/fields/ButtonInput.svelte +2 -0
- package/src/client/app/ui/fields/CheckboxInput.svelte +13 -11
- package/src/client/app/ui/fields/ColorInput.svelte +16 -11
- package/src/client/app/ui/fields/GradientInput.svelte +607 -0
- package/src/client/app/ui/fields/Input.svelte +10 -6
- package/src/client/app/ui/fields/IntervalInput.svelte +27 -35
- package/src/client/app/ui/fields/NumberInput.svelte +51 -13
- package/src/client/app/ui/fields/PaletteInput.svelte +181 -0
- package/src/client/app/ui/fields/ProgressInput.svelte +44 -16
- package/src/client/app/ui/fields/TextareaInput.svelte +93 -0
- package/src/client/app/utils/canvas.utils.js +105 -28
- package/src/client/app/utils/color.utils.js +74 -17
- package/src/client/app/utils/fields.utils.js +70 -17
- package/src/client/app/utils/file.utils.js +86 -31
- package/src/client/app/utils/glsl.utils.js +11 -2
- package/src/client/app/utils/glslErrors.js +31 -21
- package/src/client/app/utils/index.js +28 -12
- package/src/client/main.js +7 -1
- package/src/types/global.d.ts +143 -0
- package/src/types/props.d.ts +41 -15
- package/tsconfig.json +1 -1
|
@@ -10,7 +10,63 @@ import { exportCanvas } from '../lib/canvas-recorder/utils';
|
|
|
10
10
|
import { map } from './math.utils';
|
|
11
11
|
import { createDataURLFromBlob, saveFiles } from './file.utils';
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {Object} FilenameParams
|
|
15
|
+
* @property {string} year - Four-digit year
|
|
16
|
+
* @property {string} month - Two-digit month
|
|
17
|
+
* @property {string} day - Two-digit day
|
|
18
|
+
* @property {string} hours - Two-digit hours
|
|
19
|
+
* @property {string} minutes - Two-digit minutes
|
|
20
|
+
* @property {string} seconds - Two-digit seconds
|
|
21
|
+
* @property {string} timestamp - Full timestamp string
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {Object} ScreenshotOptions
|
|
26
|
+
* @property {string} [filename='Screenshot'] - Base filename
|
|
27
|
+
* @property {number} [index] - Optional frame index
|
|
28
|
+
* @property {FilenamePattern} [pattern] - Filename pattern function
|
|
29
|
+
* @property {string} [exportDir] - Export directory path
|
|
30
|
+
* @property {Record<string, any>} [params={}] - Additional parameters for pattern
|
|
31
|
+
* @property {string} [encoding='png'] - Image encoding format
|
|
32
|
+
* @property {number} [quality=100] - Image quality (1-100)
|
|
33
|
+
* @property {number} [pixelsPerInch=72] - Image DPI
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {Object} RecordCanvasOptions
|
|
38
|
+
* @property {string} [filename='output'] - Base filename for output
|
|
39
|
+
* @property {string} [format='mp4'] - Output format
|
|
40
|
+
* @property {number} [framerate=25] - Frames per second
|
|
41
|
+
* @property {number} [duration=Infinity] - Recording duration in seconds
|
|
42
|
+
* @property {number} [quality=100] - Recording quality (1-100)
|
|
43
|
+
* @property {FilenamePattern} [pattern] - Filename pattern function
|
|
44
|
+
* @property {import('../lib/canvas-recorder/MediaBunnyRecorder').VideoCodec} [codec] - Video codec
|
|
45
|
+
* @property {string} [exportDir] - Export directory path
|
|
46
|
+
* @property {string} [imageEncoding] - Image encoding for frame exports
|
|
47
|
+
* @property {import('../lib/canvas-recorder/CanvasRecorder').CanvasRecorderStartCallback} [onStart] - Callback when recording starts
|
|
48
|
+
* @property {import('../lib/canvas-recorder/CanvasRecorder').CanvasRecorderTickCallback} [onTick] - Callback on each frame
|
|
49
|
+
* @property {import('../lib/canvas-recorder/CanvasRecorder').CanvasRecorderCompleteCallback} [onComplete] - Callback when recording completes
|
|
50
|
+
* @property {Record<string, any>} [params={}] - Additional parameters
|
|
51
|
+
*/
|
|
52
|
+
/**
|
|
53
|
+
* @typedef {Object} PatternParams
|
|
54
|
+
* @property {number} [index] - Frame or sequence index
|
|
55
|
+
* @property {string} filename - Base filename
|
|
56
|
+
* @property {string} timestamp - Timestamp string
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @callback FilenamePattern
|
|
61
|
+
* @param {PatternParams & Record<string, any>} params - Pattern parameters
|
|
62
|
+
* @returns {string} Generated filename
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get current date/time parameters for filename generation
|
|
67
|
+
* @returns {FilenameParams}
|
|
68
|
+
*/
|
|
69
|
+
export function getFilenameParams() {
|
|
14
70
|
const now = new Date();
|
|
15
71
|
|
|
16
72
|
const year = now.toLocaleString('default', { year: 'numeric' });
|
|
@@ -43,10 +99,14 @@ function getFilenameParams() {
|
|
|
43
99
|
};
|
|
44
100
|
}
|
|
45
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Default filename pattern generator
|
|
104
|
+
* @type {FilenamePattern}
|
|
105
|
+
*/
|
|
46
106
|
export const defaultFilenamePattern = ({ index, filename, timestamp }) => {
|
|
47
107
|
let name = `${filename}.${timestamp}`;
|
|
48
108
|
|
|
49
|
-
if (!isNaN(index)) {
|
|
109
|
+
if (typeof index === 'number' && !isNaN(index)) {
|
|
50
110
|
name += `-${index}`;
|
|
51
111
|
}
|
|
52
112
|
|
|
@@ -54,14 +114,12 @@ export const defaultFilenamePattern = ({ index, filename, timestamp }) => {
|
|
|
54
114
|
};
|
|
55
115
|
|
|
56
116
|
/**
|
|
57
|
-
*
|
|
58
|
-
* @param {HTMLCanvasElement} canvas
|
|
59
|
-
* @param {
|
|
60
|
-
* @
|
|
61
|
-
* @param {number} [index]
|
|
62
|
-
* @param {Promise<string[]>}
|
|
117
|
+
* Capture and save a screenshot of a canvas
|
|
118
|
+
* @param {HTMLCanvasElement} canvas - The canvas to capture
|
|
119
|
+
* @param {ScreenshotOptions} [options={}] - Screenshot options
|
|
120
|
+
* @returns {{ filename: string, exportDir: string | undefined, data: string, encoding: string }}
|
|
63
121
|
*/
|
|
64
|
-
export
|
|
122
|
+
export function screenshotCanvas(
|
|
65
123
|
canvas,
|
|
66
124
|
{
|
|
67
125
|
filename = 'Screenshot',
|
|
@@ -83,23 +141,20 @@ export async function screenshotCanvas(
|
|
|
83
141
|
let patternParams = getFilenameParams();
|
|
84
142
|
let name = pattern({ filename, index, ...params, ...patternParams });
|
|
85
143
|
|
|
86
|
-
|
|
87
|
-
{
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
},
|
|
93
|
-
];
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
await saveFiles(files);
|
|
97
|
-
} catch (error) {
|
|
98
|
-
console.error(`[fragment] Error while saving screenshot.`);
|
|
99
|
-
console.log(error);
|
|
100
|
-
}
|
|
144
|
+
return {
|
|
145
|
+
filename: `${name}${extension}`,
|
|
146
|
+
exportDir,
|
|
147
|
+
data: dataURL,
|
|
148
|
+
encoding: 'base64',
|
|
149
|
+
};
|
|
101
150
|
}
|
|
102
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Record video using MediaBunny recorder
|
|
154
|
+
* @param {HTMLCanvasElement} canvas - The canvas to record
|
|
155
|
+
* @param {import('../lib/canvas-recorder/MediaBunnyRecorder').MediaBunnyRecorderOptions} options - Recording options
|
|
156
|
+
* @returns {MediaBunnyRecorder}
|
|
157
|
+
*/
|
|
103
158
|
function record(canvas, options) {
|
|
104
159
|
let recorder = new MediaBunnyRecorder(canvas, options);
|
|
105
160
|
recorder.start();
|
|
@@ -107,6 +162,12 @@ function record(canvas, options) {
|
|
|
107
162
|
return recorder;
|
|
108
163
|
}
|
|
109
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Record GIF animation
|
|
167
|
+
* @param {HTMLCanvasElement} canvas - The canvas to record
|
|
168
|
+
* @param {import('../lib/canvas-recorder/CanvasRecorder').CanvasRecorderOptions} options - Recording options
|
|
169
|
+
* @returns {GIFRecorder}
|
|
170
|
+
*/
|
|
110
171
|
function recordGIF(canvas, options) {
|
|
111
172
|
let recorder = new GIFRecorder(canvas, options);
|
|
112
173
|
recorder.start();
|
|
@@ -114,6 +175,12 @@ function recordGIF(canvas, options) {
|
|
|
114
175
|
return recorder;
|
|
115
176
|
}
|
|
116
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Record individual frames
|
|
180
|
+
* @param {HTMLCanvasElement} canvas - The canvas to record
|
|
181
|
+
* @param {import('../lib/canvas-recorder/FrameRecorder').FrameRecorderOptions} options - Recording options
|
|
182
|
+
* @returns {FrameRecorder}
|
|
183
|
+
*/
|
|
117
184
|
function recordFrames(canvas, options) {
|
|
118
185
|
let recorder = new FrameRecorder(canvas, options);
|
|
119
186
|
recorder.start();
|
|
@@ -121,6 +188,12 @@ function recordFrames(canvas, options) {
|
|
|
121
188
|
return recorder;
|
|
122
189
|
}
|
|
123
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Start recording canvas output
|
|
193
|
+
* @param {HTMLCanvasElement} canvas - The canvas to record
|
|
194
|
+
* @param {RecordCanvasOptions} [options={}] - Recording options
|
|
195
|
+
* @returns {MediaBunnyRecorder | GIFRecorder | FrameRecorder | undefined}
|
|
196
|
+
*/
|
|
124
197
|
export function recordCanvas(
|
|
125
198
|
canvas,
|
|
126
199
|
{
|
|
@@ -130,18 +203,22 @@ export function recordCanvas(
|
|
|
130
203
|
duration = Infinity,
|
|
131
204
|
quality = 100,
|
|
132
205
|
pattern = defaultFilenamePattern,
|
|
133
|
-
codec,
|
|
206
|
+
codec = 'avc',
|
|
134
207
|
exportDir,
|
|
135
208
|
imageEncoding,
|
|
136
209
|
onStart = () => {},
|
|
137
210
|
onTick = () => {},
|
|
138
211
|
onComplete = () => {},
|
|
139
|
-
params = {},
|
|
140
212
|
} = {},
|
|
141
213
|
) {
|
|
142
214
|
let patternParams = getFilenameParams();
|
|
143
215
|
let name = pattern({ filename, ...patternParams });
|
|
144
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Handle recording completion
|
|
219
|
+
* @param {Blob | Blob[] | null} result - Recording result (blob or array of blobs)
|
|
220
|
+
* @returns {Promise<void>}
|
|
221
|
+
*/
|
|
145
222
|
async function complete(result) {
|
|
146
223
|
const files = [];
|
|
147
224
|
|
|
@@ -163,7 +240,7 @@ export function recordCanvas(
|
|
|
163
240
|
encoding: 'base64',
|
|
164
241
|
});
|
|
165
242
|
}
|
|
166
|
-
} else {
|
|
243
|
+
} else if (result) {
|
|
167
244
|
const blob = result;
|
|
168
245
|
const data = await createDataURLFromBlob(blob);
|
|
169
246
|
|
|
@@ -177,7 +254,7 @@ export function recordCanvas(
|
|
|
177
254
|
}
|
|
178
255
|
|
|
179
256
|
await saveFiles(files);
|
|
180
|
-
onComplete();
|
|
257
|
+
onComplete(result);
|
|
181
258
|
}
|
|
182
259
|
|
|
183
260
|
const options = {
|
|
@@ -5,7 +5,9 @@ export const FORMATS = {
|
|
|
5
5
|
HSL_STRING: 'hsl-string',
|
|
6
6
|
HSLA_STRING: 'hsla-string',
|
|
7
7
|
RGB_OBJECT: 'rgb-object',
|
|
8
|
+
RGB_OBJECT_STRING: 'rgb-object-string',
|
|
8
9
|
RGBA_OBJECT: 'rgba-object',
|
|
10
|
+
RGBA_OBJECT_STRING: 'rgba-object-string',
|
|
9
11
|
VEC3_STRING: 'vec3-string',
|
|
10
12
|
VEC4_STRING: 'vec4-string',
|
|
11
13
|
VEC3_ARRAY: 'vec3-array',
|
|
@@ -32,6 +34,11 @@ export function toHex(color, format = getColorFormat(color)) {
|
|
|
32
34
|
if (format === FORMATS.VEC3_ARRAY || format === FORMATS.VEC4_ARRAY)
|
|
33
35
|
return vecArrayToHex(color);
|
|
34
36
|
if (format === FORMATS.CSS_COLOR) return nameToHex(color);
|
|
37
|
+
if (
|
|
38
|
+
format === FORMATS.RGB_OBJECT_STRING ||
|
|
39
|
+
format === FORMATS.RGBA_OBJECT_STRING
|
|
40
|
+
)
|
|
41
|
+
return objectStringToHex(color);
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
export function toComponents(color, format = getColorFormat(color)) {
|
|
@@ -50,6 +57,11 @@ export function toComponents(color, format = getColorFormat(color)) {
|
|
|
50
57
|
if (format === FORMATS.VEC3_STRING || format === FORMATS.VEC4_STRING)
|
|
51
58
|
return vecStringToComponents(color);
|
|
52
59
|
if (format === FORMATS.CSS_COLOR) return nameToComponents(color);
|
|
60
|
+
if (
|
|
61
|
+
format === FORMATS.RGB_OBJECT_STRING ||
|
|
62
|
+
format === FORMATS.RGBA_OBJECT_STRING
|
|
63
|
+
)
|
|
64
|
+
return objectStringToComponents(color);
|
|
53
65
|
}
|
|
54
66
|
|
|
55
67
|
export function toString(color, format = getColorFormat(color)) {
|
|
@@ -61,6 +73,8 @@ export function toString(color, format = getColorFormat(color)) {
|
|
|
61
73
|
format === FORMATS.HEX_STRING ||
|
|
62
74
|
format === FORMATS.RGB_STRING ||
|
|
63
75
|
format === FORMATS.RGBA_STRING ||
|
|
76
|
+
format === FORMATS.RGB_OBJECT_STRING ||
|
|
77
|
+
format === FORMATS.RGBA_OBJECT_STRING ||
|
|
64
78
|
format === FORMATS.HSL_STRING ||
|
|
65
79
|
format === FORMATS.HSLA_STRING ||
|
|
66
80
|
format === FORMATS.VEC3_STRING ||
|
|
@@ -74,9 +88,9 @@ export function toString(color, format = getColorFormat(color)) {
|
|
|
74
88
|
return componentsToRGBAString(vecArrayToComponents(color));
|
|
75
89
|
if (format === FORMATS.THREE) return threeToHex(color);
|
|
76
90
|
if (format === FORMATS.RGB_OBJECT)
|
|
77
|
-
return
|
|
91
|
+
return componentsToRGBObjectString([color.r, color.g, color.b]);
|
|
78
92
|
if (format === FORMATS.RGBA_OBJECT)
|
|
79
|
-
return
|
|
93
|
+
return componentsToRGBAObjectString([
|
|
80
94
|
color.r,
|
|
81
95
|
color.g,
|
|
82
96
|
color.b,
|
|
@@ -278,12 +292,12 @@ export function toVec3String(color) {
|
|
|
278
292
|
return componentsToVec3String(toComponents(color));
|
|
279
293
|
}
|
|
280
294
|
|
|
281
|
-
export function
|
|
282
|
-
return
|
|
295
|
+
export function toRGBObjectString(color) {
|
|
296
|
+
return componentsToRGBObjectString(toComponents(color));
|
|
283
297
|
}
|
|
284
298
|
|
|
285
|
-
export function
|
|
286
|
-
return
|
|
299
|
+
export function toRGBAObjectString(color) {
|
|
300
|
+
return componentsToRGBAObjectString(toComponents(color));
|
|
287
301
|
}
|
|
288
302
|
|
|
289
303
|
export function toVec4String(color) {
|
|
@@ -312,7 +326,19 @@ export function componentsToVec3String(components = []) {
|
|
|
312
326
|
return `vec3(${rn}, ${gn}, ${bn})`;
|
|
313
327
|
}
|
|
314
328
|
|
|
315
|
-
export function
|
|
329
|
+
export function componentsToRGBObject(components) {
|
|
330
|
+
const [r = 0, g = 0, b = 0] = components;
|
|
331
|
+
|
|
332
|
+
return { r, g, b };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export function componentsToRGBAObject(components) {
|
|
336
|
+
const [r = 0, g = 0, b = 0, a = 1] = components;
|
|
337
|
+
|
|
338
|
+
return { r, g, b, a };
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export function componentsToRGBObjectString(components) {
|
|
316
342
|
const [r = 0, g = 0, b = 0] = components;
|
|
317
343
|
|
|
318
344
|
let rn = `${Math.round(r * 1000) / 1000}`;
|
|
@@ -322,14 +348,15 @@ export function componentsToRGBStringObject(components) {
|
|
|
322
348
|
return `{ r: ${rn}, g: ${gn}, b: ${bn} }`;
|
|
323
349
|
}
|
|
324
350
|
|
|
325
|
-
export function
|
|
351
|
+
export function componentsToRGBAObjectString(components) {
|
|
326
352
|
const [r = 0, g = 0, b = 0, a = 1] = components;
|
|
327
353
|
|
|
328
354
|
let rn = `${Math.round(r * 1000) / 1000}`;
|
|
329
355
|
let gn = `${Math.round(g * 1000) / 1000}`;
|
|
330
356
|
let bn = `${Math.round(b * 1000) / 1000}`;
|
|
357
|
+
let an = `${Math.round(a * 1000) / 1000}`;
|
|
331
358
|
|
|
332
|
-
return `{ r: ${rn}, g: ${gn}, b: ${bn}, a: ${
|
|
359
|
+
return `{ r: ${rn}, g: ${gn}, b: ${bn}, a: ${an} }`;
|
|
333
360
|
}
|
|
334
361
|
|
|
335
362
|
export function componentsToVec4String(components = []) {
|
|
@@ -450,6 +477,14 @@ export function componentsToHex(components = []) {
|
|
|
450
477
|
return `#${hex}`;
|
|
451
478
|
}
|
|
452
479
|
|
|
480
|
+
export function objectStringToComponents(value) {
|
|
481
|
+
const parsable = value.replace(/([{,]\s*)([a-z]+)(\s*:)/g, '$1"$2"$3');
|
|
482
|
+
|
|
483
|
+
const { r, g, b, a = 1 } = JSON.parse(parsable);
|
|
484
|
+
|
|
485
|
+
return [r, g, b, a];
|
|
486
|
+
}
|
|
487
|
+
|
|
453
488
|
export function isHexString(value, isString = typeof value === 'string') {
|
|
454
489
|
return isString && /^#([a-f0-9]{3,4}){1,2}$/i.test(value);
|
|
455
490
|
}
|
|
@@ -470,19 +505,19 @@ export function isRGBString(value, isString = typeof value === 'string') {
|
|
|
470
505
|
}
|
|
471
506
|
|
|
472
507
|
export function isHSLAString(value, isString = typeof value === 'string') {
|
|
473
|
-
return isString && value.
|
|
508
|
+
return isString && value.startsWith('hsla');
|
|
474
509
|
}
|
|
475
510
|
|
|
476
511
|
export function isHSLString(value, isString = typeof value === 'string') {
|
|
477
|
-
return isString && value.
|
|
512
|
+
return isString && value.startsWith('hsl');
|
|
478
513
|
}
|
|
479
514
|
|
|
480
515
|
export function isVec3String(value, isString = typeof value === 'string') {
|
|
481
|
-
return isString && value.
|
|
516
|
+
return isString && value.startsWith('vec3(');
|
|
482
517
|
}
|
|
483
518
|
|
|
484
519
|
export function isVec4String(value, isString = typeof value === 'string') {
|
|
485
|
-
return isString && value.
|
|
520
|
+
return isString && value.startsWith('vec4(');
|
|
486
521
|
}
|
|
487
522
|
|
|
488
523
|
export function isRGBAObject(value) {
|
|
@@ -516,6 +551,16 @@ export function isRGBObject(value) {
|
|
|
516
551
|
return false;
|
|
517
552
|
}
|
|
518
553
|
|
|
554
|
+
export function isRGBObjectString(value) {
|
|
555
|
+
const parsable = value.replace(/([{,]\s*)([a-z]+)(\s*:)/g, '$1"$2"$3');
|
|
556
|
+
return typeof value === 'string' && isRGBObject(JSON.parse(parsable));
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
export function isRGBAObjectString(value) {
|
|
560
|
+
const parsable = value.replace(/([{,]\s*)([a-z]+)(\s*:)/g, '$1"$2"$3');
|
|
561
|
+
return typeof value === 'string' && isRGBAObject(JSON.parse(parsable));
|
|
562
|
+
}
|
|
563
|
+
|
|
519
564
|
export function isVec3Array(value) {
|
|
520
565
|
if (Array.isArray(value)) {
|
|
521
566
|
return value.length == 3 && value.every((c) => c >= 0 && c <= 1);
|
|
@@ -575,6 +620,8 @@ export function getColorFormat(value) {
|
|
|
575
620
|
if (isRGBString(value)) return FORMATS.RGB_STRING;
|
|
576
621
|
if (isRGBObject(value)) return FORMATS.RGB_OBJECT;
|
|
577
622
|
if (isRGBAObject(value)) return FORMATS.RGBA_OBJECT;
|
|
623
|
+
if (isRGBObjectString(value)) return FORMATS.RGB_OBJECT_STRING;
|
|
624
|
+
if (isRGBAObjectString(value)) return FORMATS.RGBA_OBJECT_STRING;
|
|
578
625
|
if (isVec3String(value)) return FORMATS.VEC3_STRING;
|
|
579
626
|
if (isVec4String(value)) return FORMATS.VEC4_STRING;
|
|
580
627
|
if (isVec3Array(value)) return FORMATS.VEC3_ARRAY;
|
|
@@ -585,16 +632,26 @@ export function getColorFormat(value) {
|
|
|
585
632
|
}
|
|
586
633
|
|
|
587
634
|
export function componentsToFormat(components, format) {
|
|
588
|
-
const [r, g, b, a] = components;
|
|
635
|
+
const [r, g, b, a = 1] = components;
|
|
589
636
|
|
|
590
637
|
switch (format) {
|
|
638
|
+
case FORMATS.HEX_STRING:
|
|
639
|
+
return componentsToHex([r, g, b, a]);
|
|
591
640
|
case FORMATS.RGB_STRING:
|
|
592
|
-
return componentsToRGBString(
|
|
641
|
+
return componentsToRGBString([r, g, b, a]);
|
|
593
642
|
case FORMATS.RGBA_STRING:
|
|
594
643
|
return componentsToRGBAString([r, g, b, a]);
|
|
595
644
|
case FORMATS.VEC3_STRING:
|
|
596
|
-
return componentsToVec3String(
|
|
645
|
+
return componentsToVec3String([r, g, b, a]);
|
|
597
646
|
case FORMATS.VEC4_STRING:
|
|
598
|
-
return componentsToVec4String(
|
|
647
|
+
return componentsToVec4String([r, g, b, a]);
|
|
648
|
+
case FORMATS.RGB_OBJECT:
|
|
649
|
+
return componentsToRGBObject([r, g, b, a]);
|
|
650
|
+
case FORMATS.RGBA_OBJECT:
|
|
651
|
+
return componentsToRGBAObject([r, g, b, a]);
|
|
652
|
+
case FORMATS.RGB_OBJECT_STRING:
|
|
653
|
+
return componentsToRGBObjectString([r, g, b, a]);
|
|
654
|
+
case FORMATS.RGBA_OBJECT_STRING:
|
|
655
|
+
return componentsToRGBAObjectString([r, g, b, a]);
|
|
599
656
|
}
|
|
600
657
|
}
|
|
@@ -6,6 +6,7 @@ export const fieldTypes = {
|
|
|
6
6
|
VEC: 'vec',
|
|
7
7
|
CHECKBOX: 'checkbox',
|
|
8
8
|
TEXT: 'text',
|
|
9
|
+
TEXTAREA: 'textarea',
|
|
9
10
|
LIST: 'list',
|
|
10
11
|
COLOR: 'color',
|
|
11
12
|
BUTTON: 'button',
|
|
@@ -13,17 +14,48 @@ export const fieldTypes = {
|
|
|
13
14
|
IMPORT: 'import',
|
|
14
15
|
IMAGE: 'image',
|
|
15
16
|
INTERVAL: 'interval',
|
|
17
|
+
WRAPPER: 'wrapper',
|
|
18
|
+
PALETTE: 'palette',
|
|
19
|
+
GRADIENT: 'gradient',
|
|
16
20
|
};
|
|
17
21
|
|
|
18
22
|
/** @type string[] */
|
|
19
23
|
const types = Object.values(fieldTypes);
|
|
20
24
|
|
|
25
|
+
/**
|
|
26
|
+
* @param {string} url
|
|
27
|
+
* @returns {boolean}
|
|
28
|
+
*/
|
|
21
29
|
function isImageURL(url) {
|
|
22
|
-
return
|
|
30
|
+
return (
|
|
31
|
+
url.match(/\.(jpeg|jpg|gif|png|webp)$/) !== null ||
|
|
32
|
+
url.startsWith('data:image')
|
|
33
|
+
);
|
|
23
34
|
}
|
|
24
35
|
|
|
36
|
+
/**
|
|
37
|
+
* @param {any} value
|
|
38
|
+
* @returns {boolean}
|
|
39
|
+
*/
|
|
25
40
|
function isImage(value) {
|
|
26
|
-
return
|
|
41
|
+
return (
|
|
42
|
+
typeof value === HTMLImageElement ||
|
|
43
|
+
(typeof value === 'string' && isImageURL(value))
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
*
|
|
49
|
+
* @param {any[]} value
|
|
50
|
+
* @returns {boolean}
|
|
51
|
+
*/
|
|
52
|
+
function isGradient(value) {
|
|
53
|
+
return value.every(
|
|
54
|
+
(v) =>
|
|
55
|
+
typeof v === 'object' &&
|
|
56
|
+
typeof v.position === 'number' &&
|
|
57
|
+
isColor(v.color),
|
|
58
|
+
);
|
|
27
59
|
}
|
|
28
60
|
|
|
29
61
|
export function inferFieldType({ type, value, params, key }) {
|
|
@@ -81,6 +113,10 @@ export function inferFieldType({ type, value, params, key }) {
|
|
|
81
113
|
typeof params.max === 'number'
|
|
82
114
|
) {
|
|
83
115
|
return fieldTypes.INTERVAL;
|
|
116
|
+
} else if (isArray && isGradient(value)) {
|
|
117
|
+
return fieldTypes.GRADIENT;
|
|
118
|
+
} else if (isArray && values.every((v) => isColor(v))) {
|
|
119
|
+
return fieldTypes.PALETTE;
|
|
84
120
|
} else if (isColor(value)) {
|
|
85
121
|
return fieldTypes.COLOR;
|
|
86
122
|
} else if (typeof value === 'number') {
|
|
@@ -114,24 +150,41 @@ export function inferFieldType({ type, value, params, key }) {
|
|
|
114
150
|
* @param {string} folder
|
|
115
151
|
*/
|
|
116
152
|
export function parseFolder(folder) {
|
|
117
|
-
const
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
const results =
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
153
|
+
const segments = folder.split('.');
|
|
154
|
+
const regex = /(?<name>[^\[]+)(?:\[(?<attributes>[^\]]+)\])?/;
|
|
155
|
+
|
|
156
|
+
const results = segments
|
|
157
|
+
.map((segment) => {
|
|
158
|
+
const match = segment.match(regex);
|
|
159
|
+
|
|
160
|
+
if (match) {
|
|
161
|
+
return {
|
|
162
|
+
name: match.groups?.name,
|
|
163
|
+
attributes: match.groups?.attributes
|
|
164
|
+
? Object.fromEntries(
|
|
165
|
+
match.groups.attributes
|
|
166
|
+
.split(', ')
|
|
167
|
+
.map((attr) =>
|
|
168
|
+
attr
|
|
169
|
+
.split('=')
|
|
170
|
+
.map((v) =>
|
|
171
|
+
v === 'false'
|
|
172
|
+
? false
|
|
173
|
+
: v === 'true'
|
|
174
|
+
? true
|
|
175
|
+
: v,
|
|
176
|
+
),
|
|
177
|
+
),
|
|
178
|
+
)
|
|
179
|
+
: {},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
.filter((result) => result !== undefined);
|
|
132
184
|
|
|
133
185
|
let names = results.map((match) => match.name);
|
|
134
186
|
|
|
187
|
+
/** @type {string|undefined} */
|
|
135
188
|
let rootId;
|
|
136
189
|
|
|
137
190
|
results.forEach((match, index) => {
|