@hkdigital/lib-core 0.5.39 → 0.5.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ui/components/drag-drop/Draggable.svelte +8 -8
- package/dist/ui/components/game-box/GameBox.svelte +146 -206
- package/dist/ui/components/game-box/GameBox.svelte.d.ts +2 -0
- package/dist/ui/components/game-box/ScaledContainer.svelte +22 -34
- package/dist/ui/components/game-box/ScaledContainer.svelte.d.ts +7 -5
- package/dist/util/unique/index.d.ts +10 -3
- package/dist/util/unique/index.js +26 -7
- package/package.json +1 -1
|
@@ -162,13 +162,13 @@
|
|
|
162
162
|
* @param {DragEvent} event
|
|
163
163
|
*/
|
|
164
164
|
function handleDragStart(event) {
|
|
165
|
-
console.debug('handleDragStart called', {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
});
|
|
165
|
+
// console.debug('handleDragStart called', {
|
|
166
|
+
// target: event.target,
|
|
167
|
+
// currentTarget: event.currentTarget,
|
|
168
|
+
// disabled,
|
|
169
|
+
// canDrag: canDrag(item),
|
|
170
|
+
// dataTransfer: !!event.dataTransfer
|
|
171
|
+
// });
|
|
172
172
|
|
|
173
173
|
if (disabled || !canDrag(item)) {
|
|
174
174
|
console.debug('Drag prevented: disabled or cannot drag');
|
|
@@ -189,7 +189,7 @@
|
|
|
189
189
|
return;
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
console.debug('Starting drag operation');
|
|
192
|
+
// console.debug('Starting drag operation');
|
|
193
193
|
currentState = DRAGGING;
|
|
194
194
|
startDrag(event);
|
|
195
195
|
}
|
|
@@ -12,10 +12,7 @@
|
|
|
12
12
|
getIsMobile
|
|
13
13
|
} from '../../../browser/info/device.js';
|
|
14
14
|
|
|
15
|
-
import {
|
|
16
|
-
getIsPwa,
|
|
17
|
-
getIsFullscreen
|
|
18
|
-
} from '../../../browser/info/display.js';
|
|
15
|
+
import { getIsPwa, getIsFullscreen } from '../../../browser/info/display.js';
|
|
19
16
|
|
|
20
17
|
import ScaledContainer from './ScaledContainer.svelte';
|
|
21
18
|
|
|
@@ -53,6 +50,7 @@
|
|
|
53
50
|
* snippetPortrait?: GameBoxSnippet,
|
|
54
51
|
* snippetRequireFullscreen?: GameBoxSnippet,
|
|
55
52
|
* snippetInstallOnHomeScreen?: GameBoxSnippet,
|
|
53
|
+
* allowDevModeOnLocalhost?: boolean,
|
|
56
54
|
* debug?: boolean,
|
|
57
55
|
* [attr: string]: any
|
|
58
56
|
* }}
|
|
@@ -87,6 +85,7 @@
|
|
|
87
85
|
snippetRequireFullscreen,
|
|
88
86
|
snippetInstallOnHomeScreen,
|
|
89
87
|
|
|
88
|
+
allowDevModeOnLocalhost = true,
|
|
90
89
|
debug = false
|
|
91
90
|
} = $props();
|
|
92
91
|
|
|
@@ -105,23 +104,21 @@
|
|
|
105
104
|
|
|
106
105
|
let isLandscape = $state();
|
|
107
106
|
|
|
108
|
-
let gameWidth = $derived.by(
|
|
109
|
-
if(
|
|
107
|
+
let gameWidth = $derived.by(() => {
|
|
108
|
+
if (isLandscape) {
|
|
110
109
|
return gameWidthOnLandscape;
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
110
|
+
} else {
|
|
113
111
|
return gameWidthOnPortrait;
|
|
114
112
|
}
|
|
115
|
-
}
|
|
113
|
+
});
|
|
116
114
|
|
|
117
|
-
let gameHeight = $derived.by(
|
|
118
|
-
if(
|
|
115
|
+
let gameHeight = $derived.by(() => {
|
|
116
|
+
if (isLandscape) {
|
|
119
117
|
return gameHeightOnLandscape;
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
118
|
+
} else {
|
|
122
119
|
return gameHeightOnPortrait;
|
|
123
120
|
}
|
|
124
|
-
}
|
|
121
|
+
});
|
|
125
122
|
|
|
126
123
|
// iPad is also considered Apple mobile
|
|
127
124
|
const isAppleMobile = getIsAppleMobile();
|
|
@@ -132,12 +129,7 @@
|
|
|
132
129
|
|
|
133
130
|
// Update iOS dimensions when window size changes
|
|
134
131
|
$effect(() => {
|
|
135
|
-
if (
|
|
136
|
-
isPwa &&
|
|
137
|
-
isAppleMobile &&
|
|
138
|
-
windowWidth &&
|
|
139
|
-
windowHeight
|
|
140
|
-
) {
|
|
132
|
+
if (isPwa && isAppleMobile && windowWidth && windowHeight) {
|
|
141
133
|
updateIosWidthHeightAndOrientation();
|
|
142
134
|
}
|
|
143
135
|
});
|
|
@@ -146,8 +138,9 @@
|
|
|
146
138
|
// Use matchMedia as a trigger for orientation changes
|
|
147
139
|
// The actual orientation is determined in updateIosWidthHeightAndOrientation()
|
|
148
140
|
if (typeof window !== 'undefined') {
|
|
149
|
-
const isPortraitMedia =
|
|
150
|
-
|
|
141
|
+
const isPortraitMedia = window.matchMedia(
|
|
142
|
+
'(orientation: portrait)'
|
|
143
|
+
).matches;
|
|
151
144
|
|
|
152
145
|
// Trigger iOS dimension update when orientation might have changed
|
|
153
146
|
if (isPwa && isAppleMobile) {
|
|
@@ -164,10 +157,20 @@
|
|
|
164
157
|
if (debug) {
|
|
165
158
|
console.log('[GameBox] isLandscape:', isLandscape);
|
|
166
159
|
console.log('[GameBox] windowWidth/Height:', windowWidth, windowHeight);
|
|
167
|
-
console.log(
|
|
168
|
-
iosWindowWidth,
|
|
169
|
-
|
|
170
|
-
|
|
160
|
+
console.log(
|
|
161
|
+
'[GameBox] iosWindowWidth/Height:',
|
|
162
|
+
iosWindowWidth,
|
|
163
|
+
iosWindowHeight
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
console.log({
|
|
167
|
+
isPwa,
|
|
168
|
+
isAppleMobile,
|
|
169
|
+
isMobile,
|
|
170
|
+
isIos,
|
|
171
|
+
isAndroid,
|
|
172
|
+
isFullscreen
|
|
173
|
+
});
|
|
171
174
|
}
|
|
172
175
|
});
|
|
173
176
|
|
|
@@ -181,8 +184,7 @@
|
|
|
181
184
|
const availWidth = width - marginLeft - marginRight;
|
|
182
185
|
const availHeight = height - marginTop - marginBottom;
|
|
183
186
|
|
|
184
|
-
if(
|
|
185
|
-
{
|
|
187
|
+
if (debug) {
|
|
186
188
|
console.debug('GameBox margins:', {
|
|
187
189
|
marginLeft,
|
|
188
190
|
marginRight,
|
|
@@ -201,30 +203,24 @@
|
|
|
201
203
|
aspectOnLandscape
|
|
202
204
|
});
|
|
203
205
|
|
|
204
|
-
if(
|
|
205
|
-
{
|
|
206
|
+
if (aspectOnLandscape) {
|
|
206
207
|
gameHeightOnLandscape = gameWidthOnLandscape / aspectOnLandscape;
|
|
207
|
-
}
|
|
208
|
-
else {
|
|
208
|
+
} else {
|
|
209
209
|
gameHeightOnLandscape = availHeight;
|
|
210
210
|
}
|
|
211
|
-
}
|
|
212
|
-
else {
|
|
211
|
+
} else {
|
|
213
212
|
gameWidthOnPortrait = getGameWidthOnPortrait({
|
|
214
213
|
windowWidth: availWidth,
|
|
215
214
|
windowHeight: availHeight,
|
|
216
215
|
aspectOnPortrait
|
|
217
216
|
});
|
|
218
217
|
|
|
219
|
-
if(
|
|
220
|
-
{
|
|
218
|
+
if (aspectOnPortrait) {
|
|
221
219
|
gameHeightOnPortrait = gameWidthOnPortrait / aspectOnPortrait;
|
|
222
|
-
}
|
|
223
|
-
else {
|
|
220
|
+
} else {
|
|
224
221
|
gameHeightOnPortrait = availHeight;
|
|
225
222
|
}
|
|
226
223
|
}
|
|
227
|
-
|
|
228
224
|
});
|
|
229
225
|
|
|
230
226
|
let show = $state(false);
|
|
@@ -242,7 +238,6 @@
|
|
|
242
238
|
|
|
243
239
|
function updateIosWidthHeightAndOrientation() {
|
|
244
240
|
if (isAppleMobile) {
|
|
245
|
-
|
|
246
241
|
// unreliable on ios >>
|
|
247
242
|
// const angle = screen.orientation.angle;
|
|
248
243
|
// if( window.matchMedia('(orientation: portrait)').matches ) {
|
|
@@ -276,16 +271,13 @@
|
|
|
276
271
|
iosWindowWidth = window.innerWidth;
|
|
277
272
|
iosWindowHeight = window.innerHeight;
|
|
278
273
|
|
|
279
|
-
if(
|
|
280
|
-
{
|
|
274
|
+
if (iosWindowHeight > iosWindowWidth) {
|
|
281
275
|
isLandscape = false;
|
|
282
|
-
}
|
|
283
|
-
else {
|
|
276
|
+
} else {
|
|
284
277
|
isLandscape = true;
|
|
285
278
|
}
|
|
286
279
|
|
|
287
|
-
if(
|
|
288
|
-
{
|
|
280
|
+
if (debug) {
|
|
289
281
|
console.debug('updateIosWidthHeightAndOrientation', {
|
|
290
282
|
'screen.orientation.type': screen.orientation.type,
|
|
291
283
|
isLandscape,
|
|
@@ -330,8 +322,7 @@
|
|
|
330
322
|
// App visibility detection for iOS debugging
|
|
331
323
|
const handleVisibilityChange = () => {
|
|
332
324
|
if (document.visibilityState === 'visible') {
|
|
333
|
-
|
|
334
|
-
if( debug ) {
|
|
325
|
+
if (debug) {
|
|
335
326
|
console.log('App became visible:', {
|
|
336
327
|
'window.innerWidth': window.innerWidth,
|
|
337
328
|
'window.innerHeight': window.innerHeight,
|
|
@@ -339,13 +330,13 @@
|
|
|
339
330
|
'screen.height': screen.height,
|
|
340
331
|
'screen.orientation.angle': screen.orientation.angle,
|
|
341
332
|
'screen.orientation.type': screen.orientation.type,
|
|
342
|
-
'matchMedia portrait':
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
333
|
+
'matchMedia portrait': window.matchMedia('(orientation: portrait)')
|
|
334
|
+
.matches,
|
|
335
|
+
isLandscape: isLandscape,
|
|
336
|
+
gameWidth: gameWidth,
|
|
337
|
+
gameHeight: gameHeight,
|
|
338
|
+
iosWindowWidth: iosWindowWidth,
|
|
339
|
+
iosWindowHeight: iosWindowHeight
|
|
349
340
|
});
|
|
350
341
|
}
|
|
351
342
|
|
|
@@ -384,7 +375,7 @@
|
|
|
384
375
|
// };
|
|
385
376
|
return () => {
|
|
386
377
|
html.classList.remove(gameBoxNoScroll);
|
|
387
|
-
}
|
|
378
|
+
};
|
|
388
379
|
});
|
|
389
380
|
|
|
390
381
|
async function requestFullscreen() {
|
|
@@ -432,7 +423,7 @@
|
|
|
432
423
|
}
|
|
433
424
|
|
|
434
425
|
$effect(() => {
|
|
435
|
-
if (location.hostname === 'localhost') {
|
|
426
|
+
if (allowDevModeOnLocalhost && location.hostname === 'localhost') {
|
|
436
427
|
isDevMode = true;
|
|
437
428
|
}
|
|
438
429
|
});
|
|
@@ -444,6 +435,24 @@
|
|
|
444
435
|
window.history.pushState({}, '', url);
|
|
445
436
|
}
|
|
446
437
|
});
|
|
438
|
+
|
|
439
|
+
let snippetParams = $derived({
|
|
440
|
+
isLandscape,
|
|
441
|
+
isPortrait: !isLandscape,
|
|
442
|
+
isMobile,
|
|
443
|
+
isIos,
|
|
444
|
+
isAndroid,
|
|
445
|
+
isIpadOS,
|
|
446
|
+
isPwa,
|
|
447
|
+
isFullscreen,
|
|
448
|
+
isDevMode,
|
|
449
|
+
requestDevmode,
|
|
450
|
+
requestFullscreen,
|
|
451
|
+
gameWidth,
|
|
452
|
+
gameHeight
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
// $inspect('snippetParams', snippetParams);
|
|
447
456
|
</script>
|
|
448
457
|
|
|
449
458
|
<svelte:window bind:innerWidth={windowWidth} bind:innerHeight={windowHeight} />
|
|
@@ -467,213 +476,144 @@
|
|
|
467
476
|
style:margin="{marginTop}px {marginRight}px {marginBottom}px {marginLeft}px"
|
|
468
477
|
{style}
|
|
469
478
|
>
|
|
470
|
-
{#if show}
|
|
471
|
-
|
|
479
|
+
{#if show && clamping }
|
|
480
|
+
|
|
472
481
|
{#if snippetRequireFullscreen}
|
|
473
|
-
|
|
482
|
+
|
|
474
483
|
{#if isFullscreen && !isDevMode}
|
|
484
|
+
|
|
485
|
+
<!--
|
|
486
|
+
snippetRequireFullscreen &&
|
|
487
|
+
Fullscreen
|
|
488
|
+
=> Render both orientations, set visibility to preserve state
|
|
489
|
+
-->
|
|
490
|
+
|
|
475
491
|
<!-- Landscape content -->
|
|
476
492
|
<ScaledContainer
|
|
477
|
-
|
|
493
|
+
{enableScaling}
|
|
478
494
|
design={designLandscape}
|
|
479
495
|
{clamping}
|
|
480
496
|
width={gameWidthOnLandscape}
|
|
481
497
|
height={gameHeightOnLandscape}
|
|
498
|
+
snippet={snippetLandscape}
|
|
499
|
+
{snippetParams}
|
|
482
500
|
hidden={!isLandscape}
|
|
483
|
-
>
|
|
484
|
-
|
|
485
|
-
isLandscape,
|
|
486
|
-
isPortrait: !isLandscape,
|
|
487
|
-
isMobile,
|
|
488
|
-
isIos,
|
|
489
|
-
isAndroid,
|
|
490
|
-
isIpadOS,
|
|
491
|
-
isPwa,
|
|
492
|
-
isFullscreen,
|
|
493
|
-
isDevMode,
|
|
494
|
-
requestDevmode,
|
|
495
|
-
requestFullscreen,
|
|
496
|
-
gameWidth,
|
|
497
|
-
gameHeight
|
|
498
|
-
})}
|
|
499
|
-
</ScaledContainer>
|
|
501
|
+
></ScaledContainer>
|
|
502
|
+
|
|
500
503
|
<!-- Portrait content -->
|
|
501
504
|
<ScaledContainer
|
|
502
|
-
|
|
505
|
+
{enableScaling}
|
|
503
506
|
design={designPortrait}
|
|
504
507
|
{clamping}
|
|
505
508
|
width={gameWidthOnPortrait}
|
|
506
509
|
height={gameHeightOnPortrait}
|
|
510
|
+
snippet={snippetPortrait}
|
|
511
|
+
{snippetParams}
|
|
507
512
|
hidden={isLandscape}
|
|
508
|
-
>
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
requestFullscreen,
|
|
521
|
-
gameWidth,
|
|
522
|
-
gameHeight
|
|
523
|
-
})}
|
|
524
|
-
</ScaledContainer>
|
|
525
|
-
{:else if supportsFullscreen && !isDevMode}
|
|
526
|
-
<!-- Require fullscreen -->
|
|
513
|
+
></ScaledContainer>
|
|
514
|
+
|
|
515
|
+
{:else if isMobile && snippetInstallOnHomeScreen && !isPwa && !isDevMode}
|
|
516
|
+
|
|
517
|
+
<!--
|
|
518
|
+
snippetRequireFullscreen &&
|
|
519
|
+
isMobile &&
|
|
520
|
+
snippetInstallOnHomeScreen &&
|
|
521
|
+
Not PWA
|
|
522
|
+
=> show install on home screen message
|
|
523
|
+
-->
|
|
524
|
+
|
|
527
525
|
<ScaledContainer
|
|
528
|
-
|
|
526
|
+
{enableScaling}
|
|
529
527
|
design={isLandscape ? designLandscape : designPortrait}
|
|
530
528
|
{clamping}
|
|
531
529
|
width={gameWidth}
|
|
532
530
|
height={gameHeight}
|
|
533
|
-
|
|
534
|
-
{
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
gameHeight
|
|
548
|
-
})}
|
|
549
|
-
</ScaledContainer>
|
|
550
|
-
{:else if isMobile && snippetInstallOnHomeScreen && !isPwa && !isDevMode}
|
|
551
|
-
<!-- Require install on home screen on mobile -->
|
|
531
|
+
snippet={snippetInstallOnHomeScreen}
|
|
532
|
+
{snippetParams}
|
|
533
|
+
></ScaledContainer>
|
|
534
|
+
|
|
535
|
+
{:else if supportsFullscreen && !isDevMode}
|
|
536
|
+
|
|
537
|
+
<!--
|
|
538
|
+
snippetRequireFullscreen &&
|
|
539
|
+
Fullscreen supported &&
|
|
540
|
+
Not fullscreen &&
|
|
541
|
+
(Not mobile OR no install snippet OR already PWA)
|
|
542
|
+
=> show fullscreen required message
|
|
543
|
+
-->
|
|
544
|
+
|
|
552
545
|
<ScaledContainer
|
|
553
|
-
|
|
546
|
+
{enableScaling}
|
|
554
547
|
design={isLandscape ? designLandscape : designPortrait}
|
|
555
548
|
{clamping}
|
|
556
549
|
width={gameWidth}
|
|
557
550
|
height={gameHeight}
|
|
558
|
-
|
|
559
|
-
{
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
isMobile,
|
|
563
|
-
isIos,
|
|
564
|
-
isAndroid,
|
|
565
|
-
isIpadOS,
|
|
566
|
-
isPwa,
|
|
567
|
-
isFullscreen,
|
|
568
|
-
isDevMode,
|
|
569
|
-
requestDevmode,
|
|
570
|
-
requestFullscreen,
|
|
571
|
-
gameWidth,
|
|
572
|
-
gameHeight
|
|
573
|
-
})}
|
|
574
|
-
</ScaledContainer>
|
|
551
|
+
snippet={snippetRequireFullscreen}
|
|
552
|
+
{snippetParams}
|
|
553
|
+
></ScaledContainer>
|
|
554
|
+
|
|
575
555
|
{:else}
|
|
556
|
+
<!--
|
|
557
|
+
snippetRequireFullscreen &&
|
|
558
|
+
(Dev mode OR
|
|
559
|
+
Fullscreen not supported OR
|
|
560
|
+
Not mobile OR
|
|
561
|
+
No install snippet OR
|
|
562
|
+
Already PWA)
|
|
563
|
+
=> show game content
|
|
564
|
+
-->
|
|
565
|
+
|
|
576
566
|
<!-- Landscape content -->
|
|
567
|
+
|
|
577
568
|
<ScaledContainer
|
|
578
|
-
|
|
569
|
+
{enableScaling}
|
|
579
570
|
design={designLandscape}
|
|
580
571
|
{clamping}
|
|
581
572
|
width={gameWidthOnLandscape}
|
|
582
573
|
height={gameHeightOnLandscape}
|
|
574
|
+
snippet={snippetLandscape}
|
|
575
|
+
{snippetParams}
|
|
583
576
|
hidden={!isLandscape}
|
|
584
|
-
>
|
|
585
|
-
|
|
586
|
-
isLandscape,
|
|
587
|
-
isPortrait: !isLandscape,
|
|
588
|
-
isMobile,
|
|
589
|
-
isIos,
|
|
590
|
-
isAndroid,
|
|
591
|
-
isIpadOS,
|
|
592
|
-
isPwa,
|
|
593
|
-
isFullscreen,
|
|
594
|
-
isDevMode,
|
|
595
|
-
requestDevmode,
|
|
596
|
-
requestFullscreen,
|
|
597
|
-
gameWidth,
|
|
598
|
-
gameHeight
|
|
599
|
-
})}
|
|
600
|
-
</ScaledContainer>
|
|
577
|
+
></ScaledContainer>
|
|
578
|
+
|
|
601
579
|
<!-- Portrait content -->
|
|
602
580
|
<ScaledContainer
|
|
603
|
-
|
|
581
|
+
{enableScaling}
|
|
604
582
|
design={designPortrait}
|
|
605
583
|
{clamping}
|
|
606
584
|
width={gameWidthOnPortrait}
|
|
607
585
|
height={gameHeightOnPortrait}
|
|
586
|
+
snippet={snippetPortrait}
|
|
587
|
+
{snippetParams}
|
|
608
588
|
hidden={isLandscape}
|
|
609
|
-
>
|
|
610
|
-
{@render snippetPortrait({
|
|
611
|
-
isLandscape,
|
|
612
|
-
isPortrait: !isLandscape,
|
|
613
|
-
isMobile,
|
|
614
|
-
isIos,
|
|
615
|
-
isAndroid,
|
|
616
|
-
isIpadOS,
|
|
617
|
-
isPwa,
|
|
618
|
-
isFullscreen,
|
|
619
|
-
isDevMode,
|
|
620
|
-
requestDevmode,
|
|
621
|
-
requestFullscreen,
|
|
622
|
-
gameWidth,
|
|
623
|
-
gameHeight
|
|
624
|
-
})}
|
|
625
|
-
</ScaledContainer>
|
|
589
|
+
></ScaledContainer>
|
|
626
590
|
{/if}
|
|
627
591
|
{:else}
|
|
628
592
|
<!-- Do not require fullscreen -->
|
|
593
|
+
|
|
629
594
|
<!-- Landscape content -->
|
|
630
595
|
<ScaledContainer
|
|
631
|
-
|
|
596
|
+
{enableScaling}
|
|
632
597
|
design={designLandscape}
|
|
633
598
|
{clamping}
|
|
634
599
|
width={gameWidthOnLandscape}
|
|
635
600
|
height={gameHeightOnLandscape}
|
|
601
|
+
snippet={snippetLandscape}
|
|
602
|
+
{snippetParams}
|
|
636
603
|
hidden={!isLandscape}
|
|
637
|
-
>
|
|
638
|
-
|
|
639
|
-
isLandscape,
|
|
640
|
-
isPortrait: !isLandscape,
|
|
641
|
-
isMobile,
|
|
642
|
-
isIos,
|
|
643
|
-
isAndroid,
|
|
644
|
-
os,
|
|
645
|
-
isFullscreen,
|
|
646
|
-
isDevMode,
|
|
647
|
-
requestDevmode,
|
|
648
|
-
requestFullscreen,
|
|
649
|
-
gameWidth,
|
|
650
|
-
gameHeight
|
|
651
|
-
})}
|
|
652
|
-
</ScaledContainer>
|
|
604
|
+
></ScaledContainer>
|
|
605
|
+
|
|
653
606
|
<!-- Portrait content -->
|
|
654
607
|
<ScaledContainer
|
|
655
|
-
|
|
608
|
+
{enableScaling}
|
|
656
609
|
design={designPortrait}
|
|
657
610
|
{clamping}
|
|
658
611
|
width={gameWidthOnPortrait}
|
|
659
612
|
height={gameHeightOnPortrait}
|
|
613
|
+
snippet={snippetPortrait}
|
|
614
|
+
{snippetParams}
|
|
660
615
|
hidden={isLandscape}
|
|
661
|
-
>
|
|
662
|
-
{@render snippetPortrait({
|
|
663
|
-
isLandscape,
|
|
664
|
-
isPortrait: !isLandscape,
|
|
665
|
-
isMobile,
|
|
666
|
-
isIos,
|
|
667
|
-
isAndroid,
|
|
668
|
-
os,
|
|
669
|
-
isFullscreen,
|
|
670
|
-
isDevMode,
|
|
671
|
-
requestDevmode,
|
|
672
|
-
requestFullscreen,
|
|
673
|
-
gameWidth,
|
|
674
|
-
gameHeight
|
|
675
|
-
})}
|
|
676
|
-
</ScaledContainer>
|
|
616
|
+
></ScaledContainer>
|
|
677
617
|
{/if}
|
|
678
618
|
{/if}
|
|
679
619
|
</div>
|
|
@@ -45,6 +45,7 @@ type GameBox = {
|
|
|
45
45
|
snippetPortrait?: Snippet<[SnippetParams]> | undefined;
|
|
46
46
|
snippetRequireFullscreen?: Snippet<[SnippetParams]> | undefined;
|
|
47
47
|
snippetInstallOnHomeScreen?: Snippet<[SnippetParams]> | undefined;
|
|
48
|
+
allowDevModeOnLocalhost?: boolean | undefined;
|
|
48
49
|
debug?: boolean | undefined;
|
|
49
50
|
}>): void;
|
|
50
51
|
};
|
|
@@ -92,5 +93,6 @@ declare const GameBox: import("svelte").Component<{
|
|
|
92
93
|
snippetPortrait?: import("svelte").Snippet<[import("./typedef.js").SnippetParams]>;
|
|
93
94
|
snippetRequireFullscreen?: import("svelte").Snippet<[import("./typedef.js").SnippetParams]>;
|
|
94
95
|
snippetInstallOnHomeScreen?: import("svelte").Snippet<[import("./typedef.js").SnippetParams]>;
|
|
96
|
+
allowDevModeOnLocalhost?: boolean;
|
|
95
97
|
debug?: boolean;
|
|
96
98
|
}, {}, "">;
|
|
@@ -1,22 +1,5 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import { enableContainerScaling } from '../../../design/index.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @typedef {{
|
|
6
|
-
* enableScaling?: boolean,
|
|
7
|
-
* design?: {width: number, height: number},
|
|
8
|
-
* clamping?: {
|
|
9
|
-
* ui: {min: number, max: number},
|
|
10
|
-
* textBase: {min: number, max: number},
|
|
11
|
-
* textHeading: {min: number, max: number},
|
|
12
|
-
* textUi: {min: number, max: number}
|
|
13
|
-
* },
|
|
14
|
-
* width: number,
|
|
15
|
-
* height: number,
|
|
16
|
-
* hidden?: boolean,
|
|
17
|
-
* children: import('svelte').Snippet
|
|
18
|
-
* }}
|
|
19
|
-
*/
|
|
2
|
+
import { clamp, enableContainerScaling } from '../../../design/index.js';
|
|
20
3
|
|
|
21
4
|
/**
|
|
22
5
|
* Wrapper component that applies container scaling to its children
|
|
@@ -24,7 +7,7 @@
|
|
|
24
7
|
* @type {{
|
|
25
8
|
* enableScaling?: boolean,
|
|
26
9
|
* design?: {width: number, height: number},
|
|
27
|
-
* clamping
|
|
10
|
+
* clamping: {
|
|
28
11
|
* ui: {min: number, max: number},
|
|
29
12
|
* textBase: {min: number, max: number},
|
|
30
13
|
* textHeading: {min: number, max: number},
|
|
@@ -33,17 +16,19 @@
|
|
|
33
16
|
* width: number,
|
|
34
17
|
* height: number,
|
|
35
18
|
* hidden?: boolean,
|
|
36
|
-
*
|
|
19
|
+
* snippet?: import('./typedef.js').GameBoxSnippet,
|
|
20
|
+
* snippetParams: import('./typedef.js').SnippetParams
|
|
37
21
|
* }}
|
|
38
22
|
*/
|
|
39
23
|
let {
|
|
40
24
|
enableScaling = false,
|
|
41
|
-
design
|
|
42
|
-
clamping
|
|
25
|
+
design,
|
|
26
|
+
clamping,
|
|
43
27
|
width,
|
|
44
28
|
height,
|
|
45
29
|
hidden = false,
|
|
46
|
-
|
|
30
|
+
snippet,
|
|
31
|
+
snippetParams
|
|
47
32
|
} = $props();
|
|
48
33
|
|
|
49
34
|
let container = $state();
|
|
@@ -51,12 +36,13 @@
|
|
|
51
36
|
// Apply container scaling when enabled and not hidden
|
|
52
37
|
$effect(() => {
|
|
53
38
|
if (
|
|
54
|
-
!enableScaling ||
|
|
55
39
|
!container ||
|
|
40
|
+
!enableScaling ||
|
|
56
41
|
!width ||
|
|
57
42
|
!height ||
|
|
58
|
-
|
|
59
|
-
!
|
|
43
|
+
!design ||
|
|
44
|
+
!clamping ||
|
|
45
|
+
hidden
|
|
60
46
|
) {
|
|
61
47
|
return;
|
|
62
48
|
}
|
|
@@ -72,14 +58,16 @@
|
|
|
72
58
|
});
|
|
73
59
|
</script>
|
|
74
60
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
61
|
+
{#if snippet && snippetParams}
|
|
62
|
+
<div
|
|
63
|
+
bind:this={container}
|
|
64
|
+
class:hidden
|
|
65
|
+
style:width="{width}px"
|
|
66
|
+
style:height="{height}px"
|
|
67
|
+
>
|
|
68
|
+
{@render snippet(snippetParams)}
|
|
69
|
+
</div>
|
|
70
|
+
{/if}
|
|
83
71
|
|
|
84
72
|
<style>
|
|
85
73
|
.hidden {
|
|
@@ -7,7 +7,7 @@ type ScaledContainer = {
|
|
|
7
7
|
width: number;
|
|
8
8
|
height: number;
|
|
9
9
|
} | undefined;
|
|
10
|
-
clamping
|
|
10
|
+
clamping: {
|
|
11
11
|
ui: {
|
|
12
12
|
min: number;
|
|
13
13
|
max: number;
|
|
@@ -24,11 +24,12 @@ type ScaledContainer = {
|
|
|
24
24
|
min: number;
|
|
25
25
|
max: number;
|
|
26
26
|
};
|
|
27
|
-
}
|
|
27
|
+
};
|
|
28
28
|
width: number;
|
|
29
29
|
height: number;
|
|
30
30
|
hidden?: boolean | undefined;
|
|
31
|
-
|
|
31
|
+
snippet?: Snippet<[SnippetParams]> | undefined;
|
|
32
|
+
snippetParams: SnippetParams;
|
|
32
33
|
}>): void;
|
|
33
34
|
};
|
|
34
35
|
declare const ScaledContainer: import("svelte").Component<{
|
|
@@ -37,7 +38,7 @@ declare const ScaledContainer: import("svelte").Component<{
|
|
|
37
38
|
width: number;
|
|
38
39
|
height: number;
|
|
39
40
|
};
|
|
40
|
-
clamping
|
|
41
|
+
clamping: {
|
|
41
42
|
ui: {
|
|
42
43
|
min: number;
|
|
43
44
|
max: number;
|
|
@@ -58,5 +59,6 @@ declare const ScaledContainer: import("svelte").Component<{
|
|
|
58
59
|
width: number;
|
|
59
60
|
height: number;
|
|
60
61
|
hidden?: boolean;
|
|
61
|
-
|
|
62
|
+
snippet?: import("./typedef.js").GameBoxSnippet;
|
|
63
|
+
snippetParams: import("./typedef.js").SnippetParams;
|
|
62
64
|
}, {}, "">;
|
|
@@ -69,18 +69,25 @@ export function generateOrGetInstanceId(randomSize?: number, reset?: boolean): s
|
|
|
69
69
|
* @param {number} [timeMs]
|
|
70
70
|
* Custom time value to be used instead of Date.now()
|
|
71
71
|
*
|
|
72
|
-
* @returns {string}
|
|
72
|
+
* @returns {string}
|
|
73
|
+
* Global id (max 39 chars: 1 length prefix + 23 instance + 15 local)
|
|
73
74
|
*/
|
|
74
75
|
export function generateGlobalId(timeMs?: number): string;
|
|
75
76
|
/**
|
|
76
77
|
* Generates and returns a new unique local id
|
|
77
|
-
* - The generated id is
|
|
78
|
+
* - The generated id is guaranteed to be unique on the currently running
|
|
78
79
|
* local system
|
|
80
|
+
* - Format: <boot-prefix><length-indicator><time-based><count-based>
|
|
81
|
+
* - Boot prefix provides uniqueness across process restarts
|
|
82
|
+
* - Time-based component changes every 30 seconds
|
|
83
|
+
* - Counter resets every 30 seconds (656M IDs per 30s window = 5 chars)
|
|
84
|
+
* - Optimized for high-frequency generation (thousands per second)
|
|
79
85
|
*
|
|
80
86
|
* @param {number} [timeMs]
|
|
81
87
|
* Custom time value to be used instead of Date.now()
|
|
82
88
|
*
|
|
83
|
-
* @returns {string}
|
|
89
|
+
* @returns {string}
|
|
90
|
+
* Local id (max 15 chars: 3 boot prefix + 1 length + 6 time + 5 count)
|
|
84
91
|
*/
|
|
85
92
|
export function generateLocalId(timeMs?: number): string;
|
|
86
93
|
/**
|
|
@@ -141,8 +141,8 @@ export function generateClientSessionId() {
|
|
|
141
141
|
*/
|
|
142
142
|
export function generateOrGetInstanceId(randomSize = 16, reset = false) {
|
|
143
143
|
if (!vars.instanceId || reset) {
|
|
144
|
-
const timeBasedPart = getTimeBasedNumber30sBase58();
|
|
145
|
-
const lengthPrefix = base58fromNumber(timeBasedPart.length);
|
|
144
|
+
const timeBasedPart = getTimeBasedNumber30sBase58(); // max 6 chars
|
|
145
|
+
const lengthPrefix = base58fromNumber(timeBasedPart.length); // 1 char
|
|
146
146
|
|
|
147
147
|
vars.instanceId =
|
|
148
148
|
lengthPrefix +
|
|
@@ -166,25 +166,35 @@ export function generateOrGetInstanceId(randomSize = 16, reset = false) {
|
|
|
166
166
|
* @param {number} [timeMs]
|
|
167
167
|
* Custom time value to be used instead of Date.now()
|
|
168
168
|
*
|
|
169
|
-
* @returns {string}
|
|
169
|
+
* @returns {string}
|
|
170
|
+
* Global id (max 39 chars: 1 length prefix + 23 instance + 15 local)
|
|
170
171
|
*/
|
|
171
172
|
export function generateGlobalId(timeMs) {
|
|
172
|
-
const instanceId = generateOrGetInstanceId();
|
|
173
|
-
const localId = generateLocalId(timeMs);
|
|
173
|
+
const instanceId = generateOrGetInstanceId(); // max 1 + 6 + 16 = 23 chars
|
|
174
|
+
const localId = generateLocalId(timeMs); // max 15 chars
|
|
175
|
+
|
|
176
|
+
// Max 1 char (23 + 15 = 38)
|
|
174
177
|
const lengthPrefix = base58fromNumber(instanceId.length + localId.length);
|
|
175
178
|
|
|
179
|
+
// Max 39 chars (1 + 23 + 15 = 39)
|
|
176
180
|
return lengthPrefix + instanceId + localId;
|
|
177
181
|
}
|
|
178
182
|
|
|
179
183
|
/**
|
|
180
184
|
* Generates and returns a new unique local id
|
|
181
|
-
* - The generated id is
|
|
185
|
+
* - The generated id is guaranteed to be unique on the currently running
|
|
182
186
|
* local system
|
|
187
|
+
* - Format: <boot-prefix><length-indicator><time-based><count-based>
|
|
188
|
+
* - Boot prefix provides uniqueness across process restarts
|
|
189
|
+
* - Time-based component changes every 30 seconds
|
|
190
|
+
* - Counter resets every 30 seconds (656M IDs per 30s window = 5 chars)
|
|
191
|
+
* - Optimized for high-frequency generation (thousands per second)
|
|
183
192
|
*
|
|
184
193
|
* @param {number} [timeMs]
|
|
185
194
|
* Custom time value to be used instead of Date.now()
|
|
186
195
|
*
|
|
187
|
-
* @returns {string}
|
|
196
|
+
* @returns {string}
|
|
197
|
+
* Local id (max 15 chars: 3 boot prefix + 1 length + 6 time + 5 count)
|
|
188
198
|
*/
|
|
189
199
|
export function generateLocalId(timeMs) {
|
|
190
200
|
const timeBasedNumber = getTimeBasedNumber30s(timeMs);
|
|
@@ -222,6 +232,15 @@ export function generateLocalId(timeMs) {
|
|
|
222
232
|
//
|
|
223
233
|
// @note ALPHABET_BASE_58 is used because it is faster than
|
|
224
234
|
// base58fromNumber for single character encoding
|
|
235
|
+
|
|
236
|
+
//
|
|
237
|
+
// bootTimePrefix => 3 chars
|
|
238
|
+
// ALPHABET_BASE_58[timeBasedValue58.length] => 1 char
|
|
239
|
+
// timeBasedValue58 => max 6 chars
|
|
240
|
+
// countBasedValue58 => max 5 chars
|
|
241
|
+
// (resets every 30 seconds => 58^5 = 656.356.768)
|
|
242
|
+
//
|
|
243
|
+
// Realistic max length: 3 + 1 + 6 + 5 = 15 chars
|
|
225
244
|
//
|
|
226
245
|
const id =
|
|
227
246
|
// idFormatPrefix
|