@hkdigital/lib-sveltekit 0.2.21 → 0.2.22

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