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.
Files changed (67) 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 +2 -2
  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/cli/templates/blank/index.ts +1 -1
  11. package/src/cli/templates/default/index.js +10 -2
  12. package/src/cli/templates/default/index.ts +5 -2
  13. package/src/cli/templates/fragment-gl/index.ts +1 -1
  14. package/src/cli/templates/p5/index.ts +1 -1
  15. package/src/cli/templates/p5-webgl/index.ts +1 -1
  16. package/src/cli/templates/three-fragment/index.js +5 -3
  17. package/src/cli/templates/three-fragment/index.ts +5 -4
  18. package/src/cli/templates/three-orthographic/index.js +6 -1
  19. package/src/cli/templates/three-orthographic/index.ts +6 -2
  20. package/src/cli/templates/three-perspective/index.js +6 -1
  21. package/src/cli/templates/three-perspective/index.ts +6 -2
  22. package/src/client/app/actions/resize.js +8 -1
  23. package/src/client/app/attachments/draggable.js +93 -0
  24. package/src/client/app/client.js +90 -18
  25. package/src/client/app/components/IconFlip.svelte +46 -0
  26. package/src/client/app/hooks.js +25 -1
  27. package/src/client/app/lib/canvas-recorder/CanvasRecorder.js +95 -3
  28. package/src/client/app/lib/canvas-recorder/FrameRecorder.js +45 -3
  29. package/src/client/app/lib/canvas-recorder/GIFRecorder.js +72 -13
  30. package/src/client/app/lib/canvas-recorder/MediaBunnyRecorder.js +43 -9
  31. package/src/client/app/lib/canvas-recorder/utils.js +18 -9
  32. package/src/client/app/modules/Params.svelte +1 -0
  33. package/src/client/app/renderers/2DRenderer.js +20 -16
  34. package/src/client/app/renderers/P5GLRenderer.js +13 -5
  35. package/src/client/app/renderers/P5Renderer.js +9 -1
  36. package/src/client/app/renderers/THREERenderer.js +63 -48
  37. package/src/client/app/state/Sketch.svelte.js +150 -10
  38. package/src/client/app/state/errors.svelte.js +19 -0
  39. package/src/client/app/state/exports.svelte.js +14 -1
  40. package/src/client/app/state/rendering.svelte.js +90 -13
  41. package/src/client/app/state/sketches.svelte.js +43 -7
  42. package/src/client/app/state/utils.svelte.js +49 -0
  43. package/src/client/app/ui/Field.svelte +63 -16
  44. package/src/client/app/ui/FieldSection.svelte +4 -4
  45. package/src/client/app/ui/ParamsOutput.svelte +7 -5
  46. package/src/client/app/ui/SketchRenderer.svelte +21 -0
  47. package/src/client/app/ui/fields/ButtonInput.svelte +2 -0
  48. package/src/client/app/ui/fields/CheckboxInput.svelte +13 -11
  49. package/src/client/app/ui/fields/ColorInput.svelte +16 -11
  50. package/src/client/app/ui/fields/GradientInput.svelte +607 -0
  51. package/src/client/app/ui/fields/Input.svelte +10 -6
  52. package/src/client/app/ui/fields/IntervalInput.svelte +27 -35
  53. package/src/client/app/ui/fields/NumberInput.svelte +51 -13
  54. package/src/client/app/ui/fields/PaletteInput.svelte +181 -0
  55. package/src/client/app/ui/fields/ProgressInput.svelte +44 -16
  56. package/src/client/app/ui/fields/TextareaInput.svelte +93 -0
  57. package/src/client/app/utils/canvas.utils.js +105 -28
  58. package/src/client/app/utils/color.utils.js +74 -17
  59. package/src/client/app/utils/fields.utils.js +70 -17
  60. package/src/client/app/utils/file.utils.js +86 -31
  61. package/src/client/app/utils/glsl.utils.js +11 -2
  62. package/src/client/app/utils/glslErrors.js +31 -21
  63. package/src/client/app/utils/index.js +28 -12
  64. package/src/client/main.js +7 -1
  65. package/src/types/global.d.ts +143 -0
  66. package/src/types/props.d.ts +41 -15
  67. package/tsconfig.json +1 -1
@@ -3,15 +3,24 @@ import { changeDpiDataUrl } from 'changedpi';
3
3
  const supportedEncodings = ['image/png', 'image/jpeg', 'image/webp'];
4
4
 
