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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { saveFiles } from '@fragment/utils/file.utils';
|
|
1
2
|
import { screenshotCanvas, recordCanvas } from '../utils/canvas.utils';
|
|
2
3
|
import { hydrate, persist } from './utils.svelte';
|
|
3
4
|
|
|
@@ -58,6 +59,7 @@ class Exports {
|
|
|
58
59
|
imageCount = $state(1);
|
|
59
60
|
recording = $state(false);
|
|
60
61
|
capturing = $state(false);
|
|
62
|
+
committing = $state(false);
|
|
61
63
|
imageCollapsed = $state(false);
|
|
62
64
|
videoCollapsed = $state(false);
|
|
63
65
|
|
|
@@ -96,6 +98,8 @@ class Exports {
|
|
|
96
98
|
quality = this.imageQuality,
|
|
97
99
|
pixelsPerInch = this.pixelsPerInch,
|
|
98
100
|
filename,
|
|
101
|
+
files = [],
|
|
102
|
+
commit = false,
|
|
99
103
|
pattern,
|
|
100
104
|
exportDir,
|
|
101
105
|
params = {},
|
|
@@ -117,7 +121,7 @@ class Exports {
|
|
|
117
121
|
for (let i = 0; i < count; i++) {
|
|
118
122
|
onBeforeCapture(captureParams);
|
|
119
123
|
|
|
120
|
-
|
|
124
|
+
const file = screenshotCanvas(canvas, {
|
|
121
125
|
filename,
|
|
122
126
|
pattern,
|
|
123
127
|
exportDir,
|
|
@@ -128,10 +132,19 @@ class Exports {
|
|
|
128
132
|
pixelsPerInch,
|
|
129
133
|
});
|
|
130
134
|
|
|
135
|
+
files.push(file);
|
|
136
|
+
|
|
131
137
|
onAfterCapture(captureParams);
|
|
132
138
|
}
|
|
133
139
|
|
|
134
140
|
onComplete(captureParams);
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
await saveFiles(files, [], { commit });
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.error(`[fragment] Error while saving screenshot.`);
|
|
146
|
+
console.log(error);
|
|
147
|
+
}
|
|
135
148
|
}
|
|
136
149
|
|
|
137
150
|
record(
|
|
@@ -12,6 +12,11 @@ import { layout } from './layout.svelte.js';
|
|
|
12
12
|
import { persist, hydrate } from './utils.svelte';
|
|
13
13
|
import presets from '../lib/presets';
|
|
14
14
|
import { client } from '../client.js';
|
|
15
|
+
import { saveFiles } from '@fragment/utils/file.utils.js';
|
|
16
|
+
import {
|
|
17
|
+
defaultFilenamePattern,
|
|
18
|
+
getFilenameParams,
|
|
19
|
+
} from '@fragment/utils/canvas.utils.js';
|
|
15
20
|
|
|
16
21
|
export const SIZES = {
|
|
17
22
|
FIXED: 'fixed',
|
|
@@ -94,6 +99,10 @@ class Rendering {
|
|
|
94
99
|
this.estimateRefreshRate();
|
|
95
100
|
}
|
|
96
101
|
|
|
102
|
+
/**
|
|
103
|
+
*
|
|
104
|
+
* @param {string} renderingMode
|
|
105
|
+
*/
|
|
97
106
|
loadRenderer(renderingMode) {
|
|
98
107
|
if (__THREE_RENDERER__ && renderingMode === 'three') {
|
|
99
108
|
return import('../renderers/THREERenderer.js');
|
|
@@ -321,7 +330,8 @@ export class Render {
|
|
|
321
330
|
this.time = 0;
|
|
322
331
|
this.recording = false;
|
|
323
332
|
|
|
324
|
-
|
|
333
|
+
/** @type {number|null} */
|
|
334
|
+
let resizeTimeout = null;
|
|
325
335
|
|
|
326
336
|
$effect.pre(() => {
|
|
327
337
|
const { width, height, pixelRatio } = rendering;
|
|
@@ -330,7 +340,10 @@ export class Render {
|
|
|
330
340
|
if (resizeTimeout) clearTimeout(resizeTimeout);
|
|
331
341
|
|
|
332
342
|
resizeTimeout = setTimeout(() => {
|
|
333
|
-
|
|
343
|
+
if (resizeTimeout) {
|
|
344
|
+
clearTimeout(resizeTimeout);
|
|
345
|
+
}
|
|
346
|
+
|
|
334
347
|
resizeTimeout = null;
|
|
335
348
|
|
|
336
349
|
this.resize(width, height, pixelRatio);
|
|
@@ -399,6 +412,7 @@ export class Render {
|
|
|
399
412
|
this.then = performance.now();
|
|
400
413
|
|
|
401
414
|
this.playhead = 0;
|
|
415
|
+
this.playheadLast = 0;
|
|
402
416
|
this.playcount = 0;
|
|
403
417
|
this.frame = 0;
|
|
404
418
|
|
|
@@ -443,24 +457,23 @@ export class Render {
|
|
|
443
457
|
return;
|
|
444
458
|
}
|
|
445
459
|
|
|
446
|
-
let
|
|
447
|
-
playhead
|
|
448
|
-
|
|
460
|
+
let totalPlayhead = time / 1000 / duration;
|
|
461
|
+
let playhead = fps === 0 ? 0 : totalPlayhead % 1;
|
|
462
|
+
|
|
463
|
+
if (playhead < this.playheadLast) {
|
|
464
|
+
this.playcount++;
|
|
465
|
+
}
|
|
466
|
+
this.playheadLast = playhead;
|
|
467
|
+
|
|
449
468
|
if (isFinite(interval)) {
|
|
450
469
|
// round values for low framerates
|
|
451
470
|
playhead = Math.floor(playhead / interval) * interval;
|
|
452
471
|
}
|
|
453
|
-
|
|
454
|
-
playcount = Math.floor(this.timeTotal / 1000 / duration);
|
|
455
|
-
}
|
|
456
|
-
if (fps === 0) {
|
|
457
|
-
playhead = 0;
|
|
458
|
-
}
|
|
472
|
+
|
|
459
473
|
const now = performance.now();
|
|
460
474
|
const deltaTime = now - this.thenLoop;
|
|
461
475
|
|
|
462
476
|
this.playhead = playhead;
|
|
463
|
-
this.playcount = playcount;
|
|
464
477
|
this.frame = Math.floor(map(playhead, 0, 1, 1, frameCount + 1));
|
|
465
478
|
if (
|
|
466
479
|
this.elapsed === 0 ||
|
|
@@ -499,6 +512,9 @@ export class Render {
|
|
|
499
512
|
|
|
500
513
|
this.raf = requestAnimationFrame(() => {
|
|
501
514
|
this.time = new Date().getTime() - rendering.today;
|
|
515
|
+
this.initialTime = this.time;
|
|
516
|
+
this.playheadLast = 0;
|
|
517
|
+
this.playcount = 0;
|
|
502
518
|
this.then = this.time;
|
|
503
519
|
this.thenLoop = performance.now();
|
|
504
520
|
this.update(this.time);
|
|
@@ -522,6 +538,14 @@ export class Render {
|
|
|
522
538
|
});
|
|
523
539
|
}
|
|
524
540
|
|
|
541
|
+
/**
|
|
542
|
+
*
|
|
543
|
+
* @param {Object} params
|
|
544
|
+
* @param {HTMLElement} params.container
|
|
545
|
+
* @param {HTMLCanvasElement} [params.canvas]
|
|
546
|
+
* @param {string} params.context
|
|
547
|
+
* @returns
|
|
548
|
+
*/
|
|
525
549
|
createCanvas({
|
|
526
550
|
container,
|
|
527
551
|
canvas = document.createElement('canvas'),
|
|
@@ -543,6 +567,10 @@ export class Render {
|
|
|
543
567
|
return canvas;
|
|
544
568
|
}
|
|
545
569
|
|
|
570
|
+
/**
|
|
571
|
+
*
|
|
572
|
+
* @param {HTMLCanvasElement} canvas
|
|
573
|
+
*/
|
|
546
574
|
destroyCanvas(canvas) {
|
|
547
575
|
if (canvas) {
|
|
548
576
|
this.observer.disconnect();
|
|
@@ -551,7 +579,6 @@ export class Render {
|
|
|
551
579
|
canvas.onmousemove = null;
|
|
552
580
|
canvas.onmouseup = null;
|
|
553
581
|
canvas.onclick = null;
|
|
554
|
-
canvas = null;
|
|
555
582
|
}
|
|
556
583
|
}
|
|
557
584
|
|
|
@@ -559,6 +586,8 @@ export class Render {
|
|
|
559
586
|
filename = this.sketch.key,
|
|
560
587
|
pattern = this.sketch.filenamePattern,
|
|
561
588
|
exportDir = this.sketch.exportDir,
|
|
589
|
+
files = [],
|
|
590
|
+
commit = false,
|
|
562
591
|
} = {}) {
|
|
563
592
|
const { sketch } = this;
|
|
564
593
|
|
|
@@ -569,6 +598,8 @@ export class Render {
|
|
|
569
598
|
params: {
|
|
570
599
|
props: sketch.props,
|
|
571
600
|
},
|
|
601
|
+
files,
|
|
602
|
+
commit,
|
|
572
603
|
onBeforeCapture: (params) => {
|
|
573
604
|
sketch.beforeCapture.forEach((fn) => fn(params));
|
|
574
605
|
this.renderSketch();
|
|
@@ -580,6 +611,44 @@ export class Render {
|
|
|
580
611
|
});
|
|
581
612
|
}
|
|
582
613
|
|
|
614
|
+
async commit({
|
|
615
|
+
filename = this.sketch.key,
|
|
616
|
+
pattern = this.sketch.filenamePattern ?? defaultFilenamePattern,
|
|
617
|
+
exportDir = this.sketch.exportDir,
|
|
618
|
+
} = {}) {
|
|
619
|
+
const { sketch } = this;
|
|
620
|
+
const { props } = sketch;
|
|
621
|
+
|
|
622
|
+
const data = {};
|
|
623
|
+
|
|
624
|
+
for (const key in props) {
|
|
625
|
+
data[key] = { value: props[key].value };
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
let patternParams = getFilenameParams();
|
|
629
|
+
let name = pattern({
|
|
630
|
+
filename,
|
|
631
|
+
params: { props },
|
|
632
|
+
...patternParams,
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
let files = [
|
|
636
|
+
{
|
|
637
|
+
filename: `${name}.props.json`,
|
|
638
|
+
exportDir,
|
|
639
|
+
data: JSON.stringify(data),
|
|
640
|
+
},
|
|
641
|
+
];
|
|
642
|
+
|
|
643
|
+
await this.screenshot({
|
|
644
|
+
filename,
|
|
645
|
+
pattern,
|
|
646
|
+
exportDir,
|
|
647
|
+
files,
|
|
648
|
+
commit: true,
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
|
|
583
652
|
get params() {
|
|
584
653
|
return {
|
|
585
654
|
...this.mountParams,
|
|
@@ -616,6 +685,8 @@ export class Render {
|
|
|
616
685
|
onStart: (params) => {
|
|
617
686
|
this.time = 0;
|
|
618
687
|
this.elapsed = 0;
|
|
688
|
+
this.playcount = 0;
|
|
689
|
+
this.playheadLast = 0;
|
|
619
690
|
this.thenLoop = performance.now();
|
|
620
691
|
|
|
621
692
|
sketch.beforeRecord.forEach((fn) => fn(params));
|
|
@@ -686,6 +757,10 @@ export class Render {
|
|
|
686
757
|
}
|
|
687
758
|
}
|
|
688
759
|
|
|
760
|
+
/**
|
|
761
|
+
*
|
|
762
|
+
* @param {number} time
|
|
763
|
+
*/
|
|
689
764
|
update(time) {
|
|
690
765
|
if (!this.paused) {
|
|
691
766
|
const deltaTime = time - this.then;
|
|
@@ -705,6 +780,8 @@ export class Render {
|
|
|
705
780
|
invalidate() {
|
|
706
781
|
this.time = 0;
|
|
707
782
|
this.elapsed = 0;
|
|
783
|
+
this.playcount = 0;
|
|
784
|
+
this.playheadLast = 0;
|
|
708
785
|
}
|
|
709
786
|
|
|
710
787
|
dispose() {
|
|
@@ -6,11 +6,28 @@ import Sketch from './Sketch.svelte.js';
|
|
|
6
6
|
import { rendering } from './rendering.svelte.js';
|
|
7
7
|
import { removeHotListeners } from '../triggers/index.js';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {Object} SketchInstance
|
|
11
|
+
* @property {string} [rendering]
|
|
12
|
+
* @property {any} [renderer]
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {Record<string, () => Promise<SketchInstance>>} SketchCollection
|
|
17
|
+
*/
|
|
18
|
+
|
|
9
19
|
class SketchesManager {
|
|
20
|
+
/** @type {Record<string, Sketch>} */
|
|
10
21
|
sketches = $state({});
|
|
11
22
|
keys = $derived(Object.keys(this.sketches));
|
|
12
23
|
count = $derived(this.keys.length);
|
|
13
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Load a single sketch from a collection
|
|
27
|
+
* @param {SketchCollection} collection - The collection of sketches
|
|
28
|
+
* @param {string} key - The key of the sketch to load
|
|
29
|
+
* @returns {Promise<SketchInstance | undefined>}
|
|
30
|
+
*/
|
|
14
31
|
async loadSketch(collection, key) {
|
|
15
32
|
try {
|
|
16
33
|
let sketch = await collection[key]();
|
|
@@ -27,6 +44,11 @@ class SketchesManager {
|
|
|
27
44
|
}
|
|
28
45
|
}
|
|
29
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Load all sketches from a collection
|
|
49
|
+
* @param {SketchCollection} collection - The collection of sketches to load
|
|
50
|
+
* @returns {Promise<void>}
|
|
51
|
+
*/
|
|
30
52
|
async loadAll(collection) {
|
|
31
53
|
const keys = [...Object.keys(collection)];
|
|
32
54
|
|
|
@@ -42,16 +64,30 @@ class SketchesManager {
|
|
|
42
64
|
keys.map((key) => this.loadSketch(collection, key)),
|
|
43
65
|
);
|
|
44
66
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
67
|
+
/** @type {Record<string, SketchInstance>} */
|
|
68
|
+
const newSketches = keys.reduce(
|
|
69
|
+
/**
|
|
70
|
+
* @param {Record<string, SketchInstance>} all
|
|
71
|
+
* @param {string} key
|
|
72
|
+
* @param {number} index
|
|
73
|
+
*/
|
|
74
|
+
(all, key, index) => {
|
|
75
|
+
if (loadedSketches[index]) {
|
|
76
|
+
all[key] = loadedSketches[index];
|
|
77
|
+
}
|
|
49
78
|
|
|
50
|
-
|
|
51
|
-
|
|
79
|
+
return all;
|
|
80
|
+
},
|
|
81
|
+
{},
|
|
82
|
+
);
|
|
52
83
|
|
|
84
|
+
/** @type {Record<string, Sketch>} */
|
|
53
85
|
const newInstancedSketches = Object.keys(newSketches).reduce(
|
|
54
|
-
|
|
86
|
+
/**
|
|
87
|
+
* @param {Record<string, Sketch>} all
|
|
88
|
+
* @param {string} key
|
|
89
|
+
*/
|
|
90
|
+
(all, key) => {
|
|
55
91
|
const prevSketch = this.sketches[key];
|
|
56
92
|
|
|
57
93
|
const instanced = new Sketch({
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persists data to localStorage with a "fragment." prefix
|
|
3
|
+
* @param {string} key - The storage key (will be prefixed with "fragment.")
|
|
4
|
+
* @param {any} data - The data to store (will be JSON stringified)
|
|
5
|
+
* @throws {Error} If localStorage operation fails
|
|
6
|
+
*/
|
|
1
7
|
export function persist(key, data) {
|
|
2
8
|
try {
|
|
3
9
|
window.localStorage.setItem(`fragment.${key}`, JSON.stringify(data));
|
|
@@ -6,6 +12,13 @@ export function persist(key, data) {
|
|
|
6
12
|
}
|
|
7
13
|
}
|
|
8
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Retrieves and optionally merges data from localStorage
|
|
17
|
+
* @param {string} key - The storage key (will be prefixed with "fragment.")
|
|
18
|
+
* @param {Record<string, any>} [target={}] - Optional target object to merge data into
|
|
19
|
+
* @param {any} [defaultValue={}] - Default value to return if no data found
|
|
20
|
+
* @returns {any} The retrieved data, defaultValue if not found, or undefined on error
|
|
21
|
+
*/
|
|
9
22
|
export function hydrate(key, target = {}, defaultValue = {}) {
|
|
10
23
|
try {
|
|
11
24
|
const storageKey = `fragment.${key}`;
|
|
@@ -31,14 +44,39 @@ export function hydrate(key, target = {}, defaultValue = {}) {
|
|
|
31
44
|
}
|
|
32
45
|
}
|
|
33
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Checks if a value is an object
|
|
49
|
+
* @param {any} item - The value to check
|
|
50
|
+
* @returns {boolean} True if the value is an object
|
|
51
|
+
*/
|
|
34
52
|
export function isObject(item) {
|
|
35
53
|
return item && typeof item === 'object';
|
|
36
54
|
}
|
|
37
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Checks if a value is a function
|
|
58
|
+
* @param {any} item - The value to check
|
|
59
|
+
* @returns {boolean} True if the value is a function
|
|
60
|
+
*/
|
|
38
61
|
export function isFunction(item) {
|
|
39
62
|
return item && typeof item === 'function';
|
|
40
63
|
}
|
|
41
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Returns true if the given cache key contains the data:image scheme.
|
|
67
|
+
* @param {any} value
|
|
68
|
+
* @return {boolean} Whether the given cache url contains the blob: scheme or not.
|
|
69
|
+
*/
|
|
70
|
+
export function isDataURL(value) {
|
|
71
|
+
return typeof value === 'string' && value.startsWith('data:image/');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Recursively assigns properties from source to target
|
|
76
|
+
* @param {Record<string, any>} target - The target object to assign to
|
|
77
|
+
* @param {Record<string, any>} source - The source object to assign from
|
|
78
|
+
* @returns {void}
|
|
79
|
+
*/
|
|
42
80
|
export function deepAssign(target, source) {
|
|
43
81
|
for (const key in source) {
|
|
44
82
|
if (isObject(source[key]) && isObject(target[key])) {
|
|
@@ -49,6 +87,12 @@ export function deepAssign(target, source) {
|
|
|
49
87
|
}
|
|
50
88
|
}
|
|
51
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Recursively compares two values for deep equality
|
|
92
|
+
* @param {any} target - The first value to compare
|
|
93
|
+
* @param {any} source - The second value to compare
|
|
94
|
+
* @returns {boolean} True if the values are deeply equal
|
|
95
|
+
*/
|
|
52
96
|
export function deepEqual(target, source) {
|
|
53
97
|
if (isObject(target) && isObject(source)) {
|
|
54
98
|
let isEqual = true;
|
|
@@ -75,6 +119,11 @@ export function deepEqual(target, source) {
|
|
|
75
119
|
return target === source;
|
|
76
120
|
}
|
|
77
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Creates a deep clone of a value
|
|
124
|
+
* @param {any} value - The value to clone
|
|
125
|
+
* @returns {any} A deep clone of the value, or the original value if cloning fails
|
|
126
|
+
*/
|
|
78
127
|
export function deepClone(value) {
|
|
79
128
|
if (isFunction(value)) {
|
|
80
129
|
return value;
|
|
@@ -4,11 +4,14 @@
|
|
|
4
4
|
import CheckboxInput from './fields/CheckboxInput.svelte';
|
|
5
5
|
import VectorInput from './fields/VectorInput.svelte';
|
|
6
6
|
import TextInput from './fields/TextInput.svelte';
|
|
7
|
+
import TextareaInput from './fields/TextareaInput.svelte';
|
|
7
8
|
import ColorInput from './fields/ColorInput.svelte';
|
|
8
9
|
import ListInput from './fields/ListInput.svelte';
|
|
9
10
|
import ButtonInput from './fields/ButtonInput.svelte';
|
|
10
11
|
import ImageInput from './fields/ImageInput.svelte';
|
|
11
12
|
import IntervalInput from './fields/IntervalInput.svelte';
|
|
13
|
+
import PaletteInput from './fields/PaletteInput.svelte';
|
|
14
|
+
import GradientInput from './fields/GradientInput.svelte';
|
|
12
15
|
import { fieldTypes } from '../utils/fields.utils.js';
|
|
13
16
|
|
|
14
17
|
const fields = {
|
|
@@ -17,13 +20,17 @@
|
|
|
17
20
|
[`${fieldTypes.VEC}`]: VectorInput,
|
|
18
21
|
[`${fieldTypes.CHECKBOX}`]: CheckboxInput,
|
|
19
22
|
[`${fieldTypes.TEXT}`]: TextInput,
|
|
23
|
+
[`${fieldTypes.TEXTAREA}`]: TextareaInput,
|
|
20
24
|
[`${fieldTypes.LIST}`]: ListInput,
|
|
21
25
|
[`${fieldTypes.COLOR}`]: ColorInput,
|
|
26
|
+
[`${fieldTypes.PALETTE}`]: PaletteInput,
|
|
22
27
|
[`${fieldTypes.BUTTON}`]: ButtonInput,
|
|
23
28
|
[`${fieldTypes.DOWNLOAD}`]: ButtonInput,
|
|
24
29
|
[`${fieldTypes.IMPORT}`]: ImportInput,
|
|
25
30
|
[`${fieldTypes.IMAGE}`]: ImageInput,
|
|
26
31
|
[`${fieldTypes.INTERVAL}`]: IntervalInput,
|
|
32
|
+
[`${fieldTypes.GRADIENT}`]: GradientInput,
|
|
33
|
+
[`${fieldTypes.WRAPPER}`]: null,
|
|
27
34
|
};
|
|
28
35
|
</script>
|
|
29
36
|
|
|
@@ -52,6 +59,7 @@
|
|
|
52
59
|
onchange,
|
|
53
60
|
onclick = () => {},
|
|
54
61
|
children,
|
|
62
|
+
trackChanges = false,
|
|
55
63
|
triggers = $bindable([]),
|
|
56
64
|
} = $props();
|
|
57
65
|
|
|
@@ -63,10 +71,18 @@
|
|
|
63
71
|
|
|
64
72
|
onchange(value);
|
|
65
73
|
},
|
|
74
|
+
/**
|
|
75
|
+
*
|
|
76
|
+
* @param {MouseEvent} event
|
|
77
|
+
*/
|
|
66
78
|
button: (event) => {
|
|
67
79
|
value(event);
|
|
68
80
|
onclick(event);
|
|
69
81
|
},
|
|
82
|
+
/**
|
|
83
|
+
*
|
|
84
|
+
* @param {MouseEvent} event
|
|
85
|
+
*/
|
|
70
86
|
download: async (event) => {
|
|
71
87
|
try {
|
|
72
88
|
let [data, filename] = await value(event);
|
|
@@ -105,7 +121,12 @@
|
|
|
105
121
|
fieldType === fieldTypes.BUTTON),
|
|
106
122
|
);
|
|
107
123
|
let triggersActive = $derived(triggers.length > 0);
|
|
124
|
+
let changed = $derived(trackChanges && !deepEqual(value, initialValue));
|
|
108
125
|
|
|
126
|
+
/**
|
|
127
|
+
*
|
|
128
|
+
* @param {MouseEvent} event
|
|
129
|
+
*/
|
|
109
130
|
function toggleTriggers(event) {
|
|
110
131
|
event.preventDefault();
|
|
111
132
|
|
|
@@ -123,16 +144,15 @@
|
|
|
123
144
|
};
|
|
124
145
|
}
|
|
125
146
|
|
|
126
|
-
function
|
|
127
|
-
|
|
128
|
-
return changed;
|
|
147
|
+
function restoreInitialValue() {
|
|
148
|
+
onchange($state.snapshot(initialValue));
|
|
129
149
|
}
|
|
130
150
|
</script>
|
|
131
151
|
|
|
132
152
|
<div
|
|
133
153
|
class="field"
|
|
134
154
|
class:disabled
|
|
135
|
-
class:changed={!disabled &&
|
|
155
|
+
class:changed={!disabled && changed}
|
|
136
156
|
style="--index: {index};"
|
|
137
157
|
>
|
|
138
158
|
<FieldSection
|
|
@@ -166,6 +186,15 @@
|
|
|
166
186
|
<Component {value} {...fieldProps} {onchange} onclick={onTrigger} />
|
|
167
187
|
{@render children?.()}
|
|
168
188
|
</FieldSection>
|
|
189
|
+
{#if changed}
|
|
190
|
+
<button
|
|
191
|
+
class="field__changed"
|
|
192
|
+
onclick={restoreInitialValue}
|
|
193
|
+
title="Restore initial value"
|
|
194
|
+
>
|
|
195
|
+
<span class="visually-hidden">Restore initial value</span>
|
|
196
|
+
</button>
|
|
197
|
+
{/if}
|
|
169
198
|
{#if triggerable}
|
|
170
199
|
<FieldSection {key} visible={showTriggers} secondary>
|
|
171
200
|
<FieldTriggers
|
|
@@ -192,28 +221,45 @@
|
|
|
192
221
|
border-bottom: 1px solid var(--fragment-spacing-color);
|
|
193
222
|
}
|
|
194
223
|
|
|
195
|
-
.
|
|
196
|
-
content: '';
|
|
197
|
-
|
|
224
|
+
.field__changed {
|
|
198
225
|
position: absolute;
|
|
199
226
|
top: 0px;
|
|
200
227
|
left: 0px;
|
|
201
228
|
bottom: 0px;
|
|
202
229
|
z-index: 1;
|
|
203
230
|
|
|
204
|
-
width:
|
|
231
|
+
width: 13px;
|
|
205
232
|
/* height: 4px; */
|
|
206
233
|
/* border-radius: 2px; */
|
|
207
234
|
|
|
208
|
-
|
|
235
|
+
background: transparent;
|
|
236
|
+
cursor: pointer;
|
|
237
|
+
|
|
238
|
+
&:before {
|
|
239
|
+
content: '';
|
|
240
|
+
|
|
241
|
+
position: absolute;
|
|
242
|
+
top: 0;
|
|
243
|
+
left: 0;
|
|
244
|
+
|
|
245
|
+
display: block;
|
|
246
|
+
width: 4px;
|
|
247
|
+
height: 100%;
|
|
248
|
+
|
|
249
|
+
--stripes-offset: calc(var(--index) * 1.9px);
|
|
250
|
+
|
|
251
|
+
background: repeating-linear-gradient(
|
|
252
|
+
45deg,
|
|
253
|
+
var(--fragment-accent-color) calc(0px + var(--stripes-offset)),
|
|
254
|
+
var(--fragment-accent-color) calc(2px + var(--stripes-offset)),
|
|
255
|
+
transparent calc(2px + var(--stripes-offset)),
|
|
256
|
+
transparent calc(4px + var(--stripes-offset))
|
|
257
|
+
);
|
|
258
|
+
}
|
|
209
259
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
var(--fragment-accent-color) calc(2px + var(--stripes-offset)),
|
|
214
|
-
transparent calc(2px + var(--stripes-offset)),
|
|
215
|
-
transparent calc(4px + var(--stripes-offset))
|
|
216
|
-
);
|
|
260
|
+
&:hover:before {
|
|
261
|
+
width: 7px;
|
|
262
|
+
}
|
|
217
263
|
}
|
|
218
264
|
|
|
219
265
|
:global(.field__input .field) {
|
|
@@ -229,6 +275,7 @@
|
|
|
229
275
|
.field__actions {
|
|
230
276
|
display: flex;
|
|
231
277
|
align-items: center;
|
|
278
|
+
gap: var(--column-gap);
|
|
232
279
|
}
|
|
233
280
|
|
|
234
281
|
.field__action {
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
let {
|
|
3
3
|
key,
|
|
4
4
|
visible = true,
|
|
5
|
-
secondary,
|
|
6
|
-
interactive,
|
|
5
|
+
secondary = false,
|
|
6
|
+
interactive = false,
|
|
7
7
|
displayName = undefined,
|
|
8
8
|
disabled = false,
|
|
9
9
|
children,
|
|
10
|
-
infos,
|
|
11
|
-
onclick,
|
|
10
|
+
infos = undefined,
|
|
11
|
+
onclick = () => {},
|
|
12
12
|
} = $props();
|
|
13
13
|
</script>
|
|
14
14
|
|
|
@@ -101,8 +101,8 @@
|
|
|
101
101
|
}}
|
|
102
102
|
/>
|
|
103
103
|
{/if}
|
|
104
|
-
|
|
105
|
-
<Field key="preset">
|
|
104
|
+
{#if rendering.resizing === SIZES.PRESET}
|
|
105
|
+
<Field key="preset" type="wrapper" value={`${rendering.preset}-${rendering.presetOrientation}`}>
|
|
106
106
|
<FieldInputRow --grid-template-columns="1fr 1fr">
|
|
107
107
|
<Select
|
|
108
108
|
value={rendering.preset}
|
|
@@ -123,18 +123,20 @@
|
|
|
123
123
|
/>
|
|
124
124
|
</FieldInputRow>
|
|
125
125
|
</Field>
|
|
126
|
-
{/if}
|
|
127
|
-
|
|
126
|
+
{/if}
|
|
128
127
|
{#if rendering.resizing !== SIZES.PRESET}
|
|
129
128
|
<Field
|
|
130
129
|
key="pixelRatio"
|
|
131
130
|
value={Number(rendering.pixelRatio)}
|
|
132
|
-
onchange={(pixelRatio) =>
|
|
131
|
+
onchange={(pixelRatio) => {
|
|
132
|
+
rendering.pixelRatio = pixelRatio;
|
|
133
|
+
}}
|
|
133
134
|
params={{
|
|
134
135
|
step: 0.1,
|
|
135
136
|
}}
|
|
136
137
|
/>
|
|
137
138
|
{/if}
|
|
139
|
+
|
|
138
140
|
<!-- {#if $sketchesCount > 1 && $monitors.length > 1}
|
|
139
141
|
<ParamsMultisampling />
|
|
140
142
|
{/if} -->
|
|
@@ -69,6 +69,16 @@
|
|
|
69
69
|
} else if (render?.recording && !exports.recording) {
|
|
70
70
|
render.stopRecording();
|
|
71
71
|
}
|
|
72
|
+
|
|
73
|
+
if (exports.capturing) {
|
|
74
|
+
render.screenshot();
|
|
75
|
+
exports.capturing = false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (exports.committing) {
|
|
79
|
+
render.commit();
|
|
80
|
+
exports.committing = false;
|
|
81
|
+
}
|
|
72
82
|
});
|
|
73
83
|
|
|
74
84
|
function checkForRefresh(event) {
|
|
@@ -108,6 +118,16 @@
|
|
|
108
118
|
}
|
|
109
119
|
}
|
|
110
120
|
|
|
121
|
+
function checkForCommit(event) {
|
|
122
|
+
if (event.metaKey || event.ctrlKey) {
|
|
123
|
+
event.preventDefault();
|
|
124
|
+
|
|
125
|
+
if (!exports.committing) {
|
|
126
|
+
render.commit();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
111
131
|
let backgroundColor = $derived.by(() => {
|
|
112
132
|
if (layout.previewing) {
|
|
113
133
|
return (
|
|
@@ -149,6 +169,7 @@
|
|
|
149
169
|
<KeyBinding type="down" key=" " onTrigger={checkForPause} />
|
|
150
170
|
<KeyBinding type="down" key="s" onTrigger={checkForScreenshot} />
|
|
151
171
|
<KeyBinding type="down" key="S" onTrigger={checkForRecord} />
|
|
172
|
+
<KeyBinding type="down" key="k" onTrigger={checkForCommit} />
|
|
152
173
|
|
|
153
174
|
<style>
|
|
154
175
|
.sketch-renderer {
|