@hkdigital/lib-sveltekit 0.0.81 → 0.0.83
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/dist/classes/svelte/audio/AudioLoader.svelte.d.ts +30 -0
- package/dist/classes/svelte/audio/AudioLoader.svelte.js +58 -0
- package/dist/classes/svelte/audio/AudioScene.svelte.d.ts +52 -0
- package/dist/classes/svelte/audio/AudioScene.svelte.js +282 -0
- package/dist/classes/svelte/audio/mocks.d.ts +7 -0
- package/dist/classes/svelte/audio/mocks.js +35 -0
- package/dist/classes/svelte/final-state-machine/FiniteStateMachine.svelte.d.ts +50 -0
- package/dist/classes/svelte/final-state-machine/FiniteStateMachine.svelte.js +133 -0
- package/dist/classes/svelte/final-state-machine/index.d.ts +1 -0
- package/dist/classes/svelte/final-state-machine/index.js +1 -0
- package/dist/classes/svelte/image/ImageLoader.svelte.d.ts +8 -0
- package/dist/classes/svelte/image/ImageLoader.svelte.js +12 -0
- package/dist/classes/svelte/image/ImageVariantsLoader.svelte.d.ts +39 -0
- package/dist/classes/svelte/image/ImageVariantsLoader.svelte.js +151 -0
- package/dist/classes/svelte/image/index.d.ts +2 -0
- package/dist/classes/svelte/image/index.js +4 -0
- package/dist/classes/svelte/image/mocks.d.ts +7 -0
- package/dist/classes/svelte/image/mocks.js +38 -0
- package/dist/classes/svelte/image/typedef.d.ts +16 -0
- package/dist/classes/svelte/image/typedef.js +10 -0
- package/dist/classes/svelte/loading-state-machine/LoadingStateMachine.svelte.d.ts +12 -0
- package/dist/classes/svelte/loading-state-machine/LoadingStateMachine.svelte.js +107 -0
- package/dist/classes/svelte/loading-state-machine/constants.d.ts +12 -0
- package/dist/classes/svelte/loading-state-machine/constants.js +16 -0
- package/dist/classes/svelte/loading-state-machine/index.d.ts +2 -0
- package/dist/classes/svelte/loading-state-machine/index.js +3 -0
- package/dist/classes/svelte/network-loader/NetworkLoader.svelte.d.ts +85 -0
- package/dist/classes/svelte/network-loader/NetworkLoader.svelte.js +295 -0
- package/dist/classes/svelte/network-loader/constants.d.ts +2 -0
- package/dist/classes/svelte/network-loader/constants.js +3 -0
- package/dist/classes/svelte/network-loader/index.d.ts +2 -0
- package/dist/classes/svelte/network-loader/index.js +3 -0
- package/dist/components/image/ResponsiveImage.svelte +103 -0
- package/dist/components/image/ResponsiveImage.svelte.d.ts +15 -0
- package/dist/components/image/index.d.ts +4 -1
- package/dist/components/image/index.js +5 -1
- package/dist/types/imagetools.d.ts +16 -13
- package/dist/types/imagetools.js +8 -0
- package/dist/util/expect/arrays.d.ts +4 -0
- package/dist/util/expect/arrays.js +6 -1
- package/dist/util/http/response.js +1 -0
- package/dist/util/svelte/wait/index.d.ts +15 -0
- package/dist/util/svelte/wait/index.js +38 -0
- package/package.json +3 -1
@@ -0,0 +1,30 @@
|
|
1
|
+
/**
|
2
|
+
* AudioLoader instance
|
3
|
+
* - Loads audio data from network into an ArrayBuffer
|
4
|
+
* - Loaded data can be transferred to an AudioBufferSourceNode
|
5
|
+
*/
|
6
|
+
export default class AudioLoader extends NetworkLoader {
|
7
|
+
/**
|
8
|
+
* Get an AudioBufferSourceNode instance
|
9
|
+
*
|
10
|
+
* @note AudioBufferSourceNodes can play only once, a new source node
|
11
|
+
* must be created otherwise
|
12
|
+
*
|
13
|
+
* @param {AudioContext} audioContext
|
14
|
+
*
|
15
|
+
* @returns {Promise<AudioBufferSourceNode>}
|
16
|
+
*/
|
17
|
+
getAudioBufferSourceNode(audioContext: AudioContext): Promise<AudioBufferSourceNode>;
|
18
|
+
/**
|
19
|
+
* Gets data as AudioBuffer
|
20
|
+
* - Stores created AudioBuffer instance internally
|
21
|
+
* - Transfers data from internal ArrayBuffer, which will be detached
|
22
|
+
*
|
23
|
+
* @param {AudioContext} audioContext
|
24
|
+
*
|
25
|
+
* @returns {Promise<AudioBuffer>}
|
26
|
+
*/
|
27
|
+
getAudioBuffer(audioContext: AudioContext): Promise<AudioBuffer>;
|
28
|
+
#private;
|
29
|
+
}
|
30
|
+
import { NetworkLoader } from '../network-loader/index.js';
|
@@ -0,0 +1,58 @@
|
|
1
|
+
import {
|
2
|
+
NetworkLoader,
|
3
|
+
ERROR_NOT_LOADED,
|
4
|
+
ERROR_TRANSFERRED
|
5
|
+
} from '../network-loader/index.js';
|
6
|
+
|
7
|
+
/**
|
8
|
+
* AudioLoader instance
|
9
|
+
* - Loads audio data from network into an ArrayBuffer
|
10
|
+
* - Loaded data can be transferred to an AudioBufferSourceNode
|
11
|
+
*/
|
12
|
+
export default class AudioLoader extends NetworkLoader {
|
13
|
+
/** @type {AudioBuffer|null} */
|
14
|
+
#audioBuffer = null;
|
15
|
+
|
16
|
+
/**
|
17
|
+
* Get an AudioBufferSourceNode instance
|
18
|
+
*
|
19
|
+
* @note AudioBufferSourceNodes can play only once, a new source node
|
20
|
+
* must be created otherwise
|
21
|
+
*
|
22
|
+
* @param {AudioContext} audioContext
|
23
|
+
*
|
24
|
+
* @returns {Promise<AudioBufferSourceNode>}
|
25
|
+
*/
|
26
|
+
async getAudioBufferSourceNode(audioContext) {
|
27
|
+
if (!this.#audioBuffer) {
|
28
|
+
this.#audioBuffer = await this.getAudioBuffer(audioContext);
|
29
|
+
}
|
30
|
+
|
31
|
+
return new AudioBufferSourceNode(audioContext, {
|
32
|
+
buffer: this.#audioBuffer
|
33
|
+
});
|
34
|
+
}
|
35
|
+
|
36
|
+
/**
|
37
|
+
* Gets data as AudioBuffer
|
38
|
+
* - Stores created AudioBuffer instance internally
|
39
|
+
* - Transfers data from internal ArrayBuffer, which will be detached
|
40
|
+
*
|
41
|
+
* @param {AudioContext} audioContext
|
42
|
+
*
|
43
|
+
* @returns {Promise<AudioBuffer>}
|
44
|
+
*/
|
45
|
+
async getAudioBuffer(audioContext) {
|
46
|
+
if (!this._buffer) {
|
47
|
+
throw new Error(ERROR_NOT_LOADED);
|
48
|
+
}
|
49
|
+
|
50
|
+
if (this._buffer.detached) {
|
51
|
+
throw new Error(ERROR_TRANSFERRED);
|
52
|
+
}
|
53
|
+
|
54
|
+
this.#audioBuffer = await audioContext.decodeAudioData(this._buffer);
|
55
|
+
|
56
|
+
return this.#audioBuffer;
|
57
|
+
}
|
58
|
+
} // end class
|
@@ -0,0 +1,52 @@
|
|
1
|
+
/**
|
2
|
+
* @typedef {object} SourceConfig
|
3
|
+
* // property ...
|
4
|
+
*/
|
5
|
+
/**
|
6
|
+
* @typedef {object} MemorySource
|
7
|
+
* @property {string} label
|
8
|
+
* @property {AudioLoader} audioLoader
|
9
|
+
* @property {SourceConfig} [config]
|
10
|
+
*/
|
11
|
+
export default class AudioScene {
|
12
|
+
state: string;
|
13
|
+
loaded: boolean;
|
14
|
+
destroy(): void;
|
15
|
+
/**
|
16
|
+
* Add in-memory audio source
|
17
|
+
* - Uses an AudioLoader instance to load audio data from network
|
18
|
+
*
|
19
|
+
* @param {object} _
|
20
|
+
* @param {string} _.label
|
21
|
+
* @param {string} _.url
|
22
|
+
* @param {SourceConfig} [_.config]
|
23
|
+
*/
|
24
|
+
defineMemorySource({ label, url, config }: {
|
25
|
+
label: string;
|
26
|
+
url: string;
|
27
|
+
config?: SourceConfig;
|
28
|
+
}): void;
|
29
|
+
/**
|
30
|
+
* Start loading all audio sources
|
31
|
+
*
|
32
|
+
* @param {AudioContext} audioContext
|
33
|
+
*/
|
34
|
+
load(audioContext: AudioContext): void;
|
35
|
+
/**
|
36
|
+
* Get a source that can be used to plat the audio once
|
37
|
+
*
|
38
|
+
* @param {string} label
|
39
|
+
*/
|
40
|
+
getSourceNode(label: string): Promise<AudioBufferSourceNode>;
|
41
|
+
#private;
|
42
|
+
}
|
43
|
+
/**
|
44
|
+
* // property ...
|
45
|
+
*/
|
46
|
+
export type SourceConfig = object;
|
47
|
+
export type MemorySource = {
|
48
|
+
label: string;
|
49
|
+
audioLoader: AudioLoader;
|
50
|
+
config?: SourceConfig;
|
51
|
+
};
|
52
|
+
import AudioLoader from './AudioLoader.svelte.js';
|
@@ -0,0 +1,282 @@
|
|
1
|
+
import * as expect from '@hkdigital/lib-sveltekit/util/expect/index.js';
|
2
|
+
|
3
|
+
import {
|
4
|
+
LoadingStateMachine,
|
5
|
+
STATE_INITIAL,
|
6
|
+
STATE_LOADING,
|
7
|
+
STATE_UNLOADING,
|
8
|
+
STATE_LOADED,
|
9
|
+
STATE_CANCELLED,
|
10
|
+
STATE_ERROR,
|
11
|
+
LOAD,
|
12
|
+
// CANCEL,
|
13
|
+
ERROR,
|
14
|
+
LOADED,
|
15
|
+
UNLOAD,
|
16
|
+
INITIAL
|
17
|
+
} from '../loading-state-machine/index.js';
|
18
|
+
|
19
|
+
import AudioLoader from './AudioLoader.svelte.js';
|
20
|
+
|
21
|
+
/**
|
22
|
+
* @typedef {object} SourceConfig
|
23
|
+
* // property ...
|
24
|
+
*/
|
25
|
+
|
26
|
+
/**
|
27
|
+
* @typedef {object} MemorySource
|
28
|
+
* @property {string} label
|
29
|
+
* @property {AudioLoader} audioLoader
|
30
|
+
* @property {SourceConfig} [config]
|
31
|
+
*/
|
32
|
+
|
33
|
+
export default class AudioScene {
|
34
|
+
#state = new LoadingStateMachine();
|
35
|
+
|
36
|
+
// @note this exported state is set by $effect's
|
37
|
+
state = $state(STATE_INITIAL);
|
38
|
+
|
39
|
+
// @note this exported state is set by $effect's
|
40
|
+
loaded = $derived.by(() => {
|
41
|
+
return this.state === STATE_LOADED;
|
42
|
+
});
|
43
|
+
|
44
|
+
/** @type {AudioContext|null} */
|
45
|
+
#audioContext = null;
|
46
|
+
|
47
|
+
/** @type {MemorySource[]} */
|
48
|
+
#memorySources = $state([]);
|
49
|
+
|
50
|
+
#progress = $derived.by(() => {
|
51
|
+
// console.log('update progress');
|
52
|
+
|
53
|
+
let totalSize = 0;
|
54
|
+
let totalBytesLoaded = 0;
|
55
|
+
let sourcesLoaded = 0;
|
56
|
+
|
57
|
+
const sources = this.#memorySources;
|
58
|
+
const numberOfSources = sources.length;
|
59
|
+
|
60
|
+
for (let j = 0; j < numberOfSources; j++) {
|
61
|
+
const source = sources[j];
|
62
|
+
const { audioLoader } = source;
|
63
|
+
|
64
|
+
const { bytesLoaded, size, loaded } = audioLoader.progress;
|
65
|
+
|
66
|
+
totalSize += size;
|
67
|
+
totalBytesLoaded += bytesLoaded;
|
68
|
+
|
69
|
+
if (loaded) {
|
70
|
+
sourcesLoaded++;
|
71
|
+
}
|
72
|
+
} // end for
|
73
|
+
|
74
|
+
return {
|
75
|
+
totalBytesLoaded,
|
76
|
+
totalSize,
|
77
|
+
sourcesLoaded,
|
78
|
+
numberOfSources
|
79
|
+
};
|
80
|
+
});
|
81
|
+
|
82
|
+
/**
|
83
|
+
* Construct AudioScene
|
84
|
+
*
|
85
|
+
* @param {object} _
|
86
|
+
* @param {AudioContext} _.audioContext
|
87
|
+
*/
|
88
|
+
constructor() {
|
89
|
+
const state = this.#state;
|
90
|
+
|
91
|
+
$effect(() => {
|
92
|
+
if (state.current === STATE_LOADING) {
|
93
|
+
// console.log(
|
94
|
+
// 'progress',
|
95
|
+
// JSON.stringify($state.snapshot(this.#progress))
|
96
|
+
// );
|
97
|
+
|
98
|
+
const { sourcesLoaded, numberOfSources } = this.#progress;
|
99
|
+
|
100
|
+
if (sourcesLoaded === numberOfSources) {
|
101
|
+
console.log(`All [${numberOfSources}] sources loaded`);
|
102
|
+
this.#state.send(LOADED);
|
103
|
+
}
|
104
|
+
}
|
105
|
+
});
|
106
|
+
|
107
|
+
$effect(() => {
|
108
|
+
switch (state.current) {
|
109
|
+
case STATE_LOADING:
|
110
|
+
{
|
111
|
+
console.log('AudioScene:loading');
|
112
|
+
this.#startLoading();
|
113
|
+
}
|
114
|
+
break;
|
115
|
+
|
116
|
+
case STATE_UNLOADING:
|
117
|
+
{
|
118
|
+
// console.log('AudioScene:unloading');
|
119
|
+
// this.#startUnLoading();
|
120
|
+
}
|
121
|
+
break;
|
122
|
+
|
123
|
+
case STATE_LOADED:
|
124
|
+
{
|
125
|
+
console.log('AudioScene:loaded');
|
126
|
+
|
127
|
+
// tODO
|
128
|
+
// this.#abortLoading = null;
|
129
|
+
}
|
130
|
+
break;
|
131
|
+
|
132
|
+
case STATE_CANCELLED:
|
133
|
+
{
|
134
|
+
// console.log('AudioScene:cancelled');
|
135
|
+
// TODO
|
136
|
+
}
|
137
|
+
break;
|
138
|
+
|
139
|
+
case STATE_ERROR:
|
140
|
+
{
|
141
|
+
console.log('AudioScene:error', state.error);
|
142
|
+
}
|
143
|
+
break;
|
144
|
+
} // end switch
|
145
|
+
|
146
|
+
this.state = state.current;
|
147
|
+
});
|
148
|
+
}
|
149
|
+
|
150
|
+
destroy() {
|
151
|
+
// TODO: disconnect all audio sources?
|
152
|
+
// TODO: Unload AUdioLoaders?
|
153
|
+
}
|
154
|
+
|
155
|
+
/**
|
156
|
+
* Add in-memory audio source
|
157
|
+
* - Uses an AudioLoader instance to load audio data from network
|
158
|
+
*
|
159
|
+
* @param {object} _
|
160
|
+
* @param {string} _.label
|
161
|
+
* @param {string} _.url
|
162
|
+
* @param {SourceConfig} [_.config]
|
163
|
+
*/
|
164
|
+
defineMemorySource({ label, url, config }) {
|
165
|
+
expect.notEmptyString(label);
|
166
|
+
expect.notEmptyString(url);
|
167
|
+
|
168
|
+
const audioLoader = new AudioLoader({ url });
|
169
|
+
|
170
|
+
this.#memorySources.push({ label, audioLoader, config });
|
171
|
+
}
|
172
|
+
|
173
|
+
/**
|
174
|
+
* Start loading all audio sources
|
175
|
+
*
|
176
|
+
* @param {AudioContext} audioContext
|
177
|
+
*/
|
178
|
+
load(audioContext) {
|
179
|
+
this.#audioContext = audioContext;
|
180
|
+
// console.log(123);
|
181
|
+
this.#state.send(LOAD);
|
182
|
+
|
183
|
+
// FIXME: in unit test when moved to startloading it hangs!
|
184
|
+
|
185
|
+
for (const { audioLoader } of this.#memorySources) {
|
186
|
+
audioLoader.load();
|
187
|
+
}
|
188
|
+
}
|
189
|
+
|
190
|
+
async #startLoading() {
|
191
|
+
console.log('#startLoading');
|
192
|
+
|
193
|
+
// FIXME: in unit test when moved to startloading it hangs!
|
194
|
+
// for (const { audioLoader } of this.#memorySources) {
|
195
|
+
// audioLoader.load();
|
196
|
+
// }
|
197
|
+
}
|
198
|
+
|
199
|
+
/**
|
200
|
+
* Get a source that can be used to plat the audio once
|
201
|
+
*
|
202
|
+
* @param {string} label
|
203
|
+
*/
|
204
|
+
async getSourceNode(label) {
|
205
|
+
// @note Gain setup
|
206
|
+
// https://stackoverflow.com/questions/46203191/should-i-disconnect-nodes-that-cant-be-used-anymore
|
207
|
+
|
208
|
+
const { audioLoader /*, config */ } = this.#getMemorySource(label);
|
209
|
+
|
210
|
+
if (!audioLoader.loaded) {
|
211
|
+
throw new Error(`Source [${label}] has not been loaded yet`);
|
212
|
+
}
|
213
|
+
|
214
|
+
const sourceNode = await audioLoader.getAudioBufferSourceNode(
|
215
|
+
// @ts-ignore
|
216
|
+
this.#audioContext
|
217
|
+
);
|
218
|
+
|
219
|
+
// @ts-ignore
|
220
|
+
sourceNode.connect(this.#audioContext.destination);
|
221
|
+
|
222
|
+
// Clean up
|
223
|
+
sourceNode.onended = () => {
|
224
|
+
// console.log(`Source [${label}] ended `);
|
225
|
+
sourceNode.disconnect();
|
226
|
+
};
|
227
|
+
|
228
|
+
return sourceNode;
|
229
|
+
}
|
230
|
+
|
231
|
+
/**
|
232
|
+
* Get memory source
|
233
|
+
*
|
234
|
+
* @param {string} label
|
235
|
+
*
|
236
|
+
* @returns {MemorySource}
|
237
|
+
*/
|
238
|
+
#getMemorySource(label) {
|
239
|
+
for (const source of this.#memorySources) {
|
240
|
+
if (label === source.label) {
|
241
|
+
return source;
|
242
|
+
}
|
243
|
+
}
|
244
|
+
|
245
|
+
throw new Error(`Source [${label}] has not been defined`);
|
246
|
+
}
|
247
|
+
|
248
|
+
// connect
|
249
|
+
// play
|
250
|
+
|
251
|
+
// source.connect(audioContext.destination);
|
252
|
+
// source.loop = true;
|
253
|
+
// source.start();
|
254
|
+
|
255
|
+
// /**
|
256
|
+
// * Get the source identified by the specified label
|
257
|
+
// *
|
258
|
+
// * @param {string} label
|
259
|
+
// */
|
260
|
+
// async getBufferSourceNode(label) {
|
261
|
+
// // expect.notEmptyString( label );
|
262
|
+
|
263
|
+
// for (const source of this.#memorySources) {
|
264
|
+
// if (label === source.label) {
|
265
|
+
// if (!source.bufferSourceNode) {
|
266
|
+
// source.bufferSourceNode =
|
267
|
+
// await source.AudioLoader.transferToBufferSource(this.#audioContext);
|
268
|
+
// }
|
269
|
+
|
270
|
+
// return source.bufferSourceNode;
|
271
|
+
// }
|
272
|
+
// }
|
273
|
+
// }
|
274
|
+
|
275
|
+
// async connectSourceToDestination(label) {
|
276
|
+
// const source = await this.getBufferSourceNode(label);
|
277
|
+
|
278
|
+
// if (source) {
|
279
|
+
// source.connect(this.#audioContext.destination);
|
280
|
+
// }
|
281
|
+
// }
|
282
|
+
}
|
@@ -0,0 +1,35 @@
|
|
1
|
+
import { CONTENT_TYPE, CONTENT_LENGTH } from '@hkdigital/lib-sveltekit/constants/http/index.js';
|
2
|
+
|
3
|
+
import { AUDIO_WAV } from '@hkdigital/lib-sveltekit/constants/mime/audio.js';
|
4
|
+
|
5
|
+
// import MockWav from './tiny-silence.wav?raw';
|
6
|
+
|
7
|
+
const BASE64_WAV =
|
8
|
+
'UklGRnwAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YVgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
|
9
|
+
|
10
|
+
/**
|
11
|
+
* Create a response value that can be used by a mocked
|
12
|
+
* fetch function
|
13
|
+
*
|
14
|
+
* @returns {Response}
|
15
|
+
*/
|
16
|
+
export function createWavResponse(/* data , options */) {
|
17
|
+
// @note encode as Uint8Array to get the proper byte size of data
|
18
|
+
// const bytes = new TextEncoder().encode(MockWav);
|
19
|
+
|
20
|
+
const binaryString = atob(BASE64_WAV);
|
21
|
+
const bytes = new Uint8Array(binaryString.length);
|
22
|
+
|
23
|
+
for (let i = 0; i < binaryString.length; i++) {
|
24
|
+
bytes[i] = binaryString.charCodeAt(i);
|
25
|
+
}
|
26
|
+
|
27
|
+
const response = new Response(bytes, {
|
28
|
+
headers: new Headers({
|
29
|
+
[CONTENT_TYPE]: AUDIO_WAV,
|
30
|
+
[CONTENT_LENGTH]: String(bytes.length)
|
31
|
+
})
|
32
|
+
});
|
33
|
+
|
34
|
+
return response;
|
35
|
+
}
|
@@ -0,0 +1,50 @@
|
|
1
|
+
/**
|
2
|
+
* Initial code borrowed from:
|
3
|
+
*
|
4
|
+
* @see {@link https://runed.dev/docs/utilities/finite-state-machine}
|
5
|
+
*/
|
6
|
+
/**
|
7
|
+
* Check if the value is valid meta data
|
8
|
+
*
|
9
|
+
* @param {any} meta
|
10
|
+
*/
|
11
|
+
export function isLifecycleFnMeta(meta: any): boolean;
|
12
|
+
/**
|
13
|
+
* Defines a Finite State Machine
|
14
|
+
*/
|
15
|
+
export default class FiniteStateMachine {
|
16
|
+
/**
|
17
|
+
* Constructor
|
18
|
+
*
|
19
|
+
* @param {string} initial
|
20
|
+
* @param {{ [key: string]: { [key: string]: (string|((...args: any[])=>void)) } }} states
|
21
|
+
*/
|
22
|
+
constructor(initial: string, states: {
|
23
|
+
[key: string]: {
|
24
|
+
[key: string]: (string | ((...args: any[]) => void));
|
25
|
+
};
|
26
|
+
});
|
27
|
+
states: {
|
28
|
+
[key: string]: {
|
29
|
+
[key: string]: string | ((...args: any[]) => void);
|
30
|
+
};
|
31
|
+
};
|
32
|
+
/**
|
33
|
+
* Triggers a new event and returns the new state.
|
34
|
+
*
|
35
|
+
* @param {string} event
|
36
|
+
* @param {any[]} args
|
37
|
+
*/
|
38
|
+
send(event: string, ...args: any[]): any;
|
39
|
+
/**
|
40
|
+
* Debounces the triggering of an event.
|
41
|
+
*
|
42
|
+
* @param {number} wait
|
43
|
+
* @param {string} event
|
44
|
+
* @param {any[]} args
|
45
|
+
*/
|
46
|
+
debounce(wait: number, event: string, ...args: any[]): Promise<any>;
|
47
|
+
/** The current state. */
|
48
|
+
get current(): any;
|
49
|
+
#private;
|
50
|
+
}
|
@@ -0,0 +1,133 @@
|
|
1
|
+
/**
|
2
|
+
* Initial code borrowed from:
|
3
|
+
*
|
4
|
+
* @see {@link https://runed.dev/docs/utilities/finite-state-machine}
|
5
|
+
*/
|
6
|
+
|
7
|
+
/**
|
8
|
+
* Check if the value is valid meta data
|
9
|
+
*
|
10
|
+
* @param {any} meta
|
11
|
+
*/
|
12
|
+
export function isLifecycleFnMeta(meta) {
|
13
|
+
return (
|
14
|
+
!!meta &&
|
15
|
+
typeof meta === 'object' &&
|
16
|
+
'to' in meta &&
|
17
|
+
'from' in meta &&
|
18
|
+
'event' in meta &&
|
19
|
+
'args' in meta
|
20
|
+
);
|
21
|
+
}
|
22
|
+
|
23
|
+
/**
|
24
|
+
* Defines a Finite State Machine
|
25
|
+
*/
|
26
|
+
export default class FiniteStateMachine {
|
27
|
+
#current = $state();
|
28
|
+
states;
|
29
|
+
#timeout = {};
|
30
|
+
|
31
|
+
/**
|
32
|
+
* Constructor
|
33
|
+
*
|
34
|
+
* @param {string} initial
|
35
|
+
* @param {{ [key: string]: { [key: string]: (string|((...args: any[])=>void)) } }} states
|
36
|
+
*/
|
37
|
+
constructor(initial, states) {
|
38
|
+
this.#current = initial;
|
39
|
+
this.states = states;
|
40
|
+
|
41
|
+
// synthetically trigger _enter for the initial state.
|
42
|
+
this.#dispatch('_enter', {
|
43
|
+
from: null,
|
44
|
+
to: initial,
|
45
|
+
event: null,
|
46
|
+
args: []
|
47
|
+
});
|
48
|
+
}
|
49
|
+
|
50
|
+
/**
|
51
|
+
* Transition to new state
|
52
|
+
*
|
53
|
+
* @param {string} newState
|
54
|
+
* @param {string} event
|
55
|
+
* @param {any[]} [args]
|
56
|
+
*/
|
57
|
+
#transition(newState, event, args) {
|
58
|
+
const metadata = { from: this.#current, to: newState, event, args };
|
59
|
+
this.#dispatch('_exit', metadata);
|
60
|
+
this.#current = newState;
|
61
|
+
this.#dispatch('_enter', metadata);
|
62
|
+
}
|
63
|
+
|
64
|
+
/**
|
65
|
+
* Dispatch an event
|
66
|
+
*
|
67
|
+
* @param {string} event
|
68
|
+
* @param {any[]} args
|
69
|
+
*/
|
70
|
+
#dispatch(event, ...args) {
|
71
|
+
const action =
|
72
|
+
this.states[this.#current]?.[event] ?? this.states['*']?.[event];
|
73
|
+
if (action instanceof Function) {
|
74
|
+
if (event === '_enter' || event === '_exit') {
|
75
|
+
if (isLifecycleFnMeta(args[0])) {
|
76
|
+
action(args[0]);
|
77
|
+
} else {
|
78
|
+
console.warn(
|
79
|
+
'Invalid metadata passed to lifecycle function of the FSM.'
|
80
|
+
);
|
81
|
+
}
|
82
|
+
} else {
|
83
|
+
return action(...args);
|
84
|
+
}
|
85
|
+
} else if (typeof action === 'string') {
|
86
|
+
return action;
|
87
|
+
} else if (event !== '_enter' && event !== '_exit') {
|
88
|
+
console.warn(
|
89
|
+
'No action defined for event',
|
90
|
+
event,
|
91
|
+
'in state',
|
92
|
+
this.#current
|
93
|
+
);
|
94
|
+
}
|
95
|
+
}
|
96
|
+
/**
|
97
|
+
* Triggers a new event and returns the new state.
|
98
|
+
*
|
99
|
+
* @param {string} event
|
100
|
+
* @param {any[]} args
|
101
|
+
*/
|
102
|
+
send(event, ...args) {
|
103
|
+
const newState = this.#dispatch(event, ...args);
|
104
|
+
if (newState && newState !== this.#current) {
|
105
|
+
this.#transition(newState, event, args);
|
106
|
+
}
|
107
|
+
|
108
|
+
return this.#current;
|
109
|
+
}
|
110
|
+
/**
|
111
|
+
* Debounces the triggering of an event.
|
112
|
+
*
|
113
|
+
* @param {number} wait
|
114
|
+
* @param {string} event
|
115
|
+
* @param {any[]} args
|
116
|
+
*/
|
117
|
+
async debounce(wait = 500, event, ...args) {
|
118
|
+
if (this.#timeout[event]) {
|
119
|
+
clearTimeout(this.#timeout[event]);
|
120
|
+
}
|
121
|
+
return new Promise((resolve) => {
|
122
|
+
this.#timeout[event] = setTimeout(() => {
|
123
|
+
delete this.#timeout[event];
|
124
|
+
resolve(this.send(event, ...args));
|
125
|
+
}, wait);
|
126
|
+
});
|
127
|
+
}
|
128
|
+
|
129
|
+
/** The current state. */
|
130
|
+
get current() {
|
131
|
+
return this.#current;
|
132
|
+
}
|
133
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
export { default as FiniteStateMachine } from "./FiniteStateMachine.svelte";
|
@@ -0,0 +1 @@
|
|
1
|
+
export { default as FiniteStateMachine } from './FiniteStateMachine.svelte';
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import {
|
2
|
+
NetworkLoader,
|
3
|
+
ERROR_NOT_LOADED,
|
4
|
+
ERROR_TRANSFERRED
|
5
|
+
} from '../network-loader/index.js';
|
6
|
+
|
7
|
+
/**
|
8
|
+
* ImageLoader instance
|
9
|
+
* - Loads image data from network
|
10
|
+
* - The loading process can be monitored
|
11
|
+
*/
|
12
|
+
export default class ImageLoader extends NetworkLoader {} // end class
|