fragment-tools 0.1.19 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/bin/index.js +1 -0
  2. package/package.json +5 -6
  3. package/src/cli/templates/three-fragment/index.js +6 -6
  4. package/src/cli/templates/three-orthographic/index.js +3 -3
  5. package/src/cli/templates/three-perspective/index.js +3 -3
  6. package/src/client/app/actions/resize.js +14 -0
  7. package/src/client/app/components/HintLoading.svelte +94 -0
  8. package/src/client/app/components/HintPaused.svelte +88 -0
  9. package/src/client/app/components/HintRecord.svelte +62 -0
  10. package/src/client/app/components/IconLocked.svelte +51 -0
  11. package/src/client/app/components/IconTriggers.svelte +48 -0
  12. package/src/client/app/components/Init.svelte +14 -27
  13. package/src/client/app/components/KeyBinding.svelte +3 -6
  14. package/src/client/app/helpers.js +4 -40
  15. package/src/client/app/hooks.js +41 -17
  16. package/src/client/app/inputs/MIDI.js +2 -1
  17. package/src/client/app/lib/canvas-recorder/CanvasRecorder.js +6 -1
  18. package/src/client/app/lib/gl/Renderer.js +1 -0
  19. package/src/client/app/lib/svelte-json-tree/ErrorNode.svelte +28 -0
  20. package/src/client/app/lib/svelte-json-tree/ErrorStack.svelte +31 -0
  21. package/src/client/app/lib/svelte-json-tree/Expandable.svelte +25 -0
  22. package/src/client/app/lib/svelte-json-tree/JSONArrayNode.svelte +38 -0
  23. package/src/client/app/lib/svelte-json-tree/JSONArrow.svelte +47 -0
  24. package/src/client/app/lib/svelte-json-tree/JSONFunctionNode.svelte +114 -0
  25. package/src/client/app/lib/svelte-json-tree/JSONIterableArrayNode.svelte +60 -0
  26. package/src/client/app/lib/svelte-json-tree/JSONIterableMapNode.svelte +87 -0
  27. package/src/client/app/lib/svelte-json-tree/JSONNested.svelte +94 -0
  28. package/src/client/app/lib/svelte-json-tree/JSONNode.svelte +91 -0
  29. package/src/client/app/lib/svelte-json-tree/JSONObjectNode.svelte +40 -0
  30. package/src/client/app/lib/svelte-json-tree/JSONStringNode.svelte +31 -0
  31. package/src/client/app/lib/svelte-json-tree/JSONValueNode.svelte +31 -0
  32. package/src/client/app/lib/svelte-json-tree/PreviewList.svelte +38 -0
  33. package/src/client/app/lib/svelte-json-tree/RegExpNode.svelte +42 -0
  34. package/src/client/app/lib/svelte-json-tree/Root.svelte +75 -0
  35. package/src/client/app/lib/svelte-json-tree/Summary.svelte +9 -0
  36. package/src/client/app/lib/svelte-json-tree/TypedArrayNode.svelte +56 -0
  37. package/src/client/app/lib/svelte-json-tree/index.js +1 -0
  38. package/src/client/app/lib/svelte-json-tree/utils.js +57 -0
  39. package/src/client/app/modules/Console/ConsoleLine.svelte +12 -11
  40. package/src/client/app/modules/Console.svelte +82 -17
  41. package/src/client/app/modules/Exports.svelte +48 -48
  42. package/src/client/app/modules/MidiPanel.svelte +12 -19
  43. package/src/client/app/modules/Monitor.svelte +147 -55
  44. package/src/client/app/modules/Params.svelte +127 -80
  45. package/src/client/app/renderers/2DRenderer.js +1 -0
  46. package/src/client/app/renderers/FragmentRenderer.js +1 -1
  47. package/src/client/app/renderers/P5GLRenderer.js +11 -5
  48. package/src/client/app/renderers/P5Renderer.js +7 -3
  49. package/src/client/app/renderers/THREERenderer.js +42 -79
  50. package/src/client/app/state/Sketch.svelte.js +538 -0
  51. package/src/client/app/state/errors.svelte.js +17 -0
  52. package/src/client/app/state/exports.svelte.js +152 -0
  53. package/src/client/app/state/layout.svelte.js +205 -0
  54. package/src/client/app/state/monitors.svelte.js +36 -0
  55. package/src/client/app/state/renderers.svelte.js +77 -0
  56. package/src/client/app/state/rendering.svelte.js +697 -0
  57. package/src/client/app/state/sketches.svelte.js +73 -0
  58. package/src/client/app/state/utils.svelte.js +65 -0
  59. package/src/client/app/ui/Build.svelte +53 -60
  60. package/src/client/app/ui/ErrorOverlay.svelte +2 -2
  61. package/src/client/app/ui/Field.svelte +63 -189
  62. package/src/client/app/ui/FieldGroup.svelte +4 -5
  63. package/src/client/app/ui/FieldSection.svelte +14 -9
  64. package/src/client/app/ui/FieldSpace.svelte +1 -1
  65. package/src/client/app/ui/FieldTrigger.svelte +86 -84
  66. package/src/client/app/ui/FieldTriggers.svelte +25 -24
  67. package/src/client/app/ui/FloatingParams.svelte +50 -12
  68. package/src/client/app/ui/Layout.svelte +24 -13
  69. package/src/client/app/ui/LayoutColumn.svelte +2 -2
  70. package/src/client/app/ui/LayoutComponent.svelte +86 -195
  71. package/src/client/app/ui/LayoutResizer.svelte +25 -37
  72. package/src/client/app/ui/LayoutRoot.svelte +3 -5
  73. package/src/client/app/ui/LayoutRow.svelte +2 -2
  74. package/src/client/app/ui/LayoutToolbar.svelte +17 -76
  75. package/src/client/app/ui/Module.svelte +31 -35
  76. package/src/client/app/ui/ModuleHeaderAction.svelte +23 -16
  77. package/src/client/app/ui/ModuleHeaderButton.svelte +3 -3
  78. package/src/client/app/ui/ModuleHeaderSelect.svelte +4 -12
  79. package/src/client/app/ui/ModuleRenderer.svelte +84 -22
  80. package/src/client/app/ui/ParamsOutput.svelte +61 -77
  81. package/src/client/app/ui/Preview.svelte +15 -4
  82. package/src/client/app/ui/SelectChevrons.svelte +1 -2
  83. package/src/client/app/ui/SketchRenderer.svelte +89 -701
  84. package/src/client/app/ui/SketchSelect.svelte +14 -49
  85. package/src/client/app/ui/fields/ButtonInput.svelte +14 -11
  86. package/src/client/app/ui/fields/CheckboxInput.svelte +5 -12
  87. package/src/client/app/ui/fields/ColorInput.svelte +46 -121
  88. package/src/client/app/ui/fields/FieldInputRow.svelte +5 -1
  89. package/src/client/app/ui/fields/ImageInput.svelte +14 -14
  90. package/src/client/app/ui/fields/Input.svelte +19 -25
  91. package/src/client/app/ui/fields/IntervalInput.svelte +22 -22
  92. package/src/client/app/ui/fields/NumberInput.svelte +32 -38
  93. package/src/client/app/ui/fields/ProgressInput.svelte +14 -13
  94. package/src/client/app/ui/fields/Select.svelte +34 -45
  95. package/src/client/app/ui/fields/TextInput.svelte +10 -6
  96. package/src/client/app/ui/fields/VectorInput.svelte +25 -30
  97. package/src/client/app/utils/canvas.utils.js +8 -8
  98. package/src/client/app/utils/color.utils.js +46 -13
  99. package/src/client/app/utils/fields.utils.js +1 -1
  100. package/src/client/app/utils/glsl.utils.js +1 -1
  101. package/src/client/app/utils/glslErrors.js +1 -1
  102. package/src/client/main.js +2 -2
