fragment-tools 0.1.13 → 0.1.14
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 +2 -2
- package/package.json +4 -5
- package/src/cli/log.js +31 -21
- package/src/cli/plugins/check-dependencies.js +47 -30
- package/src/cli/plugins/hot-shader-replacement.js +384 -0
- package/src/cli/plugins/hot-sketch-reload.js +3 -13
- package/src/cli/plugins/screenshot.js +57 -20
- package/src/cli/server.js +144 -133
- package/src/client/app/App.svelte +3 -3
- package/src/client/app/client.js +55 -39
- package/src/client/app/components/Init.svelte +12 -9
- package/src/client/app/helpers.js +42 -0
- package/src/client/app/hooks.js +20 -0
- package/src/client/app/inputs/Keyboard.js +13 -15
- package/src/client/app/inputs/MIDI.js +14 -15
- package/src/client/app/lib/canvas-recorder/CanvasRecorder.js +41 -21
- package/src/client/app/lib/gl/Renderer.js +127 -139
- package/src/client/app/modules/Exports.svelte +62 -43
- package/src/client/app/modules/MidiPanel.svelte +100 -101
- package/src/client/app/modules/Params.svelte +116 -103
- package/src/client/app/renderers/2DRenderer.js +3 -3
- package/src/client/app/renderers/FragmentRenderer.js +30 -23
- package/src/client/app/renderers/P5Renderer.js +10 -7
- package/src/client/app/renderers/THREERenderer.js +136 -94
- package/src/client/app/stores/exports.js +36 -20
- package/src/client/app/stores/props.js +28 -5
- package/src/client/app/stores/renderers.js +22 -15
- package/src/client/app/stores/sketches.js +7 -9
- package/src/client/app/stores/utils.js +95 -38
- package/src/client/app/triggers/Keyboard.js +88 -79
- package/src/client/app/triggers/MIDI.js +110 -84
- package/src/client/app/ui/Field.svelte +343 -240
- package/src/client/app/ui/FieldGroup.svelte +106 -94
- package/src/client/app/ui/FieldSection.svelte +125 -116
- package/src/client/app/ui/ParamsMultisampling.svelte +96 -95
- package/src/client/app/ui/ParamsOutput.svelte +113 -113
- package/src/client/app/ui/SelectChevrons.svelte +27 -15
- package/src/client/app/ui/SketchRenderer.svelte +761 -667
- package/src/client/app/ui/fields/ButtonInput.svelte +61 -48
- package/src/client/app/ui/fields/CheckboxInput.svelte +67 -61
- package/src/client/app/ui/fields/ColorInput.svelte +294 -238
- package/src/client/app/ui/fields/ImageInput.svelte +123 -121
- package/src/client/app/ui/fields/Input.svelte +100 -111
- package/src/client/app/ui/fields/ListInput.svelte +96 -96
- package/src/client/app/ui/fields/NumberInput.svelte +121 -116
- package/src/client/app/ui/fields/ProgressInput.svelte +80 -73
- package/src/client/app/ui/fields/Select.svelte +137 -124
- package/src/client/app/ui/fields/VectorInput.svelte +86 -82
- package/src/client/app/utils/canvas.utils.js +228 -201
- package/src/client/app/utils/file.utils.js +38 -34
- package/src/client/public/css/global.css +27 -21
- package/src/cli/plugins/hot-shader-reload.js +0 -86
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { WebGLRenderer, Scene } from
|
|
2
|
-
import { Texture, fragment } from
|
|
3
|
-
import { client } from
|
|
4
|
-
import { getShaderPath } from
|
|
5
|
-
import { clearError } from
|
|
1
|
+
import { WebGLRenderer, Scene } from 'three';
|
|
2
|
+
import { Texture, fragment } from '@fragment/lib/gl';
|
|
3
|
+
import { client } from '@fragment/client';
|
|
4
|
+
import { getShaderPath } from '../utils/glsl.utils';
|
|
5
|
+
import { clearError } from '../stores/errors';
|
|
6
6
|
|
|
7
7
|
let renderer;
|
|
8
8
|
let previews = [];
|
|
9
|
-
let fragmentShader = /* glsl
|
|
9
|
+
let fragmentShader = /* glsl */ `
|
|
10
10
|
precision highp float;
|
|
11
11
|
uniform sampler2D uSampler;
|
|
12
12
|
varying vec2 vUv;
|
|
@@ -18,111 +18,153 @@ let fragmentShader = /* glsl */`
|
|
|
18
18
|
`;
|
|
19
19
|
|
|
20
20
|
export let init = ({ canvas }) => {
|
|
21
|
-
|
|
21
|
+
renderer = new WebGLRenderer({ antialias: true });
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
const render = renderer.render;
|
|
24
|
+
|
|
25
|
+
renderer.render = (scene, camera) => {
|
|
26
|
+
handleHotShaderUpdate(scene);
|
|
27
|
+
|
|
28
|
+
render.call(renderer, scene, camera);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
renderer,
|
|
33
|
+
};
|
|
26
34
|
};
|
|
27
35
|
|
|
28
36
|
export let onMountPreview = ({ id, canvas, width, height, pixelRatio }) => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
37
|
+
let { gl, render, resize, uniforms, destroy } = fragment({
|
|
38
|
+
canvas,
|
|
39
|
+
shader: fragmentShader,
|
|
40
|
+
uniforms: {
|
|
41
|
+
uSampler: { value: null, type: 'sampler2D' },
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
let texture = new Texture(gl, {
|
|
46
|
+
image: renderer.domElement,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
uniforms.uSampler.value = texture;
|
|
50
|
+
|
|
51
|
+
let scene = new Scene();
|
|
52
|
+
|
|
53
|
+
previews.push({
|
|
54
|
+
id,
|
|
55
|
+
scene,
|
|
56
|
+
texture,
|
|
57
|
+
render,
|
|
58
|
+
resize,
|
|
59
|
+
destroy,
|
|
60
|
+
rendered: false,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
scene,
|
|
65
|
+
renderer,
|
|
66
|
+
};
|
|
58
67
|
};
|
|
59
68
|
|
|
60
69
|
export let onDestroyPreview = ({ id, canvas }) => {
|
|
61
|
-
|
|
62
|
-
|
|
70
|
+
const previewIndex = previews.findIndex((p) => p.id === id);
|
|
71
|
+
const preview = previews[previewIndex];
|
|
63
72
|
|
|
64
|
-
|
|
73
|
+
clearError(renderer.getContext().__uuid);
|
|
65
74
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
75
|
+
if (preview) {
|
|
76
|
+
preview.texture.destroy();
|
|
77
|
+
preview.destroy();
|
|
78
|
+
previews.splice(previewIndex, 1);
|
|
79
|
+
}
|
|
71
80
|
};
|
|
72
81
|
|
|
73
|
-
export let
|
|
74
|
-
|
|
82
|
+
export let onBeforeUpdatePreview = ({ id }) => {
|
|
83
|
+
const preview = previews.find((p) => p.id === id);
|
|
75
84
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
85
|
+
if (preview) {
|
|
86
|
+
preview.rendered = false;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export let onAfterUpdatePreview = ({ id }) => {
|
|
91
|
+
const preview = previews.find((p) => p.id === id);
|
|
92
|
+
|
|
93
|
+
if (preview) {
|
|
94
|
+
preview.texture.needsUpdate = true;
|
|
95
|
+
preview.render();
|
|
96
|
+
preview.rendered = true;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (
|
|
100
|
+
previews.every((preview) => preview.rendered) &&
|
|
101
|
+
_shaderUpdates.length > 0
|
|
102
|
+
) {
|
|
103
|
+
clearShaderUpdates();
|
|
104
|
+
}
|
|
80
105
|
};
|
|
81
106
|
|
|
82
107
|
export let resize = ({ width, height, pixelRatio }) => {
|
|
83
|
-
|
|
84
|
-
|
|
108
|
+
renderer.setPixelRatio(pixelRatio);
|
|
109
|
+
renderer.setSize(width, height);
|
|
85
110
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
111
|
+
for (let i = 0; i < previews.length; i++) {
|
|
112
|
+
const preview = previews[i];
|
|
113
|
+
preview.resize({ width, height, pixelRatio });
|
|
114
|
+
}
|
|
90
115
|
};
|
|
91
116
|
|
|
92
117
|
/* HOT SHADER RELOADING */
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
118
|
+
let _shaderUpdates = [];
|
|
119
|
+
|
|
120
|
+
function handleHotShaderUpdate(scene) {
|
|
121
|
+
if (_shaderUpdates.length > 0) {
|
|
122
|
+
scene.traverse((child) => {
|
|
123
|
+
if (child.material) {
|
|
124
|
+
const { material } = child;
|
|
125
|
+
|
|
126
|
+
if (material.isShaderMaterial || material.isRawShaderMaterial) {
|
|
127
|
+
const { vertexShader, fragmentShader } = material;
|
|
128
|
+
|
|
129
|
+
Object.keys({ vertexShader, fragmentShader }).forEach(
|
|
130
|
+
(key) => {
|
|
131
|
+
const shader = material[key];
|
|
132
|
+
const shaderPath = getShaderPath(shader);
|
|
133
|
+
const shaderUpdate = _shaderUpdates.find(
|
|
134
|
+
(shaderUpdate) =>
|
|
135
|
+
shaderUpdate.filepath === shaderPath,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
if (shaderUpdate) {
|
|
139
|
+
console.log(
|
|
140
|
+
`[fragment-plugin-hsr] hsr update ${shaderPath.replace(
|
|
141
|
+
__CWD__,
|
|
142
|
+
'',
|
|
143
|
+
)}`,
|
|
144
|
+
);
|
|
145
|
+
material[key] = shaderUpdate.source;
|
|
146
|
+
material.needsUpdate = true;
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function clearShaderUpdates() {
|
|
157
|
+
_shaderUpdates = [];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (import.meta.hot) {
|
|
161
|
+
import.meta.hot.on('sketch-update', (data) => {
|
|
162
|
+
clearShaderUpdates();
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
client.on('shader-update', (shaderUpdates) => {
|
|
167
|
+
clearError(renderer.getContext().__uuid);
|
|
168
|
+
|
|
169
|
+
_shaderUpdates = shaderUpdates;
|
|
128
170
|
});
|
|
@@ -1,28 +1,44 @@
|
|
|
1
|
-
import { writable } from
|
|
2
|
-
import { createStore } from
|
|
1
|
+
import { writable } from 'svelte/store';
|
|
2
|
+
import { createHookStore, createStore } from './utils';
|
|
3
3
|
|
|
4
|
-
export const IMAGE_ENCODINGS = [
|
|
4
|
+
export const IMAGE_ENCODINGS = ['png', 'jpeg', 'webp'];
|
|
5
5
|
|
|
6
6
|
export const VIDEO_FORMATS = {
|
|
7
|
-
FRAMES:
|
|
8
|
-
MP4:
|
|
9
|
-
GIF:
|
|
10
|
-
WEBM:
|
|
7
|
+
FRAMES: 'frames',
|
|
8
|
+
MP4: 'mp4',
|
|
9
|
+
GIF: 'gif',
|
|
10
|
+
WEBM: 'webm',
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
export const exports = createStore(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
13
|
+
export const exports = createStore(
|
|
14
|
+
`exports`,
|
|
15
|
+
{
|
|
16
|
+
imageEncoding: IMAGE_ENCODINGS[0],
|
|
17
|
+
videoFormat: Object.values(VIDEO_FORMATS)[0],
|
|
18
|
+
pixelsPerInch: 72,
|
|
19
|
+
framerate: 60,
|
|
20
|
+
useDuration: true,
|
|
21
|
+
loopCount: 1,
|
|
22
|
+
imageQuality: 100,
|
|
23
|
+
videoQuality: 100,
|
|
24
|
+
imageCount: 1,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
persist: !__BUILD__,
|
|
28
|
+
reset: false,
|
|
29
|
+
},
|
|
30
|
+
);
|
|
26
31
|
|
|
27
32
|
export const recording = writable(false);
|
|
28
33
|
export const capturing = writable(false);
|
|
34
|
+
|
|
35
|
+
export const [beforeCapture, onBeforeCapture, removeBeforeCaptureFrom] =
|
|
36
|
+
createHookStore('onBeforeCapture');
|
|
37
|
+
export const [afterCapture, onAfterCapture, removeAfterCaptureFrom] =
|
|
38
|
+
createHookStore('onAfterCapture');
|
|
39
|
+
|
|
40
|
+
export const [beforeRecord, onBeforeRecord, removeBeforeRecordFrom] =
|
|
41
|
+
createHookStore('onBeforeRecord');
|
|
42
|
+
|
|
43
|
+
export const [afterRecord, onAfterRecord, removeAfterRecordFrom] =
|
|
44
|
+
createHookStore('onAfterRecord');
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { sketches } from './sketches';
|
|
2
|
+
import { getStore } from './utils';
|
|
3
3
|
|
|
4
|
-
export const props =
|
|
4
|
+
export const props = getStore('props', {});
|
|
5
5
|
|
|
6
6
|
sketches.subscribe((sketches) => {
|
|
7
7
|
props.update((currentProps) => {
|
|
8
8
|
Object.keys(sketches).forEach((key) => {
|
|
9
9
|
const sketch = sketches[key];
|
|
10
10
|
|
|
11
|
-
if (sketch) {
|
|
11
|
+
if (sketch) {
|
|
12
|
+
// sketch can be undefined if failed to load
|
|
12
13
|
currentProps[key] = reconcile(sketch.props, currentProps[key]);
|
|
13
14
|
}
|
|
14
15
|
});
|
|
@@ -17,7 +18,7 @@ sketches.subscribe((sketches) => {
|
|
|
17
18
|
});
|
|
18
19
|
});
|
|
19
20
|
|
|
20
|
-
function reconcile(newProps = {}, prevProps = {}) {
|
|
21
|
+
export function reconcile(newProps = {}, prevProps = {}) {
|
|
21
22
|
Object.keys(newProps).forEach((propKey) => {
|
|
22
23
|
let newProp = newProps[propKey];
|
|
23
24
|
|
|
@@ -44,3 +45,25 @@ function reconcile(newProps = {}, prevProps = {}) {
|
|
|
44
45
|
|
|
45
46
|
return newProps;
|
|
46
47
|
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Update prop value based on sketch key and prop key
|
|
51
|
+
* @param {string} sketchKey
|
|
52
|
+
* @param {string} propKey
|
|
53
|
+
* @param {any} newValue
|
|
54
|
+
*/
|
|
55
|
+
export function updateProp(sketchKey, propKey, newValue) {
|
|
56
|
+
props.update((currentProps) => {
|
|
57
|
+
const prop = currentProps[sketchKey][propKey];
|
|
58
|
+
|
|
59
|
+
if (prop) {
|
|
60
|
+
prop.value = newValue;
|
|
61
|
+
|
|
62
|
+
if (typeof prop.onChange === 'function') {
|
|
63
|
+
prop.onChange(prop);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return currentProps;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
@@ -1,30 +1,37 @@
|
|
|
1
|
-
import { rendering } from
|
|
1
|
+
import { rendering } from './rendering';
|
|
2
2
|
|
|
3
3
|
export let renderers = {};
|
|
4
4
|
|
|
5
5
|
function loadRenderer(renderingMode) {
|
|
6
|
-
if (
|
|
7
|
-
|
|
6
|
+
if (renderers[renderingMode]) return renderers[renderingMode];
|
|
7
|
+
|
|
8
|
+
if (__THREE_RENDERER__ && renderingMode === 'three') {
|
|
9
|
+
return import('../renderers/THREERenderer.js');
|
|
8
10
|
}
|
|
9
11
|
|
|
10
|
-
if (__FRAGMENT_RENDERER__ && renderingMode ===
|
|
11
|
-
return import(
|
|
12
|
+
if (__FRAGMENT_RENDERER__ && renderingMode === 'fragment') {
|
|
13
|
+
return import('../renderers/FragmentRenderer.js');
|
|
12
14
|
}
|
|
13
15
|
|
|
14
|
-
if (__P5_RENDERER__ && renderingMode ===
|
|
15
|
-
return import(
|
|
16
|
+
if (__P5_RENDERER__ && renderingMode === 'p5') {
|
|
17
|
+
return import('../renderers/P5Renderer.js');
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
if (__2D_RENDERER__ && renderingMode ===
|
|
19
|
-
return import(
|
|
20
|
+
if (__2D_RENDERER__ && renderingMode === '2d') {
|
|
21
|
+
return import('../renderers/2DRenderer.js');
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
export async function findRenderer(
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
export async function findRenderer({
|
|
26
|
+
rendering: renderingMode,
|
|
27
|
+
renderer: customRenderer,
|
|
28
|
+
}) {
|
|
26
29
|
// load and save
|
|
27
|
-
renderers[renderingMode] =
|
|
30
|
+
renderers[renderingMode] = customRenderer
|
|
31
|
+
? typeof customRenderer === 'function'
|
|
32
|
+
? await customRenderer()
|
|
33
|
+
: customRenderer
|
|
34
|
+
: await loadRenderer(renderingMode);
|
|
28
35
|
|
|
29
36
|
// get
|
|
30
37
|
let renderer = renderers[renderingMode];
|
|
@@ -35,7 +42,7 @@ export async function findRenderer(renderingMode) {
|
|
|
35
42
|
let r;
|
|
36
43
|
|
|
37
44
|
if (!initialized) {
|
|
38
|
-
if (typeof renderer.init ===
|
|
45
|
+
if (typeof renderer.init === 'function') {
|
|
39
46
|
r = renderer.init({
|
|
40
47
|
canvas: document.createElement('canvas'),
|
|
41
48
|
pixelRatio: current.pixelRatio,
|
|
@@ -47,7 +54,7 @@ export async function findRenderer(renderingMode) {
|
|
|
47
54
|
|
|
48
55
|
initialized = true;
|
|
49
56
|
|
|
50
|
-
if (typeof renderer.resize ===
|
|
57
|
+
if (typeof renderer.resize === 'function') {
|
|
51
58
|
renderer.resize({
|
|
52
59
|
width: current.width,
|
|
53
60
|
height: current.height,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { createStore } from
|
|
2
|
-
import { displayError } from
|
|
3
|
-
import { sketches as all
|
|
1
|
+
import { createStore } from './utils.js';
|
|
2
|
+
import { displayError } from '../stores/errors';
|
|
3
|
+
import { sketches as all } from '@fragment/sketches';
|
|
4
4
|
|
|
5
5
|
export const sketches = createStore('sketches', {});
|
|
6
6
|
export const sketchesKeys = createStore('sketchesKeys', Object.keys(all));
|
|
@@ -16,9 +16,11 @@ async function loadSketch(collection, key) {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
async function loadAll(collection) {
|
|
19
|
+
export async function loadAll(collection) {
|
|
20
20
|
const keys = [...Object.keys(collection)];
|
|
21
|
-
const loadedSketches = await Promise.all(
|
|
21
|
+
const loadedSketches = await Promise.all(
|
|
22
|
+
keys.map((key) => loadSketch(collection, key)),
|
|
23
|
+
);
|
|
22
24
|
|
|
23
25
|
const newSketches = keys.reduce((all, key, index) => {
|
|
24
26
|
if (loadedSketches[index]) {
|
|
@@ -34,7 +36,3 @@ async function loadAll(collection) {
|
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
loadAll(all);
|
|
37
|
-
|
|
38
|
-
onSketchReload(({ sketches }) => {
|
|
39
|
-
loadAll(sketches);
|
|
40
|
-
});
|
|
@@ -1,66 +1,123 @@
|
|
|
1
|
-
import { writable } from
|
|
1
|
+
import { writable } from 'svelte/store';
|
|
2
|
+
import { getContext } from '../triggers/shared';
|
|
2
3
|
|
|
3
4
|
let stores = new Map();
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Returns the value stored in localStorage for key or return defaultValue if it doesn't exist
|
|
7
|
-
* @param {string} key
|
|
8
|
-
* @param {any} defaultValue
|
|
9
|
-
* @param {boolean} override
|
|
8
|
+
* @param {string} key
|
|
9
|
+
* @param {any} defaultValue
|
|
10
|
+
* @param {boolean} override
|
|
10
11
|
* @returns {any} result
|
|
11
12
|
*/
|
|
12
13
|
export function rehydrate(key, defaultValue) {
|
|
13
|
-
|
|
14
|
+
const storedValue = localStorage.getItem(`fragment.${key}`);
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
if (storedValue) {
|
|
17
|
+
return typeof storedValue === 'string'
|
|
18
|
+
? JSON.parse(storedValue)
|
|
19
|
+
: storedValue;
|
|
20
|
+
}
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
}
|
|
22
|
+
return defaultValue;
|
|
23
|
+
}
|
|
21
24
|
|
|
22
25
|
/**
|
|
23
26
|
* Save value in localStorage
|
|
24
|
-
* @param {string} key
|
|
25
|
-
* @param {any} value
|
|
27
|
+
* @param {string} key
|
|
28
|
+
* @param {any} value
|
|
26
29
|
*/
|
|
27
30
|
export function save(key, value) {
|
|
28
|
-
|
|
29
|
-
}
|
|
31
|
+
localStorage.setItem(`fragment.${key}`, JSON.stringify(value));
|
|
32
|
+
}
|
|
30
33
|
|
|
31
34
|
/**
|
|
32
35
|
* Create store and register it for later usage
|
|
33
|
-
* @param {string} key
|
|
34
|
-
* @param {any} initialValue
|
|
36
|
+
* @param {string} key
|
|
37
|
+
* @param {any} initialValue
|
|
35
38
|
* @param {object} options
|
|
36
|
-
* @returns {
|
|
39
|
+
* @returns {import('svelte/store').Writable} store
|
|
37
40
|
*/
|
|
38
|
-
export function createStore(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
export function createStore(
|
|
42
|
+
key,
|
|
43
|
+
initialValue,
|
|
44
|
+
{ persist = false, reset = false } = {},
|
|
45
|
+
) {
|
|
46
|
+
const value =
|
|
47
|
+
persist && !reset ? rehydrate(key, initialValue) : initialValue;
|
|
48
|
+
const store = writable(value);
|
|
41
49
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
stores.set(key, store);
|
|
50
|
+
if (persist) {
|
|
51
|
+
store.subscribe((current) => {
|
|
52
|
+
save(key, current);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
49
55
|
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
stores.set(key, store);
|
|
57
|
+
|
|
58
|
+
return store;
|
|
59
|
+
}
|
|
52
60
|
|
|
53
61
|
/**
|
|
54
62
|
* Get an existing store from key or create it if it doesn't exist yet
|
|
55
|
-
* @param {string} key
|
|
56
|
-
* @param {any} initialValue
|
|
63
|
+
* @param {string} key
|
|
64
|
+
* @param {any} initialValue
|
|
57
65
|
* @param {object} options
|
|
58
|
-
* @returns {
|
|
66
|
+
* @returns {import('svelte/store').Writable} store
|
|
59
67
|
*/
|
|
60
|
-
export function getStore(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
68
|
+
export function getStore(
|
|
69
|
+
key,
|
|
70
|
+
initialValue,
|
|
71
|
+
{ persist = false, reset = false } = {},
|
|
72
|
+
) {
|
|
73
|
+
if (!stores.has(key)) {
|
|
74
|
+
return createStore(key, initialValue, { persist, reset });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return stores.get(key);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Create a new store to register callbacks grouped by context
|
|
82
|
+
* @param {string} name
|
|
83
|
+
*/
|
|
84
|
+
export function createHookStore(name) {
|
|
85
|
+
const store = writable(new Map());
|
|
86
|
+
|
|
87
|
+
const hook = (fn, { context = getContext() } = {}) => {
|
|
88
|
+
if (typeof fn !== 'function') {
|
|
89
|
+
console.warn(`${name} argument must be a function.`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
store.update((hooks) => {
|
|
93
|
+
if (!hooks.has(context)) {
|
|
94
|
+
hooks.set(context, []);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
hooks.set(context, [...hooks.get(context), fn]);
|
|
98
|
+
|
|
99
|
+
return hooks;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return () => {
|
|
103
|
+
store.update((hooks) => {
|
|
104
|
+
hooks.set(
|
|
105
|
+
context,
|
|
106
|
+
[...hooks.get(context)].filter((hook) => hook !== fn),
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
return hooks;
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const remove = (context) => {
|
|
115
|
+
store.update((hooks) => {
|
|
116
|
+
hooks.delete(context);
|
|
117
|
+
|
|
118
|
+
return hooks;
|
|
119
|
+
});
|
|
120
|
+
};
|
|
64
121
|
|
|
65
|
-
|
|
66
|
-
}
|
|
122
|
+
return [store, hook, remove];
|
|
123
|
+
}
|