fragment-tools 0.1.11 → 0.1.12

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/bin/index.js CHANGED
@@ -11,6 +11,7 @@ sade('fragment [entry]')
11
11
  .option('-p, --port', 'Port to bind', 3000)
12
12
  .option('-dev, --development', 'Enable development mode', false)
13
13
  .option('-b, --build', 'Build sketch for production', false)
14
+ .option('--exportDir', 'Directory used for exports', process.cwd())
14
15
  .option('--outDir', 'Directory used for static build', null)
15
16
  .option('--emptyOutDir', "Empty outDir before static build", false)
16
17
  .action((entry, options) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fragment-tools",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "A web development environment for creative coding",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -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
@@ -62,7 +62,7 @@ export async function start({ options, filepaths, entries, fragment }) {
62
62
  }
63
63
  },
64
64
  dbPlugin(),
65
- screenshotPlugin({ cwd }),
65
+ screenshotPlugin({ cwd, exportDir: options.exportDir }),
66
66
  checkDependencies({
67
67
  cwd,
68
68
  app,
@@ -83,7 +83,9 @@ export async function start({ options, filepaths, entries, fragment }) {
83
83
  '__FRAGMENT_PORT__': fragment.server ? fragment.server.port : undefined,
84
84
  '__START_TIME__': Date.now(),
85
85
  '__SEED__': Date.now(),
86
- '__PRODUCTION__': options.build,
86
+ '__BUILD__': options.build,
87
+ '__DEV__': !options.build,
88
+
87
89
  },
88
90
  optimizeDeps: {
89
91
  include: ['convert-length', 'webm-writer', 'changedpi'],
@@ -128,18 +130,7 @@ export async function start({ options, filepaths, entries, fragment }) {
128
130
 
129
131
  await server.listen();
130
132
  log.success(`Server started at:`);
131
-
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
- })
133
+ server.printUrls();
143
134
 
144
135
  return server;
145
136
  }
@@ -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
 
@@ -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
  });
@@ -2,7 +2,7 @@ import { rendering } from "./rendering";
2
2
 
3
3
  export let renderers = {};
4
4
 
