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,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;
@@ -10,6 +10,8 @@
10
10
  import ButtonInput from './fields/ButtonInput.svelte';
11
11
  import ImageInput from './fields/ImageInput.svelte';
12
12
  import IntervalInput from './fields/IntervalInput.svelte';
13
+ import PaletteInput from './fields/PaletteInput.svelte';
14
+ import GradientInput from './fields/GradientInput.svelte';
13
15
  import { fieldTypes } from '../utils/fields.utils.js';
14
16
 
15
17
  const fields = {
@@ -21,11 +23,14 @@
21
23
  [`${fieldTypes.TEXTAREA}`]: TextareaInput,
22
24
  [`${fieldTypes.LIST}`]: ListInput,
23
25
  [`${fieldTypes.COLOR}`]: ColorInput,
26
+ [`${fieldTypes.PALETTE}`]: PaletteInput,
24
27
  [`${fieldTypes.BUTTON}`]: ButtonInput,
25
28
  [`${fieldTypes.DOWNLOAD}`]: ButtonInput,
26
29
  [`${fieldTypes.IMPORT}`]: ImportInput,
27
30
  [`${fieldTypes.IMAGE}`]: ImageInput,
28
31
  [`${fieldTypes.INTERVAL}`]: IntervalInput,
32
+ [`${fieldTypes.GRADIENT}`]: GradientInput,
33
+ [`${fieldTypes.WRAPPER}`]: null,
29
34
  };
30
35
  </script>
31
36
 
@@ -140,7 +145,7 @@
140
145
  }
141
146
 
142
147
  function restoreInitialValue() {
143
- onchange(initialValue);
148
+ onchange($state.snapshot(initialValue));
144
149
  }
145
150
  </script>
146
151
 
@@ -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
 
@@ -102,7 +102,7 @@
102
102
  />
103
103
  {/if}
104
104
  {#if rendering.resizing === SIZES.PRESET}
105
- <Field key="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}
@@ -74,6 +74,11 @@
74
74
  render.screenshot();
75
75
  exports.capturing = false;
76
76
  }
77
+
78
+ if (exports.committing) {
79
+ render.commit();
80
+ exports.committing = false;
81
+ }
77
82
  });
78
83
 
79
84
  function checkForRefresh(event) {
@@ -113,6 +118,16 @@
113
118
  }
114
119
  }
115
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
+
116
131
  let backgroundColor = $derived.by(() => {
117
132
  if (layout.previewing) {
118
133
  return (
@@ -154,6 +169,7 @@
154
169
  <KeyBinding type="down" key=" " onTrigger={checkForPause} />
155
170
  <KeyBinding type="down" key="s" onTrigger={checkForScreenshot} />
156
171
  <KeyBinding type="down" key="S" onTrigger={checkForRecord} />
172
+ <KeyBinding type="down" key="k" onTrigger={checkForCommit} />
157
173
 
158
174
  <style>
159
175
  .sketch-renderer {
@@ -32,6 +32,8 @@
32
32
  }
33
33
 
34
34
  .button {
35
+ position: relative;
36
+
35
37
  display: flex;
36
38
  width: 100%;
37
39
  min-width: var(--fragment-input-height);
@@ -1,15 +1,17 @@
1
1
  <script>
2
- let {
3
- value = $bindable(),
4
- context,
5
- key = '',
6
- disabled = false,
7
- onchange,
8
- } = $props();
2
+ /**
3
+ * @typedef {Object} Props
4
+ * @property {boolean} value
5
+ * @property {boolean} disabled
6
+ * @property {(value: boolean) => void|undefined} onchange
7
+ */
9
8
 
10
- const handleChange = (event) => {
11
- onchange(value);
12
- };
9
+ /** @type {Props} */
10
+ let { value = $bindable(), disabled = false, onchange } = $props();
11
+
12
+ function handleChange() {
13
+ onchange?.(value);
14
+ }
13
15
  </script>
14
16
 
15
17
  <div class="checkbox" class:disabled>
@@ -18,7 +20,7 @@
18
20
  bind:checked={value}
19
21
  type="checkbox"
20
22
  onchange={handleChange}
21
- disabled={disabled ? 'disabled' : null}
23
+ {disabled}
22
24
  />
23
25
  </div>
24
26
 
@@ -16,18 +16,21 @@
16
16
  let textValue = $state();
17
17
  let alpha = $state(1);
18
18
  let hasAlpha = $derived(
19
- [
20
- color.FORMATS.RGBA_STRING,
21
- color.FORMATS.VEC4_STRING,
22
- color.FORMATS.VEC4_ARRAY,
23
- color.FORMATS.RGBA_OBJECT,
24
- color.FORMATS.HSLA_STRING,
25
- ].includes(format),
19
+ format &&
20
+ [
21
+ color.FORMATS.RGBA_STRING,
22
+ color.FORMATS.VEC4_STRING,
23
+ color.FORMATS.VEC4_ARRAY,
24
+ color.FORMATS.RGBA_OBJECT,
25
+ color.FORMATS.RGBA_OBJECT_STRING,
26
+ color.FORMATS.HSLA_STRING,
27
+ ].includes(format),
26
28
  );
27
29
 
28
30
  $effect(() => {
29
31
  if (hasAlpha) {
30
- const [r, g, b, a = 1] = color.toComponents(value);
32
+ const components = color.toComponents(value);
33
+ const a = components[3] ?? 1;
31
34
  alpha = a;
32
35
  } else {
33
36
  alpha = 1;
@@ -143,7 +146,7 @@
143
146
  <input
144
147
  class="input"
145
148
  type="color"
146
- disabled={disabled ? 'disabled' : null}
149
+ {disabled}
147
150
  value={hexValue}
148
151
  onblur={handleBlur}
149
152
  oninput={onInput}
@@ -212,7 +215,7 @@
212
215
  bottom: var(--gap);
213
216
 
214
217
  background-color: var(--currentColor);
215
- border-radius: calc(var(--fragment-input-border-radius) * 0.5);
218
+ border-radius: calc(var(--fragment-input-border-radius) - var(--gap));
216
219
  opacity: var(--opacity, 1);
217
220
  pointer-events: none;
218
221
  }
@@ -224,7 +227,9 @@
224
227
  var(--box-shadow-color, var(--fragment-accent-color));
225
228
  }
226
229
 
227
- .mirror:focus-within {
230
+ :global(body:not(.fragment-dragging))
231
+ .color-input:not(.disabled)
232
+ .mirror:focus-within {
228
233
  box-shadow: 0 0 0 2px
229
234
  var(--box-shadow-color, var(--fragment-accent-color));
230
235
  }