fragment-tools 0.2.12 → 0.2.14

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.
Files changed (55) hide show
  1. package/package.json +12 -11
  2. package/src/cli/build.js +1 -0
  3. package/src/cli/create.js +22 -4
  4. package/src/cli/createConfig.js +8 -3
  5. package/src/cli/getEntries.js +10 -1
  6. package/src/cli/plugins/hot-shader-replacement.js +54 -16
  7. package/src/cli/plugins/save.js +97 -38
  8. package/src/cli/prompts.js +89 -36
  9. package/src/cli/run.js +1 -1
  10. package/src/client/app/actions/resize.js +8 -1
  11. package/src/client/app/attachments/draggable.js +93 -0
  12. package/src/client/app/client.js +90 -18
  13. package/src/client/app/components/IconFlip.svelte +46 -0
  14. package/src/client/app/hooks.js +25 -1
  15. package/src/client/app/lib/canvas-recorder/CanvasRecorder.js +95 -3
  16. package/src/client/app/lib/canvas-recorder/FrameRecorder.js +45 -3
  17. package/src/client/app/lib/canvas-recorder/GIFRecorder.js +72 -13
  18. package/src/client/app/lib/canvas-recorder/MediaBunnyRecorder.js +43 -9
  19. package/src/client/app/lib/canvas-recorder/utils.js +18 -9
  20. package/src/client/app/renderers/2DRenderer.js +20 -16
  21. package/src/client/app/renderers/FragmentRenderer.js +1 -1
  22. package/src/client/app/renderers/P5GLRenderer.js +14 -6
  23. package/src/client/app/renderers/P5Renderer.js +9 -1
  24. package/src/client/app/renderers/THREERenderer.js +62 -48
  25. package/src/client/app/state/Sketch.svelte.js +149 -9
  26. package/src/client/app/state/errors.svelte.js +19 -0
  27. package/src/client/app/state/exports.svelte.js +14 -1
  28. package/src/client/app/state/rendering.svelte.js +47 -0
  29. package/src/client/app/state/sketches.svelte.js +43 -7
  30. package/src/client/app/state/utils.svelte.js +49 -0
  31. package/src/client/app/ui/Field.svelte +6 -1
  32. package/src/client/app/ui/FieldSection.svelte +4 -4
  33. package/src/client/app/ui/ParamsOutput.svelte +1 -1
  34. package/src/client/app/ui/SketchRenderer.svelte +16 -0
  35. package/src/client/app/ui/fields/ButtonInput.svelte +2 -0
  36. package/src/client/app/ui/fields/CheckboxInput.svelte +13 -11
  37. package/src/client/app/ui/fields/ColorInput.svelte +16 -11
  38. package/src/client/app/ui/fields/GradientInput.svelte +607 -0
  39. package/src/client/app/ui/fields/Input.svelte +10 -6
  40. package/src/client/app/ui/fields/IntervalInput.svelte +27 -35
  41. package/src/client/app/ui/fields/NumberInput.svelte +51 -13
  42. package/src/client/app/ui/fields/PaletteInput.svelte +181 -0
  43. package/src/client/app/ui/fields/ProgressInput.svelte +44 -16
  44. package/src/client/app/ui/fields/TextareaInput.svelte +10 -10
  45. package/src/client/app/utils/canvas.utils.js +105 -28
  46. package/src/client/app/utils/color.utils.js +74 -17
  47. package/src/client/app/utils/fields.utils.js +68 -26
  48. package/src/client/app/utils/file.utils.js +86 -31
  49. package/src/client/app/utils/glsl.utils.js +11 -2
  50. package/src/client/app/utils/glslErrors.js +31 -21
  51. package/src/client/app/utils/index.js +28 -12
  52. package/src/client/main.js +7 -1
  53. package/src/types/global.d.ts +143 -0
  54. package/src/types/props.d.ts +40 -15
  55. package/tsconfig.json +1 -1
@@ -1,37 +1,55 @@
1
1
  import { WebGLRenderer, Scene } from 'three';
2
- import { client } from '@fragment/client';
2
+ import { client } from '../client';
3
3
  import { getShaderPath } from '../utils/glsl.utils';
