fragment-tools 0.1.13 → 0.1.15

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 (184) hide show
  1. package/.prettierignore +1 -2
  2. package/.prettierrc +23 -7
  3. package/README.md +28 -9
  4. package/bin/index.js +70 -10
  5. package/package.json +14 -6
  6. package/src/cli/build.js +125 -0
  7. package/src/cli/create.js +238 -0
  8. package/src/cli/createConfig.js +82 -0
  9. package/src/cli/createFragmentFile.js +70 -0
  10. package/src/cli/getEntries.js +85 -0
  11. package/src/cli/log.js +36 -24
  12. package/src/cli/plugins/check-dependencies.js +88 -42
  13. package/src/cli/plugins/hot-shader-replacement.js +408 -0
  14. package/src/cli/plugins/hot-sketch-reload.js +21 -25
  15. package/src/cli/plugins/save.js +101 -0
  16. package/src/cli/preview.js +55 -0
  17. package/src/cli/prompts.js +260 -0
  18. package/src/cli/run.js +131 -0
  19. package/src/cli/templates/blank/index.js +33 -0
  20. package/src/cli/templates/blank/meta.json +4 -0
  21. package/src/cli/templates/default/index.js +39 -0
  22. package/src/cli/templates/default/meta.json +5 -0
  23. package/src/cli/templates/fragment-gl/index.js +37 -0
  24. package/src/cli/templates/fragment-gl/meta.json +4 -0
  25. package/src/cli/templates/p5/index.js +32 -0
  26. package/src/cli/templates/p5/meta.json +5 -0
  27. package/src/cli/templates/p5-webgl/fragment.fs +14 -0
  28. package/src/cli/templates/p5-webgl/index.js +67 -0
  29. package/src/cli/templates/p5-webgl/meta.json +5 -0
  30. package/src/cli/templates/three-fragment/fragment.fs +10 -0
  31. package/src/cli/templates/three-fragment/index.js +95 -0
  32. package/src/cli/templates/three-fragment/meta.json +5 -0
  33. package/src/cli/templates/three-orthographic/index.js +55 -0
  34. package/src/cli/templates/three-orthographic/meta.json +5 -0
  35. package/src/cli/templates/three-perspective/index.js +52 -0
  36. package/src/cli/templates/three-perspective/meta.json +5 -0
  37. package/src/cli/utils.js +70 -0
  38. package/src/cli/ws.js +87 -78
  39. package/src/client/app/App.svelte +3 -3
  40. package/src/client/app/client.js +55 -39
  41. package/src/client/app/components/IconCross.svelte +18 -18
  42. package/src/client/app/components/Init.svelte +40 -8
  43. package/src/client/app/components/KeyBinding.svelte +22 -22
  44. package/src/client/app/helpers.js +42 -0
  45. package/src/client/app/hooks.js +20 -0
  46. package/src/client/app/inputs/Input.js +9 -9
  47. package/src/client/app/inputs/Keyboard.js +13 -15
  48. package/src/client/app/inputs/MIDI.js +14 -15
  49. package/src/client/app/inputs/Mouse.js +1 -1
  50. package/src/client/app/inputs/Webcam.js +89 -88
  51. package/src/client/app/lib/canvas-recorder/CanvasRecorder.js +41 -21
  52. package/src/client/app/lib/canvas-recorder/FrameRecorder.js +7 -6
  53. package/src/client/app/lib/canvas-recorder/H264Recorder.js +45 -0
  54. package/src/client/app/lib/canvas-recorder/MP4Recorder.js +7 -9
  55. package/src/client/app/lib/canvas-recorder/WebMRecorder.js +3 -4
  56. package/src/client/app/lib/canvas-recorder/mp4.js +1649 -15
  57. package/src/client/app/lib/canvas-recorder/utils.js +33 -17
  58. package/src/client/app/lib/gl/Geometry.js +11 -8
  59. package/src/client/app/lib/gl/Program.js +38 -19
  60. package/src/client/app/lib/gl/Renderer.js +163 -156
  61. package/src/client/app/lib/gl/Texture.js +113 -85
  62. package/src/client/app/lib/gl/index.js +12 -12
  63. package/src/client/app/lib/gl/utils.js +1 -3
  64. package/src/client/app/lib/helpers/frameDebounce.js +30 -30
  65. package/src/client/app/lib/loader/index.js +10 -10
  66. package/src/client/app/lib/loader/loadImage.js +15 -15
  67. package/src/client/app/lib/loader/loadScript.js +1 -1
  68. package/src/client/app/lib/paper-sizes.js +75 -76
  69. package/src/client/app/lib/presets.js +25 -5
  70. package/src/client/app/lib/tempo/Analyser.js +18 -17
  71. package/src/client/app/lib/tempo/Range.js +15 -12
  72. package/src/client/app/lib/tempo/index.js +34 -27
  73. package/src/client/app/modules/AudioAnalyser/Range.svelte +69 -72
  74. package/src/client/app/modules/AudioAnalyser/Spectrum.svelte +20 -19
  75. package/src/client/app/modules/AudioAnalyser.svelte +52 -35
  76. package/src/client/app/modules/Console/ConsoleLine.svelte +193 -172
  77. package/src/client/app/modules/Console.svelte +76 -74
  78. package/src/client/app/modules/Exports.svelte +62 -43
  79. package/src/client/app/modules/MidiPanel.svelte +100 -101
  80. package/src/client/app/modules/Monitor.svelte +57 -57
  81. package/src/client/app/modules/Params.svelte +128 -103
  82. package/src/client/app/renderers/2DRenderer.js +3 -3
  83. package/src/client/app/renderers/FragmentRenderer.js +30 -23
  84. package/src/client/app/renderers/P5GLRenderer.js +144 -0
  85. package/src/client/app/renderers/P5Renderer.js +10 -7
  86. package/src/client/app/renderers/THREERenderer.js +136 -94
  87. package/src/client/app/stores/audioAnalysis.js +3 -4
  88. package/src/client/app/stores/console.js +9 -10
  89. package/src/client/app/stores/errors.js +1 -1
  90. package/src/client/app/stores/exports.js +36 -20
  91. package/src/client/app/stores/index.js +2 -2
  92. package/src/client/app/stores/layout.js +143 -138
  93. package/src/client/app/stores/multisampling.js +4 -4
  94. package/src/client/app/stores/props.js +76 -13
  95. package/src/client/app/stores/renderers.js +26 -15
  96. package/src/client/app/stores/rendering.js +108 -89
  97. package/src/client/app/stores/sketches.js +7 -9
  98. package/src/client/app/stores/time.js +18 -18
  99. package/src/client/app/stores/utils.js +95 -38
  100. package/src/client/app/transitions/fade.js +3 -3
  101. package/src/client/app/transitions/index.js +6 -7
  102. package/src/client/app/transitions/splitX.js +2 -2
  103. package/src/client/app/transitions/splitY.js +2 -2
  104. package/src/client/app/triggers/Keyboard.js +88 -79
  105. package/src/client/app/triggers/MIDI.js +110 -84
  106. package/src/client/app/triggers/Mouse.js +73 -65
  107. package/src/client/app/triggers/Trigger.js +59 -58
  108. package/src/client/app/triggers/index.js +7 -7
  109. package/src/client/app/triggers/shared.js +5 -5
  110. package/src/client/app/ui/Build.svelte +70 -71
  111. package/src/client/app/ui/ErrorOverlay.svelte +118 -104
  112. package/src/client/app/ui/Field.svelte +393 -258
  113. package/src/client/app/ui/FieldGroup.svelte +106 -94
  114. package/src/client/app/ui/FieldSection.svelte +127 -116
  115. package/src/client/app/ui/FieldSpace.svelte +29 -30
  116. package/src/client/app/ui/FieldTrigger.svelte +256 -244
  117. package/src/client/app/ui/FieldTriggers.svelte +46 -46
  118. package/src/client/app/ui/FloatingParams.svelte +29 -30
  119. package/src/client/app/ui/Layout.svelte +31 -32
  120. package/src/client/app/ui/LayoutColumn.svelte +4 -4
  121. package/src/client/app/ui/LayoutComponent.svelte +239 -225
  122. package/src/client/app/ui/LayoutResizer.svelte +195 -176
  123. package/src/client/app/ui/LayoutRoot.svelte +6 -6
  124. package/src/client/app/ui/LayoutRow.svelte +4 -4
  125. package/src/client/app/ui/LayoutToolbar.svelte +191 -194
  126. package/src/client/app/ui/Module.svelte +134 -135
  127. package/src/client/app/ui/ModuleHeaderAction.svelte +81 -78
  128. package/src/client/app/ui/ModuleHeaderButton.svelte +12 -12
  129. package/src/client/app/ui/ModuleHeaderSelect.svelte +47 -37
  130. package/src/client/app/ui/ModuleRenderer.svelte +26 -27
  131. package/src/client/app/ui/OutputRenderer.svelte +112 -105
  132. package/src/client/app/ui/ParamsMultisampling.svelte +96 -95
  133. package/src/client/app/ui/ParamsOutput.svelte +130 -113
  134. package/src/client/app/ui/Preview.svelte +7 -8
  135. package/src/client/app/ui/SelectChevrons.svelte +27 -15
  136. package/src/client/app/ui/SketchRenderer.svelte +780 -667
  137. package/src/client/app/ui/SketchSelect.svelte +50 -44
  138. package/src/client/app/ui/fields/ButtonInput.svelte +61 -48
  139. package/src/client/app/ui/fields/CheckboxInput.svelte +67 -61
  140. package/src/client/app/ui/fields/ColorInput.svelte +294 -238
  141. package/src/client/app/ui/fields/FieldInputRow.svelte +8 -8
  142. package/src/client/app/ui/fields/ImageInput.svelte +123 -121
  143. package/src/client/app/ui/fields/Input.svelte +100 -111
  144. package/src/client/app/ui/fields/IntervalInput.svelte +268 -0
  145. package/src/client/app/ui/fields/ListInput.svelte +96 -96
  146. package/src/client/app/ui/fields/NumberInput.svelte +120 -116
  147. package/src/client/app/ui/fields/ProgressInput.svelte +99 -73
  148. package/src/client/app/ui/fields/Select.svelte +137 -124
  149. package/src/client/app/ui/fields/TextInput.svelte +10 -11
  150. package/src/client/app/ui/fields/VectorInput.svelte +86 -82
  151. package/src/client/app/utils/canvas.utils.js +189 -208
  152. package/src/client/app/utils/color.utils.js +138 -101
  153. package/src/client/app/utils/fields.utils.js +131 -0
  154. package/src/client/app/utils/file.utils.js +209 -37
  155. package/src/client/app/utils/glsl.utils.js +2 -2
  156. package/src/client/app/utils/glslErrors.js +49 -31
  157. package/src/client/app/utils/index.js +32 -29
  158. package/src/client/app/utils/math.utils.js +14 -10
  159. package/src/client/index.html +16 -16
  160. package/src/client/main.js +4 -4
  161. package/src/client/public/css/global.css +26 -16
  162. package/src/cli/db.js +0 -17
  163. package/src/cli/index.js +0 -198
  164. package/src/cli/plugins/db.js +0 -12
  165. package/src/cli/plugins/hot-shader-reload.js +0 -86
  166. package/src/cli/plugins/screenshot.js +0 -46
  167. package/src/cli/server.js +0 -153
  168. package/src/cli/templates/2d.js +0 -15
  169. package/src/cli/templates/blank.js +0 -13
  170. package/src/cli/templates/fragment.js +0 -18
  171. package/src/cli/templates/index.js +0 -27
  172. package/src/cli/templates/p5.js +0 -13
  173. package/src/cli/templates/three-fragment.js +0 -53
  174. package/src/cli/templates/three-orthographic.js +0 -23
  175. package/src/cli/templates/three-perspective.js +0 -20
  176. package/src/client/app/lib/canvas-recorder/FFMPEGRecorder.js +0 -56
  177. package/src/client/app/utils/props.utils.js +0 -51
  178. package/src/client/public/fonts/Inter-Bold.woff2 +0 -0
  179. package/src/client/public/fonts/Inter-Italic.woff2 +0 -0
  180. package/src/client/public/fonts/Inter-Regular.woff2 +0 -0
  181. package/src/client/public/fonts/Inter-SemiBold.woff2 +0 -0
  182. package/src/client/public/js/ffmpeg.min.js +0 -2
  183. package/src/client/public/js/ffmpeg.min.js.map +0 -1
  184. /package/src/cli/templates/{fragment.fs → fragment-gl/fragment.fs} +0 -0
