@hkdigital/lib-sveltekit 0.1.62 → 0.1.65
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/README.md +135 -135
- package/dist/assets/autospuiten/car-paint-picker.js +41 -41
- package/dist/assets/autospuiten/labels.js +7 -7
- package/dist/classes/data/IterableTree.js +243 -243
- package/dist/classes/data/Selector.js +190 -190
- package/dist/classes/data/index.js +2 -2
- package/dist/classes/index.js +4 -4
- package/dist/classes/promise/HkPromise.js +377 -377
- package/dist/classes/promise/index.js +1 -1
- package/dist/classes/stores/SubscribersCount.js +107 -107
- package/dist/classes/stores/index.js +1 -1
- package/dist/classes/streams/LogTransformStream.js +19 -19
- package/dist/classes/streams/ServerEventsStore.js +110 -110
- package/dist/classes/streams/TimeStampSource.js +26 -26
- package/dist/classes/streams/index.js +3 -3
- package/dist/classes/svelte/audio/AudioLoader.svelte.js +58 -58
- package/dist/classes/svelte/audio/AudioScene.svelte.js +295 -295
- package/dist/classes/svelte/audio/mocks.js +35 -35
- package/dist/classes/svelte/finite-state-machine/FiniteStateMachine.svelte.js +133 -133
- package/dist/classes/svelte/finite-state-machine/index.js +1 -1
- package/dist/classes/svelte/image/ImageLoader.svelte.js +47 -47
- package/dist/classes/svelte/image/ImageScene.svelte.js +253 -253
- package/dist/classes/svelte/image/ImageVariantsLoader.svelte.js +152 -152
- package/dist/classes/svelte/image/index.js +4 -4
- package/dist/classes/svelte/image/mocks.js +35 -35
- package/dist/classes/svelte/image/typedef.js +8 -8
- package/dist/classes/svelte/index.js +14 -14
- package/dist/classes/svelte/loading-state-machine/LoadingStateMachine.svelte.js +109 -109
- package/dist/classes/svelte/loading-state-machine/constants.js +16 -16
- package/dist/classes/svelte/loading-state-machine/index.js +3 -3
- package/dist/classes/svelte/network-loader/NetworkLoader.svelte.js +331 -331
- package/dist/classes/svelte/network-loader/constants.js +3 -3
- package/dist/classes/svelte/network-loader/index.js +3 -3
- package/dist/classes/svelte/network-loader/mocks.js +30 -30
- package/dist/classes/svelte/network-loader/typedef.js +8 -8
- package/dist/components/area/HkArea.svelte +49 -49
- package/dist/components/area/HkGridArea.svelte +77 -77
- package/dist/components/area/index.js +2 -2
- package/dist/components/buttons/button/Button.svelte +82 -82
- package/dist/components/buttons/button-icon-steeze/SteezeIconButton.svelte +30 -30
- package/dist/components/buttons/button-text/TextButton.svelte +21 -21
- package/dist/components/buttons/index.js +3 -3
- package/dist/components/debug/debug-panel-design-scaling/DebugPanelDesignScaling.svelte +146 -146
- package/dist/components/debug/index.js +1 -1
- package/dist/components/hkdev/blocks/TextBlock.svelte +46 -46
- package/dist/components/hkdev/buttons/CheckButton.svelte +62 -62
- package/dist/components/icons/HkIcon.svelte +86 -86
- package/dist/components/icons/HkTabIcon.svelte +116 -116
- package/dist/components/icons/SteezeIcon.svelte +97 -97
- package/dist/components/icons/index.js +6 -6
- package/dist/components/icons/typedef.js +16 -16
- package/dist/components/index.js +2 -2
- package/dist/components/inputs/index.js +1 -1
- package/dist/components/inputs/text-input/TestTextInput.svelte__ +102 -102
- package/dist/components/inputs/text-input/TextInput.svelte +223 -223
- package/dist/components/inputs/text-input/TextInput.svelte___ +83 -83
- package/dist/components/inputs/text-input/assets/IconInvalid.svelte +14 -14
- package/dist/components/inputs/text-input/assets/IconValid.svelte +12 -12
- package/dist/components/layout/grid-layers/GridLayers.svelte +167 -167
- package/dist/components/layout/index.js +1 -1
- package/dist/components/panels/index.js +1 -1
- package/dist/components/panels/panel/Panel.svelte +43 -43
- package/dist/components/rows/index.js +3 -3
- package/dist/components/rows/panel-grid-row/PanelGridRow.svelte +104 -104
- package/dist/components/rows/panel-row-2/PanelRow2.svelte +40 -40
- package/dist/components/tab-bar/HkTabBar.state.svelte.js +149 -149
- package/dist/components/tab-bar/HkTabBar.svelte +74 -74
- package/dist/components/tab-bar/HkTabBarSelector.state.svelte.js +93 -93
- package/dist/components/tab-bar/HkTabBarSelector.svelte +49 -49
- package/dist/components/tab-bar/index.js +17 -17
- package/dist/components/tab-bar/typedef.js +8 -8
- package/dist/config/imagetools-config.js +189 -189
- package/dist/config/imagetools.d.ts +71 -71
- package/dist/config/typedef.js +8 -8
- package/dist/constants/bases.js +13 -13
- package/dist/constants/errors/api.js +9 -9
- package/dist/constants/errors/generic.js +5 -5
- package/dist/constants/errors/index.js +3 -3
- package/dist/constants/errors/jwt.js +5 -5
- package/dist/constants/http/headers.js +6 -6
- package/dist/constants/http/index.js +2 -2
- package/dist/constants/http/methods.js +2 -2
- package/dist/constants/index.js +3 -3
- package/dist/constants/mime/application.js +5 -5
- package/dist/constants/mime/audio.js +13 -13
- package/dist/constants/mime/image.js +3 -3
- package/dist/constants/mime/index.js +4 -4
- package/dist/constants/mime/text.js +2 -2
- package/dist/constants/regexp/index.js +31 -31
- package/dist/constants/regexp/inspiratie.js__ +95 -95
- package/dist/constants/regexp/text.js +49 -49
- package/dist/constants/regexp/user.js +32 -32
- package/dist/constants/regexp/web.js +3 -3
- package/dist/constants/state-labels/input-states.js +11 -11
- package/dist/constants/state-labels/submit-states.js +4 -4
- package/dist/constants/time.js +28 -28
- package/dist/css/utilities.css +43 -43
- package/dist/design/design-config.js +73 -73
- package/dist/design/tailwind-theme-extend.js +158 -158
- package/dist/schemas/index.js +1 -1
- package/dist/schemas/validate-url.js +180 -180
- package/dist/server/index.js +1 -1
- package/dist/server/logger.js +94 -94
- package/dist/states/index.js +1 -1
- package/dist/states/navigation.svelte.js +55 -55
- package/dist/stores/index.js +1 -1
- package/dist/stores/theme.js +80 -80
- package/dist/themes/hkdev/components/blocks/text-block.css +41 -41
- package/dist/themes/hkdev/components/boxes/game-box.css +12 -12
- package/dist/themes/hkdev/components/buttons/button-icon-steeze.css +22 -22
- package/dist/themes/hkdev/components/buttons/button-text.css +32 -32
- package/dist/themes/hkdev/components/buttons/button.css +142 -142
- package/dist/themes/hkdev/components/buttons/skip-button.css +6 -6
- package/dist/themes/hkdev/components/icons/icon-steeze.css +22 -22
- package/dist/themes/hkdev/components/inputs/text-input.css +104 -104
- package/dist/themes/hkdev/components/panels/panel.css +27 -27
- package/dist/themes/hkdev/components/rows/panel-grid-row.css +6 -6
- package/dist/themes/hkdev/components/rows/panel-row-2.css +7 -7
- package/dist/themes/hkdev/components.css +47 -47
- package/dist/themes/hkdev/debug.css +1 -1
- package/dist/themes/hkdev/global/layout.css +39 -39
- package/dist/themes/hkdev/global/on-colors.css +53 -53
- package/dist/themes/hkdev/globals.css +11 -11
- package/dist/themes/hkdev/responsive.css +12 -12
- package/dist/themes/hkdev/theme-ext.js +15 -15
- package/dist/themes/hkdev/theme.js +235 -235
- package/dist/themes/index.js +1 -1
- package/dist/util/array/index.js +455 -455
- package/dist/util/bases/base58.js +262 -262
- package/dist/util/bases/index.js +1 -1
- package/dist/util/compare/index.js +247 -247
- package/dist/util/css/css-vars.js +83 -83
- package/dist/util/css/index.js +1 -1
- package/dist/util/design-system/components/states.js +22 -22
- package/dist/util/design-system/css/clamp.js +66 -66
- package/dist/util/design-system/css/root-design-vars.js +100 -100
- package/dist/util/design-system/index.js +5 -5
- package/dist/util/design-system/layout/scaling.js +228 -228
- package/dist/util/design-system/skeleton.js +208 -208
- package/dist/util/design-system/tailwind.js +288 -288
- package/dist/util/expect/arrays.js +47 -47
- package/dist/util/expect/index.js +259 -259
- package/dist/util/expect/primitives.js +55 -55
- package/dist/util/expect/url.js +60 -60
- package/dist/util/function/index.js +218 -218
- package/dist/util/http/errors.js +97 -97
- package/dist/util/http/headers.js +45 -45
- package/dist/util/http/http-request.js +294 -294
- package/dist/util/http/index.js +22 -22
- package/dist/util/http/json-request.js +143 -143
- package/dist/util/http/mocks.js +65 -65
- package/dist/util/http/response.js +241 -241
- package/dist/util/http/test-data__/content-length-test-hkdigital-small.V4HfZyBQ.avif +0 -0
- package/dist/util/http/url.js +52 -52
- package/dist/util/image/index.js +86 -86
- package/dist/util/index.js +2 -2
- package/dist/util/is/index.js +140 -140
- package/dist/util/iterate/index.js +234 -234
- package/dist/util/object/index.js +1361 -1361
- package/dist/util/singleton/index.js +97 -97
- package/dist/util/string/array-path.js +75 -75
- package/dist/util/string/convert.js +54 -54
- package/dist/util/string/fs.js +226 -226
- package/dist/util/string/index.js +5 -5
- package/dist/util/string/interpolate.js +61 -61
- package/dist/util/string/pad.js +10 -10
- package/dist/util/svelte/index.js +4 -4
- package/dist/util/svelte/loading/loading-tracker.svelte.js +108 -108
- package/dist/util/svelte/observe/index.js +49 -49
- package/dist/util/svelte/state-context/index.js +83 -83
- package/dist/util/svelte/wait/index.js +38 -38
- package/dist/util/sveltekit/index.js +1 -1
- package/dist/util/sveltekit/route-folders/index.js +101 -101
- package/dist/util/time/index.js +323 -323
- package/dist/util/unique/index.js +249 -249
- package/dist/valibot/date.js__ +10 -10
- package/dist/valibot/index.js +9 -9
- package/dist/valibot/url.js +95 -95
- package/dist/valibot/user.js +23 -23
- package/dist/widgets/button-group/ButtonGroup.svelte +82 -94
- package/dist/widgets/button-group/ButtonGroup.svelte.d.ts +0 -2
- package/dist/widgets/button-group/typedef.js +10 -10
- package/dist/widgets/compare-left-right/CompareLeftRight.svelte +179 -179
- package/dist/widgets/compare-left-right/index.js +1 -1
- package/dist/widgets/game-box/GameBox.svelte +579 -186
- package/dist/widgets/game-box/GameBox.svelte.d.ts +64 -4
- package/dist/widgets/game-box/gamebox.util.js +83 -83
- package/dist/widgets/hk-app-layout/HkAppLayout.state.svelte.js +25 -25
- package/dist/widgets/hk-app-layout/HkAppLayout.svelte +251 -251
- package/dist/widgets/image-box/ImageBox.svelte +212 -212
- package/dist/widgets/image-box/index.js +5 -5
- package/dist/widgets/image-box/typedef.js +32 -32
- package/dist/widgets/index.js +23 -23
- package/dist/widgets/presenter/(broken) Presenter.state.svelte.js__ +613 -0
- package/dist/widgets/presenter/ImageSlide.svelte +64 -64
- package/dist/widgets/presenter/Presenter.state.svelte.js +636 -636
- package/dist/widgets/presenter/Presenter.svelte +140 -140
- package/dist/widgets/presenter/Presenter.svelte__ +125 -0
- package/dist/widgets/presenter/constants.js +7 -7
- package/dist/widgets/presenter/index.js +10 -10
- package/dist/widgets/presenter/typedef.js +106 -106
- package/dist/widgets/presenter/util.js +210 -210
- package/dist/widgets/virtual-viewport/VirtualViewport.svelte +196 -196
- package/dist/zod/all.js +33 -33
- package/dist/zod/generic.js +11 -11
- package/dist/zod/javascript.js +32 -32
- package/dist/zod/user.js +16 -16
- package/dist/zod/web.js +52 -52
- package/package.json +102 -102
@@ -1,636 +1,636 @@
|
|
1
|
-
import { tick } from 'svelte';
|
2
|
-
|
3
|
-
import { findFirst } from '../../util/array/index.js';
|
4
|
-
|
5
|
-
import { untrack } from 'svelte';
|
6
|
-
|
7
|
-
import { HkPromise } from '../../classes/promise/index.js';
|
8
|
-
|
9
|
-
import { STAGE_BEFORE, STAGE_SHOW } from './constants.js';
|
10
|
-
|
11
|
-
/**
|
12
|
-
* @typedef {import("./typedef").Slide} Slide
|
13
|
-
*/
|
14
|
-
|
15
|
-
/**
|
16
|
-
* @typedef {import("./typedef").Transition} Transition
|
17
|
-
*/
|
18
|
-
|
19
|
-
/**
|
20
|
-
* @typedef {import("./typedef").Layer} Layer
|
21
|
-
*/
|
22
|
-
|
23
|
-
/**
|
24
|
-
* @typedef {import("./typedef").PresenterRef} PresenterRef
|
25
|
-
*/
|
26
|
-
|
27
|
-
/**
|
28
|
-
* @typedef {import("./typedef").LoadController} LoadController
|
29
|
-
*/
|
30
|
-
|
31
|
-
/**
|
32
|
-
* @typedef {import("./typedef").ListenerParams} ListenerParams
|
33
|
-
*/
|
34
|
-
|
35
|
-
const Z_BACK = 0;
|
36
|
-
const Z_FRONT = 10;
|
37
|
-
|
38
|
-
const LABEL_A = 'A';
|
39
|
-
const LABEL_B = 'B';
|
40
|
-
|
41
|
-
export class PresenterState {
|
42
|
-
/** @type {Slide[]} */
|
43
|
-
slides = $state.raw([]);
|
44
|
-
|
45
|
-
/** @type {Layer} */
|
46
|
-
layerA = $state.raw({ z: Z_BACK, visible: false, stageIdle: true });
|
47
|
-
|
48
|
-
/** @type {Layer} */
|
49
|
-
layerB = $state.raw({ z: Z_FRONT, visible: false, stageIdle: true });
|
50
|
-
|
51
|
-
/** @type {Slide|null} */
|
52
|
-
slideA = $state.raw(null);
|
53
|
-
|
54
|
-
/** @type {Slide|null} */
|
55
|
-
slideB = $state.raw(null);
|
56
|
-
|
57
|
-
/** @type {string} */
|
58
|
-
currentLayerLabel = $state(LABEL_B);
|
59
|
-
|
60
|
-
/** @type {string} */
|
61
|
-
nextLayerLabel = $state(LABEL_A);
|
62
|
-
|
63
|
-
/** @type {HkPromise[]} */
|
64
|
-
transitionPromises = $state.raw([]);
|
65
|
-
|
66
|
-
/** @type {HkPromise} */
|
67
|
-
slideLoadingPromise = null;
|
68
|
-
|
69
|
-
/** @type {boolean} */
|
70
|
-
isSlideLoading = $state(false);
|
71
|
-
|
72
|
-
/** @type {boolean} */
|
73
|
-
loadingSpinner = $state(false);
|
74
|
-
|
75
|
-
/** @type {boolean} */
|
76
|
-
busy = $derived.by(() => {
|
77
|
-
const { layerA, layerB, isSlideLoading } = this;
|
78
|
-
|
79
|
-
const layerAStable =
|
80
|
-
layerA.stageShow || layerA.stageAfter || layerA.stageIdle;
|
81
|
-
const layerBStable =
|
82
|
-
layerB.stageShow || layerB.stageAfter || layerB.stageIdle;
|
83
|
-
|
84
|
-
return !(layerAStable && layerBStable) || isSlideLoading;
|
85
|
-
});
|
86
|
-
|
87
|
-
/** @type {string} */
|
88
|
-
currentSlideName = $derived.by(() => {
|
89
|
-
const currentSlide = this.#getSlide(this.currentLayerLabel);
|
90
|
-
return currentSlide?.name || '';
|
91
|
-
});
|
92
|
-
|
93
|
-
/** @type {string} */
|
94
|
-
nextSlideName;
|
95
|
-
|
96
|
-
/** @type {string} */
|
97
|
-
pendingSlideName;
|
98
|
-
|
99
|
-
/** @type {boolean} */
|
100
|
-
configured = false;
|
101
|
-
|
102
|
-
/** @type {Map<Symbol, ( params: ListenerParams ) => void>} */
|
103
|
-
onBeforeListeners = new Map();
|
104
|
-
|
105
|
-
/** @type {Map<Symbol, ( params: ListenerParams ) => void>} */
|
106
|
-
onShowListeners = new Map();
|
107
|
-
|
108
|
-
/**
|
109
|
-
* Initialize the presenter state and set up reactivity
|
110
|
-
*/
|
111
|
-
constructor() {
|
112
|
-
// this.#setupStageTransitions();
|
113
|
-
|
114
|
-
let timeout;
|
115
|
-
|
116
|
-
$effect((slideLoadingPromise) => {
|
117
|
-
if (this.isSlideLoading) {
|
118
|
-
// Enable spinner after a short delay
|
119
|
-
clearTimeout(timeout);
|
120
|
-
setTimeout(() => {
|
121
|
-
untrack(() => {
|
122
|
-
if (this.isSlideLoading) {
|
123
|
-
this.loadingSpinner = true;
|
124
|
-
} else {
|
125
|
-
this.loadingSpinner = false;
|
126
|
-
}
|
127
|
-
});
|
128
|
-
}, 500);
|
129
|
-
} else {
|
130
|
-
this.loadingSpinner = false;
|
131
|
-
}
|
132
|
-
});
|
133
|
-
}
|
134
|
-
|
135
|
-
/**
|
136
|
-
* Set up reactivity for stage transitions between the before/after states
|
137
|
-
* This handles the animation timing for both layers
|
138
|
-
*/
|
139
|
-
// #setupStageTransitions() {
|
140
|
-
// // Handle layer A stage transitions
|
141
|
-
// $effect(() => {
|
142
|
-
// if (this.layerA.stageBeforeIn || this.layerA.stageBeforeOut) {
|
143
|
-
// this.layerA = this.#processStageTransition(this.layerA);
|
144
|
-
// }
|
145
|
-
// });
|
146
|
-
|
147
|
-
// // Handle layer B stage transitions
|
148
|
-
// $effect(() => {
|
149
|
-
// if (this.layerB.stageBeforeIn || this.layerB.stageBeforeOut) {
|
150
|
-
// this.layerB = this.#processStageTransition(this.layerB);
|
151
|
-
// }
|
152
|
-
// });
|
153
|
-
// }
|
154
|
-
|
155
|
-
/**
|
156
|
-
* Process a single stage transition for a layer
|
157
|
-
*
|
158
|
-
* @param {Layer} layer - The layer to process
|
159
|
-
* @returns {Layer} - The updated layer with new stage
|
160
|
-
*/
|
161
|
-
#processStageTransition(layer) {
|
162
|
-
const updatedLayer = { ...layer };
|
163
|
-
|
164
|
-
if (updatedLayer.stageBeforeIn) {
|
165
|
-
delete updatedLayer.stageBeforeIn;
|
166
|
-
updatedLayer.stageIn = true;
|
167
|
-
} else if (updatedLayer.stageBeforeOut) {
|
168
|
-
delete updatedLayer.stageBeforeOut;
|
169
|
-
updatedLayer.stageOut = true;
|
170
|
-
}
|
171
|
-
|
172
|
-
return updatedLayer;
|
173
|
-
}
|
174
|
-
|
175
|
-
/**
|
176
|
-
* Waiting for all transition timing promises to finish,
|
177
|
-
* this should be the same amount of time as it takes for the real
|
178
|
-
* transitions to finish
|
179
|
-
*
|
180
|
-
* @param {HkPromise[]} promises
|
181
|
-
* Array of transition promises to wait for
|
182
|
-
*/
|
183
|
-
async #waitForTransitionPromises(promises) {
|
184
|
-
try {
|
185
|
-
// console.debug('waitForTransitionPromises', promises);
|
186
|
-
|
187
|
-
await Promise.allSettled(promises);
|
188
|
-
|
189
|
-
// console.debug('waitForTransitionPromises:done', promises);
|
190
|
-
|
191
|
-
untrack(() => {
|
192
|
-
this.#completeTransition();
|
193
|
-
});
|
194
|
-
} catch (error) {
|
195
|
-
console.log('transition promises cancelled', error);
|
196
|
-
}
|
197
|
-
}
|
198
|
-
|
199
|
-
/**
|
200
|
-
* Complete the transition by updating layers and swapping them
|
201
|
-
*/
|
202
|
-
#completeTransition() {
|
203
|
-
// Hide current layer and set stage to AFTER
|
204
|
-
this.#updateLayer(this.currentLayerLabel, {
|
205
|
-
z: Z_BACK,
|
206
|
-
visible: false,
|
207
|
-
stageAfter: true
|
208
|
-
});
|
209
|
-
|
210
|
-
// Set next layer stage to SHOW
|
211
|
-
this.#updateLayer(this.nextLayerLabel, {
|
212
|
-
z: Z_FRONT,
|
213
|
-
visible: true,
|
214
|
-
stageShow: true
|
215
|
-
});
|
216
|
-
|
217
|
-
// Remove slide from current layer
|
218
|
-
this.#updateSlide(this.currentLayerLabel, null);
|
219
|
-
|
220
|
-
// Swap current and next layer labels
|
221
|
-
this.#swapLayers();
|
222
|
-
}
|
223
|
-
|
224
|
-
/**
|
225
|
-
* Swap the current and next layer labels
|
226
|
-
*/
|
227
|
-
#swapLayers() {
|
228
|
-
if (this.currentLayerLabel === LABEL_A) {
|
229
|
-
this.currentLayerLabel = LABEL_B;
|
230
|
-
this.nextLayerLabel = LABEL_A;
|
231
|
-
} else {
|
232
|
-
this.currentLayerLabel = LABEL_A;
|
233
|
-
this.nextLayerLabel = LABEL_B;
|
234
|
-
}
|
235
|
-
}
|
236
|
-
|
237
|
-
/**
|
238
|
-
* Configure the presentation
|
239
|
-
*
|
240
|
-
* @param {object} _
|
241
|
-
* @param {Slide[]} [_.slides] - Array of slides for the presentation
|
242
|
-
*/
|
243
|
-
configure({ slides }) {
|
244
|
-
this.configured = true;
|
245
|
-
|
246
|
-
if (slides) {
|
247
|
-
// Only update slides if provided
|
248
|
-
this.slides = slides;
|
249
|
-
}
|
250
|
-
}
|
251
|
-
|
252
|
-
/**
|
253
|
-
* Configure the presentation slides
|
254
|
-
*
|
255
|
-
* @param {Slide[]} slides - Array of slides for the presentation
|
256
|
-
*/
|
257
|
-
configureSlides(slides) {
|
258
|
-
this.slides = slides ?? [];
|
259
|
-
}
|
260
|
-
|
261
|
-
/**
|
262
|
-
* Transition to another slide by name
|
263
|
-
*
|
264
|
-
* @param {string} name - Name of the slide to transition to
|
265
|
-
*/
|
266
|
-
async gotoSlide(name) {
|
267
|
-
// throw new Error('gotoSlide');
|
268
|
-
|
269
|
-
untrack(() => {
|
270
|
-
const slide = findFirst(this.slides, { name });
|
271
|
-
|
272
|
-
if (!slide) {
|
273
|
-
console.log('available slides', this.slides);
|
274
|
-
throw new Error(`Slide [${name}] has not been defined`);
|
275
|
-
}
|
276
|
-
|
277
|
-
this.#gotoSlide(slide);
|
278
|
-
});
|
279
|
-
}
|
280
|
-
|
281
|
-
/**
|
282
|
-
* Internal method to transition to another slide
|
283
|
-
*
|
284
|
-
* @param {Slide} slide - The slide to transition to
|
285
|
-
*/
|
286
|
-
async #gotoSlide(slide) {
|
287
|
-
if (!this.configured) {
|
288
|
-
throw new Error('Not configured yet');
|
289
|
-
}
|
290
|
-
|
291
|
-
if (slide.name === this.currentSlideName) {
|
292
|
-
throw new Error(`gotoSlide cannot transition to current slide`);
|
293
|
-
}
|
294
|
-
|
295
|
-
this.nextSlideName = slide.name;
|
296
|
-
|
297
|
-
if (this.busy) {
|
298
|
-
this.pendingSlideName = slide.name;
|
299
|
-
return;
|
300
|
-
}
|
301
|
-
|
302
|
-
this.#callOnBeforeListeners();
|
303
|
-
|
304
|
-
this.slideLoadingPromise = null;
|
305
|
-
|
306
|
-
// Get a presenter reference to pass to the slide
|
307
|
-
const presenterRef = this.getPresenterRef();
|
308
|
-
|
309
|
-
// Create a copy of the slide to avoid mutating the original
|
310
|
-
const slideWithProps = {
|
311
|
-
...slide,
|
312
|
-
data: {
|
313
|
-
...slide.data,
|
314
|
-
props: {
|
315
|
-
...(slide.data.props || {}),
|
316
|
-
getLoadingController: () => {
|
317
|
-
this.isSlideLoading = true;
|
318
|
-
this.slideLoadingPromise = new HkPromise(() => {});
|
319
|
-
|
320
|
-
return this.#getLoadingController();
|
321
|
-
// this.slideLoadingPromise should be a HkPromise now
|
322
|
-
// console.log('slideLoadingPromise', this.slideLoadingPromise);
|
323
|
-
},
|
324
|
-
presenter: presenterRef // Add presenter reference to props
|
325
|
-
}
|
326
|
-
}
|
327
|
-
};
|
328
|
-
|
329
|
-
// console.debug('Checkpoint 1');
|
330
|
-
|
331
|
-
// Add next slide to next layer
|
332
|
-
this.#updateSlide(this.nextLayerLabel, slideWithProps);
|
333
|
-
|
334
|
-
// console.debug('Checkpoint 2');
|
335
|
-
|
336
|
-
await tick();
|
337
|
-
|
338
|
-
// console.debug('Checkpoint 3');
|
339
|
-
|
340
|
-
if (this.slideLoadingPromise) {
|
341
|
-
// console.debug('Waiting for slide to load');
|
342
|
-
// @ts-ignore
|
343
|
-
await this.slideLoadingPromise;
|
344
|
-
this.isSlideLoading = false;
|
345
|
-
// console.debug('Done waiting for slide loading');
|
346
|
-
}
|
347
|
-
|
348
|
-
const currentSlide = this.#getSlide(this.currentLayerLabel);
|
349
|
-
const nextSlide = this.#getSlide(this.nextLayerLabel);
|
350
|
-
|
351
|
-
// console.debug('Checkpoint 4', currentSlide, nextSlide);
|
352
|
-
|
353
|
-
// Make next layer visible, move to front, and prepare for
|
354
|
-
// transition in
|
355
|
-
this.#updateLayer(this.nextLayerLabel, {
|
356
|
-
z: Z_FRONT,
|
357
|
-
visible: true,
|
358
|
-
stageBeforeIn: true,
|
359
|
-
transitions: nextSlide?.intro ?? []
|
360
|
-
});
|
361
|
-
|
362
|
-
// Move current layer to back, keep visible, and prepare for
|
363
|
-
// transition out
|
364
|
-
this.#updateLayer(this.currentLayerLabel, {
|
365
|
-
z: Z_BACK,
|
366
|
-
visible: true,
|
367
|
-
stageBeforeOut: true,
|
368
|
-
transitions: currentSlide?.outro ?? []
|
369
|
-
});
|
370
|
-
|
371
|
-
// console.debug('Checkpoint 5');
|
372
|
-
|
373
|
-
// Wait briefly to ensure the stageBeforeIn/stageBeforeOut states are rendered
|
374
|
-
await tick();
|
375
|
-
|
376
|
-
// Now manually process the transitions for both layers
|
377
|
-
const layerA = this.layerA;
|
378
|
-
const layerB = this.layerB;
|
379
|
-
|
380
|
-
// Process stageBeforeIn transition for both layers
|
381
|
-
if (layerA.stageBeforeIn) {
|
382
|
-
this.layerA = this.#processStageTransition(layerA);
|
383
|
-
}
|
384
|
-
|
385
|
-
if (layerB.stageBeforeIn) {
|
386
|
-
this.layerB = this.#processStageTransition(layerB);
|
387
|
-
}
|
388
|
-
|
389
|
-
// Wait for another tick to ensure the stageIn states are rendered
|
390
|
-
await tick();
|
391
|
-
|
392
|
-
// Process stageBeforeOut transition for both layers
|
393
|
-
if (layerA.stageBeforeOut) {
|
394
|
-
this.layerA = this.#processStageTransition(layerA);
|
395
|
-
}
|
396
|
-
|
397
|
-
if (layerB.stageBeforeOut) {
|
398
|
-
this.layerB = this.#processStageTransition(layerB);
|
399
|
-
}
|
400
|
-
|
401
|
-
// console.debug('Checkpoint 7');
|
402
|
-
|
403
|
-
// Start transitions
|
404
|
-
this.#createTransitionPromises();
|
405
|
-
|
406
|
-
// console.debug('Checkpoint 8');
|
407
|
-
|
408
|
-
await this.#waitForTransitionPromises(this.transitionPromises);
|
409
|
-
|
410
|
-
// Check if there's a pending slide transition
|
411
|
-
if (this.pendingSlideName) {
|
412
|
-
const pendingName = this.pendingSlideName;
|
413
|
-
|
414
|
-
this.nextSlideName = pendingName;
|
415
|
-
this.pendingSlideName = null;
|
416
|
-
|
417
|
-
untrack(() => {
|
418
|
-
if (pendingName !== this.currentSlideName) {
|
419
|
-
this.gotoSlide(pendingName);
|
420
|
-
}
|
421
|
-
});
|
422
|
-
} else {
|
423
|
-
this.nextSlideName = null;
|
424
|
-
}
|
425
|
-
|
426
|
-
this.#callOnShowListeners();
|
427
|
-
}
|
428
|
-
|
429
|
-
#callOnBeforeListeners() {
|
430
|
-
let nextSlideName = this.nextSlideName;
|
431
|
-
|
432
|
-
for (const fn of this.onBeforeListeners.values()) {
|
433
|
-
fn({ stage: STAGE_BEFORE, slideName: nextSlideName });
|
434
|
-
}
|
435
|
-
}
|
436
|
-
|
437
|
-
#callOnShowListeners() {
|
438
|
-
let currentSlideName = this.currentSlideName;
|
439
|
-
|
440
|
-
for (const fn of this.onShowListeners.values()) {
|
441
|
-
fn({ stage: STAGE_SHOW, slideName: currentSlideName });
|
442
|
-
}
|
443
|
-
}
|
444
|
-
|
445
|
-
/**
|
446
|
-
* Create transition promises that can be used to determine the timing
|
447
|
-
* of the transitions between current and next slide
|
448
|
-
*/
|
449
|
-
#createTransitionPromises() {
|
450
|
-
// Cancel existing transitions
|
451
|
-
let transitionPromises = this.transitionPromises;
|
452
|
-
|
453
|
-
for (const current of transitionPromises) {
|
454
|
-
current.tryCancel();
|
455
|
-
}
|
456
|
-
|
457
|
-
// Start new transitions
|
458
|
-
transitionPromises = [];
|
459
|
-
|
460
|
-
const currentSlide = this.#getSlide(this.currentLayerLabel);
|
461
|
-
const nextSlide = this.#getSlide(this.nextLayerLabel);
|
462
|
-
|
463
|
-
// Apply transitions `out` from currentslide
|
464
|
-
const transitionsOut = currentSlide?.outro;
|
465
|
-
|
466
|
-
// console.log('transitionsOut', transitionsOut);
|
467
|
-
|
468
|
-
if (transitionsOut) {
|
469
|
-
for (const transition of transitionsOut) {
|
470
|
-
const promise = this.#createTransitionPromise(transition);
|
471
|
-
transitionPromises.push(promise);
|
472
|
-
}
|
473
|
-
}
|
474
|
-
|
475
|
-
// Apply transitions `in` from next slide
|
476
|
-
const transitionsIn = nextSlide?.intro;
|
477
|
-
|
478
|
-
// console.log('transitionsIn', transitionsIn);
|
479
|
-
|
480
|
-
if (transitionsIn) {
|
481
|
-
for (const transition of transitionsIn) {
|
482
|
-
const promise = this.#createTransitionPromise(transition);
|
483
|
-
transitionPromises.push(promise);
|
484
|
-
}
|
485
|
-
}
|
486
|
-
|
487
|
-
this.transitionPromises = transitionPromises;
|
488
|
-
}
|
489
|
-
|
490
|
-
/**
|
491
|
-
* Create a transition promise for the specified transition
|
492
|
-
*
|
493
|
-
*
|
494
|
-
* @param {Transition} transition - The transition to apply
|
495
|
-
*
|
496
|
-
* @returns {HkPromise}
|
497
|
-
* Promise that resolves after the same amount of time that it
|
498
|
-
* takes for the transition to finish
|
499
|
-
*/
|
500
|
-
#createTransitionPromise(transition) {
|
501
|
-
const delay = (transition.delay ?? 0) + (transition.duration ?? 0);
|
502
|
-
|
503
|
-
if (0 === delay) {
|
504
|
-
const promise = new HkPromise(() => {});
|
505
|
-
promise.resolve(true);
|
506
|
-
return promise;
|
507
|
-
}
|
508
|
-
|
509
|
-
let promise = new HkPromise((/** @type {function} */ resolve) => {
|
510
|
-
if (delay) {
|
511
|
-
setTimeout(() => {
|
512
|
-
resolve(true);
|
513
|
-
}, delay);
|
514
|
-
}
|
515
|
-
});
|
516
|
-
|
517
|
-
return promise;
|
518
|
-
}
|
519
|
-
|
520
|
-
/**
|
521
|
-
* Get slide by layer label
|
522
|
-
*
|
523
|
-
* @param {string} label - Layer label (A or B)
|
524
|
-
* @returns {Slide|null} The slide for the specified layer or null
|
525
|
-
*/
|
526
|
-
#getSlide(label) {
|
527
|
-
if (label === LABEL_A) {
|
528
|
-
return this.slideA;
|
529
|
-
}
|
530
|
-
|
531
|
-
if (label === LABEL_B) {
|
532
|
-
return this.slideB;
|
533
|
-
}
|
534
|
-
|
535
|
-
return null;
|
536
|
-
}
|
537
|
-
|
538
|
-
/**
|
539
|
-
* Update layer by label
|
540
|
-
*
|
541
|
-
* @param {string} label - Layer label (A or B)
|
542
|
-
* @param {Layer} data - Layer data to update
|
543
|
-
*/
|
544
|
-
#updateLayer(label, data) {
|
545
|
-
if (label === LABEL_A) {
|
546
|
-
this.layerA = data;
|
547
|
-
return;
|
548
|
-
}
|
549
|
-
|
550
|
-
if (label === LABEL_B) {
|
551
|
-
this.layerB = data;
|
552
|
-
return;
|
553
|
-
}
|
554
|
-
|
555
|
-
throw new Error(`Missing layer [${label}]`);
|
556
|
-
}
|
557
|
-
|
558
|
-
/**
|
559
|
-
* Update slide by label
|
560
|
-
*
|
561
|
-
* @param {string} label - Layer label (A or B)
|
562
|
-
* @param {Slide|null} data - Slide data to update or null to clear
|
563
|
-
*/
|
564
|
-
#updateSlide(label, data) {
|
565
|
-
if (label === LABEL_A) {
|
566
|
-
this.slideA = data;
|
567
|
-
return;
|
568
|
-
}
|
569
|
-
|
570
|
-
if (label === LABEL_B) {
|
571
|
-
this.slideB = data;
|
572
|
-
return;
|
573
|
-
}
|
574
|
-
|
575
|
-
throw new Error(`Missing slide [${label}]`);
|
576
|
-
}
|
577
|
-
|
578
|
-
/**
|
579
|
-
* Returns a simplified presenter reference with essential methods
|
580
|
-
* for slide components to use
|
581
|
-
*
|
582
|
-
* @returns {PresenterRef} A reference object with presenter methods
|
583
|
-
*/
|
584
|
-
getPresenterRef() {
|
585
|
-
return {
|
586
|
-
gotoSlide: (name) => this.gotoSlide(name),
|
587
|
-
getCurrentSlideName: () => this.currentSlideName,
|
588
|
-
onBefore: (callback) => {
|
589
|
-
const key = Symbol();
|
590
|
-
this.onBeforeListeners.set(key, callback);
|
591
|
-
|
592
|
-
return () => {
|
593
|
-
this.onBeforeListeners.delete(key);
|
594
|
-
};
|
595
|
-
},
|
596
|
-
onShow: (callback) => {
|
597
|
-
const key = Symbol();
|
598
|
-
this.onShowListeners.set(key, callback);
|
599
|
-
|
600
|
-
return () => {
|
601
|
-
this.onShowListeners.delete(key);
|
602
|
-
};
|
603
|
-
}
|
604
|
-
};
|
605
|
-
}
|
606
|
-
|
607
|
-
/**
|
608
|
-
* Returns a controller object for managing manual loading
|
609
|
-
* Components can use this to signal when they're done loading
|
610
|
-
* or to cancel and go back to the previous slide
|
611
|
-
*
|
612
|
-
* @returns {LoadController}
|
613
|
-
* Object with loaded() and cancel() methods
|
614
|
-
*/
|
615
|
-
#getLoadingController() {
|
616
|
-
// console.debug('getLoadingController was called');
|
617
|
-
|
618
|
-
return {
|
619
|
-
/**
|
620
|
-
* Call when component has finished loading
|
621
|
-
*/
|
622
|
-
loaded: () => {
|
623
|
-
// console.debug('Slide said loading has completed');
|
624
|
-
this.slideLoadingPromise?.tryResolve();
|
625
|
-
},
|
626
|
-
|
627
|
-
/**
|
628
|
-
* Call to cancel loading and return to previous slide
|
629
|
-
*/
|
630
|
-
cancel: () => {
|
631
|
-
// console.debug('Slide said loading has cancelled');
|
632
|
-
this.slideLoadingPromise?.tryReject();
|
633
|
-
}
|
634
|
-
};
|
635
|
-
}
|
636
|
-
}
|
1
|
+
import { tick } from 'svelte';
|
2
|
+
|
3
|
+
import { findFirst } from '../../util/array/index.js';
|
4
|
+
|
5
|
+
import { untrack } from 'svelte';
|
6
|
+
|
7
|
+
import { HkPromise } from '../../classes/promise/index.js';
|
8
|
+
|
9
|
+
import { STAGE_BEFORE, STAGE_SHOW } from './constants.js';
|
10
|
+
|
11
|
+
/**
|
12
|
+
* @typedef {import("./typedef").Slide} Slide
|
13
|
+
*/
|
14
|
+
|
15
|
+
/**
|
16
|
+
* @typedef {import("./typedef").Transition} Transition
|
17
|
+
*/
|
18
|
+
|
19
|
+
/**
|
20
|
+
* @typedef {import("./typedef").Layer} Layer
|
21
|
+
*/
|
22
|
+
|
23
|
+
/**
|
24
|
+
* @typedef {import("./typedef").PresenterRef} PresenterRef
|
25
|
+
*/
|
26
|
+
|
27
|
+
/**
|
28
|
+
* @typedef {import("./typedef").LoadController} LoadController
|
29
|
+
*/
|
30
|
+
|
31
|
+
/**
|
32
|
+
* @typedef {import("./typedef").ListenerParams} ListenerParams
|
33
|
+
*/
|
34
|
+
|
35
|
+
const Z_BACK = 0;
|
36
|
+
const Z_FRONT = 10;
|
37
|
+
|
38
|
+
const LABEL_A = 'A';
|
39
|
+
const LABEL_B = 'B';
|
40
|
+
|
41
|
+
export class PresenterState {
|
42
|
+
/** @type {Slide[]} */
|
43
|
+
slides = $state.raw([]);
|
44
|
+
|
45
|
+
/** @type {Layer} */
|
46
|
+
layerA = $state.raw({ z: Z_BACK, visible: false, stageIdle: true });
|
47
|
+
|
48
|
+
/** @type {Layer} */
|
49
|
+
layerB = $state.raw({ z: Z_FRONT, visible: false, stageIdle: true });
|
50
|
+
|
51
|
+
/** @type {Slide|null} */
|
52
|
+
slideA = $state.raw(null);
|
53
|
+
|
54
|
+
/** @type {Slide|null} */
|
55
|
+
slideB = $state.raw(null);
|
56
|
+
|
57
|
+
/** @type {string} */
|
58
|
+
currentLayerLabel = $state(LABEL_B);
|
59
|
+
|
60
|
+
/** @type {string} */
|
61
|
+
nextLayerLabel = $state(LABEL_A);
|
62
|
+
|
63
|
+
/** @type {HkPromise[]} */
|
64
|
+
transitionPromises = $state.raw([]);
|
65
|
+
|
66
|
+
/** @type {HkPromise} */
|
67
|
+
slideLoadingPromise = null;
|
68
|
+
|
69
|
+
/** @type {boolean} */
|
70
|
+
isSlideLoading = $state(false);
|
71
|
+
|
72
|
+
/** @type {boolean} */
|
73
|
+
loadingSpinner = $state(false);
|
74
|
+
|
75
|
+
/** @type {boolean} */
|
76
|
+
busy = $derived.by(() => {
|
77
|
+
const { layerA, layerB, isSlideLoading } = this;
|
78
|
+
|
79
|
+
const layerAStable =
|
80
|
+
layerA.stageShow || layerA.stageAfter || layerA.stageIdle;
|
81
|
+
const layerBStable =
|
82
|
+
layerB.stageShow || layerB.stageAfter || layerB.stageIdle;
|
83
|
+
|
84
|
+
return !(layerAStable && layerBStable) || isSlideLoading;
|
85
|
+
});
|
86
|
+
|
87
|
+
/** @type {string} */
|
88
|
+
currentSlideName = $derived.by(() => {
|
89
|
+
const currentSlide = this.#getSlide(this.currentLayerLabel);
|
90
|
+
return currentSlide?.name || '';
|
91
|
+
});
|
92
|
+
|
93
|
+
/** @type {string} */
|
94
|
+
nextSlideName;
|
95
|
+
|
96
|
+
/** @type {string} */
|
97
|
+
pendingSlideName;
|
98
|
+
|
99
|
+
/** @type {boolean} */
|
100
|
+
configured = false;
|
101
|
+
|
102
|
+
/** @type {Map<Symbol, ( params: ListenerParams ) => void>} */
|
103
|
+
onBeforeListeners = new Map();
|
104
|
+
|
105
|
+
/** @type {Map<Symbol, ( params: ListenerParams ) => void>} */
|
106
|
+
onShowListeners = new Map();
|
107
|
+
|
108
|
+
/**
|
109
|
+
* Initialize the presenter state and set up reactivity
|
110
|
+
*/
|
111
|
+
constructor() {
|
112
|
+
// this.#setupStageTransitions();
|
113
|
+
|
114
|
+
let timeout;
|
115
|
+
|
116
|
+
$effect((slideLoadingPromise) => {
|
117
|
+
if (this.isSlideLoading) {
|
118
|
+
// Enable spinner after a short delay
|
119
|
+
clearTimeout(timeout);
|
120
|
+
setTimeout(() => {
|
121
|
+
untrack(() => {
|
122
|
+
if (this.isSlideLoading) {
|
123
|
+
this.loadingSpinner = true;
|
124
|
+
} else {
|
125
|
+
this.loadingSpinner = false;
|
126
|
+
}
|
127
|
+
});
|
128
|
+
}, 500);
|
129
|
+
} else {
|
130
|
+
this.loadingSpinner = false;
|
131
|
+
}
|
132
|
+
});
|
133
|
+
}
|
134
|
+
|
135
|
+
/**
|
136
|
+
* Set up reactivity for stage transitions between the before/after states
|
137
|
+
* This handles the animation timing for both layers
|
138
|
+
*/
|
139
|
+
// #setupStageTransitions() {
|
140
|
+
// // Handle layer A stage transitions
|
141
|
+
// $effect(() => {
|
142
|
+
// if (this.layerA.stageBeforeIn || this.layerA.stageBeforeOut) {
|
143
|
+
// this.layerA = this.#processStageTransition(this.layerA);
|
144
|
+
// }
|
145
|
+
// });
|
146
|
+
|
147
|
+
// // Handle layer B stage transitions
|
148
|
+
// $effect(() => {
|
149
|
+
// if (this.layerB.stageBeforeIn || this.layerB.stageBeforeOut) {
|
150
|
+
// this.layerB = this.#processStageTransition(this.layerB);
|
151
|
+
// }
|
152
|
+
// });
|
153
|
+
// }
|
154
|
+
|
155
|
+
/**
|
156
|
+
* Process a single stage transition for a layer
|
157
|
+
*
|
158
|
+
* @param {Layer} layer - The layer to process
|
159
|
+
* @returns {Layer} - The updated layer with new stage
|
160
|
+
*/
|
161
|
+
#processStageTransition(layer) {
|
162
|
+
const updatedLayer = { ...layer };
|
163
|
+
|
164
|
+
if (updatedLayer.stageBeforeIn) {
|
165
|
+
delete updatedLayer.stageBeforeIn;
|
166
|
+
updatedLayer.stageIn = true;
|
167
|
+
} else if (updatedLayer.stageBeforeOut) {
|
168
|
+
delete updatedLayer.stageBeforeOut;
|
169
|
+
updatedLayer.stageOut = true;
|
170
|
+
}
|
171
|
+
|
172
|
+
return updatedLayer;
|
173
|
+
}
|
174
|
+
|
175
|
+
/**
|
176
|
+
* Waiting for all transition timing promises to finish,
|
177
|
+
* this should be the same amount of time as it takes for the real
|
178
|
+
* transitions to finish
|
179
|
+
*
|
180
|
+
* @param {HkPromise[]} promises
|
181
|
+
* Array of transition promises to wait for
|
182
|
+
*/
|
183
|
+
async #waitForTransitionPromises(promises) {
|
184
|
+
try {
|
185
|
+
// console.debug('waitForTransitionPromises', promises);
|
186
|
+
|
187
|
+
await Promise.allSettled(promises);
|
188
|
+
|
189
|
+
// console.debug('waitForTransitionPromises:done', promises);
|
190
|
+
|
191
|
+
untrack(() => {
|
192
|
+
this.#completeTransition();
|
193
|
+
});
|
194
|
+
} catch (error) {
|
195
|
+
console.log('transition promises cancelled', error);
|
196
|
+
}
|
197
|
+
}
|
198
|
+
|
199
|
+
/**
|
200
|
+
* Complete the transition by updating layers and swapping them
|
201
|
+
*/
|
202
|
+
#completeTransition() {
|
203
|
+
// Hide current layer and set stage to AFTER
|
204
|
+
this.#updateLayer(this.currentLayerLabel, {
|
205
|
+
z: Z_BACK,
|
206
|
+
visible: false,
|
207
|
+
stageAfter: true
|
208
|
+
});
|
209
|
+
|
210
|
+
// Set next layer stage to SHOW
|
211
|
+
this.#updateLayer(this.nextLayerLabel, {
|
212
|
+
z: Z_FRONT,
|
213
|
+
visible: true,
|
214
|
+
stageShow: true
|
215
|
+
});
|
216
|
+
|
217
|
+
// Remove slide from current layer
|
218
|
+
this.#updateSlide(this.currentLayerLabel, null);
|
219
|
+
|
220
|
+
// Swap current and next layer labels
|
221
|
+
this.#swapLayers();
|
222
|
+
}
|
223
|
+
|
224
|
+
/**
|
225
|
+
* Swap the current and next layer labels
|
226
|
+
*/
|
227
|
+
#swapLayers() {
|
228
|
+
if (this.currentLayerLabel === LABEL_A) {
|
229
|
+
this.currentLayerLabel = LABEL_B;
|
230
|
+
this.nextLayerLabel = LABEL_A;
|
231
|
+
} else {
|
232
|
+
this.currentLayerLabel = LABEL_A;
|
233
|
+
this.nextLayerLabel = LABEL_B;
|
234
|
+
}
|
235
|
+
}
|
236
|
+
|
237
|
+
/**
|
238
|
+
* Configure the presentation
|
239
|
+
*
|
240
|
+
* @param {object} _
|
241
|
+
* @param {Slide[]} [_.slides] - Array of slides for the presentation
|
242
|
+
*/
|
243
|
+
configure({ slides }) {
|
244
|
+
this.configured = true;
|
245
|
+
|
246
|
+
if (slides) {
|
247
|
+
// Only update slides if provided
|
248
|
+
this.slides = slides;
|
249
|
+
}
|
250
|
+
}
|
251
|
+
|
252
|
+
/**
|
253
|
+
* Configure the presentation slides
|
254
|
+
*
|
255
|
+
* @param {Slide[]} slides - Array of slides for the presentation
|
256
|
+
*/
|
257
|
+
configureSlides(slides) {
|
258
|
+
this.slides = slides ?? [];
|
259
|
+
}
|
260
|
+
|
261
|
+
/**
|
262
|
+
* Transition to another slide by name
|
263
|
+
*
|
264
|
+
* @param {string} name - Name of the slide to transition to
|
265
|
+
*/
|
266
|
+
async gotoSlide(name) {
|
267
|
+
// throw new Error('gotoSlide');
|
268
|
+
|
269
|
+
untrack(() => {
|
270
|
+
const slide = findFirst(this.slides, { name });
|
271
|
+
|
272
|
+
if (!slide) {
|
273
|
+
console.log('available slides', this.slides);
|
274
|
+
throw new Error(`Slide [${name}] has not been defined`);
|
275
|
+
}
|
276
|
+
|
277
|
+
this.#gotoSlide(slide);
|
278
|
+
});
|
279
|
+
}
|
280
|
+
|
281
|
+
/**
|
282
|
+
* Internal method to transition to another slide
|
283
|
+
*
|
284
|
+
* @param {Slide} slide - The slide to transition to
|
285
|
+
*/
|
286
|
+
async #gotoSlide(slide) {
|
287
|
+
if (!this.configured) {
|
288
|
+
throw new Error('Not configured yet');
|
289
|
+
}
|
290
|
+
|
291
|
+
if (slide.name === this.currentSlideName) {
|
292
|
+
throw new Error(`gotoSlide cannot transition to current slide`);
|
293
|
+
}
|
294
|
+
|
295
|
+
this.nextSlideName = slide.name;
|
296
|
+
|
297
|
+
if (this.busy) {
|
298
|
+
this.pendingSlideName = slide.name;
|
299
|
+
return;
|
300
|
+
}
|
301
|
+
|
302
|
+
this.#callOnBeforeListeners();
|
303
|
+
|
304
|
+
this.slideLoadingPromise = null;
|
305
|
+
|
306
|
+
// Get a presenter reference to pass to the slide
|
307
|
+
const presenterRef = this.getPresenterRef();
|
308
|
+
|
309
|
+
// Create a copy of the slide to avoid mutating the original
|
310
|
+
const slideWithProps = {
|
311
|
+
...slide,
|
312
|
+
data: {
|
313
|
+
...slide.data,
|
314
|
+
props: {
|
315
|
+
...(slide.data.props || {}),
|
316
|
+
getLoadingController: () => {
|
317
|
+
this.isSlideLoading = true;
|
318
|
+
this.slideLoadingPromise = new HkPromise(() => {});
|
319
|
+
|
320
|
+
return this.#getLoadingController();
|
321
|
+
// this.slideLoadingPromise should be a HkPromise now
|
322
|
+
// console.log('slideLoadingPromise', this.slideLoadingPromise);
|
323
|
+
},
|
324
|
+
presenter: presenterRef // Add presenter reference to props
|
325
|
+
}
|
326
|
+
}
|
327
|
+
};
|
328
|
+
|
329
|
+
// console.debug('Checkpoint 1');
|
330
|
+
|
331
|
+
// Add next slide to next layer
|
332
|
+
this.#updateSlide(this.nextLayerLabel, slideWithProps);
|
333
|
+
|
334
|
+
// console.debug('Checkpoint 2');
|
335
|
+
|
336
|
+
await tick();
|
337
|
+
|
338
|
+
// console.debug('Checkpoint 3');
|
339
|
+
|
340
|
+
if (this.slideLoadingPromise) {
|
341
|
+
// console.debug('Waiting for slide to load');
|
342
|
+
// @ts-ignore
|
343
|
+
await this.slideLoadingPromise;
|
344
|
+
this.isSlideLoading = false;
|
345
|
+
// console.debug('Done waiting for slide loading');
|
346
|
+
}
|
347
|
+
|
348
|
+
const currentSlide = this.#getSlide(this.currentLayerLabel);
|
349
|
+
const nextSlide = this.#getSlide(this.nextLayerLabel);
|
350
|
+
|
351
|
+
// console.debug('Checkpoint 4', currentSlide, nextSlide);
|
352
|
+
|
353
|
+
// Make next layer visible, move to front, and prepare for
|
354
|
+
// transition in
|
355
|
+
this.#updateLayer(this.nextLayerLabel, {
|
356
|
+
z: Z_FRONT,
|
357
|
+
visible: true,
|
358
|
+
stageBeforeIn: true,
|
359
|
+
transitions: nextSlide?.intro ?? []
|
360
|
+
});
|
361
|
+
|
362
|
+
// Move current layer to back, keep visible, and prepare for
|
363
|
+
// transition out
|
364
|
+
this.#updateLayer(this.currentLayerLabel, {
|
365
|
+
z: Z_BACK,
|
366
|
+
visible: true,
|
367
|
+
stageBeforeOut: true,
|
368
|
+
transitions: currentSlide?.outro ?? []
|
369
|
+
});
|
370
|
+
|
371
|
+
// console.debug('Checkpoint 5');
|
372
|
+
|
373
|
+
// Wait briefly to ensure the stageBeforeIn/stageBeforeOut states are rendered
|
374
|
+
await tick();
|
375
|
+
|
376
|
+
// Now manually process the transitions for both layers
|
377
|
+
const layerA = this.layerA;
|
378
|
+
const layerB = this.layerB;
|
379
|
+
|
380
|
+
// Process stageBeforeIn transition for both layers
|
381
|
+
if (layerA.stageBeforeIn) {
|
382
|
+
this.layerA = this.#processStageTransition(layerA);
|
383
|
+
}
|
384
|
+
|
385
|
+
if (layerB.stageBeforeIn) {
|
386
|
+
this.layerB = this.#processStageTransition(layerB);
|
387
|
+
}
|
388
|
+
|
389
|
+
// Wait for another tick to ensure the stageIn states are rendered
|
390
|
+
await tick();
|
391
|
+
|
392
|
+
// Process stageBeforeOut transition for both layers
|
393
|
+
if (layerA.stageBeforeOut) {
|
394
|
+
this.layerA = this.#processStageTransition(layerA);
|
395
|
+
}
|
396
|
+
|
397
|
+
if (layerB.stageBeforeOut) {
|
398
|
+
this.layerB = this.#processStageTransition(layerB);
|
399
|
+
}
|
400
|
+
|
401
|
+
// console.debug('Checkpoint 7');
|
402
|
+
|
403
|
+
// Start transitions
|
404
|
+
this.#createTransitionPromises();
|
405
|
+
|
406
|
+
// console.debug('Checkpoint 8');
|
407
|
+
|
408
|
+
await this.#waitForTransitionPromises(this.transitionPromises);
|
409
|
+
|
410
|
+
// Check if there's a pending slide transition
|
411
|
+
if (this.pendingSlideName) {
|
412
|
+
const pendingName = this.pendingSlideName;
|
413
|
+
|
414
|
+
this.nextSlideName = pendingName;
|
415
|
+
this.pendingSlideName = null;
|
416
|
+
|
417
|
+
untrack(() => {
|
418
|
+
if (pendingName !== this.currentSlideName) {
|
419
|
+
this.gotoSlide(pendingName);
|
420
|
+
}
|
421
|
+
});
|
422
|
+
} else {
|
423
|
+
this.nextSlideName = null;
|
424
|
+
}
|
425
|
+
|
426
|
+
this.#callOnShowListeners();
|
427
|
+
}
|
428
|
+
|
429
|
+
#callOnBeforeListeners() {
|
430
|
+
let nextSlideName = this.nextSlideName;
|
431
|
+
|
432
|
+
for (const fn of this.onBeforeListeners.values()) {
|
433
|
+
fn({ stage: STAGE_BEFORE, slideName: nextSlideName });
|
434
|
+
}
|
435
|
+
}
|
436
|
+
|
437
|
+
#callOnShowListeners() {
|
438
|
+
let currentSlideName = this.currentSlideName;
|
439
|
+
|
440
|
+
for (const fn of this.onShowListeners.values()) {
|
441
|
+
fn({ stage: STAGE_SHOW, slideName: currentSlideName });
|
442
|
+
}
|
443
|
+
}
|
444
|
+
|
445
|
+
/**
|
446
|
+
* Create transition promises that can be used to determine the timing
|
447
|
+
* of the transitions between current and next slide
|
448
|
+
*/
|
449
|
+
#createTransitionPromises() {
|
450
|
+
// Cancel existing transitions
|
451
|
+
let transitionPromises = this.transitionPromises;
|
452
|
+
|
453
|
+
for (const current of transitionPromises) {
|
454
|
+
current.tryCancel();
|
455
|
+
}
|
456
|
+
|
457
|
+
// Start new transitions
|
458
|
+
transitionPromises = [];
|
459
|
+
|
460
|
+
const currentSlide = this.#getSlide(this.currentLayerLabel);
|
461
|
+
const nextSlide = this.#getSlide(this.nextLayerLabel);
|
462
|
+
|
463
|
+
// Apply transitions `out` from currentslide
|
464
|
+
const transitionsOut = currentSlide?.outro;
|
465
|
+
|
466
|
+
// console.log('transitionsOut', transitionsOut);
|
467
|
+
|
468
|
+
if (transitionsOut) {
|
469
|
+
for (const transition of transitionsOut) {
|
470
|
+
const promise = this.#createTransitionPromise(transition);
|
471
|
+
transitionPromises.push(promise);
|
472
|
+
}
|
473
|
+
}
|
474
|
+
|
475
|
+
// Apply transitions `in` from next slide
|
476
|
+
const transitionsIn = nextSlide?.intro;
|
477
|
+
|
478
|
+
// console.log('transitionsIn', transitionsIn);
|
479
|
+
|
480
|
+
if (transitionsIn) {
|
481
|
+
for (const transition of transitionsIn) {
|
482
|
+
const promise = this.#createTransitionPromise(transition);
|
483
|
+
transitionPromises.push(promise);
|
484
|
+
}
|
485
|
+
}
|
486
|
+
|
487
|
+
this.transitionPromises = transitionPromises;
|
488
|
+
}
|
489
|
+
|
490
|
+
/**
|
491
|
+
* Create a transition promise for the specified transition
|
492
|
+
*
|
493
|
+
*
|
494
|
+
* @param {Transition} transition - The transition to apply
|
495
|
+
*
|
496
|
+
* @returns {HkPromise}
|
497
|
+
* Promise that resolves after the same amount of time that it
|
498
|
+
* takes for the transition to finish
|
499
|
+
*/
|
500
|
+
#createTransitionPromise(transition) {
|
501
|
+
const delay = (transition.delay ?? 0) + (transition.duration ?? 0);
|
502
|
+
|
503
|
+
if (0 === delay) {
|
504
|
+
const promise = new HkPromise(() => {});
|
505
|
+
promise.resolve(true);
|
506
|
+
return promise;
|
507
|
+
}
|
508
|
+
|
509
|
+
let promise = new HkPromise((/** @type {function} */ resolve) => {
|
510
|
+
if (delay) {
|
511
|
+
setTimeout(() => {
|
512
|
+
resolve(true);
|
513
|
+
}, delay);
|
514
|
+
}
|
515
|
+
});
|
516
|
+
|
517
|
+
return promise;
|
518
|
+
}
|
519
|
+
|
520
|
+
/**
|
521
|
+
* Get slide by layer label
|
522
|
+
*
|
523
|
+
* @param {string} label - Layer label (A or B)
|
524
|
+
* @returns {Slide|null} The slide for the specified layer or null
|
525
|
+
*/
|
526
|
+
#getSlide(label) {
|
527
|
+
if (label === LABEL_A) {
|
528
|
+
return this.slideA;
|
529
|
+
}
|
530
|
+
|
531
|
+
if (label === LABEL_B) {
|
532
|
+
return this.slideB;
|
533
|
+
}
|
534
|
+
|
535
|
+
return null;
|
536
|
+
}
|
537
|
+
|
538
|
+
/**
|
539
|
+
* Update layer by label
|
540
|
+
*
|
541
|
+
* @param {string} label - Layer label (A or B)
|
542
|
+
* @param {Layer} data - Layer data to update
|
543
|
+
*/
|
544
|
+
#updateLayer(label, data) {
|
545
|
+
if (label === LABEL_A) {
|
546
|
+
this.layerA = data;
|
547
|
+
return;
|
548
|
+
}
|
549
|
+
|
550
|
+
if (label === LABEL_B) {
|
551
|
+
this.layerB = data;
|
552
|
+
return;
|
553
|
+
}
|
554
|
+
|
555
|
+
throw new Error(`Missing layer [${label}]`);
|
556
|
+
}
|
557
|
+
|
558
|
+
/**
|
559
|
+
* Update slide by label
|
560
|
+
*
|
561
|
+
* @param {string} label - Layer label (A or B)
|
562
|
+
* @param {Slide|null} data - Slide data to update or null to clear
|
563
|
+
*/
|
564
|
+
#updateSlide(label, data) {
|
565
|
+
if (label === LABEL_A) {
|
566
|
+
this.slideA = data;
|
567
|
+
return;
|
568
|
+
}
|
569
|
+
|
570
|
+
if (label === LABEL_B) {
|
571
|
+
this.slideB = data;
|
572
|
+
return;
|
573
|
+
}
|
574
|
+
|
575
|
+
throw new Error(`Missing slide [${label}]`);
|
576
|
+
}
|
577
|
+
|
578
|
+
/**
|
579
|
+
* Returns a simplified presenter reference with essential methods
|
580
|
+
* for slide components to use
|
581
|
+
*
|
582
|
+
* @returns {PresenterRef} A reference object with presenter methods
|
583
|
+
*/
|
584
|
+
getPresenterRef() {
|
585
|
+
return {
|
586
|
+
gotoSlide: (name) => this.gotoSlide(name),
|
587
|
+
getCurrentSlideName: () => this.currentSlideName,
|
588
|
+
onBefore: (callback) => {
|
589
|
+
const key = Symbol();
|
590
|
+
this.onBeforeListeners.set(key, callback);
|
591
|
+
|
592
|
+
return () => {
|
593
|
+
this.onBeforeListeners.delete(key);
|
594
|
+
};
|
595
|
+
},
|
596
|
+
onShow: (callback) => {
|
597
|
+
const key = Symbol();
|
598
|
+
this.onShowListeners.set(key, callback);
|
599
|
+
|
600
|
+
return () => {
|
601
|
+
this.onShowListeners.delete(key);
|
602
|
+
};
|
603
|
+
}
|
604
|
+
};
|
605
|
+
}
|
606
|
+
|
607
|
+
/**
|
608
|
+
* Returns a controller object for managing manual loading
|
609
|
+
* Components can use this to signal when they're done loading
|
610
|
+
* or to cancel and go back to the previous slide
|
611
|
+
*
|
612
|
+
* @returns {LoadController}
|
613
|
+
* Object with loaded() and cancel() methods
|
614
|
+
*/
|
615
|
+
#getLoadingController() {
|
616
|
+
// console.debug('getLoadingController was called');
|
617
|
+
|
618
|
+
return {
|
619
|
+
/**
|
620
|
+
* Call when component has finished loading
|
621
|
+
*/
|
622
|
+
loaded: () => {
|
623
|
+
// console.debug('Slide said loading has completed');
|
624
|
+
this.slideLoadingPromise?.tryResolve();
|
625
|
+
},
|
626
|
+
|
627
|
+
/**
|
628
|
+
* Call to cancel loading and return to previous slide
|
629
|
+
*/
|
630
|
+
cancel: () => {
|
631
|
+
// console.debug('Slide said loading has cancelled');
|
632
|
+
this.slideLoadingPromise?.tryReject();
|
633
|
+
}
|
634
|
+
};
|
635
|
+
}
|
636
|
+
}
|