fragment-tools 0.1.11 → 0.1.13

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.
@@ -0,0 +1,5 @@
1
+ LICENSE.md
2
+ pnpm-lock.yaml
3
+ pnpm-workspace.yaml
4
+ package.json
5
+ /docs
package/.prettierrc ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "semi": true,
3
+ "tabWidth": 4,
4
+ "useTabs": true,
5
+ "singleQuote": true,
6
+ "printWidth": 80,
7
+ "bracketSpacing": true,
8
+ "trailingComma": "all"
9
+ }
package/bin/index.js CHANGED
@@ -1,19 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import sade from "sade";
4
- import { run } from "../src/cli/index.js";
3
+ import sade from 'sade';
4
+ import { run } from '../src/cli/index.js';
5
5
 
6
6
  sade('fragment [entry]')
7
- .version('0.1.0')
8
- .describe('Run a dev environment for fragment')
9
- .option('-n, --new', 'Create file if it does not exist', false)
10
- .option('-t, --template', 'Specify template to create the file from', '2d')
11
- .option('-p, --port', 'Port to bind', 3000)
12
- .option('-dev, --development', 'Enable development mode', false)
13
- .option('-b, --build', 'Build sketch for production', false)
14
- .option('--outDir', 'Directory used for static build', null)
15
- .option('--emptyOutDir', "Empty outDir before static build", false)
16
- .action((entry, options) => {
17
- run(entry, options);
18
- })
19
- .parse(process.argv);
7
+ .version('0.1.13')
8
+ .describe('Run a dev environment for fragment')
9
+ .option('-n, --new', 'Create file if it does not exist', false)
10
+ .option('-t, --template', 'Specify template to create the file from', '2d')
11
+ .option('-p, --port', 'Port to bind', 3000)
12
+ .option('-dev, --development', 'Enable development mode', false)
13
+ .option('-b, --build', 'Build sketch for production', false)
14
+ .option('--exportDir', 'Directory used for exports', process.cwd())
15
+ .option('--outDir', 'Directory used for static build', null)
16
+ .option('--emptyOutDir', 'Empty outDir before static build', false)
17
+ .action((entry, options) => {
18
+ run(entry, options);
19
+ })
20
+ .parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fragment-tools",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "A web development environment for creative coding",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -25,6 +25,7 @@
25
25
  "chokidar": "^3.5.2",
26
26
  "convert-length": "^1.0.1",
27
27
  "get-port": "^6.0.0",
28
+ "gifenc": "^1.0.3",
28
29
  "glslify": "^7.1.1",
29
30
  "kleur": "^4.1.4",
30
31
  "sade": "^1.7.4",
@@ -1,26 +1,41 @@
1
1
  import path from "path";
2
- import fs from "fs";
2
+ import fs from "fs/promises";
3
+ import fsSync from "fs";
3
4
  import bodyParser from "body-parser";