5
- function loadRenderer(renderingMode = "2d") {
5
+ function loadRenderer(renderingMode) {
6
6
  if (__THREE_RENDERER__ && renderingMode === "three") {
7
7
  return import("../renderers/THREERenderer.js");
8
8
  }
@@ -20,7 +20,7 @@ function loadRenderer(renderingMode = "2d") {
20
20
  }
21
21
  }
22
22
 
23
- export async function findRenderer(renderingMode = "2d") {
23
+ export async function findRenderer(renderingMode) {
24
24
  if (renderers[renderingMode]) return renderers[renderingMode];
25
25
 
26
26
  // load and save
@@ -30,31 +30,33 @@ export async function findRenderer(renderingMode = "2d") {
30
30
  let renderer = renderers[renderingMode];
31
31
  let initialized = false;
32
32
 
33
- rendering.subscribe((current) => {
34
- let r;
33
+ if (renderer) {
34
+ rendering.subscribe((current) => {
35
+ let r;
36
+
37
+ if (!initialized) {
38
+ if (typeof renderer.init === "function") {
39
+ r = renderer.init({
40
+ canvas: document.createElement('canvas'),
41
+ pixelRatio: current.pixelRatio,
42
+ width: current.width,
43
+ height: current.height,
44
+ });
45
+ }
46
+ }
35
47
 
36
- if (!initialized) {
37
- if (typeof renderer.init === "function") {
38
- r = renderer.init({
39
- canvas: document.createElement('canvas'),
40
- pixelRatio: current.pixelRatio,
48
+ initialized = true;
49
+
50
+ if (typeof renderer.resize === "function") {
51
+ renderer.resize({
41
52
  width: current.width,
42
53
  height: current.height,
54
+ pixelRatio: current.pixelRatio,
55
+ ...r,
43
56
  });
44
57
  }
45
- }
46
-
47
- initialized = true;
48
-
49
- if (typeof renderer.resize === "function") {
50
- renderer.resize({
51
- width: current.width,
52
- height: current.height,
53
- pixelRatio: current.pixelRatio,
54
- ...r,
55
- });
56
- }
57
- });
58
+ });
59
+ }
58
60
 
59
61
  return renderer;
60
62
  }
@@ -20,7 +20,7 @@ export const rendering = createStore(`rendering`, {
20
20
  scale: 1,
21
21
  preset: 'a4',
22
22
  }, {
23
- persist: !__PRODUCTION__,
23
+ persist: !__BUILD__,
24
24
  reset: false,
25
25
  });
26
26
 
@@ -3,7 +3,7 @@ import { displayError } from "../stores/errors";
3
3
  import { sketches as all, onSketchReload } from "@fragment/sketches";
4
4
 
5
5
  export const sketches = createStore('sketches', {});
6
- export const sketchesKeys = createStore('sketchesKeys', []);
6
+ export const sketchesKeys = createStore('sketchesKeys', Object.keys(all));
7
7
  export const sketchesCount = createStore('sketchesCount', 0);
8
8
 
9
9
  async function loadSketch(collection, key) {
@@ -61,7 +61,7 @@ function createTrigger(eventName, collection) {
61
61
  const { hot, enabled, ...params } = options;
62
62
  const context = getContext();
63
63
 
64
- const keys = Array.isArray(key) ? key : [...key.split(',').map(k => k.trim())];
64
+ const keys = Array.isArray(key) ? key : [...key.split(',').map(k => k !== " " ? k.trim() : k)];
65
65
 
66
66
  const trigger = new Trigger({
67
67
  inputType: 'Keyboard',
@@ -22,37 +22,40 @@ let defaultGUIConfig = {
22
22
  };
23
23
 
24
24
  let guiConfig = defaultGUIConfig;
25
- $: sketchKey = ($layout.previewing && $preview) ? $preview : sketchesKeys[0];
25
+ $: sketchKey = ($layout.previewing && $preview) ? $preview : $sketchesKeys[0];
26
26
  $: sketch = $sketches[sketchKey];
27
27
 
28
28
  $: {
29
- if (sketch.buildConfig) {
30
- override(sketch.buildConfig);
31
- }
29
+ if (sketch) {
30
+ if (sketch.buildConfig) {
31
+ override(sketch.buildConfig);
32
+ }
32
33
 
33
- const config = sketch.buildConfig ? sketch.buildConfig : {};
34
- gui = config.gui;
34
+ const config = sketch.buildConfig ? sketch.buildConfig : {};
35
+ gui = config.gui;
35
36
 
36
- if (gui && typeof gui === "object") {
37
- guiConfig = {
38
- ...defaultGUIConfig,
39
- ...gui,
40
- };
41
- }
37
+ if (gui && typeof gui === "object") {
38
+ guiConfig = {
39
+ ...defaultGUIConfig,
40
+ ...gui,
41
+ };
42
+ }
43
+
44
+ const { styles = "" } = config;
42
45
 
43
- const { styles = "" } = config;
46
+ if (styles !== "") {
47
+ head = document.getElementsByTagName('head')[0];
44
48
 
45
- if (styles !== "") {
46
- head = document.getElementsByTagName('head')[0];
49
+ if (style) {
50
+ head.removeChild(style);
51
+ }
47
52
 
48
- if (style) {
49
- head.removeChild(style);
53
+ style = document.createElement('style');
54
+ style.setAttribute('type', 'text/css');
55
+ style.appendChild(document.createTextNode(styles));
56
+ head.appendChild(style);
50
57
  }
51
58
 
52
- style = document.createElement('style');
53
- style.setAttribute('type', 'text/css');
54
- style.appendChild(document.createTextNode(styles));
55
- head.appendChild(style);
56
59
  }
57
60
  }
58
61
 
@@ -23,7 +23,7 @@ function togglePreview() {
23
23
  </script>
24
24
 
25
25
  <Root>
26
- {#if __PRODUCTION__ || $layout.previewing }
26
+ {#if __BUILD__ || $layout.previewing }
27
27
  <Build />
28
28
  {:else}
29
29
  <Row size={1}>
@@ -41,7 +41,7 @@ function togglePreview() {
41
41
  </Row>
42
42
  {/if}
43
43
  </Root>
44
- {#if !__PRODUCTION__}
44
+ {#if !__BUILD__}
45
45
  <KeyBinding key="w" on:trigger={toggleEdition} />
46
46
  <KeyBinding key="p" on:trigger={togglePreview} />
47
47
  {/if}
@@ -82,7 +82,7 @@ if (parent) {
82
82
  parent.registerChild(current);
83
83
  }
84
84
 
85
- if (!__PRODUCTION__) {
85
+ if (!__BUILD__) {
86
86
  $layout.registerChild(current, () => $children);
87
87
  }
88
88
 
@@ -25,6 +25,7 @@ let node, container;
25
25
  let framerate = 60;
26
26
  let elapsed = 0;
27
27
  let elapsedRenderingTime = 0;
28
+ let now = performance.now(), then = performance.now(), dt = 0, lastTime = performance.now();
28
29
  let canvas;
29
30
  let _raf;
30
31
  let _key = key;
@@ -135,7 +136,7 @@ function destroyCanvas(canvas) {
135
136
 
136
137
  function setBackgroundColor() {
137
138
  if (sketch) {
138
- if (($layout.previewing || __PRODUCTION__) && sketch.buildConfig && sketch.buildConfig.backgroundColor) {
139
+ if (($layout.previewing || __BUILD__) && sketch.buildConfig && sketch.buildConfig.backgroundColor) {
139
140
  backgroundColor = sketch.buildConfig.backgroundColor;
140
141
  } else if (!$layout.previewing && sketch.backgroundColor) {
141
142
  backgroundColor = sketch.backgroundColor;
@@ -190,7 +191,7 @@ async function createSketch(key) {
190
191
 
191
192
  let mountParams = {};
192
193
 
193
- if (typeof renderer.onMountPreview === "function") {
194
+ if (renderer && typeof renderer.onMountPreview === "function") {
194
195
  mountParams = renderer.onMountPreview({
195
196
  id,
196
197
  canvas,
@@ -329,29 +330,27 @@ function createRenderLoop() {
329
330
  let playcount = 0;
330
331
  let hasDuration = isFinite(duration);
331
332
 
332
- let onBeforeUpdatePreview = renderer.onBeforeUpdatePreview || noop;
333
- let onAfterUpdatePreview = renderer.onAfterUpdatePreview || noop;
333
+ let onBeforeUpdatePreview = (renderer && renderer.onBeforeUpdatePreview) || noop;
334
+ let onAfterUpdatePreview = (renderer && renderer.onAfterUpdatePreview) || noop;
334
335
 
335
336
  let frameLength = 1000 / framerate;
336
337
  let frameCount = framerate * duration;
337
338
  let interval = 1 / frameCount;
338
339
 
339
- let then = $currentTime.time;
340
-
341
- return ({ time = $currentTime.time, deltaTime = time - then } = {}) => {
340
+ return ({ time = performance.now(), deltaTime = time - lastTime } = {}) => {
342
341
  needsRender = false;
343
- then = time;
342
+ lastTime = time;
344
343
 
345
344
  try {
346
345
  onBeforeUpdatePreview({ id, canvas });
347
346
 
348
- let t = !$sync ? time : Math.floor(time / frameLength) * frameLength;
347
+ let t = !$sync ? elapsedRenderingTime : Math.floor(time / frameLength) * frameLength;
349
348
 
350
349
  if (hasDuration && framerate > 0) {
351
350
  playhead = (t / 1000) / duration;
352
351
  playhead %= 1;
353
352
  playhead = Math.floor(playhead / interval) * interval;
354
- playcount = Math.floor(((((elapsedRenderingTime) / 1000)) / duration));
353
+ playcount = Math.floor(elapsedRenderingTime / 1000 / duration);
355
354
  }
356
355
 
357
356
  draw({
@@ -360,30 +359,30 @@ function createRenderLoop() {
360
359
  props: sketch.props,
361
360
  playhead,
362
361
  playcount,
363
- width: width * pixelRatio,
364
- height: height * pixelRatio,
362
+ width,
363
+ height,
365
364
  pixelRatio,
366
365
  time: t,
367
366
  deltaTime,
368
367
  });
369
368
  onAfterUpdatePreview({ id, canvas });
369
+
370
+ elapsedRenderingTime += deltaTime;
370
371
  } catch(error) {
371
372
  onError(error);
372
373
  }
373
374
  };
374
375
  }
375
376
 
376
- let then = performance.now();
377
-
378
377
  function render() {
379
378
  _raf = requestAnimationFrame(render);
380
379
 
381
- if (!paused) {
382
- let now = performance.now();
383
- let deltaTime = now - then;
384
- then = now;
380
+ now = performance.now();
381
+ dt = now - then;
382
+ then = now;
385
383
 
386
- elapsed += deltaTime;
384
+ if (!paused) {
385
+ elapsed += dt;
387
386
 
388
387
  if (!$sync) {
389
388
  if ((elapsed) >= ((1 / framerate) * 1000)) {
@@ -393,8 +392,8 @@ function render() {
393
392
  } else {
394
393
  _renderSketch();
395
394
  }
396
-
397
- elapsedRenderingTime += deltaTime;
395
+ } else {
396
+ lastTime = now;
398
397
  }
399
398
 
400
399
  if (needsRender) {
@@ -440,6 +439,8 @@ rendering.subscribe((current) => {
440
439
  async function save() {
441
440
  paused = true;
442
441
 
442
+ _renderSketch();
443
+
443
444
  await screenshotCanvas(canvas, {
444
445
  filename: key,
445
446
  pattern: sketch?.filenamePattern,
@@ -480,8 +481,13 @@ function checkForPause(event) {
480
481
 
481
482
  if (!keyboardEvent.metaKey || !keyboardEvent.ctrlKey) {
482
483
  keyboardEvent.preventDefault();
483
- paused = !paused;
484
- then = Date.now();
484
+
485
+ if (!$recording) {
486
+ then = performance.now();
487
+ paused = !paused;
488
+ } else {
489
+ console.warn(`Cannot pause while recording.`);
490
+ }
485
491
  }
486
492
  }
487
493
 
@@ -490,7 +496,12 @@ function checkForSave(event) {
490
496
 
491
497
  if (keyboardEvent.metaKey || keyboardEvent.ctrlKey) {
492
498
  keyboardEvent.preventDefault();
493
- save();
499
+
500
+ if (!$recording) {
501
+ save();
502
+ } else {
503
+ console.warn(`Cannot save while recording.`);
504
+ }
494
505
  }
495
506
  }
496
507
 
@@ -17,6 +17,7 @@ $: alpha = 1;
17
17
  $: hasAlpha = [
18
18
  color.FORMATS.RGBA_STRING,
19
19
  color.FORMATS.VEC4_STRING,
20
+ color.FORMATS.VEC4_ARRAY,
20
21
  color.FORMATS.RGBA_OBJECT,
21
22
  color.FORMATS.HSLA_STRING
22
23
  ].includes(format);
@@ -34,6 +35,19 @@ function dispatchChange() {
34
35
 
35
36
  // support THREE.Color
36
37
  switch (format) {
38
+ case color.FORMATS.VEC3_ARRAY:
39
+ value[0] = r;
40
+ value[1] = g;
41
+ value[2] = b;
42
+ dispatch('change', value);
43
+ break;
44
+ case color.FORMATS.VEC4_ARRAY:
45
+ value[0] = r;
46
+ value[1] = g;
47
+ value[2] = b;
48
+ value[3] = alpha;
49
+ dispatch('change', value);
50
+ break;
37
51
  case color.FORMATS.THREE:
38
52
  case color.FORMATS.RGB_OBJECT:
39
53
  value.r = r;
@@ -36,7 +36,9 @@ function onDrag(event) {
36
36
  let v = clamp(map(event.clientX, rect.left, rect.right, min, max), min, max);
37
37
  v = Math.floor(v * (1 / step)) / (1 / step);
38
38
 
39
- dispatch("change", v);
39
+ if (v !== value) {
40
+ dispatch("change", v);
41
+ }
40
42
  }
41
43
 
42
44
  function handleMouseUp() {
@@ -11,34 +11,62 @@ export let context = null;
11
11
  export let key = "";
12
12
 
13
13
  let node;
14
- let sanitizedOptions = [];
14
+ let sanitizedValue, sanitizedOptions = [];
15
15
 
16
16
  const dispatch = createEventDispatcher();
17
17
 
18
+ function toStringifiedValue(option, optionType = typeof option) {
19
+ if (option === null) {
20
+ return `null`;
21
+ } else if (option === undefined) {
22
+ return `undefined`;
23
+ } else if (optionType === "object") {
24
+ return toStringifiedValue(option.value);
25
+ } else if (optionType === "function") {
26
+ return option.name;
27
+ }
28
+
29
+ return option.toString();
30
+ }
31
+
18
32
  $: {
19
33
  sanitizedOptions = [];
20
34
 
21
35
  for (let i = 0; i < options.length; i++) {
22
- const { value, label, disabled } = options[i];
23
-
24
- if (["number", "string"].includes(typeof options[i])) {
25
- sanitizedOptions[i] = {
26
- value: options[i],
27
- label: options[i],
28
- disabled,
29
- };
36
+ let option = options[i];
37
+ let optionType = typeof option;
38
+ let disabled = (optionType === "object" && typeof option.disabled === "boolean") ? option.disabled : false;
39
+ let _value = optionType === "object" ? option.value : option;
40
+
41
+ let stringifiedValue = toStringifiedValue(option);
42
+ let label;
43
+
44
+ if (_value === value) {
45
+ sanitizedValue = stringifiedValue;
46
+ }
47
+
48
+ if (option.label) {
49
+ label = option.label;
30
50
  } else {
31
- sanitizedOptions[i] = {
32
- value: value,
33
- label: label ? label : value,
34
- disabled,
35
- }
51
+ label = stringifiedValue;
36
52
  }
53
+
54
+ sanitizedOptions[i] = {
55
+ label,
56
+ value: stringifiedValue,
57
+ disabled
58
+ };
37
59
  }
38
60
  }
39
61
 
40
62
  function handleChange(event) {
41
- dispatch("change", event.currentTarget.value);
63
+ const index = sanitizedOptions.findIndex((opt) => opt.value === event.currentTarget.value);
64
+ const option = options[index]
65
+ const newValue = typeof option === "object" ? option.value : option;
66
+
67
+ value = newValue;
68
+
69
+ dispatch("change", newValue);
42
70
  }
43
71
 
44
72
  </script>
@@ -49,9 +77,9 @@ function handleChange(event) {
49
77
  class:single={sanitizedOptions.length === 1}
50
78
  >
51
79
  <div class="container">
52
- <select class="select" bind:this={node} on:change={handleChange} {name} {disabled} {title} bind:value={value}>
80
+ <select class="select" bind:this={node} on:change={handleChange} {name} {disabled} {title} bind:value={sanitizedValue}>
53
81
  {#each sanitizedOptions as option}
54
- <option value={option.value} selected={value === option.value} disabled={option.disabled}>{option.label}</option>
82
+ <option value={option.value} selected={sanitizedValue === option.value} disabled={option.disabled}>{option.label}</option>
55
83
  {/each}
56
84
  </select>
57
85
  {#if sanitizedOptions.length > 1 }
@@ -15,11 +15,7 @@ import { exportCanvas } from "../lib/canvas-recorder/utils";
15
15
  import { map } from "./math.utils";
16
16
 
17
17
  export async function saveDataURL(dataURL, options, blob) {
18
- async function onError(err) {
19
- if (typeof options.onError === "function") {
20
- options.onError(err);
21
- }
22
-
18
+ async function saveInBrowser() {
23
19
  if (!blob) {
24
20
  blob = await createBlobFromDataURL(dataURL);
25
21
  }
@@ -27,25 +23,37 @@ export async function saveDataURL(dataURL, options, blob) {
27
23
  await downloadBlob(blob, options);
28
24
  }
29
25
 
26
+ async function onError(err) {
27
+ if (typeof options.onError === "function") {
28
+ options.onError(err);
29
+ }
30
+
31
+ await saveInBrowser();
32
+ }
33
+
30
34
  try {
31
- const body = {
32
- dataURL: dataURL.split(',')[1], // remove extension,
33
- ...options,
34
- };
35
- const response = await fetch('/save', {
36
- method: "POST",
37
- body: JSON.stringify(body),
38
- headers: {
39
- 'Accept': 'application/json',
40
- 'Content-Type': 'application/json'
41
- },
42
- });
43
- const { filepath, error } = await response.json();
35
+ if (__DEV__) {
36
+ const body = {
37
+ dataURL: dataURL.split(',')[1], // remove extension,
38
+ ...options,
39
+ };
40
+ const response = await fetch('/save', {
41
+ method: "POST",
42
+ body: JSON.stringify(body),
43
+ headers: {
44
+ 'Accept': 'application/json',
45
+ 'Content-Type': 'application/json'
46
+ },
47
+ });
48
+ const { filepath, error } = await response.json();
44
49
 
45
- if (response.ok && filepath) {
46
- console.log(`[fragment] Saved ${filepath}`);
50
+ if (response.ok && filepath) {
51
+ console.log(`[fragment] Saved ${filepath}`);
52
+ } else {
53
+ onError(error);
54
+ }
47
55
  } else {
48
- onError(error);
56
+ await saveInBrowser();
49
57
  }
50
58
  } catch(error) {
51
59
  onError(error);
@@ -8,6 +8,8 @@ export const FORMATS = {
8
8
  RGBA_OBJECT: "rgba-object",
9
9
  VEC3_STRING: "vec3-string",
10
10
  VEC4_STRING: "vec4-string",
11
+ VEC3_ARRAY: "vec3-array",
12
+ VEC4_ARRAY: "vec4-array",
11
13
  THREE: "three",
12
14
  CSS_COLOR: "css-color",
13
15
  };
@@ -22,7 +24,8 @@ export function toHex(color, format = getColorFormat(color)) {
22
24
  if (format === FORMATS.HSL_STRING || format === FORMATS.HSLA_STRING) return hslToHex(color);
23
25
  if (format === FORMATS.RGB_STRING || format === FORMATS.RGBA_STRING) return stringToHex(color);
24
26
  if (format === FORMATS.RGB_OBJECT || format === FORMATS.RGBA_OBJECT) return componentsToHex([color.r, color.g, color.b, color.a]);
25
- if (format === FORMATS.VEC3_STRING || format === FORMATS.VEC4_STRING) return vecToHex(color);
27
+ if (format === FORMATS.VEC3_STRING || format === FORMATS.VEC4_STRING) return vecStringToHex(color);
28
+ if (format === FORMATS.VEC3_ARRAY || format === FORMATS.VEC4_ARRAY) return vecArrayToHex(color);
26
29
  if (format === FORMATS.CSS_COLOR) return nameToHex(color);
27
30
  }
28
31
 
@@ -36,7 +39,7 @@ export function toComponents(color, format = getColorFormat(color)) {
36
39
  if (format === FORMATS.HSL_STRING || format === FORMATS.HSLA_STRING) return hslToComponents(color);
37
40
  if (format === FORMATS.RGB_STRING || format === FORMATS.RGBA_STRING) return stringToComponents(color);
38
41
  if (format === FORMATS.RGB_OBJECT || format === FORMATS.RGBA_OBJECT) return [color.r, color.g, color.b, isFinite(color.a) ? color.a : 1];
39
- if (format === FORMATS.VEC3_STRING || format === FORMATS.VEC4_STRING) return vecToComponents(color);
42
+ if (format === FORMATS.VEC3_STRING || format === FORMATS.VEC4_STRING) return vecStringToComponents(color);
40
43
  if (format === FORMATS.CSS_COLOR) return nameToComponents(color);
41
44
  }
42
45
 
@@ -55,6 +58,8 @@ export function toString(color, format = getColorFormat(color)) {
55
58
  format === FORMATS.VEC4_STRING ||
56
59
  format === FORMATS.CSS_COLOR
57
60
  ) return color;
61
+ if (format === FORMATS.VEC3_ARRAY) return componentsToRGBString(vecArrayToComponents(color));
62
+ if (format === FORMATS.VEC4_ARRAY) return componentsToRGBAString(vecArrayToComponents(color));
58
63
  if (format === FORMATS.THREE) return threeToHex(color);
59
64
  if (format === FORMATS.RGB_OBJECT) return componentsToRGBString([color.r, color.g, color.b])
60
65
  if (format === FORMATS.RGBA_OBJECT) return componentsToRGBAString([color.r, color.g, color.b, color.a ? color.a : 1]);
@@ -119,7 +124,7 @@ export function stringToHex(color) {
119
124
  return componentsToHex(stringToComponents(color));
120
125
  }
121
126
 
122
- export function vecToComponents(color) {
127
+ export function vecStringToComponents(color) {
123
128
  const match = color.match(/vec[3-4]?\((\d*(?:\.\d*?)), ?(\d*(?:\.\d*?)), ?(\d*(?:\.\d*))?(?:, ?(\d*(?:\.?\d*?))\))?/);
124
129
 
125
130
  if (match) {
@@ -130,13 +135,33 @@ export function vecToComponents(color) {
130
135
  match[4] ? Math.min(1, Math.max(Number(match[4]), 0)) : 1];
131
136
  }
132
137
 
133
- console.error(`color.vecToComponents :: cannot parse color`, color);
138
+ console.error(`color.vecStringToComponents :: cannot parse color`, color);
134
139
 
135
140
  return [];
136
141
  }
137
142
 
138
- export function vecToHex(color) {
139
- return componentsToHex(vecToComponents(color));
143
+ export function vecArrayToComponents(color) {
144
+ if (color.every((c) => isFinite(c))) {
145
+ const [r, g, b, a = 1] = color;
146
+ return [
147
+ r,
148
+ g,
149
+ b,
150
+ a
151
+ ];
152
+ }
153
+
154
+ console.error(`color.vecArrayToComponents :: cannot parse color`, color);
155
+
156
+ return [];
157
+ }
158
+
159
+ export function vecStringToHex(color) {
160
+ return componentsToHex(vecStringToComponents(color));
161
+ }
162
+
163
+ export function vecArrayToHex(color) {
164
+ return componentsToHex(vecArrayToComponents(color));
140
165
  }
141
166
 
142
167
  // https://stackoverflow.com/questions/39118528/rgb-to-hsl-conversion
@@ -436,6 +461,23 @@ export function isRGBObject(value) {
436
461
  return false;
437
462
  }
438
463
 
464
+
465
+ export function isVec3Array(value) {
466
+ if (Array.isArray(value)) {
467
+ return value.length == 3 && value.every(c => c >= 0 && c <= 1);
468
+ }
469
+
470
+ return false;
471
+ }
472
+
473
+ export function isVec4Array(value) {
474
+ if (Array.isArray(value)) {
475
+ return value.length == 4 && value.every(c => c >= 0 && c <= 1);
476
+ }
477
+
478
+ return false;
479
+ }
480
+
439
481
  export function isCSSColor(value, isString = typeof value === "string") {
440
482
  const components = isString && nameToComponents(value);
441
483
 
@@ -480,6 +522,8 @@ export function getColorFormat(value) {
480
522
  if (isRGBAObject(value)) return FORMATS.RGBA_OBJECT;
481
523
  if (isVec3String(value)) return FORMATS.VEC3_STRING;
482
524
  if (isVec4String(value)) return FORMATS.VEC4_STRING;
525
+ if (isVec3Array(value)) return FORMATS.VEC3_ARRAY;
526
+ if (isVec4Array(value)) return FORMATS.VEC4_ARRAY;
483
527
  if (isHSLAString(value)) return FORMATS.HSLA_STRING;
484
528
  if (isHSLString(value)) return FORMATS.HSL_STRING;
485
529
  if (isCSSColor(value)) return FORMATS.CSS_COLOR;
@@ -1,5 +1,5 @@
1
1
  export function createBlobFromDataURL(dataURL) {
2
- return new Promise((resolve) => {
2
+ return new Promise((resolve, reject) => {
3
3
  const splitIndex = dataURL.indexOf(',');
4
4
 
5
5
  if (splitIndex === -1) {