@@ -0,0 +1,408 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+ import { readFile } from 'node:fs/promises';
4
+ import glslify from 'glslify';
5
+ import { log, dim, green, yellow } from '../log.js';
6
+
7
+ /**
8
+ * @typedef {Object} ShaderUpdate
9
+ * @property {string} filepath - The path of the shader on the filesystem
10
+ * @property {string} source - The source code of the shader
11
+ * @property {boolean} nohsr - Whether the shader can be injected on the fly or if the sketch needs to be fully reloaded
12
+ * @property {string[]} warnings - Indicates whether the Wisdom component is present.
13
+ */
14
+
15
+ /**
16
+ * Resolve shader include directives and send shader code via WebSocket
17
+ * @param {object} params
18
+ * @param {string} [params.cwd=process.cwd()] - Current working directory
19
+ * @param {import('ws').WebSocketServer} params.wss
20
+ * @returns {import('vite').Plugin}
21
+ */
22
+ export default function hotShaderReplacement({ cwd = process.cwd(), wss }) {
23
+ const name = 'fragment-plugin-hsr';
24
+ const prefix = log.prefix(name);
25
+ const fileRegex = /\.(?:frag|vert|glsl|vs|fs)$/;
26
+ const includeRegex = /#include(\s+([^\s<>]+));?/gi;
27
+ const ignoreRegex = /^(?:\/|\*)*\s*@fragment-nohsr/;
28
+ const commentRegex =
29
+ /(\/\*([^*]|[\r\n]|(\*+([^*\/]|[\r\n])))*\*+\/)|(\/\/.*)/gi;
30
+ const base = process.cwd().split(path.sep).join(path.posix.sep);
31
+
32
+ let dependencies = new Map();
33
+ let shaders = [];
34
+ let modulesToReload = [];
35
+
36
+ function reloadSketch() {
37
+ const clone = [...modulesToReload];
38
+
39
+ modulesToReload = [];
40
+
41
+ if (clone.length > 0) {
42
+ const { file } = clone[0];
43
+ const filepath = path.relative(cwd, file);
44
+ log.message(`${green(`hmr update`)} /${filepath}`);
45
+
46
+ server.ws.send({
47
+ type: 'custom',
48
+ event: 'sketch-update',
49
+ data: {
50
+ file,
51
+ filepath,
52
+ cwd,
53
+ },
54
+ });
55
+ }
56
+
57
+ return clone;
58
+ }
59
+
60
+ function addShaderFilepath(shaderSource, shaderPath) {
61
+ let keyword = `void main`;
62
+ let shaderParts = shaderSource.split(keyword);
63
+ let hint = `// <filepath://${shaderPath}>`;
64
+
65
+ return `${shaderParts[0]}
66
+ ${hint}
67
+ ${keyword}${shaderParts[1]}
68
+ `;
69
+ }
70
+
71
+ function getUnixPath(shaderPath) {
72
+ return shaderPath.split(path.sep).join(path.posix.sep);
73
+ }
74
+
75
+ function compileGLSL(shaderSource, shaderPath) {
76
+ // test if shader source contains hint to avoid shader injection
77
+ const nohsr = ignoreRegex.test(shaderSource);
78
+
79
+ // remove current shader from dependency list before resolving dependencies again
80
+ dependencies.forEach((shadersPaths, dependency) => {
81
+ const shadersList = shadersPaths.filter((p) => p !== shaderPath);
82
+ dependencies.set(dependency, shadersList);
83
+ });
84
+
85
+ // if a dependency no longer have any parent shader, we can remove it from the watch
86
+ dependencies.forEach((shadersPaths, dependency) => {
87
+ if (shadersPaths.length === 0) {
88
+ if (server) {
89
+ server.watcher.unwatch(dependency);
90
+ }
91
+
92
+ dependencies.delete(dependency);
93
+ }
94
+ });
95
+
96
+ /**
97
+ *
98
+ * @param {string} parentSource
99
+ * @param {string} parentPath
100
+ * @param {string[]} deps
101
+ * @returns {}
102
+ */
103
+ function resolveDependencies(
104
+ parentSource,
105
+ parentPath,
106
+ deps = [],
107
+ warnings = [],
108
+ ) {
109
+ // remove comments
110
+ parentSource = parentSource.replace(commentRegex, '');
111
+
112
+ let parentUnixPath = getUnixPath(parentPath);
113
+ let directory = path.dirname(parentUnixPath);
114
+
115
+ if (includeRegex.test(parentSource)) {
116
+ const currentDirectory = directory;
117
+
118
+ parentSource = parentSource.replace(
119
+ includeRegex,
120
+ (_, include) => {
121
+ include = include.trim();
122
+ let chunkPath = include.replace(
123
+ /^(?:"|')?|(?:"|')?;?$/gi,
124
+ '',
125
+ );
126
+
127
+ if (!chunkPath.indexOf('/')) {
128
+ chunkPath = `${base}/${chunkPath}`;
129
+ }
130
+
131
+ const directoryIndex = chunkPath.lastIndexOf('/');
132
+ directory = currentDirectory;
133
+
134
+ if (directoryIndex !== -1) {
135
+ directory = path.resolve(
136
+ directory,
137
+ chunkPath.slice(0, directoryIndex + 1),
138
+ );
139
+ chunkPath = chunkPath.slice(
140
+ directoryIndex + 1,
141
+ chunkPath.length,
142
+ );
143
+ }
144
+
145
+ let chunkResolvedPath = path.resolve(
146
+ directory,
147
+ chunkPath,
148
+ );
149
+ let extension = 'glsl';
150
+
151
+ if (!path.extname(chunkResolvedPath))
152
+ chunkResolvedPath = `${chunkResolvedPath}.${extension}`;
153
+
154
+ const chunkUnixPath = getUnixPath(chunkResolvedPath);
155
+
156
+ if (!dependencies.has(chunkUnixPath)) {
157
+ // first time chunk is detected
158
+ dependencies.set(chunkUnixPath, []);
159
+
160
+ if (server) {
161
+ server.watcher.add(chunkResolvedPath);
162
+ }
163
+ }
164
+
165
+ const parents = dependencies.get(chunkUnixPath);
166
+
167
+ if (!parents.includes(shaderPath)) {
168
+ parents.push(shaderPath);
169
+ deps.push(chunkResolvedPath);
170
+ } else {
171
+ const message = `Dependency is already included in ${shaderPath}.\nInclude was skipped to avoid errors.`;
172
+
173
+ warnings.push({
174
+ type: 'duplicated dependency',
175
+ message,
176
+ importer: parentPath,
177
+ url: chunkResolvedPath,
178
+ location: {
179
+ lineText: `#include ${include}`,
180
+ },
181
+ });
182
+
183
+ return '';
184
+ }
185
+
186
+ try {
187
+ const chunkSource = fs.readFileSync(
188
+ chunkResolvedPath,
189
+ 'utf8',
190
+ );
191
+
192
+ const { code: chunkCode } = resolveDependencies(
193
+ chunkSource,
194
+ chunkResolvedPath,
195
+ deps,
196
+ warnings,
197
+ );
198
+
199
+ const prefix = server
200
+ ? `//#include ${include}\n`
201
+ : ``;
202
+
203
+ return `${prefix}\n${chunkCode}`;
204
+ } catch (error) {
205
+ if (error.code === 'ENOENT') {
206
+ warnings.push({
207
+ type: 'not found',
208
+ message: `Cannot find ${chunkResolvedPath}`,
209
+ importer: parentPath,
210
+ url: chunkResolvedPath,
211
+ location: {
212
+ lineText: `#include ${include}`,
213
+ },
214
+ });
215
+ }
216
+
217
+ return ``;
218
+ }
219
+ },
220
+ );
221
+ }
222
+
223
+ return {
224
+ code: parentSource.trim().replace(/(\r\n|\r|\n){3,}/g, '$1\n'),
225
+ deps,
226
+ warnings,
227
+ };
228
+ }
229
+
230
+ let { code, deps, warnings } = resolveDependencies(
231
+ shaderSource,
232
+ shaderPath,
233
+ );
234
+
235
+ code = glslify(code, {
236
+ basedir: process.cwd(),
237
+ });
238
+
239
+ if (server) {
240
+ code = addShaderFilepath(code, shaderPath);
241
+ }
242
+
243
+ warnings.forEach((warning) => {
244
+ const { location } = warning;
245
+ const line = 1;
246
+ const column = 4;
247
+ log.message(`${yellow(warning.type)} ${warning.importer}`, prefix);
248
+ console.log();
249
+ console.log(` ${dim(location.lineText)}`);
250
+ console.log();
251
+ console.log(warning.message);
252
+ console.log();
253
+ });
254
+
255
+ return { code, deps, warnings, nohsr };
256
+ }
257
+
258
+ /**
259
+ * Reload shaders after changes via custom Websocket or Vite HMR (sketch reload)
260
+ * @param {ShaderUpdate[]} shaderUpdates
261
+ */
262
+ function reloadShaders(shaderUpdates) {
263
+ const shadersNeedReload = shaderUpdates.filter(
264
+ (shaderUpdate) => shaderUpdate.nohsr,
265
+ );
266
+
267
+ if (shadersNeedReload.length > 0) {
268
+ shadersNeedReload.forEach((shaderUpdate) => {
269
+ log.message(
270
+ `${yellow('hsr ignore')} /${path.relative(cwd, shaderUpdate.filepath)}`,
271
+ );
272
+ });
273
+
274
+ return reloadSketch();
275
+ } else {
276
+ shaderUpdates.forEach((shaderUpdate) => {
277
+ log.message(
278
+ `${green('hsr update')} /${path.relative(cwd, shaderUpdate.filepath)}`,
279
+ );
280
+ });
281
+
282
+ wss.send({
283
+ type: 'custom',
284
+ event: 'shader-update',
285
+ data: shaderUpdates,
286
+ });
287
+
288
+ return [];
289
+ }
290
+ }
291
+
292
+ /** @type import('vite').ViteDevServer */
293
+ let server;
294
+
295
+ return {
296
+ name: 'fragment-plugin-hsr',
297
+ config: () => ({
298
+ optimizeDeps: {
299
+ esbuildOptions: {
300
+ loader: {
301
+ '.frag': 'text',
302
+ '.vert': 'text',
303
+ '.glsl': 'text',
304
+ '.fs': 'text',
305
+ '.vs': 'text',
306
+ },
307
+ },
308
+ },
309
+ }),
310
+ configureServer(_server) {
311
+ server = _server;
312
+ },
313
+ handleHotUpdate: async ({ modules, file, read }) => {
314
+ const { moduleGraph } = server;
315
+
316
+ if (fileRegex.test(file)) {
317
+ const thisModule = moduleGraph.getModuleById(file);
318
+
319
+ if (thisModule) {
320
+ modulesToReload.push(thisModule);
321
+ }
322
+
323
+ let unixPath = getUnixPath(file);
324
+ if (shaders.includes(file)) {
325
+ let source = await read();
326
+ let {
327
+ code: glsl,
328
+ warnings,
329
+ nohsr,
330
+ } = compileGLSL(source, file);
331
+
332
+ /** @type ShaderUpdate[] */
333
+ const shaderUpdate = {
334
+ filepath: unixPath,
335
+ source: glsl,
336
+ warnings,
337
+ nohsr,
338
+ };
339
+
340
+ return reloadShaders([shaderUpdate]);
341
+ } else {
342
+ if (dependencies.has(unixPath)) {
343
+ const shadersList = dependencies.get(unixPath);
344
+
345
+ // retrieve modules from module graph
346
+ const moduleNodes = shadersList.map((moduleNode) =>
347
+ moduleGraph.getModuleById(moduleNode),
348
+ );
349
+
350
+ // save it as modules to reload to invalidate the top level shaders in case a dependency has been hot updated in between
351
+ modulesToReload.push(...moduleNodes);
352
+
353
+ const sources = await Promise.all(
354
+ shadersList.map((shader) => {
355
+ return readFile(shader, 'utf-8');
356
+ }),
357
+ );
358
+
359
+ log.message(
360
+ `${yellow(`dependency update`)} /${path.relative(cwd, unixPath)}`,
361
+ );
362
+
363
+ /** @type ShaderUpdate[] */
364
+ const shaderUpdates = shadersList.map(
365
+ (shader, index) => {
366
+ let source = sources[index];
367
+ let {
368
+ code: glsl,
369
+ warnings,
370
+ nohsr,
371
+ } = compileGLSL(source, shader);
372
+
373
+ return {
374
+ filepath: shader,
375
+ source: glsl,
376
+ warnings: warnings,
377
+ nohsr,
378
+ };
379
+ },
380
+ );
381
+
382
+ return reloadShaders(shaderUpdates);
383
+ }
384
+ }
385
+
386
+ return [];
387
+ } else if (modulesToReload.length > 0) {
388
+ // only return if some shaders have been updated in between
389
+ // otherwise, returning an empty array would prevent hmr on sketch files
390
+ return reloadSketch();
391
+ }
392
+ },
393
+ transform(source, file) {
394
+ if (!fileRegex.test(file)) return;
395
+
396
+ if (!shaders.includes(file)) {
397
+ shaders.push(file);
398
+ }
399
+
400
+ let { code: glsl } = compileGLSL(source, file);
401
+
402
+ return {
403
+ code: `export default ${JSON.stringify(glsl)}`,
404
+ map: null, // provide source map if available
405
+ };
406
+ },
407
+ };
408
+ }
@@ -1,15 +1,23 @@
1
- import path from "path";
2
- import kleur from "kleur";
3
- import log from "../log.js";
1
+ import path from 'node:path';
2
+ import { log, green } from '../log.js';
4
3
 
5
- export default function hotSketchReload({ cwd }) {
6
- return {
7
- name: 'hot-sketch-reload',
8
- handleHotUpdate: async ({ server, modules, file, read }) => {
9
- if (file.includes(cwd)) {
4
+ /**
5
+ * Send a custom event to Fragment when a sketch changes
6
+ * @param {object} [params]
7
+ * @param {string} [params.cwd=process.cwd()] - Current working directory
8
+ * @returns {import('vite').Plugin}
9
+ */
10
+ export default function hotSketchReload({ cwd = process.cwd() } = {}) {
11
+ const shaderRegex = /\.(?:frag|vert|glsl|vs|fs)$/;
12
+ const base = cwd.split(path.sep).join(path.posix.sep);
13
+ return {
14
+ name: 'hot-sketch-reload',
15
+ handleHotUpdate: async ({ server, modules, file }) => {
16
+ if (file.includes(base) && !shaderRegex.test(file)) {
10
17
  const filepath = path.relative(cwd, file);
11
- console.log(`${log.prefix} ${kleur.green(`hmr update`)} /${filepath}`);
12
-
18
+
19
+ log.message(`${green(`hmr update`)} /${filepath}`);
20
+
13
21
  server.ws.send({
14
22
  type: 'custom',
15
23
  event: 'sketch-update',
@@ -17,23 +25,11 @@ export default function hotSketchReload({ cwd }) {
17
25
  file,
18
26
  filepath,
19
27
  cwd,
20
- }
28
+ },
21
29
  });
22
-
23
30
  }
24
31
 
25
32
  return modules;
26
- },
27
- // transform: (src, id) => {
28
- // if (fileRegex.test(id)) {
29
- // let source = glslify(src);
30
- // source = addShaderFilepath(source, id);
31
-
32
- // return {
33
- // code: `export default ${JSON.stringify(source)}`,
34
- // map: null // provide source map if available
35
- // }
36
- // }
37
- // }
38
- };
33
+ },
34
+ };
39
35
  }
@@ -0,0 +1,101 @@
1
+ import path from 'node:path';
2
+ import { writeFile } from 'node:fs/promises';
3
+ import bodyParser from 'body-parser';
4
+ import { log, green, red } from '../log.js';
5
+ import { mkdirp } from '../utils.js';
6
+
7
+ /**
8
+ *
9
+ * @param {object} [params]
10
+ * @param {string} [cwd=process.cwd()] - Current working directory
11
+ * @param {string} [inlineExportDir] - Directory path used for exports
12
+ * @returns {import('vite').Plugin}
13
+ */
14
+ export default function screenshot({
15
+ cwd = process.cwd(),
16
+ inlineExportDir,
17
+ } = {}) {
18
+ function resolveDirectory(directoryPath) {
19
+ return path.isAbsolute(directoryPath)
20
+ ? directoryPath
21
+ : path.join(cwd, directoryPath);
22
+ }
23
+
24
+ function resolveExportDirectory(exportDir) {
25
+ let directory;
26
+
27
+ if (inlineExportDir) {
28
+ if (!inlineExportDirPath) {
29
+ inlineExportDirPath = resolveDirectory(inlineExportDir);
30
+ }
31
+
32
+ directory = inlineExportDirPath;
33
+
34
+ if (exportDir) {
35
+ log.warning(
36
+ `'exportDir' configuration from sketch has been overridden by --exportDir.`,
37
+ );
38
+ }
39
+ } else if (exportDir) {
40
+ directory = resolveDirectory(exportDir);
41
+ } else {
42
+ directory = cwd;
43
+ }
44
+
45
+ return directory;
46
+ }
47
+
48
+ let inlineExportDirPath;
49
+
50
+ return {
51
+ name: 'save',
52
+ configureServer(server) {
53
+ server.middlewares.use(bodyParser.json({ limit: '100mb' }));
54
+ server.middlewares.use('/save', async (req, res, next) => {
55
+ if (req.method === 'POST') {
56
+ const { files } = req.body;
57
+
58
+ try {
59
+ const filepaths = [];
60
+
61
+ for (let i = 0; i < files.length; i++) {
62
+ const { filename, data, encoding, exportDir } =
63
+ files[i];
64
+
65
+ let directory = resolveExportDirectory(exportDir);
66
+ mkdirp(directory);
67
+
68
+ let filepath = path.join(directory, filename);
69
+
70
+ let buffer = Buffer.from(
71
+ encoding === 'base64'
72
+ ? data.split(',')[1]
73
+ : data,
74
+ encoding,
75
+ );
76
+
77
+ await writeFile(filepath, buffer);
78
+
79
+ log.message(`${green(`export`)} Saved ${filepath}`);
80
+ filepaths.push(filepath);
81
+ }
82
+
83
+ res.writeHead(200, {
84
+ 'Content-Type': 'application/json',
85
+ });
86
+ res.end(JSON.stringify({ filepaths }));
87
+ } catch (error) {
88
+ log.message(`${red(`export`)} Error`);
89
+ console.error(error);
90
+ res.writeHead(500, {
91
+ 'Content-Type': 'application/json',
92
+ });
93
+ res.end(JSON.stringify({ error }));
94
+ }
95
+ } else {
96
+ next();
97
+ }
98
+ });
99
+ },
100
+ };
101
+ }
@@ -0,0 +1,55 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+ import { preview as vitePreview } from 'vite';
4
+ import { bold, cyan, log, magenta } from './log.js';
5
+ import * as p from './prompts.js';
6
+
7
+ /**
8
+ * Preview a sketch
9
+ * @param {string} dir
10
+ * @param {object} [options]
11
+ * @param {boolean} [options.open=false]
12
+ */
13
+ export async function preview(dir, options = {}) {
14
+ const cwd = process.cwd();
15
+ const prefix = log.prefix('preview');
16
+
17
+ const outDir = path.join(cwd, dir);
18
+
19
+ try {
20
+ if (!fs.existsSync(outDir)) {
21
+ throw new Error(
22
+ `Directory ${magenta(dir)} does not exist in ${cwd}`,
23
+ );
24
+ }
25
+
26
+ log.message(`${magenta(outDir)}\n`, prefix);
27
+
28
+ const previewServer = await vitePreview({
29
+ build: {
30
+ outDir,
31
+ },
32
+ preview: {
33
+ host: true,
34
+ open: options.open,
35
+ },
36
+ });
37
+
38
+ const { resolvedUrls } = previewServer;
39
+
40
+ let urls = ``;
41
+
42
+ for (const url of resolvedUrls.local) {
43
+ urls += `${bold('Local')}: ${bold(cyan(url))}\n`;
44
+ }
45
+
46
+ for (const url of resolvedUrls.network) {
47
+ urls += `${bold('Network')}: ${bold(cyan(url))}`;
48
+ }
49
+
50
+ p.note(urls);
51
+ } catch (error) {
52
+ log.error(`Error\n`, prefix);
53
+ console.error(error);
54
+ }
55
+ }