5
5
  /**
6
- * Create a Data URL from a canvas
7
- * @param {HTMLCanvasElement} canvas
8
- * @param {object} [options]
9
- * @param {string} [encoding="image/png"]
10
- * @param {number} [encodingQuality=0.92]
11
- * @param {number} [pixelsPerInch=72]
12
- * @returns {object} result
13
- * @returns {string} result.dataURL
14
- * @returns {string} result.extension
6
+ * @typedef {Object} ExportCanvasOptions
7
+ * @property {string} [encoding='image/png'] - Image MIME type (image/png, image/jpeg, image/webp)
8
+ * @property {number} [encodingQuality=0.92] - Image quality (0-1)
9
+ * @property {number} [pixelsPerInch=72] - DPI/PPI for the exported image
10
+ */
11
+
12
+ /**
13
+ * @typedef {Object} ExportCanvasResult
14
+ * @property {string} extension - File extension (e.g., '.png', '.jpg')
15
+ * @property {string} dataURL - Data URL of the exported canvas
16
+ */
17
+
18
+ /**
19
+ * Export a canvas to a data URL with specified encoding and DPI
20
+ * @param {HTMLCanvasElement} canvas - The canvas to export
21
+ * @param {ExportCanvasOptions} [options={}] - Export options
22
+ * @returns {ExportCanvasResult}
23
+ * @throws {Error} If encoding is not supported
15
24
  */