4
4
  import { clearError } from '../state/errors.svelte';
5
5
 
6
6
  /**
7
- * @typedef {object} MountParamsThreeRenderer
7
+ * @typedef {object} MountParamsTHREERenderer
8
8
  * @property {HTMLCanvasElement} canvas
9
- * @property {THREE.Scene} scene
10
- * @property {THREE.WebGLRenderer} renderer
9
+ * @property {Scene} scene
10
+ * @property {WebGLRenderer} renderer
11
11
  */
12
12
 
13
13
  /**
14
- * @typedef {object} PreviewThreeRenderer
14
+ * @typedef {object} PreviewTHREERenderer
15
15
  * @property {number} id
16
- * @property {THREE.Scene} scene
17
- * @property {THREE.WebGLrenderer} renderer
18
- * @property {rendered} boolean
16
+ * @property {Scene} scene
17
+ * @property {WebGLRenderer} renderer
18
+ * @property {boolean} rendered
19
19
  */
20
20
 
21
- /** @type {PreviewThreeRenderer[]} */
21
+ /**
22
+ * @typedef {object} PreviewParams
23
+ * @property {number} id
24
+ * @property {HTMLCanvasElement} canvas
25
+ * @property {HTMLDivElement} container
26
+ * @property {number} width
27
+ * @property {number} height
28
+ * @property {number} pixelRatio
29
+ */
30
+
31
+ /**
32
+ * @typedef {Object} ShaderWarning
33
+ * @property {string} type - Warning type
34
+ * @property {string} importer - File that imported the shader
35
+ * @property {string} message - Warning message
36
+ * @property {Object} location - Location of the warning
37
+ * @property {string} location.lineText - The line of code with the warning
38
+ */
39
+
40
+ /**
41
+ * @typedef {Object} ShaderUpdate
42
+ * @property {ShaderWarning[]} [warnings] - Array of shader warnings
43
+ */
44
+
45
+ /** @type {PreviewTHREERenderer[]} */
22
46
  let previews = [];
23
47
 
24
48
  /**
25
- * @param {object} params
26
- * @param {number} params.id
27
- * @param {HTMLCanvasElement} params.canvas
28
- * @param {HTMLDivElement} params.container
29
- * @param {number} params.width
30
- * @param {number} params.height
31
- * @param {number} params.pixelRatio
32
- * @returns {MountParamsThreeRenderer}
49
+ * @param {PreviewParams} params
50
+ * @returns {MountParamsTHREERenderer}
33
51
  */
