fragment-tools 0.2.2 → 0.2.4

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 (31) hide show
  1. package/package.json +1 -1
  2. package/src/client/app/components/Build.svelte +64 -0
  3. package/src/client/app/{ui → components}/Preview.svelte +1 -2
  4. package/src/client/app/inputs/MIDI.js +0 -1
  5. package/src/client/app/modules/MidiPanel.svelte +23 -13
  6. package/src/client/app/modules/Params.svelte +31 -16
  7. package/src/client/app/state/Sketch.svelte.js +77 -73
  8. package/src/client/app/state/layout.svelte.js +8 -2
  9. package/src/client/app/state/rendering.svelte.js +23 -5
  10. package/src/client/app/state/utils.svelte.js +28 -4
  11. package/src/client/app/ui/Field.svelte +13 -11
  12. package/src/client/app/ui/FieldGroup.svelte +5 -2
  13. package/src/client/app/ui/FieldSection.svelte +4 -4
  14. package/src/client/app/ui/Layout.svelte +1 -1
  15. package/src/client/app/ui/LayoutBuild.svelte +54 -0
  16. package/src/client/app/ui/LayoutColumn.svelte +2 -2
  17. package/src/client/app/ui/LayoutComponent.svelte +14 -4
  18. package/src/client/app/ui/LayoutResizer.svelte +11 -2
  19. package/src/client/app/ui/LayoutRow.svelte +2 -2
  20. package/src/client/app/ui/fields/ButtonInput.svelte +3 -3
  21. package/src/client/app/ui/fields/ColorInput.svelte +23 -13
  22. package/src/client/app/ui/fields/ImportInput.svelte +52 -0
  23. package/src/client/app/ui/fields/Input.svelte +4 -2
  24. package/src/client/app/ui/fields/IntervalInput.svelte +8 -6
  25. package/src/client/app/ui/fields/ProgressInput.svelte +47 -17
  26. package/src/client/app/ui/fields/Select.svelte +35 -41
  27. package/src/client/app/ui/fields/VectorInput.svelte +63 -18
  28. package/src/client/app/utils/fields.utils.js +70 -48
  29. package/src/client/app/utils/math.utils.js +6 -0
  30. package/src/client/public/css/global.css +14 -0
  31. package/src/client/app/ui/Build.svelte +0 -91
@@ -1,4 +1,5 @@
1
1
  <script>
2
+ import { isObject } from '../../state/utils.svelte';
2
3
  import SelectChevrons from '../SelectChevrons.svelte';
3
4
 
4
5
  let {
@@ -10,55 +11,48 @@
10
11
  onchange = () => {},
11
12
  } = $props();
12
13
 