@@ -0,0 +1,697 @@
1
+ import { PRESET_ORIENTATIONS, getDimensionsForPreset } from '../lib/presets';
2
+ import {
3
+ checkForTriggersClick,
4
+ checkForTriggersDown,
5
+ checkForTriggersMove,
6
+ checkForTriggersUp,
7
+ } from '../triggers/Mouse.js';
8
+ import { map } from '../utils/math.utils.js';
9
+ import { clearError, displayError } from './errors.svelte.js';
10
+ import { exports } from './exports.svelte.js';
11
+ import { layout } from './layout.svelte.js';
12
+ import { persist, hydrate } from './utils.svelte';
13
+ import presets from '../lib/presets';
14
+ import { client } from '../client.js';
15
+
16
+ export const SIZES = {
17
+ FIXED: 'fixed',
18
+ PRESET: 'preset',
19
+ ASPECT_RATIO: 'aspect-ratio',
20
+ WINDOW: 'window',
21
+ SCALE: 'scale',
22
+ };
23
+
24
+ let MONITOR_ID = 0;
25
+
26
+ class Rendering {
27
+ width = $state(1024);
28
+ fixedWidth = $state(1024);
29
+ height = $state(1024);
30
+ fixedHeight = $state(1024);
31
+ pixelRatio = $state(1);
32
+ resizing = $state(SIZES.FIXED);
33
+ aspectRatio = $state(1);
34
+ scale = $state(1);
35
+ preset = $state('a4');
36
+ presetOrientation = $state(PRESET_ORIENTATIONS.PORTRAIT);
37
+ refreshRate = $state(0);
38
+ monitors = $state([]);
39
+ renders = $state([]);
40
+
41
+ constructor() {
42
+ this.key = 'rendering';
43
+
44
+ this.renderers = {};
45
+ this.recording = false;
46
+ // sync time between multiple windows
47
+ this.today = new Date();
48
+ this.today.setHours(0);
49
+ this.today.setMinutes(0);
50
+ this.today.setSeconds(0);
51
+ this.today.setMilliseconds(0);
52
+ this.today = this.today.getTime();
53
+
54
+ $effect.root(() => {
55
+ $effect(() => {
56
+ if (!layout.previewing && !__BUILD__) {
57
+ persist(this.key, {
58
+ width: this.width,
59
+ height: this.height,
60
+ fixedWidth: this.fixedWidth,
61
+ fixedHeight: this.fixedHeight,
62
+ pixelRatio: this.pixelRatio,
63
+ resizing: this.resizing,
64
+ aspectRatio: this.aspectRatio,
65
+ scale: this.scale,
66
+ preset: this.preset,
67
+ presetOrientation: this.presetOrientation,
68
+ });
69
+ }
70
+ });
71
+
72
+ $effect(() => {
73
+ const { width, height, pixelRatio } = this;
74
+
75
+ const keys = Object.keys(this.renderers);
76
+
77
+ if (keys.length > 0) {
78
+ keys.forEach((key) => {
79
+ const { params, instance } = this.renderers[key];
80
+
81
+ instance?.resize?.({
82
+ width,
83
+ height,
84
+ pixelRatio,
85
+ ...params,
86
+ });
87
+ });
88
+ }
89
+ });
90
+ });
91
+
92
+ hydrate(this.key, this);
93
+
94
+ this.estimateRefreshRate();
95
+ }
96
+
97
+ loadRenderer(renderingMode) {
98
+ if (__THREE_RENDERER__ && renderingMode === 'three') {
99
+ return import('../renderers/THREERenderer.js');
100
+ }
101
+
102
+ if (__FRAGMENT_RENDERER__ && renderingMode === 'fragment') {
103
+ return import('../renderers/FragmentRenderer.js');
104
+ }
105
+
106
+ if (__P5_RENDERER__ && renderingMode === 'p5') {
107
+ return import('../renderers/P5Renderer.js');
108
+ }
109
+
110
+ if (__P5_WEBGL_RENDERER__ && renderingMode === 'p5-webgl') {
111
+ return import('../renderers/P5GLRenderer.js');
112
+ }
113
+
114
+ if (__2D_RENDERER__ && renderingMode === '2d') {
115
+ return import('../renderers/2DRenderer.js');
116
+ }
117
+ }
118
+
119
+ async preloadRenderer({ renderingMode, customRenderer }) {
120
+ // load and save
121
+ const instance = customRenderer
122
+ ? typeof customRenderer === 'function'
123
+ ? await customRenderer()
124
+ : customRenderer
125
+ : await this.loadRenderer(renderingMode);
126
+
127
+ if (instance) {
128
+ const params =
129
+ instance.init?.({
130
+ canvas: document.createElement('canvas'),
131
+ pixelRatio: this.pixelRatio,
132
+ width: this.width,
133
+ height: this.height,
134
+ }) ?? {};
135
+
136
+ instance.resize?.({
137
+ pixelRatio: this.pixelRatio,
138
+ width: this.width,
139
+ height: this.height,
140
+ ...params,
141
+ });
142
+
143
+ this.renderers[`${renderingMode}`] = {
144
+ instance,
145
+ params,
146
+ };
147
+
148
+ return instance;
149
+ }
150
+ }
151
+
152
+ findRenderer({ renderingMode }) {
153
+ return this.renderers[renderingMode].instance;
154
+ }
155
+
156
+ override(config) {
157
+ if (!config) return;
158
+
159
+ if (config.canvasSize) {
160
+ console.warn(
161
+ `buildConfig.canvasSize has been deprecated. Use buildConfig.resizing instead.`,
162
+ );
163
+
164
+ config.resizing = config.canvasSize;
165
+ }
166
+
167
+ const {
168
+ width,
169
+ height,
170
+ dimensions = [width, height],
171
+ resizing,
172
+ pixelRatio,
173
+ } = config;
174
+
175
+ if (resizing && Object.values(SIZES).includes(resizing)) {
176
+ this.resizing = resizing;
177
+
178
+ if (resizing === SIZES.PRESET) {
179
+ if (config.preset && presets.includes(config.preset)) {
180
+ const [width, height] = getDimensionsForPreset(
181
+ config.preset,
182
+ {
183
+ pixelsPerInch: 300,
184
+ orientation: config.presetOrientation,
185
+ },
186
+ );
187
+
188
+ this.width = width;
189
+ this.height = height;
190
+ } else {
191
+ this.resizing = SIZES.WINDOW;
192
+ console.warn(
193
+ `Cannot compute dimensions for config.preset: ${config.preset}.`,
194
+ );
195
+ }
196
+ } else if (resizing === SIZES.ASPECT_RATIO) {
197
+ const { aspectRatio } = config;
198
+
199
+ if (!isNaN(aspectRatio)) {
200
+ this.aspectRatio = config.aspectRatio;
201
+ } else {
202
+ this.resizing = SIZES.WINDOW;
203
+
204
+ console.warn(
205
+ `Cannot compute canvas size for config.aspectRatio: ${aspectRatio}.`,
206
+ );
207
+ }
208
+ } else if (resizing === SIZES.SCALE) {
209
+ const { scale } = config;
210
+
211
+ if (!dimensions) {
212
+ console.warn(
213
+ `Cannot apply resizing:"scale" if no dimensions are specified.`,
214
+ );
215
+ this.resizing = SIZES.WINDOW;
216
+ }
217
+
218
+ if (isNaN(scale)) {
219
+ console.warn(
220
+ `Cannot compute canvas size for config.scale: ${scale}`,
221
+ );
222
+ this.resizing = SIZES.WINDOW;
223
+ } else {
224
+ this.scale = scale;
225
+ }
226
+ }
227
+ }
228
+
229
+ if (
230
+ dimensions &&
231
+ dimensions.length === 2 &&
232
+ dimensions.every((d) => !isNaN(Number(d)))
233
+ ) {
234
+ this.width = dimensions[0];
235
+ this.height = dimensions[1];
236
+ }
237
+
238
+ if (pixelRatio) {
239
+ this.pixelRatio =
240
+ typeof pixelRatio === 'function' ? pixelRatio() : pixelRatio;
241
+ }
242
+ }
243
+
244
+ estimateRefreshRate() {
245
+ return new Promise((resolve) => {
246
+ const deltas = [];
247
+ const frameCount = 10;
248
+ let count = 0;
249
+ let lastTime = performance.now();
250
+
251
+ const findClosestRefreshRate = (targetRate) => {
252
+ // List of common refresh rates
253
+ const refreshRates = [60, 75, 120, 144, 165, 240, 360];
254
+
255
+ // Initialize the closest rate and the minimum difference
256
+ let closestRate = refreshRates[0];
257
+ let minDifference = Math.abs(targetRate - closestRate);
258
+
259
+ // Iterate over the refresh rates to find the closest one
260
+ for (let i = 1; i < refreshRates.length; i++) {
261
+ let currentRate = refreshRates[i];
262
+ let currentDifference = Math.abs(targetRate - currentRate);
263
+
264
+ if (currentDifference < minDifference) {
265
+ minDifference = currentDifference;
266
+ closestRate = currentRate;
267
+ }
268
+ }
269
+
270
+ return closestRate;
271
+ };
272
+
273
+ const computeRefreshRate = (time) => {
274
+ const deltaTime = time - lastTime;
275
+ lastTime = time;
276
+
277
+ if (count < frameCount) {
278
+ deltas.push(deltaTime);
279
+ requestIdleCallback(() =>
280
+ requestAnimationFrame(computeRefreshRate),
281
+ );
282
+ count++;
283
+ } else {
284
+ const refreshRate = Math.round(
285
+ (60 / (deltas[deltas.length - 1] / 1000)) * (1 / 60),
286
+ );
287
+
288
+ this.refreshRate = findClosestRefreshRate(refreshRate);
289
+ resolve(this.refreshRate);
290
+ }
291
+ };
292
+
293
+ requestIdleCallback(() =>
294
+ requestAnimationFrame(computeRefreshRate),
295
+ );
296
+ });
297
+ }
298
+
299
+ getMonitorID() {
300
+ return MONITOR_ID++;
301
+ }
302
+ }
303
+
304
+ export let rendering = new Rendering();
305
+
306
+ export class Render {
307
+ loaded = $state(false);
308
+ errored = $state(false);
309
+ paused = $state(false);
310
+ resized = $state(false);
311
+
312
+ constructor({ id, container, sketch, renderer }) {
313
+ this.id = id;
314
+ this.container = container;
315
+ this.width = undefined;
316
+ this.height = undefined;
317
+ this.pixelRatio = undefined;
318
+ this.sketch = sketch;
319
+ this.renderer = renderer;
320
+ this.time = 0;
321
+ this.recording = false;
322
+
323
+ $effect(() => {
324
+ const { width, height, pixelRatio } = rendering;
325
+
326
+ if (this.loaded) {
327
+ this.resize(width, height, pixelRatio);
328
+ } else {
329
+ this.width = width;
330
+ this.height = height;
331
+ this.pixelRatio = pixelRatio;
332
+ this.init();
333
+ }
334
+ });
335
+
336
+ $effect(() => {
337
+ const { version, fps } = this.sketch;
338
+
339
+ if (fps === 0) {
340
+ this.invalidate(version);
341
+ }
342
+ });
343
+
344
+ this.observer = new MutationObserver((mutationsList) => {
345
+ const attributes = ['width', 'height'];
346
+
347
+ let attributesMutationsList = mutationsList.filter(
348
+ (mutationRecord) =>
349
+ attributes.includes(mutationRecord.attributeName),
350
+ );
351
+
352
+ const { pixelRatio } = rendering;
353
+
354
+ attributesMutationsList.forEach((mutationRecord) => {
355
+ const { target, attributeName } = mutationRecord;
356
+
357
+ const dimension = Math.round(
358
+ target[attributeName] / pixelRatio,
359
+ );
360
+ const needsUpdate = rendering[attributeName] !== dimension;
361
+
362
+ if (needsUpdate) {
363
+ console.warn(
364
+ `Canvas ${attributeName} was changed from sketch to ${dimension}px to previous ${rendering[attributeName]}`,
365
+ );
366
+ rendering[attributeName] = dimension;
367
+
368
+ if (rendering.resizing !== SIZES.FIXED) {
369
+ if (!__BUILD__) {
370
+ console.warn(
371
+ 'Canvas resizing has been switch to fixed.',
372
+ );
373
+ }
374
+ rendering.resizing = SIZES.FIXED;
375
+ }
376
+ }
377
+ });
378
+ });
379
+
380
+ this.canvas = this.createCanvas({ container, context: sketch.key });
381
+
382
+ this.then = performance.now();
383
+
384
+ this.playhead = 0;
385
+ this.playcount = 0;
386
+ this.frame = 0;
387
+
388
+ const { duration, fps } = sketch;
389
+
390
+ let frameLength = isFinite(fps) ? (1 / fps) * 1000 : 0;
391
+ let frameCount = fps * duration;
392
+ let interval = 1 / frameCount;
393
+
394
+ this.elapsed =
395
+ isFinite(fps) && fps !== 0
396
+ ? this.time - frameLength * Math.floor(this.time / frameLength)
397
+ : 0;
398
+
399
+ this.timeTotal = isFinite(duration)
400
+ ? this.time -
401
+ duration * 1000 * Math.floor(this.time / (duration * 1000))
402
+ : 0;
403
+
404
+ this.renderSketch = (deltaTime = 0) => {
405
+ try {
406
+ this.renderer?.onBeforeUpdatePreview?.({ id });
407
+ sketch.draw({
408
+ ...this.params,
409
+ time: this.time,
410
+ deltaTime,
411
+ playhead: this.playhead,
412
+ playcount: this.playcount,
413
+ frame: this.frame,
414
+ });
415
+ this.renderer?.onAfterUpdatePreview?.({ id });
416
+ sketch.sync();
417
+ } catch (error) {
418
+ console.error(error);
419
+ displayError(error, sketch.key);
420
+ this.errored = true;
421
+ }
422
+ };
423
+
424
+ this.loop = (time) => {
425
+ if (!this.loaded || !this.resized || this.errored) {
426
+ return;
427
+ }
428
+
429
+ let playhead = time / 1000 / duration;
430
+ playhead %= 1;
431
+ let playcount = 0;
432
+ if (isFinite(interval)) {
433
+ // round values for low framerates
434
+ playhead = Math.floor(playhead / interval) * interval;
435
+ }
436
+ if (isFinite(duration)) {
437
+ playcount = Math.floor(this.timeTotal / 1000 / duration);
438
+ }
439
+ if (fps === 0) {
440
+ playhead = 0;
441
+ }
442
+ const now = performance.now();
443
+ const deltaTime = now - this.thenLoop;
444
+
445
+ this.playhead = playhead;
446
+ this.playcount = playcount;
447
+ this.frame = Math.floor(map(playhead, 0, 1, 1, frameCount + 1));
448
+ if (
449
+ this.elapsed === 0 ||
450
+ this.elapsed >= frameLength ||
451
+ this.sketch.needsUpdate()
452
+ ) {
453
+ this.elapsed = 0;
454
+ this.renderSketch(deltaTime);
455
+ this.thenLoop = now;
456
+ }
457
+
458
+ this.timeTotal += deltaTime;
459
+ this.elapsed += deltaTime;
460
+ };
461
+
462
+ this.init = async () => {
463
+ clearError(this.sketch.key);
464
+ this.mountParams = this.renderer?.onMountPreview?.(this.params);
465
+ if (this.mountParams && this.mountParams.canvas !== this.canvas) {
466
+ this.destroyCanvas(this.canvas);
467
+ this.canvas = this.createCanvas({
468
+ container: this.container,
469
+ canvas: this.mountParams.canvas,
470
+ context: this.sketch.key,
471
+ });
472
+ }
473
+ const { params } = this;
474
+
475
+ try {
476
+ await sketch.load(params);
477
+ await sketch.setup(params);
478
+
479
+ this.raf = requestAnimationFrame(() => {
480
+ this.time = new Date().getTime() - rendering.today;
481
+ this.then = this.time;
482
+ this.thenLoop = performance.now();
483
+ this.update(this.time);
484
+ });
485
+
486
+ this.loaded = true;
487
+ } catch (error) {
488
+ console.error(error);
489
+ displayError(error, sketch.key);
490
+ this.errored = true;
491
+ }
492
+ };
493
+
494
+ this.removeShaderUpdateListener = client.on('shader-update', () => {
495
+ const { sketch } = this;
496
+
497
+ if (sketch.fps === 0) {
498
+ this.renderSketch();
499
+ }
500
+ });
501
+ }
502
+
503
+ createCanvas({
504
+ container,
505
+ canvas = document.createElement('canvas'),
506
+ context,
507
+ }) {
508
+ canvas.onmousedown = (event) => checkForTriggersDown(event, context);
509
+ canvas.onmousemove = (event) => checkForTriggersMove(event, context);
510
+ canvas.onmouseup = (event) => checkForTriggersUp(event, context);
511
+ canvas.onclick = (event) => checkForTriggersClick(event, context);
512
+
513
+ this.observer.observe(canvas, {
514
+ attributes: true,
515
+ });
516
+
517
+ if (container) {
518
+ container.appendChild(canvas);
519
+ }
520
+
521
+ return canvas;
522
+ }
523
+
524
+ destroyCanvas(canvas) {
525
+ if (canvas) {
526
+ this.observer.disconnect();
527
+ canvas.parentNode?.removeChild(canvas);
528
+ canvas.onmousedown = null;
529
+ canvas.onmousemove = null;
530
+ canvas.onmouseup = null;
531
+ canvas.onclick = null;
532
+ canvas = null;
533
+ }
534
+ }
535
+
536
+ async screenshot({
537
+ filename = this.sketch.name ?? this.sketch.key,
538
+ pattern = this.sketch.filenamePattern,
539
+ exportDir = this.sketch.exportDir,
540
+ } = {}) {
541
+ const { sketch } = this;
542
+
543
+ await exports.screenshot(this.canvas, {
544
+ filename,
545
+ pattern,
546
+ exportDir,
547
+ params: {
548
+ props: sketch.props,
549
+ },
550
+ onBeforeCapture: (params) => {
551
+ sketch.beforeCapture.forEach((fn) => fn(params));
552
+ this.renderSketch();
553
+ },
554
+ onAfterCapture: (params) => {
555
+ sketch.afterCapture.forEach((fn) => fn(params));
556
+ this.renderSketch();
557
+ },
558
+ });
559
+ }
560
+
561
+ get params() {
562
+ return {
563
+ ...this.mountParams,
564
+ id: this.id,
565
+ canvas: this.canvas,
566
+ container: this.container,
567
+ width: this.width,
568
+ height: this.height,
569
+ pixelRatio: this.pixelRatio,
570
+ publicPath: `@fs${__CWD__}`,
571
+ props: this.sketch?.instance.props ?? {},
572
+ };
573
+ }
574
+
575
+ async startRecording() {
576
+ this.paused = true;
577
+ this.recording = true;
578
+
579
+ const { sketch } = this;
580
+
581
+ if (this.record) {
582
+ console.warn(`Render :: already recording`);
583
+ return;
584
+ }
585
+
586
+ this.record = await exports.record(this.canvas, {
587
+ filename: sketch.key,
588
+ pattern: sketch.filenamePattern,
589
+ exportDir: sketch.exportDir,
590
+ duration: exports.useDuration ? sketch.duration : undefined,
591
+ params: {
592
+ props: sketch.props,
593
+ },
594
+ onStart: (params) => {
595
+ this.time = 0;
596
+ this.elapsed = 0;
597
+ this.thenLoop = performance.now();
598
+
599
+ sketch.beforeRecord.forEach((fn) => fn(params));
600
+ },
601
+ onTick: ({ time, deltaTime }) => {
602
+ this.loop(time);
603
+ },
604
+ onComplete: (params) => {
605
+ sketch.afterRecord.forEach((fn) => fn(params));
606
+ this.record = null;
607
+ },
608
+ });
609
+ }
610
+
611
+ stopRecording() {
612
+ if (this.record) {
613
+ this.record.stop();
614
+ }
615
+
616
+ this.paused = false;
617
+ this.recording = false;
618
+ }
619
+
620
+ reset() {
621
+ this.renderer?.onDestroyPreview?.({ id: this.id });
622
+
623
+ try {
624
+ this.sketch.instance?.dispose?.();
625
+ this.sketch.reset();
626
+
627
+ this.init();
628
+ } catch (error) {
629
+ console.error(error);
630
+ displayError(error, this.sketch.key);
631
+ this.errored = true;
632
+ }
633
+ }
634
+
635
+ /**
636
+ *
637
+ * @param {number} width
638
+ * @param {number} height
639
+ * @param {number} pixelRatio
640
+ */
641
+ resize(width, height, pixelRatio) {
642
+ this.width = width;
643
+ this.height = height;
644
+ this.pixelRatio = pixelRatio;
645
+ const { params } = this;
646
+ this.renderer?.onResizePreview?.(params);
647
+
648
+ try {
649
+ this.sketch.resize?.(params);
650
+ this.resized = true;
651
+ // trigger re-render
652
+ if (this.sketch.fps === 0) {
653
+ this.invalidate();
654
+ }
655
+ this.observer.takeRecords();
656
+ } catch (error) {
657
+ console.error(error);
658
+ displayError(error, this.sketch.key);
659
+ this.errored = true;
660
+ }
661
+ }
662
+
663
+ update(time) {
664
+ if (!this.paused) {
665
+ const deltaTime = time - this.then;
666
+
667
+ this.time += deltaTime;
668
+ this.then = this.time;
669
+ this.loop(this.time);
670
+ }
671
+
672
+ this.raf = requestAnimationFrame(() => {
673
+ const t = new Date().getTime() - rendering.today;
674
+
675
+ this.update(t);
676
+ });
677
+ }
678
+
679
+ invalidate() {
680
+ this.time = 0;
681
+ this.elapsed = 0;
682
+ }
683
+
684
+ dispose() {
685
+ this.removeShaderUpdateListener();
686
+
687
+ const { id, sketch } = this;
688
+ clearError(sketch.key);
689
+
690
+ this.renderer?.onDestroyPreview?.({ id });
691
+ this.destroyCanvas(this.canvas);
692
+ sketch?.instance?.dispose?.();
693
+
694
+ cancelAnimationFrame(this.raf);
695
+ this.raf = null;
696
+ }
697
+ }