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 +1 -0
- package/package.json +1 -1
- package/src/cli/plugins/screenshot.js +25 -10
- package/src/cli/server.js +5 -14
- package/src/cli/templates/blank.js +13 -0
- package/src/cli/templates/index.js +3 -0
- package/src/client/app/lib/canvas-recorder/CanvasRecorder.js +4 -4
- package/src/client/app/stores/exports.js +1 -1
- package/src/client/app/stores/layout.js +1 -1
- package/src/client/app/stores/multisampling.js +3 -3
- package/src/client/app/stores/renderers.js +24 -22
- package/src/client/app/stores/rendering.js +1 -1
- package/src/client/app/stores/sketches.js +1 -1
- package/src/client/app/triggers/Keyboard.js +1 -1
- package/src/client/app/ui/Build.svelte +24 -21
- package/src/client/app/ui/Layout.svelte +2 -2
- package/src/client/app/ui/LayoutComponent.svelte +1 -1
- package/src/client/app/ui/SketchRenderer.svelte +35 -24
- package/src/client/app/ui/fields/ColorInput.svelte +14 -0
- package/src/client/app/ui/fields/ProgressInput.svelte +3 -1
- package/src/client/app/ui/fields/Select.svelte +45 -17
- package/src/client/app/utils/canvas.utils.js +29 -21
- package/src/client/app/utils/color.utils.js +50 -6
- package/src/client/app/utils/file.utils.js +1 -1
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,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(
|
|
18
|
+
const filepath = path.join(dir, filename);
|
|
15
19
|
const buffer = Buffer.from(dataURL, 'base64');
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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(
|
|
22
|
-
res.end(JSON.stringify(
|
|
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
|
-
'
|
|
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
|
}
|
|
@@ -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,16 +1,16 @@
|
|
|
1
1
|
import { createStore } from "./utils";
|
|
2
2
|
|
|
3
3
|
export const multisampling = createStore("multisampling", [], {
|
|
4
|
-
persist: !
|
|
4
|
+
persist: !__BUILD__,
|
|
5
5
|
reset: true,
|
|
6
6
|
});
|
|
7
7
|
|
|
8
8
|
export const threshold = createStore("threshold", 0, {
|
|
9
|
-
persist: !
|
|
9
|
+
persist: !__BUILD__,
|
|
10
10
|
reset: false,
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
export const transition = createStore("transition", false, {
|
|
14
|
-
persist: !
|
|
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
|
|
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
|
|
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
|
-
|
|
34
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
}
|
|
@@ -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
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
if (sketch) {
|
|
30
|
+
if (sketch.buildConfig) {
|
|
31
|
+
override(sketch.buildConfig);
|
|
32
|
+
}
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
const config = sketch.buildConfig ? sketch.buildConfig : {};
|
|
35
|
+
gui = config.gui;
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
if (gui && typeof gui === "object") {
|
|
38
|
+
guiConfig = {
|
|
39
|
+
...defaultGUIConfig,
|
|
40
|
+
...gui,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const { styles = "" } = config;
|
|
42
45
|
|
|
43
|
-
|
|
46
|
+
if (styles !== "") {
|
|
47
|
+
head = document.getElementsByTagName('head')[0];
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
if (style) {
|
|
50
|
+
head.removeChild(style);
|
|
51
|
+
}
|
|
47
52
|
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
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 !
|
|
44
|
+
{#if !__BUILD__}
|
|
45
45
|
<KeyBinding key="w" on:trigger={toggleEdition} />
|
|
46
46
|
<KeyBinding key="p" on:trigger={togglePreview} />
|
|
47
47
|
{/if}
|
|
@@ -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
|
|
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
|
-
|
|
340
|
-
|
|
341
|
-
return ({ time = $currentTime.time, deltaTime = time - then } = {}) => {
|
|
340
|
+
return ({ time = performance.now(), deltaTime = time - lastTime } = {}) => {
|
|
342
341
|
needsRender = false;
|
|
343
|
-
|
|
342
|
+
lastTime = time;
|
|
344
343
|
|
|
345
344
|
try {
|
|
346
345
|
onBeforeUpdatePreview({ id, canvas });
|
|
347
346
|
|
|
348
|
-
let t = !$sync ?
|
|
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(
|
|
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
|
|
364
|
-
height
|
|
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
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
then = now;
|
|
380
|
+
now = performance.now();
|
|
381
|
+
dt = now - then;
|
|
382
|
+
then = now;
|
|
385
383
|
|
|
386
|
-
|
|
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
|
-
|
|
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
|
-
|
|
484
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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={
|
|
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={
|
|
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
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
46
|
-
|
|
50
|
+
if (response.ok && filepath) {
|
|
51
|
+
console.log(`[fragment] Saved ${filepath}`);
|
|
52
|
+
} else {
|
|
53
|
+
onError(error);
|
|
54
|
+
}
|
|
47
55
|
} else {
|
|
48
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
138
|
+
console.error(`color.vecStringToComponents :: cannot parse color`, color);
|
|
134
139
|
|
|
135
140
|
return [];
|
|
136
141
|
}
|
|
137
142
|
|
|
138
|
-
export function
|
|
139
|
-
|
|
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;
|