5
+ import log from "../log.js";
6
+
7
+ export default function screenshot({ cwd, exportDir = cwd }) {
8
+ let dir = (path.isAbsolute(exportDir) ? exportDir : path.join(cwd, exportDir));
4
9
 
5
- export default function screenshot({ cwd }) {
6
10
  return {
7
11
  name: 'screenshot',
8
12
  configureServer(server){
9
13
  server.middlewares.use(bodyParser.json({ limit: '100mb'}))
10
- server.middlewares.use('/save', (req, res, next) => {
14
+ server.middlewares.use('/save', async (req, res, next) => {
11
15
  if (req.method === "POST") {
12
16
  const { filename, dataURL } = req.body;
13
17
 
14
- const filepath = path.join(cwd, filename);
18
+ const filepath = path.join(dir, filename);
15
19
  const buffer = Buffer.from(dataURL, 'base64');
16
20
 
17
- fs.writeFile(filepath, buffer, (error) => {
18
- let statusCode = error ? 500 : 200;
19
- let body = error ? { error } : { filepath };
21
+ if (!fsSync.existsSync(dir)) {
22
+ try {
23
+ await fs.mkdir(dir, { recursive: true });
24
+ } catch(error) {
25
+ log.error('Cannot create directory for exports');
26
+ console.log(error);
27
+ }
28
+ }
29
+
30
+ try {
31
+ await fs.writeFile(filepath, buffer);
20
32
 
21
- res.writeHead(statusCode, {'Content-Type': 'application/json'});
22
- res.end(JSON.stringify(body));
23
- });
33
+ res.writeHead(200, {'Content-Type': 'application/json'});
34
+ res.end(JSON.stringify({ filepath }));
35
+ } catch(error) {
36
+ res.writeHead(500, {'Content-Type': 'application/json'});
37
+ res.end(JSON.stringify({ error }));
38
+ }
24
39
  } else {
25
40
  next();
26
41
  }
package/src/cli/server.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import path from "path";
2
- import os from 'os';
3
- import fs from "fs/promises";
2
+ import kleur from "kleur";
4
3
  import { fileURLToPath } from 'url';
5
4
  import { createServer, defineConfig, build } from "vite";
6
5
  import { svelte } from '@sveltejs/vite-plugin-svelte'
@@ -62,7 +61,7 @@ export async function start({ options, filepaths, entries, fragment }) {
62
61
  }
63
62
  },
64
63
  dbPlugin(),
65
- screenshotPlugin({ cwd }),
64
+ screenshotPlugin({ cwd, exportDir: options.exportDir }),
66
65
  checkDependencies({
67
66
  cwd,
68
67
  app,
@@ -83,7 +82,9 @@ export async function start({ options, filepaths, entries, fragment }) {
83
82
  '__FRAGMENT_PORT__': fragment.server ? fragment.server.port : undefined,
84
83
  '__START_TIME__': Date.now(),
85
84
  '__SEED__': Date.now(),
86
- '__PRODUCTION__': options.build,
85
+ '__BUILD__': options.build,
86
+ '__DEV__': !options.build,
87
+
87
88
  },
88
89
  optimizeDeps: {
89
90
  include: ['convert-length', 'webm-writer', 'changedpi'],
@@ -127,19 +128,25 @@ export async function start({ options, filepaths, entries, fragment }) {
127
128
  });
128
129
 
129
130
  await server.listen();
131
+
130
132
  log.success(`Server started at:`);
131
133
 
132
- Object.values(os.networkInterfaces())
133
- .flatMap((nInterface) => nInterface ?? [])
134
- .filter((detail) => detail && detail.address && (detail.family === 'IPv4' || detail.family === 4 ))
135
- .forEach((detail) => {
136
- const type = detail.address.includes('127.0.0.1')
137
- ? 'Local: '
138
- : 'Network: '
139
- const host = detail.address.replace('127.0.0.1', 'localhost');
140
- const url = `http://${host}:${server.config.server.port}`;
141
- console.log(` ${type} ${url}`);
142
- })
134
+ const { resolvedUrls } = server;
135
+
136
+ for (const url of resolvedUrls.local) {
137
+ console.log(
138
+ ` ${kleur.green('➜')} ${kleur.bold('Local')}: ${kleur.cyan(
139
+ url,
140
+ )}`,
141
+ );
142
+ }
143
+ for (const url of resolvedUrls.network) {
144
+ console.log(
145
+ ` ${kleur.green('➜')} ${kleur.bold('Network')}: ${kleur.cyan(
146
+ url,
147
+ )}`,
148
+ );
149
+ }
143
150
 
144
151
  return server;
145
152
  }
@@ -0,0 +1,13 @@
1
+ export let props = {};
2
+
3
+ export let init = ({ canvas, width, height }) => {
4
+
5
+ };
6
+
7
+ export let update = ({ width, height, time, deltaTime }) => {
8
+
9
+ };
10
+
11
+ export let resize = ({ width, height, pixelRatio }) => {
12
+
13
+ };
@@ -1,4 +1,7 @@
1
1
  const templates = {
2
+ "blank": [
3
+ "./templates/blank.js"
4
+ ],
2
5
  "2d": [
3
6
  "./templates/2d.js"
4
7
  ],
@@ -49,7 +49,7 @@ class CanvasRecorder {
49
49
  }
50
50
 
51
51
  async _tick() {
52
- console.log(`CanvasRecorder - render frame ${this.frameCount}`);
52
+ console.log(`CanvasRecorder - render frame ${this.frameCount + 1}`);
53
53
  this.onTick({
54
54
  time: this.time,
55
55
  deltaTime: this.deltaTime,
@@ -61,14 +61,14 @@ class CanvasRecorder {
61
61
  frameCount: this.frameCount,
62
62
  });
63
63
 
64
- if (this.started && !this.stopped && (!isFinite(this.frameTotal) || (isFinite(this.frameTotal) && this.frameCount < this.frameTotal))) {
64
+ if (this.started && !this.stopped && (!isFinite(this.frameTotal) || (isFinite(this.frameTotal) && this.frameCount < this.frameTotal - 1))) {
65
65
  this.time += this.deltaTime;
66
66
  this.frameCount++;
67
67
  requestAnimationFrame(() => {
68
68
  this._tick()
69
69
  });
70
70
  } else {
71
- console.log(`CanvasRecorder - compiling ${this.frameCount} frames...`);
71
+ console.log(`CanvasRecorder - compiling ${this.frameCount + 1} frames...`);
72
72
  this.end();
73
73
  }
74
74
  }
@@ -76,7 +76,7 @@ class CanvasRecorder {
76
76
  tick() {}
77
77
 
78
78
  end() {
79
- console.log(`CanvasRecorder - compiled ${this.frameCount} frames`);
79
+ console.log(`CanvasRecorder - compiled ${this.frameCount + 1} frames`);
80
80
  this.onComplete(this.result);
81
81
  }
82
82
 
@@ -1,51 +1,46 @@
1
- import { map } from "../../utils/math.utils";
2
- import { loadScript } from "../loader/loadScript";
3
- import CanvasRecorder from "./CanvasRecorder";
1
+ import { map } from '../../utils/math.utils';
2
+ import { GIFEncoder, quantize, applyPalette } from 'gifenc';
3
+ import CanvasRecorder from './CanvasRecorder';
4
4
 
5
5
  class GIFRecorder extends CanvasRecorder {
6
+ start() {
7
+ this.encoder = GIFEncoder();
6
8
 
7
- static loaded = false;
9
+ this.tmpCanvas = document.createElement('canvas');
10
+ this.tmpContext = this.tmpCanvas.getContext('2d');
8
11
 
9
- async load() {
10
- if (!GIFRecorder.loaded) {
11
- await loadScript('/js/gif.worker.js');
12
- await loadScript('/js/gif.js');
12
+ this.maxColors = Math.floor(map(this.quality, 1, 100, 1, 256));
13
13
 
14
- GIFRecorder.loaded = true;
15
- }
16
-
17
- const quality = map(this.quality, 1, 100, 10, 0);
14
+ super.start();
15
+ }
18
16
 
19
- this.writer = new GIF({
20
- workerScript: '/js/gif.worker.js',
21
- workers: 4,
22
- quality,
23
- width: this.canvas.width,
24
- height: this.canvas.height,
25
- });
17
+ getBitmapRGBA(bitmap, width = bitmap.width, height = bitmap.height) {
18
+ this.tmpCanvas.width = width;
19
+ this.tmpCanvas.height = height;
20
+ this.tmpContext.clearRect(0, 0, width, height);
21
+ this.tmpContext.drawImage(bitmap, 0, 0, width, height);
22
+ return this.tmpContext.getImageData(0, 0, width, height).data;
26
23
  }
27
24
 
28
25
  tick() {
29
- this.writer.addFrame(this.canvas, { copy: true, delay: this.frameDuration });
30
- }
26
+ const { width, height } = this.canvas;
31
27
 
32
- end() {
33
- return new Promise((resolve, reject) => {
34
- this.writer.on('finished', (result) => {
35
- this.result = result;
36
- this.writer = null;
28
+ const pixels = this.getBitmapRGBA(this.canvas, width, height);
29
+ const palette = quantize(pixels, this.maxColors);
30
+ const index = applyPalette(pixels, palette);
37
31
 
38
- super.end();
32
+ this.encoder.writeFrame(index, width, height, {
33
+ palette: palette,
34
+ delay: this.frameDuration,
35
+ });
36
+ }
39
37
 
40
- resolve();
41
- });
38
+ end() {
39
+ this.encoder.finish();
42
40
 
43
- this.writer.on('error', (err) => {
44
- reject(err);
45
- });
41
+ this.result = new Blob([this.encoder.bytes()], { type: 'image/gif' });
46
42
 
47
- this.writer.render();
48
- });
43
+ super.end();
49
44
  }
50
45
  }
51
46
 
@@ -1,148 +1,160 @@
1
1
  class Renderer {
2
+ constructor({
3
+ canvas = document.createElement("canvas"),
4
+ antialias = false,
5
+ alpha = true,
6
+ depth = false,
7
+ stencil = false,
8
+ premultipliedAlpha = false,
9
+ pixelRatio = window.devicePixelRatio,
10
+ webgl = 2,
11
+ }) {
12
+ let gl;
13
+ let attributes = {
14
+ depth,
15
+ stencil,
16
+ antialias,
17
+ alpha,
18
+ premultipliedAlpha,
19
+ preserveDrawingBuffer: true,
20
+ };
21
+
22
+ this.canvas = canvas;
23
+
24
+ if (webgl === 2) gl = canvas.getContext("webgl2", attributes);
25
+ if (!gl) {
26
+ gl =
27
+ canvas.getContext("webgl", attributes) ||
28
+ canvas.getContext("experimental-webgl", attributes);
29
+ }
2
30
 
3
- constructor({
4
- canvas = document.createElement('canvas'),
5
- antialias = false,
6
- alpha = true,
7
- depth = false,
8
- stencil = false,
9
- premultipliedAlpha = false,
10
- pixelRatio = window.devicePixelRatio,
11
- webgl = 2,
12
- }) {
13
- let gl;
14
- let attributes = {
15
- depth,
16
- stencil,
17
- antialias,
18
- alpha,
19
- premultipliedAlpha,
20
- preserveDrawingBuffer: true
21
- };
22
-
23
- this.canvas = canvas;
24
-
25
- if (webgl === 2) gl = canvas.getContext('webgl2', attributes);
26
- if (!gl) {
27
- gl = canvas.getContext('webgl', attributes) || canvas.getContext('experimental-webgl', attributes);
28
- }
29
-
30
- this.gl = gl;
31
-
32
- this.state = {
33
- activeTextureUnit: 0,
34
- textureUnits: [],
35
- flipY: false,
36
- viewport: { width: 0, height: 0 },
37
- pixelRatio,
38
- width: 0,
39
- height: 0,
40
- };
41
- this.gl.state = this.state;
42
- }
43
-
44
- render({
45
- geometry,
46
- program,
47
- primitiveType = this.gl.TRIANGLES,
48
- offset = 0,
49
- count = 3
50
- }) {
51
- if (program.needsUpdate) {
52
- program.compile();
53
- }
31
+ this.gl = gl;
32
+
33
+ this.state = {
34
+ activeTextureUnit: 0,
35
+ textureUnits: [],
36
+ flipY: false,
37
+ viewport: { width: 0, height: 0 },
38
+ pixelRatio,
39
+ width: 0,
40
+ height: 0,
41
+ };
42
+ this.gl.state = this.state;
43
+ }
44
+
45
+ render({
46
+ geometry,
47
+ program,
48
+ primitiveType = this.gl.TRIANGLES,
49
+ offset = 0,
50
+ count = 3,
51
+ }) {
52
+ if (program.needsUpdate) {
53
+ program.compile();
54
+ }
54
55
 
55
- this.gl.clear(this.gl.COLOR_BUFFER_BIT);
56
-
57
- this.gl.useProgram(program._program);
58
-
59
- for (let attributeName in program.attributesLocations) {
60
- let location = program.attributesLocations[attributeName];
61
- let buffer = geometry.buffers[attributeName];
62
-
63
- this.gl.enableVertexAttribArray(location);
64
- this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);
65
-
66
- const size = 2; // 2 components per iteration
67
- const type = this.gl.FLOAT; // the data is 32bit floats
68
- const normalize = false; // don't normalize the data
69
- const stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
70
- const bufferOffset = 0; // start at the beginning of the buffer
71
- this.gl.vertexAttribPointer(
72
- location,
73
- size,
74
- type,
75
- normalize,
76
- stride,
77
- bufferOffset
78
- );
79
- }
56
+ this.gl.clear(this.gl.COLOR_BUFFER_BIT);
57
+
58
+ this.gl.useProgram(program._program);
59
+
60
+ for (let attributeName in program.attributesLocations) {
61
+ let location = program.attributesLocations[attributeName];
62
+ let buffer = geometry.buffers[attributeName];
63
+
64
+ this.gl.enableVertexAttribArray(location);
65
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);
66
+
67
+ const size = 2; // 2 components per iteration
68
+ const type = this.gl.FLOAT; // the data is 32bit floats
69
+ const normalize = false; // don't normalize the data
70
+ const stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
71
+ const bufferOffset = 0; // start at the beginning of the buffer
72
+ this.gl.vertexAttribPointer(
73
+ location,
74
+ size,
75
+ type,
76
+ normalize,
77
+ stride,
78
+ bufferOffset
79
+ );
80
+ }
80
81
 
81
- let textureUnit = -1;
82
-
83
- for (let uniformName in program.uniforms) {
84
- let location = program.uniformsLocations[uniformName];
85
- if (location) {
86
- let uniform = program.uniforms[uniformName];
87
-
88
- if (uniform.type === 'float') {
89
- this.gl.uniform1f(location, uniform.value);
90
- } else if (uniform.type === 'vec2') {
91
- this.gl.uniform2f(location, uniform.value[0], uniform.value[1]);
92
- } else if (uniform.type === 'vec3') {
93
- this.gl.uniform3f(location, uniform.value[0], uniform.value[1], uniform.value[2]);
94
- } else if (uniform.type === 'vec4') {
95
- this.gl.uniform4f(location, uniform.value[0], uniform.value[1], uniform.value[2], uniform.value[3]);
96
- } else if (uniform.type === 'sampler2D') {
97
- if (uniform.value) {
98
- textureUnit = textureUnit + 1;
99
- uniform.value.update(textureUnit);
100
-
101
- this.gl.uniform1i(location, textureUnit);
102
- }
103
- }
104
- }
82
+ let textureUnit = -1;
83
+
84
+ for (let uniformName in program.uniforms) {
85
+ let location = program.uniformsLocations[uniformName];
86
+ if (location) {
87
+ let uniform = program.uniforms[uniformName];
88
+
89
+ if (uniform.type === "float") {
90
+ this.gl.uniform1f(location, uniform.value);
91
+ } else if (uniform.type === "vec2") {
92
+ this.gl.uniform2f(location, uniform.value[0], uniform.value[1]);
93
+ } else if (uniform.type === "vec3") {
94
+ this.gl.uniform3f(
95
+ location,
96
+ uniform.value[0],
97
+ uniform.value[1],
98
+ uniform.value[2]
99
+ );
100
+ } else if (uniform.type === "vec4") {
101
+ this.gl.uniform4f(
102
+ location,
103
+ uniform.value[0],
104
+ uniform.value[1],
105
+ uniform.value[2],
106
+ uniform.value[3]
107
+ );
108
+ } else if (uniform.type === "sampler2D") {
109
+ if (uniform.value) {
110
+ textureUnit = textureUnit + 1;
111
+ uniform.value.update(textureUnit);
112
+
113
+ this.gl.uniform1i(location, textureUnit);
114
+ }
105
115
  }
106
-
107
- this.gl.drawArrays(primitiveType, offset, count);
116
+ }
108
117
  }
109
118
 
110
- setPixelRatio(pixelRatio = this.state.pixelRatio) {
111
- if (this.state.pixelRatio !== pixelRatio) {
112
- this.state.pixelRatio = pixelRatio;
113
- this.setSize();
114
- }
119
+ this.gl.drawArrays(primitiveType, offset, count);
120
+ }
121
+
122
+ setPixelRatio(pixelRatio = this.state.pixelRatio) {
123
+ if (this.state.pixelRatio !== pixelRatio) {
124
+ this.state.pixelRatio = pixelRatio;
125
+ this.setSize();
115
126
  }
127
+ }
116
128
 
117
- setSize({ width = this.state.width, height = this.state.height } = {}) {
118
- this.state.width = width;
119
- this.state.height = height;
129
+ setSize({ width = this.state.width, height = this.state.height } = {}) {
130
+ this.state.width = width;
131
+ this.state.height = height;
120
132
 
121
- this.canvas.width = this.state.width * this.state.pixelRatio;
122
- this.canvas.height = this.state.height * this.state.pixelRatio;
133
+ this.canvas.width = this.state.width * this.state.pixelRatio;
134
+ this.canvas.height = this.state.height * this.state.pixelRatio;
123
135
 
124
- this.setViewport();
125
- }
136
+ this.setViewport();
137
+ }
126
138
 
127
- setViewport({ width = this.state.width, height = this.state.height } = {}) {
128
- let w = Math.floor(width * this.state.pixelRatio);
129
- let h = Math.floor(height * this.state.pixelRatio);
139
+ setViewport({ width = this.state.width, height = this.state.height } = {}) {
140
+ let w = Math.floor(width * this.state.pixelRatio);
141
+ let h = Math.floor(height * this.state.pixelRatio);
130
142
 
131
- if (this.state.viewport.width !== w || this.state.viewport.height !== h) {
132
- this.gl.viewport(0, 0, w, h);
143
+ if (this.state.viewport.width !== w || this.state.viewport.height !== h) {
144
+ this.gl.viewport(0, 0, w, h);
133
145
 
134
- this.state.viewport.width = w;
135
- this.state.viewport.height = h;
136
- }
146
+ this.state.viewport.width = w;
147
+ this.state.viewport.height = h;
137
148
  }
149
+ }
138
150
 
139
- destroy() {
140
- let extension = this.gl.getExtension('WEBGL_lose_context');
151
+ destroy() {
152
+ let extension = this.gl.getExtension("WEBGL_lose_context");
141
153
 
142
- if (extension) {
143
- extension.loseContext();
144
- }
154
+ if (extension) {
155
+ extension.loseContext();
145
156
  }
157
+ }
146
158
  }
147
159
 
148
160
  export default Renderer;
@@ -20,7 +20,7 @@ export const exports = createStore(`exports`, {
20
20
  imageQuality: 100,
21
21
  videoQuality: 100,
22
22
  }, {
23
- persist: !__PRODUCTION__,
23
+ persist: !__BUILD__,
24
24
  reset: false,
25
25
  });
26
26
 
@@ -3,7 +3,7 @@ import { getStore, createStore } from "./utils";
3
3
  import { onMount } from "svelte";
4
4
 
5
5
  export const tree = getStore("layout.current", {}, {
6
- persist: !__PRODUCTION__
6
+ persist: !__BUILD__
7
7
  });
8
8
 
9
9
  export const layout = createStore('layout', {
@@ -1,16 +1,16 @@
1
1
  import { createStore } from "./utils";
2
2
 
3
3
  export const multisampling = createStore("multisampling", [], {
4
- persist: !__PRODUCTION__,
4
+ persist: !__BUILD__,
5
5
  reset: true,
6
6
  });
7
7
 
8
8
  export const threshold = createStore("threshold", 0, {
9
- persist: !__PRODUCTION__,
9
+ persist: !__BUILD__,
10
10
  reset: false,
11
11
  });
12
12
 
13
13
  export const transition = createStore("transition", false, {
14
- persist: !__PRODUCTION__,
14
+ persist: !__BUILD__,
15
15
  reset: false,
16
16
  });