13
- function toStringifiedValue(option, optionType = typeof option) {
14
- if (option === null) {
15
- return null;
16
- } else if (option === undefined) {
17
- return undefined;
18
- } else if (optionType === 'object') {
19
- return toStringifiedValue(option.value);
20
- } else if (optionType === 'function') {
21
- return option.name;
14
+ function toStringifiedValue(value) {
15
+ if (typeof value === 'function') {
16
+ return `${value.name}()`;
22
17
  }
23
18
 
24
- return option.toString();
19
+ return String(value);
25
20
  }
26
21
 
27
- let sanitizedOptions = $derived(
28
- options.map((option) => {
29
- let optionType = typeof option;
30
- let disabled =
31
- optionType === 'object' && typeof option.disabled === 'boolean'
32
- ? option.disabled
33
- : false;
34
- let value = optionType === 'object' ? option.value : option;
35
-
36
- let stringValue = toStringifiedValue(option);
37
-
38
- let label = option.label ?? stringValue;
39
-
40
- return {
41
- label,
42
- value,
43
- stringValue,
44
- disabled,
45
- };
46
- }),
47
- );
22
+ function createOption(option) {
23
+ let value =
24
+ isObject(option) && 'value' in option ? option.value : option;
25
+ let label = option?.label ?? toStringifiedValue(value);
26
+ let disabled = option?.disabled ?? false;
27
+ let stringValue = toStringifiedValue(value);
28
+
29
+ return {
30
+ label,
31
+ value,
32
+ stringValue,
33
+ disabled,
34
+ };
35
+ }
36
+
37
+ let sanitizedOptions = $derived.by(() => {
38
+ let opts = options.map((option, optionIndex) => {
39
+ return createOption(option, optionIndex);
40
+ });
41
+
42
+ return opts;
43
+ });
48
44
 
49
45
  let sanitizedValue = $derived(
50
- sanitizedOptions.find((opt) => opt.value === value),
46
+ sanitizedOptions.find((opt) => opt.value === value) ??
47
+ sanitizedOptions[0],
51
48
  );
52
49
 
53
50
  function handleChange(event) {
54
- const index = sanitizedOptions.findIndex(
51
+ const sanitizedOption = sanitizedOptions.find(
55
52
  (opt) => opt.stringValue === event.currentTarget.value,
56
53
  );
57
54
 
58
- const option = options[index];
59
- const newValue = typeof option === 'object' ? option.value : option;
60
-
61
- onchange(newValue);
55
+ onchange(sanitizedOption.value);
62
56
  }
63
57
  </script>
64
58
 
@@ -84,9 +78,7 @@
84
78
  >
85
79
  {/each}
86
80
  </select>
87
- {#if sanitizedOptions.length > 1}
88
- <SelectChevrons />
89
- {/if}
81
+ <SelectChevrons />
90
82
  </div>
91
83
  </div>
92
84
 
@@ -113,7 +105,9 @@
113
105
  background-color: var(--color-background-input);
114
106
  }
115
107
 
116
- .select-input:not(.disabled) .container:hover {
108
+ :global(body:not(.fragment-dragging))
109
+ .select-input:not(.disabled)
110
+ .container:hover {
117
111
  box-shadow: inset 0 0 0 1px var(--color-active);
118
112
  }
119
113
 
@@ -8,27 +8,71 @@
8
8
  min = -Infinity,
9
9
  max = Infinity,
10
10
  step = 0.1,
11
+ key,
11
12
  locked = false,
12
13
  disabled = false,
13
14
  context = null,
14
- key = '',
15
15
  onchange,
16
16
  } = $props();
17
17
 
18
+ const keysChecks = ['x', 'y', 'z', 'w'];
18
19
 
19
20
  let isArray = $derived(Array.isArray(value));
20
21
  let isObject = $derived(!isArray && typeof value === 'object');
21
- let components = $derived(isObject ? Object.values(value) : [...value]);
22
- let keys = $derived(isObject ? Object.keys(value) : value.map((v, i) => i));
22
+ let keys = $derived.by(() => {
23
+ let keys = [];
24
+
25
+ if (isArray) {
26
+ return value.map((_, index) => index);
27
+ }
28
+
29
+ if (!isArray && !isObject) return [0];
30
+
31
+ for (let i = 0; i < keysChecks; i++) {
32
+ let keyCheck = keysChecks[i];
33
+
34
+ if (keyCheck in value) {
35
+ keys.push(keyCheck);
36
+ }
37
+ }
38
+
39
+ if (value.isVector2) {
40
+ return ['x', 'y'];
41
+ }
42
+
43
+ if (value.isVector3) {
44
+ return ['x', 'y', 'z'];
45
+ }
46
+
47
+ if (value.isVector4 || value.isQuaternion) {
48
+ return ['x', 'y', 'z', 'w'];
49
+ }
50
+
51
+ if (isObject) {
52
+ return Object.keys(value);
53
+ }
54
+
55
+ return keys;
56
+ });
57
+ let components = $derived.by(() => {
58
+ if (!isObject && !isArray) {
59
+ return [value];
60
+ }
61
+
62
+ return keys.map((key) => value[key]);
63
+ });
64
+ let mins = $derived(keys.map((key) => min[key]));
65
+ let maxs = $derived(keys.map((key) => max[key]));
66
+ let steps = $derived(keys.map((key) => step[key]));
23
67
 
24
68
  function dispatchChange() {
25
- let newValue = keys.reduce((all, key, index) => {
26
- all[key] = components[index];
69
+ let clone = isArray ? [] : {};
27
70
 
28
- return all;
29
- }, isArray ? [] : {});
71
+ keys.forEach((key, index) => {
72
+ clone[key] = components[index];
73
+ });
30
74
 
31
- onchange(newValue);
75
+ onchange(clone);
32
76
  }
33
77
 
34
78
  function handleComponentChange(newValue, componentIndex) {
@@ -39,11 +83,13 @@
39
83
  }
40
84
 
41
85
  components.forEach((component, index) => {
42
- components[index] = index === componentIndex
43
- ? newValue
44
- : locked
45
- ? Math.round(component * ratio * (1 / step)) / (1 / step)
46
- : component;
86
+ components[index] =
87
+ index === componentIndex
88
+ ? newValue
89
+ : locked
90
+ ? Math.round(component * ratio * (1 / step)) /
91
+ (1 / step)
92
+ : component;
47
93
  });
48
94
 
49
95
  dispatchChange();
@@ -56,17 +102,16 @@
56
102
  >
57
103
  {#each components as component, index}
58
104
  <NumberInput
59
- {min}
60
- {max}
61
- {step}
105
+ min={mins[index]}
106
+ max={maxs[index]}
107
+ step={steps[index]}
62
108
  {suffix}
63
109
  {disabled}
64
110
  {context}
65
111
  {key}
66
112
  label={keys[index]}
67
113
  value={component}
68
- onchange={(value) =>
69
- handleComponentChange(value, index)}
114
+ onchange={(value) => handleComponentChange(value, index)}
70
115
  />
71
116
  {/each}
72
117
  </FieldInputRow>
@@ -10,6 +10,7 @@ export const fieldTypes = {
10
10
  COLOR: 'color',
11
11
  BUTTON: 'button',
12
12
  DOWNLOAD: 'download',
13
+ IMPORT: 'import',
13
14
  IMAGE: 'image',
14
15
  INTERVAL: 'interval',
15
16
  };
@@ -42,11 +43,40 @@ export function inferFieldType({ type, value, params, key }) {
42
43
 
43
44
  const isArray = Array.isArray(value);
44
45
  const isObject = !isArray && typeof value === 'object';
45
- const values = isObject ? Object.values(value) : value;
46
+ const getKeys = (value) => {
47
+ if (isArray) {
48
+ return value.map((_, index) => index);
49
+ }
50
+
51
+ if (!isArray && !isObject) return [0];
52
+
53
+ if (value.isVector3) {
54
+ return ['x', 'y', 'z'];
55
+ }
56
+
57
+ if (value.isVector4 || value.isQuaternion) {
58
+ return ['x', 'y', 'z', 'w'];
59
+ }
60
+
61
+ if (isObject) {
62
+ return Object.keys(value);
63
+ }
64
+ };
65
+
66
+ const getValues = (value, keys) => {
67
+ if (!isObject && !isArray) {
68
+ value = [value];
69
+ }
70
+
71
+ return keys.map((key) => value[key]);
72
+ };
73
+
74
+ const keys = getKeys(value);
75
+ const values = getValues(value, keys);
46
76
 
47
77
  if (
48
78
  isArray &&
49
- value.length === 2 &&
79
+ values.length === 2 &&
50
80
  typeof params.min === 'number' &&
51
81
  typeof params.max === 'number'
52
82
  ) {
@@ -79,53 +109,45 @@ export function inferFieldType({ type, value, params, key }) {
79
109
  console.warn(`Field: cannot find field type for ${key}`);
80
110
  }
81
111
 
82
- export function hasChanged(initialValue, currentValue) {
83
- const initialType = initialValue && typeof initialValue;
84
- const currentType = currentValue && typeof currentValue;
85
-
86
- if (initialType !== currentType) return true;
87
-
88
- if (Array.isArray(currentValue)) {
89
- if (initialValue.length !== currentValue.length) {
90
- return true;
112
+ /**
113
+ *
114
+ * @param {string} folder
115
+ */
116
+ export function parseFolder(folder) {
117
+ const regex = /(?<name>\w+)(?:\[(?<attributes>[^\]]+)\])?/g;
118
+ const matches = [...folder.matchAll(regex)];
119
+
120
+ const results = matches.map((match) => {
121
+ return {
122
+ name: match.groups.name,
123
+ attributes: match.groups.attributes
124
+ ? Object.fromEntries(
125
+ match.groups.attributes
126
+ .split(', ')
127
+ .map((attr) => attr.split('=')),
128
+ )
129
+ : {},
130
+ };
131
+ });
132
+
133
+ let names = results.map((match) => match.name);
134
+
135
+ let rootId;
136
+
137
+ results.forEach((match, index) => {
138
+ let id = [...names].slice(0, index + 1).join('.');
139
+ let parentId = [...names].slice(0, index).join('.');
140
+
141
+ if (index === 0) {
142
+ rootId = id;
91
143
  }
92
144
 
93
- for (let i = 0; i < currentValue.length; i++) {
94
- if (currentValue[i] !== initialValue[i]) {
95
- return true;
96
- }
97
- }
98
- return false;
99
- }
100
-
101
- if (initialType === 'object') {
102
- const keys1 = Object.keys(initialValue);
103
- const keys2 = Object.keys(currentValue);
104
-
105
- if (
106
- keys1.length !== keys2.length ||
107
- !keys1.every((key) => keys2.includes(key))
108
- ) {
109
- return true;
110
- }
111
-
112
- for (const key of keys1) {
113
- const value1 = initialValue[key];
114
- const value2 = currentValue[key];
115
-
116
- if (typeof value1 === 'object' && typeof value2 === 'object') {
117
- // If both values are objects, recursively compare them
118
- if (hasChanged(value1, value2)) {
119
- return true;
120
- }
121
- } else if (value1 !== value2) {
122
- // If values are not objects, directly compare them
123
- return true;
124
- }
125
-
126
- return false;
127
- }
128
- }
145
+ match.id = id;
146
+ match.parentId = parentId;
147
+ match.depth = index;
148
+ match.isCurrent = index === results.length - 1;
149
+ match.rootId = rootId;
150
+ });
129
151
 
130
- return initialValue !== currentValue;
152
+ return results;
131
153
  }
@@ -22,6 +22,12 @@ export function clamp(value, min, max) {
22
22
  return Math.max(min, Math.min(value, max));
23
23
  }
24
24
 
25
+ /**
26
+ *
27
+ * @param {number} value
28
+ * @param {number} step
29
+ * @returns {number}
30
+ */
25
31
  export function roundToStep(value, step) {
26
32
  return Math.round(value * (1 / step)) / (1 / step);
27
33
  }
@@ -54,6 +54,20 @@ body {
54
54
  overscroll-behavior: none;
55
55
  }
56
56
 
57
+ body.fragment-dragging {
58
+ -webkit-user-select: none;
59
+ user-select: none;
60
+ }
61
+
62
+ body.fragment-dragging input {
63
+ -webkit-user-select: none;
64
+ user-select: none;
65
+ }
66
+
67
+ body.fragment-dragging * {
68
+ cursor: ew-resize !important;
69
+ }
70
+
57
71
  #app {
58
72
  position: fixed;
59
73
  top: 0;
@@ -1,91 +0,0 @@
1
- <script>
2
- import { onDestroy } from 'svelte';
3
-
4
- import Monitor from '../modules/Monitor.svelte';
5
- import Params from '../modules/Params.svelte';
6
- import FloatingParams from './FloatingParams.svelte';
7
- import Column from './LayoutColumn.svelte';
8
- import Row from './LayoutRow.svelte';
9
- import { sketchesManager } from '../state/sketches.svelte';
10
- import { rendering } from '../state/rendering.svelte';
11
-
12
- console.log(`Made with Fragment. https://fragment.tools`);
13
-
14
- let sketchKey = $derived(sketchesManager.keys[0]);
15
-
16
- let sketch = $derived(sketchesManager.sketches[sketchKey]);
17
-
18
- let gui = $derived(sketch?.buildConfig?.gui);
19
- let guiOutput = $derived(gui?.output);
20
- let guiAlign = $derived(gui?.align ?? 'right');
21
- let guiHidden = $derived(gui?.hidden);
22
- let guiSize = $derived(gui?.size ?? 0.25);
23
- let guiMinimize = $derived(gui?.minimize);
24
- let guiPosition = $derived(gui?.position);
25
- let styles = $derived(sketch?.buildConfig?.styles ?? '');
26
-
27
- /** @type {HTMLHeadElement} */
28
- let head;
29
- /** @type {HTMLStyleElement} */
30
- let style;
31
-
32
- $effect(() => {
33
- rendering.override(sketch?.buildConfig);
34
- });
35
-
36
- $effect(() => {
37
- if (styles !== '') {
38
- head = document.getElementsByTagName('head')[0];
39
-
40
- if (style) {
41
- head.removeChild(style);
42
- }
43
-
44
- style = document.createElement('style');
45
- style.setAttribute('type', 'text/css');
46
- style.appendChild(document.createTextNode(styles));
47
- head.appendChild(style);
48
- }
49
- });
50
-
51
- onDestroy(() => {
52
- if (style && head) {
53
- head.removeChild(style);
54
- }
55
- });
56
- </script>
57
-
58
- {#if sketch}
59
- {#if guiPosition === 'fixed'}
60
- <Row>
61
- {#if guiAlign === 'left'}
62
- <Column size={guiSize}>
63
- <Params />
64
- </Column>
65
- <Column size={1 - guiSize}>
66
- <Monitor params={{ selected: sketchKey }} />
67
- </Column>
68
- {:else}
69
- <Column size={1 - guiSize}>
70
- <Monitor params={{ selected: sketchKey }} />
71
- </Column>
72
- <Column size={guiSize}>
73
- <Params />
74
- </Column>
75
- {/if}
76
- </Row>
77
- {:else}
78
- <Row>
79
- <Monitor headless {sketchKey} params={{ selected: sketchKey }} />
80
- {#if gui}
81
- <FloatingParams
82
- output={guiOutput}
83
- align={guiAlign}
84
- size={guiSize}
85
- hidden={guiHidden}
86
- minimize={guiMinimize}
87
- />
88
- {/if}
89
- </Row>
90
- {/if}
91
- {/if}