@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.
Files changed (209) hide show
  1. package/README.md +135 -135
  2. package/dist/assets/autospuiten/car-paint-picker.js +41 -41
  3. package/dist/assets/autospuiten/labels.js +7 -7
  4. package/dist/classes/data/IterableTree.js +243 -243
  5. package/dist/classes/data/Selector.js +190 -190
  6. package/dist/classes/data/index.js +2 -2
  7. package/dist/classes/index.js +4 -4
  8. package/dist/classes/promise/HkPromise.js +377 -377
  9. package/dist/classes/promise/index.js +1 -1
  10. package/dist/classes/stores/SubscribersCount.js +107 -107
  11. package/dist/classes/stores/index.js +1 -1
  12. package/dist/classes/streams/LogTransformStream.js +19 -19
  13. package/dist/classes/streams/ServerEventsStore.js +110 -110
  14. package/dist/classes/streams/TimeStampSource.js +26 -26
  15. package/dist/classes/streams/index.js +3 -3
  16. package/dist/classes/svelte/audio/AudioLoader.svelte.js +58 -58
  17. package/dist/classes/svelte/audio/AudioScene.svelte.js +295 -295
  18. package/dist/classes/svelte/audio/mocks.js +35 -35
  19. package/dist/classes/svelte/finite-state-machine/FiniteStateMachine.svelte.js +133 -133
  20. package/dist/classes/svelte/finite-state-machine/index.js +1 -1
  21. package/dist/classes/svelte/image/ImageLoader.svelte.js +47 -47
  22. package/dist/classes/svelte/image/ImageScene.svelte.js +253 -253
  23. package/dist/classes/svelte/image/ImageVariantsLoader.svelte.js +152 -152
  24. package/dist/classes/svelte/image/index.js +4 -4
  25. package/dist/classes/svelte/image/mocks.js +35 -35
  26. package/dist/classes/svelte/image/typedef.js +8 -8
  27. package/dist/classes/svelte/index.js +14 -14
  28. package/dist/classes/svelte/loading-state-machine/LoadingStateMachine.svelte.js +109 -109
  29. package/dist/classes/svelte/loading-state-machine/constants.js +16 -16
  30. package/dist/classes/svelte/loading-state-machine/index.js +3 -3
  31. package/dist/classes/svelte/network-loader/NetworkLoader.svelte.js +331 -331
  32. package/dist/classes/svelte/network-loader/constants.js +3 -3
  33. package/dist/classes/svelte/network-loader/index.js +3 -3
  34. package/dist/classes/svelte/network-loader/mocks.js +30 -30
  35. package/dist/classes/svelte/network-loader/typedef.js +8 -8
  36. package/dist/components/area/HkArea.svelte +49 -49
  37. package/dist/components/area/HkGridArea.svelte +77 -77
  38. package/dist/components/area/index.js +2 -2
  39. package/dist/components/buttons/button/Button.svelte +82 -82
  40. package/dist/components/buttons/button-icon-steeze/SteezeIconButton.svelte +30 -30
  41. package/dist/components/buttons/button-text/TextButton.svelte +21 -21
  42. package/dist/components/buttons/index.js +3 -3
  43. package/dist/components/debug/debug-panel-design-scaling/DebugPanelDesignScaling.svelte +146 -146
  44. package/dist/components/debug/index.js +1 -1
  45. package/dist/components/hkdev/blocks/TextBlock.svelte +46 -46
  46. package/dist/components/hkdev/buttons/CheckButton.svelte +62 -62
  47. package/dist/components/icons/HkIcon.svelte +86 -86
  48. package/dist/components/icons/HkTabIcon.svelte +116 -116
  49. package/dist/components/icons/SteezeIcon.svelte +97 -97
  50. package/dist/components/icons/index.js +6 -6
  51. package/dist/components/icons/typedef.js +16 -16
  52. package/dist/components/index.js +2 -2
  53. package/dist/components/inputs/index.js +1 -1
  54. package/dist/components/inputs/text-input/TestTextInput.svelte__ +102 -102
  55. package/dist/components/inputs/text-input/TextInput.svelte +223 -223
  56. package/dist/components/inputs/text-input/TextInput.svelte___ +83 -83
  57. package/dist/components/inputs/text-input/assets/IconInvalid.svelte +14 -14
  58. package/dist/components/inputs/text-input/assets/IconValid.svelte +12 -12
  59. package/dist/components/layout/grid-layers/GridLayers.svelte +167 -167
  60. package/dist/components/layout/index.js +1 -1
  61. package/dist/components/panels/index.js +1 -1
  62. package/dist/components/panels/panel/Panel.svelte +43 -43
  63. package/dist/components/rows/index.js +3 -3
  64. package/dist/components/rows/panel-grid-row/PanelGridRow.svelte +104 -104
  65. package/dist/components/rows/panel-row-2/PanelRow2.svelte +40 -40
  66. package/dist/components/tab-bar/HkTabBar.state.svelte.js +149 -149
  67. package/dist/components/tab-bar/HkTabBar.svelte +74 -74
  68. package/dist/components/tab-bar/HkTabBarSelector.state.svelte.js +93 -93
  69. package/dist/components/tab-bar/HkTabBarSelector.svelte +49 -49
  70. package/dist/components/tab-bar/index.js +17 -17
  71. package/dist/components/tab-bar/typedef.js +8 -8
  72. package/dist/config/imagetools-config.js +189 -189
  73. package/dist/config/imagetools.d.ts +71 -71
  74. package/dist/config/typedef.js +8 -8
  75. package/dist/constants/bases.js +13 -13
  76. package/dist/constants/errors/api.js +9 -9
  77. package/dist/constants/errors/generic.js +5 -5
  78. package/dist/constants/errors/index.js +3 -3
  79. package/dist/constants/errors/jwt.js +5 -5
  80. package/dist/constants/http/headers.js +6 -6
  81. package/dist/constants/http/index.js +2 -2
  82. package/dist/constants/http/methods.js +2 -2
  83. package/dist/constants/index.js +3 -3
  84. package/dist/constants/mime/application.js +5 -5
  85. package/dist/constants/mime/audio.js +13 -13
  86. package/dist/constants/mime/image.js +3 -3
  87. package/dist/constants/mime/index.js +4 -4
  88. package/dist/constants/mime/text.js +2 -2
  89. package/dist/constants/regexp/index.js +31 -31
  90. package/dist/constants/regexp/inspiratie.js__ +95 -95
  91. package/dist/constants/regexp/text.js +49 -49
  92. package/dist/constants/regexp/user.js +32 -32
  93. package/dist/constants/regexp/web.js +3 -3
  94. package/dist/constants/state-labels/input-states.js +11 -11
  95. package/dist/constants/state-labels/submit-states.js +4 -4
  96. package/dist/constants/time.js +28 -28
  97. package/dist/css/utilities.css +43 -43
  98. package/dist/design/design-config.js +73 -73
  99. package/dist/design/tailwind-theme-extend.js +158 -158
  100. package/dist/schemas/index.js +1 -1
  101. package/dist/schemas/validate-url.js +180 -180
  102. package/dist/server/index.js +1 -1
  103. package/dist/server/logger.js +94 -94
  104. package/dist/states/index.js +1 -1
  105. package/dist/states/navigation.svelte.js +55 -55
  106. package/dist/stores/index.js +1 -1
  107. package/dist/stores/theme.js +80 -80
  108. package/dist/themes/hkdev/components/blocks/text-block.css +41 -41
  109. package/dist/themes/hkdev/components/boxes/game-box.css +12 -12
  110. package/dist/themes/hkdev/components/buttons/button-icon-steeze.css +22 -22
  111. package/dist/themes/hkdev/components/buttons/button-text.css +32 -32
  112. package/dist/themes/hkdev/components/buttons/button.css +142 -142
  113. package/dist/themes/hkdev/components/buttons/skip-button.css +6 -6
  114. package/dist/themes/hkdev/components/icons/icon-steeze.css +22 -22
  115. package/dist/themes/hkdev/components/inputs/text-input.css +104 -104
  116. package/dist/themes/hkdev/components/panels/panel.css +27 -27
  117. package/dist/themes/hkdev/components/rows/panel-grid-row.css +6 -6
  118. package/dist/themes/hkdev/components/rows/panel-row-2.css +7 -7
  119. package/dist/themes/hkdev/components.css +47 -47
  120. package/dist/themes/hkdev/debug.css +1 -1
  121. package/dist/themes/hkdev/global/layout.css +39 -39
  122. package/dist/themes/hkdev/global/on-colors.css +53 -53
  123. package/dist/themes/hkdev/globals.css +11 -11
  124. package/dist/themes/hkdev/responsive.css +12 -12
  125. package/dist/themes/hkdev/theme-ext.js +15 -15
  126. package/dist/themes/hkdev/theme.js +235 -235
  127. package/dist/themes/index.js +1 -1
  128. package/dist/util/array/index.js +455 -455
  129. package/dist/util/bases/base58.js +262 -262
  130. package/dist/util/bases/index.js +1 -1
  131. package/dist/util/compare/index.js +247 -247
  132. package/dist/util/css/css-vars.js +83 -83
  133. package/dist/util/css/index.js +1 -1
  134. package/dist/util/design-system/components/states.js +22 -22
  135. package/dist/util/design-system/css/clamp.js +66 -66
  136. package/dist/util/design-system/css/root-design-vars.js +100 -100
  137. package/dist/util/design-system/index.js +5 -5
  138. package/dist/util/design-system/layout/scaling.js +228 -228
  139. package/dist/util/design-system/skeleton.js +208 -208
  140. package/dist/util/design-system/tailwind.js +288 -288
  141. package/dist/util/expect/arrays.js +47 -47
  142. package/dist/util/expect/index.js +259 -259
  143. package/dist/util/expect/primitives.js +55 -55
  144. package/dist/util/expect/url.js +60 -60
  145. package/dist/util/function/index.js +218 -218
  146. package/dist/util/http/errors.js +97 -97
  147. package/dist/util/http/headers.js +45 -45
  148. package/dist/util/http/http-request.js +294 -294
  149. package/dist/util/http/index.js +22 -22
  150. package/dist/util/http/json-request.js +143 -143
  151. package/dist/util/http/mocks.js +65 -65
  152. package/dist/util/http/response.js +241 -241
  153. package/dist/util/http/test-data__/content-length-test-hkdigital-small.V4HfZyBQ.avif +0 -0
  154. package/dist/util/http/url.js +52 -52
  155. package/dist/util/image/index.js +86 -86
  156. package/dist/util/index.js +2 -2
  157. package/dist/util/is/index.js +140 -140
  158. package/dist/util/iterate/index.js +234 -234
  159. package/dist/util/object/index.js +1361 -1361
  160. package/dist/util/singleton/index.js +97 -97
  161. package/dist/util/string/array-path.js +75 -75
  162. package/dist/util/string/convert.js +54 -54
  163. package/dist/util/string/fs.js +226 -226
  164. package/dist/util/string/index.js +5 -5
  165. package/dist/util/string/interpolate.js +61 -61
  166. package/dist/util/string/pad.js +10 -10
  167. package/dist/util/svelte/index.js +4 -4
  168. package/dist/util/svelte/loading/loading-tracker.svelte.js +108 -108
  169. package/dist/util/svelte/observe/index.js +49 -49
  170. package/dist/util/svelte/state-context/index.js +83 -83
  171. package/dist/util/svelte/wait/index.js +38 -38
  172. package/dist/util/sveltekit/index.js +1 -1
  173. package/dist/util/sveltekit/route-folders/index.js +101 -101
  174. package/dist/util/time/index.js +323 -323
  175. package/dist/util/unique/index.js +249 -249
  176. package/dist/valibot/date.js__ +10 -10
  177. package/dist/valibot/index.js +9 -9
  178. package/dist/valibot/url.js +95 -95
  179. package/dist/valibot/user.js +23 -23
  180. package/dist/widgets/button-group/ButtonGroup.svelte +82 -94
  181. package/dist/widgets/button-group/ButtonGroup.svelte.d.ts +0 -2
  182. package/dist/widgets/button-group/typedef.js +10 -10
  183. package/dist/widgets/compare-left-right/CompareLeftRight.svelte +179 -179
  184. package/dist/widgets/compare-left-right/index.js +1 -1
  185. package/dist/widgets/game-box/GameBox.svelte +579 -186
  186. package/dist/widgets/game-box/GameBox.svelte.d.ts +64 -4
  187. package/dist/widgets/game-box/gamebox.util.js +83 -83
  188. package/dist/widgets/hk-app-layout/HkAppLayout.state.svelte.js +25 -25
  189. package/dist/widgets/hk-app-layout/HkAppLayout.svelte +251 -251
  190. package/dist/widgets/image-box/ImageBox.svelte +212 -212
  191. package/dist/widgets/image-box/index.js +5 -5
  192. package/dist/widgets/image-box/typedef.js +32 -32
  193. package/dist/widgets/index.js +23 -23
  194. package/dist/widgets/presenter/(broken) Presenter.state.svelte.js__ +613 -0
  195. package/dist/widgets/presenter/ImageSlide.svelte +64 -64
  196. package/dist/widgets/presenter/Presenter.state.svelte.js +636 -636
  197. package/dist/widgets/presenter/Presenter.svelte +140 -140
  198. package/dist/widgets/presenter/Presenter.svelte__ +125 -0
  199. package/dist/widgets/presenter/constants.js +7 -7
  200. package/dist/widgets/presenter/index.js +10 -10
  201. package/dist/widgets/presenter/typedef.js +106 -106
  202. package/dist/widgets/presenter/util.js +210 -210
  203. package/dist/widgets/virtual-viewport/VirtualViewport.svelte +196 -196
  204. package/dist/zod/all.js +33 -33
  205. package/dist/zod/generic.js +11 -11
  206. package/dist/zod/javascript.js +32 -32
  207. package/dist/zod/user.js +16 -16
  208. package/dist/zod/web.js +52 -52
  209. 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
+ }