16
25
  export function exportCanvas(
17
26
  canvas,
@@ -151,6 +151,7 @@
151
151
  {disabled}
152
152
  bind:params={sketchProps[key].params}
153
153
  bind:triggers={prop.triggers}
154
+ trackChanges
154
155
  onclick={(event) => {
155
156
  sketch.version++;
156
157
  // value(event, sketch.params);
@@ -5,32 +5,36 @@
5
5
  */
6
6
 
7
7
  /**
8
- * @param {object} params
9
- * @param {number} params.id
10
- * @param {HTMLCanvasElement} params.canvas
11
- * @param {HTMLElement} params.container
12
- * @param {number} params.width
13
- * @param {number} params.height
14
- * @param {number} params.pixelRatio
8
+ * @typedef {object} PreviewParams
9
+ * @property {number} id
10
+ * @property {HTMLCanvasElement} canvas
11
+ * @property {HTMLElement} container
12
+ * @property {number} width
13
+ * @property {number} height
14
+ * @property {number} pixelRatio
15
+ */
16
+
17
+ /**
18
+ * @param {PreviewParams} params
15
19
  * @returns {MountParams2DRenderer}
16
20
  */
17
21
  export let onMountPreview = ({ canvas }) => {
22
+ const context = canvas.getContext('2d');
23
+
24
+ if (!context) {
25
+ throw new Error(`Cannot get CanvasRenderingContext2D from canvas`);
26
+ }
27
+
18
28
  return {
19
29
  canvas,
20
- context: canvas.getContext('2d'),
30
+ context,
21
31
  };
22
32
  };
23
33
 
24
34
  /**
25
- * @param {MountParams2DRenderer} params
26
- * @param {number} params.id
27
- * @param {HTMLCanvasElement} params.canvas
28
- * @param {HTMLElement} params.container
29
- * @param {number} params.width
30
- * @param {number} params.height
31
- * @param {number} params.pixelRatio
35
+ * @param {MountParams2DRenderer & PreviewParams} params
32
36
  */
33
- export let onResizePreview = ({ id, canvas, width, height, pixelRatio }) => {
37
+ export let onResizePreview = ({ canvas, width, height, pixelRatio }) => {
34
38
  canvas.width = width * pixelRatio;
35
39
  canvas.height = height * pixelRatio;
36
40
  canvas.style.width = `${width}px`;
@@ -29,8 +29,16 @@ let previews = [];
29
29
  * @param {number} params.pixelRatio
30
30
  * @returns {MountParamsP5GLRenderer}
31
31
  */
32
- export let onMountPreview = ({ id, container, canvas, width, height }) => {
32
+ export let onMountPreview = ({
33
+ id,
34
+ container,
35
+ canvas,
36
+ width,
37
+ height,
38
+ pixelRatio,
39
+ }) => {
33
40
  const p = new p5((sketch) => {
41
+ sketch.pixelDensity(pixelRatio);
34
42
  sketch.setup = () => {
35
43
  sketch.createCanvas(width, height, 'webgl', canvas);
36
44
  };
@@ -49,7 +57,7 @@ export let onMountPreview = ({ id, container, canvas, width, height }) => {
49
57
  };
50
58
 
51
59
  /**
52
- * @param {MountParamsP5GLRenderer} params
60
+ * @param {object} params
53
61
  * @param {number} params.id
54
62
  * @param {HTMLCanvasElement} params.canvas
55
63
  * @param {HTMLDivElement} params.container
@@ -65,7 +73,7 @@ export let onBeforeUpdatePreview = ({ id }) => {
65
73
  };
66
74
 
67
75
  /**
68
- * @param {MountParamsP5GLRenderer} params
76
+ * @param {object} params
69
77
  * @param {number} params.id
70
78
  * @param {HTMLCanvasElement} params.canvas
71
79
  * @param {HTMLDivElement} params.container
@@ -86,7 +94,7 @@ export let onAfterUpdatePreview = ({ id }) => {
86
94
  };
87
95
 
88
96
  /**
89
- * @param {MountParamsP5GLRenderer} params
97
+ * @param {object} params
90
98
  * @param {number} params.id
91
99
  * @param {HTMLCanvasElement} params.canvas
92
100
  * @param {number} params.width
@@ -103,7 +111,7 @@ export let onResizePreview = ({ id, width, height, pixelRatio }) => {
103
111
  };
104
112
 
105
113
  /**
106
- * @param {MountParamsP5GLRenderer} params
114
+ * @param {object} params
107
115
  * @param {number} params.id
108
116
  * @param {HTMLCanvasElement} params.canvas
109
117
  * @param {HTMLElement} params.container
@@ -25,8 +25,16 @@ let previews = [];
25
25
  * @param {number} params.pixelRatio
26
26
  * @returns {MountParamsP5Renderer}
27
27
  */
28
- export let onMountPreview = ({ id, container, canvas, width, height }) => {
28
+ export let onMountPreview = ({
29
+ id,
30
+ container,
31
+ canvas,
32
+ width,
33
+ height,
34
+ pixelRatio,
35
+ }) => {
29
36
  const p = new p5((sketch) => {
37
+ sketch.pixelDensity(pixelRatio);
30
38
  sketch.setup = () => {
31
39
  sketch.createCanvas(width, height, canvas);
32
40
  };
@@ -4,35 +4,53 @@ 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.renderer} 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 }) => {
35
- let renderer = new WebGLRenderer({ antialias: true, canvas });
52
+ export let onMountPreview = ({ id }) => {
53
+ let renderer = new WebGLRenderer({ antialias: true });
36
54
 
37
55
  const render = renderer.render;
38
56
 
@@ -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);
@@ -125,13 +129,22 @@ export let onDestroyPreview = ({ id }) => {
125
129
  const { renderer } = preview;
126
130
  clearError(renderer.getContext().__uuid);
127
131
  renderer.dispose();
132
+ renderer.forceContextLoss();
128
133
  previews.splice(previewIndex, 1);
129
134
  }
130
135
  };
131
136
 
132
137
  /* HOT SHADER RELOADING */
138
+ /** @type {ShaderUpdate[]} */
133
139
  let _shaderUpdates = [];
134
140
 
141
+ function clearShaderUpdates() {
142
+ _shaderUpdates = [];
143
+ }
144
+
145
+ /**
146
+ * @param {Scene} scene
147
+ */
135
148
  function handleHotShaderUpdate(scene) {
136
149
  if (_shaderUpdates.length > 0) {
137
150
  const verifyMaterial = (material) => {
@@ -172,20 +185,22 @@ function handleHotShaderUpdate(scene) {
172
185
  }
173
186
  }
174
187
 
175
- function clearShaderUpdates() {
176
- _shaderUpdates = [];
177
- }
178
-
179
188
  if (import.meta.hot) {
180
- import.meta.hot.on('sketch-update', (data) => {
189
+ import.meta.hot.on('sketch-update', () => {
181
190
  clearShaderUpdates();
182
191
  });
183
192
  }
184
193
 
185
- client.on('shader-update', (shaderUpdates) => {
186
- previews.forEach((preview) => {
187
- clearError(preview.renderer.getContext().__uuid);
188
- });
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
+ });
189
203
 
190
- _shaderUpdates = shaderUpdates;
191
- });
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;
@@ -481,7 +585,7 @@ class Sketch {
481
585
  fieldgroups.forEach((fieldgroup) => {
482
586
  const hasAllFieldsHidden = fieldgroup.children
483
587
  .filter((child) => child.type === 'field')
484
- .every((child) => this.props[child.key].__hidden());
588
+ .every((child) => this.props[child.key]?.__hidden());
485
589
  const hasAllFieldgroupsHidden = fieldgroup.children
486
590
  .filter((child) => child.type === 'fieldgroup')
487
591
  .every((child) => child.hidden);
@@ -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
  }