fragment-tools 0.2.14 → 0.2.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.
package/README.md CHANGED
@@ -1,19 +1,26 @@
1
1
  <h1 align="center">Fragment</h1>
2
- <div align="center">A web development environment for creative coding</div>
2
+ <div align="center">A modern toolkit for creative coding</div>
3
3
  <br/>
4
4
 
5
- ![Screen capture of Fragment, splitted in two columns, the left one has a centered canvas displaying squares arranged in a grid, the right column contains various controls for colors, variables and exports](https://github.com/raphaelameaume/fragment/raw/main/screenshot.png 'Screen Capture of Fragment')
5
+ ![Screen capture of Fragment, splitted in two columns, the left one has a canvas displaying circles arranged in a grid layout with chromatic aberration, grain and blur effects. The right column contains various controls for colors, circle and grid parameters and an export module with video capabilities](https://github.com/raphaelameaume/fragment/blob/dev/screenshot.jpg?raw=true 'Screen Capture of Fragment')
6
6
 
7
- `fragment` provides a simple API to work with `<canvas>`.
7
+ Fragment lets you sketch, render, and export canvas-based graphics with ease, from quick experiments to production-ready visuals.
8
8
 
9
9
  ## Features
10
10
 
11
- - Multiple rendering modes: [Canvas 2D](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API), [p5.js](https://github.com/processing/p5.js/), [three.js](https://github.com/mrdoob/three.js/), [WebGL fragment shaders](https://developer.mozilla.org/en-US/docs/Web/API/WebGLShader)
12
- - Built-in GUI from sketch files
13
- - Export `<canvas>` to images (.png, .webm, .jpg) or videos (.mp4, .webm, .gif) on the fly
14
- - Hot shader reloading & [glslify](https://github.com/glslify/glslify) support
15
- - Interactive sketches using _triggers_
16
- - Static build for production deployment
11
+ - Start instantly using built-in templates for [Canvas 2D](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API), [p5.js](https://github.com/processing/p5.js/), and [three.js](https://github.com/mrdoob/three.js/)
12
+ - Generate live GUI controls directly from your sketch parameters
13
+ - Reload GLSL shaders on the fly
14
+ - Attach mouse, keyboard, and MIDI inputs directly from the interface or your code
15
+ - Organize your creative workspace with flexible window layouts
16
+ - Export your sketches as images or videos in standard formats
17
+ - Build static bundles ready for production deployment
18
+
19
+ ## Specifications
20
+
21
+ - Fast development and production builds powered by [Vite](https://vite.dev/)
22
+ - Interactive command-line interface on top of [sade](https://github.com/lukeed/sade) and [clack](https://www.clack.cc/)
23
+ - Video encoding in the browser with [Mediabunny](https://mediabunny.dev/)
17
24
  - TypeScript support
18
25
 
19
26
  ## Installation
@@ -33,14 +40,13 @@ npm install fragment-tools --global
33
40
  You should now be able to run `fragment` from your command line.
34
41
 
35
42
  ```bash
36
- fragment sketch.js --new
43
+ fragment -v
37
44
  ```
38
45
 
39
46
  ### Installing locally
40
47
 
41
48
  ```bash
42
49
  npm install fragment-tools
43
- npx fragment sketch.js --new
44
50
  ```
45
51
 
46
52
  ## Usage
@@ -53,10 +59,13 @@ mkdir sketches
53
59
  cd sketches
54
60
 
55
61
  # Create a sketch from a template
56
- npx fragment ./sketch.js --new --template=2d
62
+ npx fragment sketch.js --new --template=2d
57
63
 
58
64
  # or with TypeScript
59
- npx fragment ./sketch.ts --new --template=2d --typescript
65
+ npx fragment sketch.ts --new --template=2d --typescript
66
+
67
+ ## Run an existing sketch
68
+ npx fragment sketch.js
60
69
  ```
61
70
 
62
71
  Learn more about the available flag options in the [CLI docs](./docs/api/CLI.md).
@@ -67,37 +76,66 @@ This is an example of a sketch drawing a blue circle on a black background with
67
76
 
68
77
  ```js
69
78
  export let props = {
70
- radius: {
71
- value: 10,
72
- params: {
73
- min: 4,
74
- max: 30,
75
- },
76
- },
79
+ background: {
80
+ value: '#0057B8', // create a color input GUI
81
+ },
82
+ fill: {
83
+ value: 'rgba(255, 215, 0, 1)', // create a color input GUI
84
+ },
85
+ size: {
86
+ value: 256, // create a number input GUI
87
+ params: {
88
+ min: 64, // add a range slider
89
+ max: 512,
90
+ },
91
+ },
92
+ shape: {
93
+ value: 'circle', // create a string input
94
+ params: {
95
+ options: ['circle', 'square'], // turns the input into a select with different options
96
+ },
97
+ },
77
98
  };
78
99
 
79
- export let update = ({ context, width, height }) => {
80
- // draw background
81
- context.fillStyle = '#000000';
82
- context.fillRect(0, 0, width, height);
100
+ export let update = ({ context, width, height, pixelRatio }) => {
101
+ // draw background
102
+ context.fillStyle = props.background.value;
103
+
104
+ const w = width * pixelRatio;
105
+ const h = height * pixelRatio;
83
106
 
84
- // draw circle
85
- const radius = props.radius.value;
107
+ context.fillRect(0, 0, w, h);
108
+
109
+ // draw circle
110
+ const shape = props.shape.value;
111
+ const size = props.size.value * pixelRatio;
112
+
113
+ context.fillStyle = props.fill.value;
114
+
115
+ if (shape === 'circle') {
116
+ context.beginPath();
117
+ context.arc(w * 0.5, h * 0.5, size, 0, 2 * Math.PI, false);
118
+ context.fill();
119
+ } else if (shape === 'square') {
120
+ context.fillRect(w * 0.5 - size, h * 0.5 - size, size * 2, size * 2);
121
+ }
122
+ };
86
123
 
87
- context.fillStyle = '#0000ff';
88
- context.beginPath();
89
- context.arc(width * 0.5, height * 0.5, radius, 0, 2 * Math.PI, false);
90
- context.fill();
124
+ export let rendering = '2d';
125
+ export let fps = 0;
126
+ export let name = 'Canvas2D Shape Example';
127
+ export let buildConfig = {
128
+ resizing: 'window',
91
129
  };
92
130
  ```
93
131
 
94
- Learn how to write your own sketch by following the [Getting started](./docs/introduction/getting-started.md) guide, reading the [API docs](./docs/api/sketch.md) or the [examples](./examples/).
132
+ Learn how to write your own sketch by following the [Getting started](./docs/introduction/getting-started.md) guide, reading the [API docs](./docs/api/sketch.md) or the other [examples](./examples/).
95
133
 
96
134
  ## Contributing
97
135
 
98
136
  If you find issues, please [file one](https://github.com/raphaelameaume/fragment/issues) with details on how to reproduce it.
99
137
 
100
- Feel free to reach out on [Twitter](https://twitter.com/raphaelameaume) if you want to discuss the project.
138
+ Feel free to reach out on [Bluesky](https://bsky.app/profile/raphaelameaume.com) if you want to discuss the project.
101
139
 
102
140
  ## Running it locally
103
141
 
package/package.json CHANGED
@@ -1,16 +1,13 @@
1
1
  {
2
2
  "name": "fragment-tools",
3
- "version": "0.2.14",
3
+ "version": "0.2.15",
4
4
  "description": "A web development environment for creative coding",
5
5
  "main": "./src/index.js",
6
6
  "bin": {
7
7
  "fragment": "bin/index.js"
8
8
  },
9
9
  "scripts": {
10
- "format": "prettier . --write",
11
- "changeset:add": "changeset add",
12
- "changeset:version": "changeset version",
13
- "changeset:publish": "changeset publish"
10
+ "format": "prettier . --write"
14
11
  },
15
12
  "type": "module",
16
13
  "repository": {
@@ -36,11 +33,10 @@
36
33
  "milliparsec": "^5.1.0",
37
34
  "sade": "^1.8.1",
38
35
  "svelte": "^5.55.9",
39
- "vite": "^8.0.14",
40
- "ws": "^8.20.1"
36
+ "vite": "^8.1.0",
37
+ "ws": "^8.21.0"
41
38
  },
42
39
  "devDependencies": {
43
- "@changesets/cli": "^2.29.7",
44
40
  "@svitejs/changesets-changelog-github-compact": "^1.2.0",
45
41
  "@types/node": "^25.9.1",
46
42
  "@types/p5": "^1.7.6",
package/screenshot.jpg ADDED
Binary file
@@ -131,38 +131,40 @@
131
131
  {/if}
132
132
  {#if typeof sketchProps === 'object'}
133
133
  {#snippet sketchField(index, key, prop)}
134
- {@const {
135
- hidden,
136
- displayName,
137
- value,
138
- type,
139
- disabled,
140
- __initialValue: initialValue,
141
- } = prop}
142
- {#if !hidden}
143
- <Field
144
- context={sketch.key}
145
- {key}
146
- {displayName}
147
- {value}
148
- {initialValue}
149
- {type}
150
- {index}
151
- {disabled}
152
- bind:params={sketchProps[key].params}
153
- bind:triggers={prop.triggers}
154
- trackChanges
155
- onclick={(event) => {
156
- sketch.version++;
157
- // value(event, sketch.params);
158
- }}
159
- onchange={(v) => {
160
- if (v?.currentTarget && v?.type === 'change') {
161
- v = v.currentTarget.value;
162
- }
163
- sketch.updateProp(key, v);
164
- }}
165
- />
134
+ {#if prop}
135
+ {@const {
136
+ hidden,
137
+ displayName,
138
+ value,
139
+ type,
140
+ disabled,
141
+ __initialValue: initialValue,
142
+ } = prop}
143
+ {#if !hidden}
144
+ <Field
145
+ context={sketch.key}
146
+ {key}
147
+ {displayName}
148
+ {value}
149
+ {initialValue}
150
+ {type}
151
+ {index}
152
+ {disabled}
153
+ bind:params={sketchProps[key].params}
154
+ bind:triggers={prop.triggers}
155
+ trackChanges
156
+ onclick={(event) => {
157
+ sketch.version++;
158
+ // value(event, sketch.params);
159
+ }}
160
+ onchange={(v) => {
161
+ if (v?.currentTarget && v?.type === 'change') {
162
+ v = v.currentTarget.value;
163
+ }
164
+ sketch.updateProp(key, v);
165
+ }}
166
+ />
167
+ {/if}
166
168
  {/if}
167
169
  {/snippet}
168
170
  {#snippet sketchPropItem(index, item)}
@@ -38,9 +38,15 @@ export let onMountPreview = ({
38
38
  pixelRatio,
39
39
  }) => {
40
40
  const p = new p5((sketch) => {
41
- sketch.pixelDensity(pixelRatio);
42
41
  sketch.setup = () => {
43
- sketch.createCanvas(width, height, 'webgl', canvas);
42
+ const dpr = window.devicePixelRatio;
43
+ sketch.pixelDensity(pixelRatio);
44
+ sketch.createCanvas(
45
+ (width / dpr) * pixelRatio,
46
+ (height / dpr) * pixelRatio,
47
+ 'webgl',
48
+ canvas,
49
+ );
44
50
  };
45
51
  }, container);
46
52
 
@@ -34,9 +34,14 @@ export let onMountPreview = ({
34
34
  pixelRatio,
35
35
  }) => {
36
36
  const p = new p5((sketch) => {
37
- sketch.pixelDensity(pixelRatio);
38
37
  sketch.setup = () => {
39
- sketch.createCanvas(width, height, canvas);
38
+ const dpr = window.devicePixelRatio;
39
+ sketch.pixelDensity(pixelRatio);
40
+ sketch.createCanvas(
41
+ (width / dpr) * pixelRatio,
42
+ (height / dpr) * pixelRatio,
43
+ canvas,
44
+ );
40
45
  };
41
46
  }, container);
42
47
 
@@ -52,10 +57,7 @@ export let onMountPreview = ({
52
57
  };
53
58
 
54
59
  /**
55
- * @param {MountParamsP5Renderer} params
56
- * @param {number} params.id
57
- * @param {HTMLCanvasElement} params.canvas
58
- * @param {HTMLDivElement} params.container
60
+ * @param {MountParamsP5Renderer & PreviewP5Renderer} params
59
61
  */
60
62
  export let onBeforeUpdatePreview = ({ id }) => {
61
63
  const preview = previews.find((p) => p.id === id);
@@ -409,7 +409,13 @@ class Sketch {
409
409
  collection.push(fieldgroup);
410
410
  }
411
411
 
412
- if (fieldgroup && isCurrent) {
412
+ if (
413
+ fieldgroup &&
414
+ isCurrent &&
415
+ !fieldgroup.children.some(
416
+ (c) => c.type === 'field' && c.key === key,
417
+ )
418
+ ) {
413
419
  fieldgroup.children.push({
414
420
  type: 'field',
415
421
  key,
@@ -1,37 +1,47 @@
1
+ import { getFileExtension, getFilename } from '../utils/file.utils';
2
+
1
3
  export const wildcard = '*';
4
+ /** @type string[] */
2
5
  export let sketchFiles = [];
3
6
 
7
+ /**
8
+ * @param {string[]} files
9
+ */
4
10
  export function assignSketchFiles(files) {
5
11
  sketchFiles.push(...files);
6
12
  }
7
13
 
14
+ /**
15
+ * Retrieves sketch key based on error stack traces
16
+ * @returns {string}
17
+ */
8
18
  export function getContext() {
9
- let context;
19
+ let context = wildcard;
10
20
 
11
21
  const { stack } = new Error();
12
22
  const { url } = import.meta;
13
23
 
14
- const callstack = stack.split('\n');
15
- const index = callstack.findIndex((call) => call.includes(url));
24
+ if (stack) {
25
+ const callstack = stack.split('\n');
26
+ const index = callstack.findIndex((call) => call.includes(url));
16
27
 
17
- if (index >= 0) {
18
- callstack.splice(0, index + 1);
19
- }
28
+ if (index >= 0) {
29
+ callstack.splice(0, index + 1);
30
+ }
20
31
 
21
- for (let i = 0; i < callstack.length; i++) {
22
- for (let j = 0; j < sketchFiles.length; j++) {
23
- const sketchFile = sketchFiles[j];
32
+ for (let i = 0; i < callstack.length; i++) {
33
+ for (let j = 0; j < sketchFiles.length; j++) {
34
+ const sketchFile = getFilename(sketchFiles[j]);
35
+ const extension = getFileExtension(sketchFile);
36
+ const filename = sketchFile.split(`.${extension}`)[0];
24
37
 
25
- if (callstack[i].includes(sketchFile)) {
26
- context = sketchFile;
27
- break;
38
+ if (callstack[i].includes(filename)) {
39
+ context = sketchFile;
40
+ break;
41
+ }
28
42
  }
29
43
  }
30
44
  }
31
45
 
32
- if (!context) {
33
- context = wildcard;
34
- }
35
-
36
46
  return context;
37
47
  }