34
- export let onMountPreview = ({ id, canvas }) => {
52
+ export let onMountPreview = ({ id }) => {
35
53
  let renderer = new WebGLRenderer({ antialias: true });
36
54
 
37
55
  const render = renderer.render;
@@ -59,10 +77,7 @@ export let onMountPreview = ({ id, canvas }) => {
59
77
  };
60
78
 
61
79
  /**
62
- * @param {MountParamsThreeRenderer} params
63
- * @param {number} params.id
64
- * @param {HTMLCanvasElement} params.canvas
65
- * @param {HTMLDivElement} params.container
80
+ * @param {PreviewParams} params
66
81
  */
67
82
  export let onBeforeUpdatePreview = ({ id }) => {
68
83
  const preview = previews.find((p) => p.id === id);
@@ -73,10 +88,7 @@ export let onBeforeUpdatePreview = ({ id }) => {
73
88
  };
74
89
 
75
90
  /**
76
- * @param {MountParamsThreeRenderer} params
77
- * @param {number} params.id
78
- * @param {HTMLCanvasElement} params.canvas
79
- * @param {HTMLDivElement} params.container
91
+ * @param {PreviewParams} params
80
92
  */
81
93
  export let onAfterUpdatePreview = ({ id }) => {
82
94
  const preview = previews.find((p) => p.id === id);
@@ -94,14 +106,9 @@ export let onAfterUpdatePreview = ({ id }) => {
94
106
  };
95
107
 
96
108
  /**
97
- * @param {MountParamsThreeRenderer} params
98
- * @param {number} params.id
99
- * @param {HTMLCanvasElement} params.canvas
100
- * @param {number} params.width
101
- * @param {number} params.height
102
- * @param {number} params.pixelRatio
109
+ * @param {PreviewParams} params
103
110
  */
104
- export let onResizePreview = ({ id, width, height, pixelRatio }) => {
111
+ export let onResizePreview = ({ id, width, height, pixelRatio, canvas }) => {
105
112
  const preview = previews.find((p) => p.id === id);
106
113
 
107
114
  if (preview) {
@@ -112,10 +119,7 @@ export let onResizePreview = ({ id, width, height, pixelRatio }) => {
112
119
  };
113
120
 
114
121
  /**
115
- * @param {MountParamsThreeRenderer} params
116
- * @param {number} params.id
117
- * @param {HTMLCanvasElement} params.canvas
118
- * @param {HTMLElement} params.container
122
+ * @param {PreviewParams} params
119
123
  */
120
124
  export let onDestroyPreview = ({ id }) => {
121
125
  const previewIndex = previews.findIndex((p) => p.id === id);
@@ -131,8 +135,16 @@ export let onDestroyPreview = ({ id }) => {
131
135
  };
132
136
 
133
137
  /* HOT SHADER RELOADING */
138
+ /** @type {ShaderUpdate[]} */
134
139
  let _shaderUpdates = [];
135
140
 
141
+ function clearShaderUpdates() {
142
+ _shaderUpdates = [];
143
+ }
144
+
145
+ /**
146
+ * @param {Scene} scene
147
+ */
136
148
  function handleHotShaderUpdate(scene) {
137
149
  if (_shaderUpdates.length > 0) {
138
150
  const verifyMaterial = (material) => {
@@ -173,20 +185,22 @@ function handleHotShaderUpdate(scene) {
173
185
  }
174
186
  }
175
187
 
176
- function clearShaderUpdates() {
177
- _shaderUpdates = [];
178
- }
179
-
180
188
  if (import.meta.hot) {
181
- import.meta.hot.on('sketch-update', (data) => {
189
+ import.meta.hot.on('sketch-update', () => {
182
190
  clearShaderUpdates();
183
191
  });
184
192
  }
185
193
 
186
- client.on('shader-update', (shaderUpdates) => {
187
- previews.forEach((preview) => {
188
- clearError(preview.renderer.getContext().__uuid);
189
- });
194
+ client.on(
195
+ 'shader-update',
196
+ /**
197
+ * @param {ShaderUpdate[]} shaderUpdates
198
+ */
199
+ (shaderUpdates) => {
200
+ previews.forEach((preview) => {
201
+ clearError(preview.renderer.getContext().__uuid);
202
+ });
190
203
 
191
- _shaderUpdates = shaderUpdates;
192
- });
204
+ _shaderUpdates = shaderUpdates;
205
+ },
206
+ );
@@ -5,6 +5,7 @@ import {
5
5
  deepClone,
6
6
  deepEqual,
7
7
  hydrate,
8
+ isDataURL,
8
9
  isFunction,
9
10
  isObject,
10
11
  persist,
@@ -12,11 +13,58 @@ import {
12
13
 
13
14
  const noop = () => {};
14
15
 
16
+ /**
17
+ * @typedef InstanceProp
18
+ * @property {any} value
19
+ * @property {Record<string, any>} params
20
+ * @property {any[]} triggers
21
+ * @property {string|undefined} group
22
+ * @property {string|undefined} type
23
+ * @property {string|undefined} folder
24
+ * @property {string|null|undefined} displayName
25
+ * @property {boolean|(() => boolean)} hidden
26
+ * @property {boolean|(() => boolean)} disabled
27
+ */
28
+
29
+ /**
30
+ * @typedef SketchProp
31
+ * @property {any} value
32
+ * @property {any} __initialValue
33
+ * @property {any} __currentValue
34
+ * @property {Record<string, any>} params
35
+ * @property {any[]} triggers
36
+ * @property {string|undefined} group
37
+ * @property {string|undefined} type
38
+ * @property {string|undefined} folder
39
+ * @property {string|null|undefined} displayName
40
+ * @property {boolean} hidden
41
+ * @property {(() => boolean)} __hidden
42
+ * @property {boolean} disabled
43
+ * @property {(() => boolean)} __disabled
44
+ */
45
+
46
+ /**
47
+ * @typedef SketchPropFolder
48
+ * @property {string} id
49
+ * @property {string} displayName
50
+ * @property {string} displayName
51
+ * @property {string} rootId
52
+ * @property {(SketchPropFolder | { type: 'field', key: string })[]} children
53
+ * @property {SketchPropFolder|undefined} parent
54
+ * @property {'fieldgroup'} type
55
+ * @property {number} depth
56
+ * @property {boolean} collapsed
57
+ * @property {boolean} hidden
58
+ * @property {boolean} __initialCollapsed
59
+ */
60
+
15
61
  class Sketch {
62
+ /** @type {Record<string, SketchProp} */
16
63
  props = $state({});
17
64
  canvas = $state(null);
18
65
  backgroundColor = $state('inherit');
19
66
  propsGroups = $state([]);
67
+ /** @type {SketchPropFolder[]} */
20
68
  propsFolders = $state([]);
21
69
  version = $state(0);
22
70
 
@@ -38,9 +86,13 @@ class Sketch {
38
86
 
39
87
  this.recording = null;
40
88
  this.params = {};
89
+ /** @type {(() => {})[]} */
41
90
  this.beforeCapture = [];
91
+ /** @type {(() => {})[]} */
42
92
  this.beforeRecord = [];
93
+ /** @type {(() => {})[]} */
43
94
  this.afterCapture = [];
95
+ /** @type {(() => {})[]} */
44
96
  this.afterRecord = [];
45
97
 
46
98
  this.reconcile(previous);
@@ -58,8 +110,10 @@ class Sketch {
58
110
 
59
111
  reconcile(previous) {
60
112
  const instanceProps = this.instance.props ?? {};
113
+ /** @type {Record<string, SketchProp>} */
61
114
  const newProps = {};
62
115
  const newPropsGroups = [];
116
+ /** @type {SketchPropFolder[]} */
63
117
  const newPropsFolders = [];
64
118
 
65
119
  Object.keys(instanceProps).forEach((key) => {
@@ -117,7 +171,15 @@ class Sketch {
117
171
  }
118
172
  };
119
173
 
174
+ /**
175
+ *
176
+ * @param {SketchPropFolder[]} prevFolders
177
+ */
120
178
  const restorePropsFoldersState = (prevFolders) => {
179
+ /**
180
+ *
181
+ * @param {SketchPropFolder} prevFolder
182
+ */
121
183
  const restoreFolder = (prevFolder) => {
122
184
  const newFolder = newPropsFolders.find((f) => {
123
185
  return prevFolder.id === f.id;
@@ -163,6 +225,15 @@ class Sketch {
163
225
  this.propsFolders = newPropsFolders;
164
226
  }
165
227
 
228
+ /**
229
+ *
230
+ * @param {Record<string, any>} target
231
+ * @param {string} key
232
+ * @param {InstanceProp} instanceProp
233
+ * @param {*} propsFoldersCollection
234
+ * @param {*} propsGroupsCollection
235
+ * @returns
236
+ */
166
237
  createProp(
167
238
  target,
168
239
  key,
@@ -208,7 +279,11 @@ class Sketch {
208
279
  }
209
280
 
210
281
  if (folder) {
211
- this.createPropFolder(folder, propsFoldersCollection, key);
282
+ // this prevent references from breaking when using Proxies
283
+ const propsFoldersCollectionCopy = [...propsFoldersCollection];
284
+ this.createPropFolder(folder, propsFoldersCollectionCopy, key);
285
+ propsFoldersCollection.length = 0;
286
+ propsFoldersCollection.push(...propsFoldersCollectionCopy);
212
287
  }
213
288
 
214
289
  let prop = {
@@ -232,6 +307,11 @@ class Sketch {
232
307
  return prop;
233
308
  }
234
309
 
310
+ /**
311
+ *
312
+ * @param {string} key
313
+ * @param {any} newValue
314
+ */
235
315
  updateProp(key, newValue) {
236
316
  const prop = this.props[key];
237
317
  const instanceProp = this.instance.props[key];
@@ -243,7 +323,13 @@ class Sketch {
243
323
 
244
324
  if (instanceProp) {
245
325
  if (!deepEqual(instanceProp.value, newValue)) {
246
- if (isObject(instanceProp.value) && isObject(newValue)) {
326
+ if (
327
+ Array.isArray(instanceProp.value) &&
328
+ Array.isArray(newValue)
329
+ ) {
330
+ instanceProp.value.length = 0;
331
+ instanceProp.value.push(...newValue);
332
+ } else if (isObject(instanceProp.value) && isObject(newValue)) {
247
333
  deepAssign(instanceProp.value, newValue);
248
334
  } else {
249
335
  instanceProp.value = newValue;
@@ -266,6 +352,13 @@ class Sketch {
266
352
  this.version++;
267
353
  }
268
354
 
355
+ /**
356
+ *
357
+ * @param {string} folder
358
+ * @param {SketchPropFolder[]} collection
359
+ * @param {string} key
360
+ * @returns {SketchPropFolder | undefined}
361
+ */
269
362
  createPropFolder(folder, collection, key) {
270
363
  if (!folder) return undefined;
271
364
 
@@ -316,7 +409,7 @@ class Sketch {
316
409
  collection.push(fieldgroup);
317
410
  }
318
411
 
319
- if (isCurrent) {
412
+ if (fieldgroup && isCurrent) {
320
413
  fieldgroup.children.push({
321
414
  type: 'field',
322
415
  key,
@@ -330,6 +423,11 @@ class Sketch {
330
423
  return propFolder;
331
424
  }
332
425
 
426
+ /**
427
+ *
428
+ * @param {SketchPropFolder} folder
429
+ * @param {boolean} collapsed
430
+ */
333
431
  updateFolder(folder, collapsed) {
334
432
  this.propsFolders.forEach((f, index) => {
335
433
  if (f.id === folder.id) {
@@ -405,17 +503,20 @@ class Sketch {
405
503
  );
406
504
  fieldgroup.children.splice(childIndex, 1);
407
505
 
506
+ /**
507
+ *
508
+ * @param {SketchPropFolder} fieldgroup
509
+ */
408
510
  const removeFolderIfNeeded = (fieldgroup) => {
409
511
  if (fieldgroup.children.length === 0) {
410
512
  const currentFolderIndex =
411
513
  this.propsFolders.findIndex(
412
- (c) => c === fieldgroup,
514
+ (c) => c.id === fieldgroup.id,
413
515
  );
414
516
  this.propsFolders.splice(currentFolderIndex, 1);
415
517
 
416
- const { parent } = fieldgroup;
417
-
418
- if (parent) {
518
+ if (fieldgroup && fieldgroup.parent) {
519
+ const { parent } = fieldgroup;
419
520
  const childIndex =
420
521
  parent.children.findIndex(
421
522
  (c) => c.id === fieldgroup.id,
@@ -430,11 +531,14 @@ class Sketch {
430
531
  }
431
532
 
432
533
  if (instanceProp.folder) {
534
+ // this prevent references from breaking when using Proxies
535
+ const propsFoldersCopy = [...this.propsFolders];
433
536
  this.createPropFolder(
434
537
  instanceProp.folder,
435
- this.propsFolders,
538
+ propsFoldersCopy,
436
539
  key,
437
540
  );
541
+ this.propsFolders = propsFoldersCopy;
438
542
  prop.folder = instanceProp.folder;
439
543
  } else {
440
544
  prop.folder = undefined;
@@ -498,25 +602,61 @@ class Sketch {
498
602
  });
499
603
  }
500
604
 
605
+ /**
606
+ * @param {() => void} fn
607
+ */
501
608
  onBeforeCapture(fn) {
502
609
  this.beforeCapture.push(fn);
503
610
  }
504
611
 
612
+ /**
613
+ *
614
+ * @param {() => void} fn
615
+ */
505
616
  onBeforeRecord(fn) {
506
617
  this.beforeRecord.push(fn);
507
618
  }
508
619
 
620
+ /**
621
+ * @param {() => void} fn
622
+ */
509
623
  onAfterCapture(fn) {
510
624
  this.afterCapture.push(fn);
511
625
  }
512
626
 
627
+ /**
628
+ * @param {() => void} fn
629
+ */
513
630
  onAfterRecord(fn) {
514
631
  this.afterRecord.push(fn);
515
632
  }
516
633
 
517
634
  toJSON() {
635
+ /**
636
+ * @typedef SketchPropJSON
637
+ * @property {any} value
638
+ * @property {Record<string, any>} params
639
+ * @property {any[]} triggers
640
+ * @property {any} __initialValue
641
+ * @property {any} __currentValue
642
+ */
643
+ /** @type {Record<string, SketchPropJSON>} */
644
+ const props = {};
645
+
646
+ for (const key in this.props) {
647
+ const prop = this.props[key];
648
+
649
+ props[key] = {
650
+ value: isDataURL(prop.value) ? prop.__initialValue : prop.value,
651
+ params: prop.params,
652
+ triggers: prop.triggers,
653
+ __initialValue: prop.__initialValue,
654
+ __currentValue: prop.__currentValue,
655
+ };
656
+ }
657
+
518
658
  return {
519
- props: this.props,
659
+ props,
520
660
  propsFolders: this.propsFolders.map(
521
661
  ({
522
662
  id,
@@ -1,17 +1,36 @@
1
1
  import { SvelteMap } from 'svelte/reactivity';
2
2
 
3
+ /**
4
+ * Map storing errors by context
5
+ * @type {SvelteMap<string, Error>}
6
+ */
3
7
  export let errors = new SvelteMap();
4
8
 
9
+ /**
10
+ * Display an error for a specific context
11
+ * @param {Error | any} error - The error to display
12
+ * @param {string} context - The context identifier for the error
13
+ * @returns {void}
14
+ */
5
15
  export function displayError(error, context) {
6
16
  errors.set(context, error);
7
17
  }
8
18
 
19
+ /**
20
+ * Clear the error for a specific context
21
+ * @param {string} context - The context identifier
22
+ * @returns {void}
23
+ */
9
24
  export function clearError(context) {
10
25
  if (errors.has(context)) {
11
26
  errors.delete(context);
12
27
  }
13
28
  }
14
29
 
30
+ /**
31
+ * Clear all errors
32
+ * @returns {void}
33
+ */
15
34
  export function clearErrors() {
16
35
  errors.clear();
17
36
  }
@@ -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
- await screenshotCanvas(canvas, {
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',
@@ -581,6 +586,8 @@ export class Render {
581
586
  filename = this.sketch.key,
582
587
  pattern = this.sketch.filenamePattern,
583
588
  exportDir = this.sketch.exportDir,
589
+ files = [],
590
+ commit = false,
584
591
  } = {}) {
585
592
  const { sketch } = this;
586
593
 
@@ -591,6 +598,8 @@ export class Render {
591
598
  params: {
592
599
  props: sketch.props,
593
600
  },
601
+ files,
602
+ commit,
594
603
  onBeforeCapture: (params) => {
595
604
  sketch.beforeCapture.forEach((fn) => fn(params));
596
605
  this.renderSketch();
@@ -602,6 +611,44 @@ export class Render {
602
611
  });
603
612
  }
604
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
+
605
652
  get params() {
606
653
  return {
607
654
  ...this.mountParams,
@@ -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
- const newSketches = keys.reduce((all, key, index) => {
46
- if (loadedSketches[index]) {
47
- all[key] = loadedSketches[index];
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
- return all;
51
- }, {});
79
+ return all;
80
+ },
81
+ {},
82
+ );
52
83
 
84
+ /** @type {Record<string, Sketch>} */
53
85
  const newInstancedSketches = Object.keys(newSketches).reduce(
54
- (all, key, index) => {
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({