@mblinkov/whats-missing 20.0.47 → 20.0.48
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/CHANGELOG.md +477 -1
- package/dist/whats-missing.cjs.js +3 -3
- package/dist/whats-missing.es.js +214 -180
- package/package.json +1 -1
- package/src/Game.tsx +85 -2
package/package.json
CHANGED
package/src/Game.tsx
CHANGED
|
@@ -92,6 +92,11 @@ export default function Game({
|
|
|
92
92
|
if (style) {
|
|
93
93
|
style.remove();
|
|
94
94
|
}
|
|
95
|
+
|
|
96
|
+
// ✅ Cancel any ongoing preloads
|
|
97
|
+
if (abortController.current) {
|
|
98
|
+
abortController.current.abort();
|
|
99
|
+
}
|
|
95
100
|
};
|
|
96
101
|
}, []);
|
|
97
102
|
|
|
@@ -126,6 +131,10 @@ export default function Game({
|
|
|
126
131
|
const [isIPadProPortrait, setIsIPadProPortrait] = useState(false);
|
|
127
132
|
const [isIPadProLandscape, setIsIPadProLandscape] = useState(false);
|
|
128
133
|
const [isHorizontalLayout, setIsHorizontalLayout] = useState(false);
|
|
134
|
+
|
|
135
|
+
// ✅ Система предзагрузки изображений
|
|
136
|
+
const preloadedUrls = useRef<Set<string>>(new Set());
|
|
137
|
+
const abortController = useRef<AbortController | null>(null);
|
|
129
138
|
|
|
130
139
|
// ✅ адаптив под мобилки, планшеты и десктоп
|
|
131
140
|
useEffect(() => {
|
|
@@ -135,6 +144,7 @@ export default function Game({
|
|
|
135
144
|
const mobile = width < 768 || (width === 926 && height === 428) || (width === 932 && height === 430); // iPhone 14 Pro Max в landscape тоже считается мобильным
|
|
136
145
|
const isLandscape = (width > height && mobile) || (width === 926 && height === 428) || (width === 932 && height === 430); // iPhone 14 Pro Max в landscape
|
|
137
146
|
const isSmallHeight = height < 700; // Nest Hub, маленькие экраны
|
|
147
|
+
const isWideScreen = width / height > 1.8; // ✅ Широкие экраны
|
|
138
148
|
|
|
139
149
|
// Определяем iPhone до 14 Pro Max в landscape режиме
|
|
140
150
|
// iPhone 14 Pro Max: 926x428 (landscape)
|
|
@@ -201,7 +211,9 @@ export default function Game({
|
|
|
201
211
|
// ✅ Вычисляем горизонтальный layout ОДИН РАЗ
|
|
202
212
|
const isHorizontal =
|
|
203
213
|
(mobile && width > height) ||
|
|
214
|
+
mobile || // ✅ ВСЕ мобильные устройства (включая portrait)
|
|
204
215
|
height < 700 ||
|
|
216
|
+
isWideScreen || // ✅ Широкие экраны
|
|
205
217
|
(width === 1366 && height === 766) ||
|
|
206
218
|
(width === 1366 && height === 768) ||
|
|
207
219
|
(width === 1280 && height === 720) ||
|
|
@@ -231,6 +243,15 @@ export default function Game({
|
|
|
231
243
|
// Маленькие экраны: используем переданный gameCubeSize или полную высоту
|
|
232
244
|
setContainerSize(gameCubeSize && gameCubeSize >= 400 ? gameCubeSize : null);
|
|
233
245
|
setScale(1);
|
|
246
|
+
} else if (isWideScreen) {
|
|
247
|
+
// ✅ Широкие экраны: используем стандартный размер но уменьшаем масштаб картинок
|
|
248
|
+
const minSize = 400;
|
|
249
|
+
const maxSize = 1200;
|
|
250
|
+
const finalSize = gameCubeSize
|
|
251
|
+
? Math.max(minSize, Math.min(maxSize, gameCubeSize))
|
|
252
|
+
: Math.min(1000, Math.min(width, height) * 0.9);
|
|
253
|
+
setContainerSize(finalSize);
|
|
254
|
+
setScale(0.85); // Уменьшаем масштаб картинок на 15%
|
|
234
255
|
} else {
|
|
235
256
|
// Десктопы: используем переданный gameCubeSize с разумными ограничениями
|
|
236
257
|
const minSize = 400;
|
|
@@ -250,6 +271,43 @@ export default function Game({
|
|
|
250
271
|
const getRandomSix = (arr: ImageItem[]) =>
|
|
251
272
|
[...arr].sort(() => Math.random() - 0.5).slice(0, 6);
|
|
252
273
|
|
|
274
|
+
// ✅ Утилиты предзагрузки
|
|
275
|
+
const preloadImage = (src: string): Promise<void> => {
|
|
276
|
+
return new Promise((resolve) => {
|
|
277
|
+
const img = new Image();
|
|
278
|
+
img.onload = () => resolve();
|
|
279
|
+
img.onerror = () => resolve(); // Continue even on error
|
|
280
|
+
img.src = src;
|
|
281
|
+
});
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const preloadImages = async (urls: string[], signal?: AbortSignal): Promise<void> => {
|
|
285
|
+
const promises = urls.map(url => preloadImage(url));
|
|
286
|
+
await Promise.allSettled(promises);
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
// ✅ Функция предзагрузки темы
|
|
290
|
+
const startThemePreload = (themeName: Theme) => {
|
|
291
|
+
// Cancel previous preload
|
|
292
|
+
if (abortController.current) {
|
|
293
|
+
abortController.current.abort();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Create new controller
|
|
297
|
+
abortController.current = new AbortController();
|
|
298
|
+
|
|
299
|
+
// Get theme images and start preloading
|
|
300
|
+
const themeImages = themes[themeName];
|
|
301
|
+
const urls = themeImages.map(item => item.src);
|
|
302
|
+
|
|
303
|
+
preloadImages(urls, abortController.current.signal).then(() => {
|
|
304
|
+
// Mark as preloaded
|
|
305
|
+
urls.forEach(url => preloadedUrls.current.add(url));
|
|
306
|
+
}).catch(() => {
|
|
307
|
+
// Ignore abort errors
|
|
308
|
+
});
|
|
309
|
+
};
|
|
310
|
+
|
|
253
311
|
const startGame = () => {
|
|
254
312
|
if (!theme) return;
|
|
255
313
|
const selected = getRandomSix(themes[theme]);
|
|
@@ -260,6 +318,15 @@ export default function Game({
|
|
|
260
318
|
setScore(0);
|
|
261
319
|
setResultsTable([]);
|
|
262
320
|
setUsedHidden([]);
|
|
321
|
+
|
|
322
|
+
// ✅ Preload current round images
|
|
323
|
+
const urls = selected.map(item => item.src);
|
|
324
|
+
preloadImages(urls, abortController.current?.signal).then(() => {
|
|
325
|
+
urls.forEach(url => preloadedUrls.current.add(url));
|
|
326
|
+
}).catch(() => {
|
|
327
|
+
// Ignore abort errors
|
|
328
|
+
});
|
|
329
|
+
|
|
263
330
|
startRound(selected, [], true);
|
|
264
331
|
};
|
|
265
332
|
|
|
@@ -280,9 +347,21 @@ export default function Game({
|
|
|
280
347
|
setInputValue("");
|
|
281
348
|
setTimeLeft(20);
|
|
282
349
|
setResult(null);
|
|
350
|
+
|
|
351
|
+
// ✅ Preload next round images in background
|
|
352
|
+
if (theme && !isFirst) {
|
|
353
|
+
const nextPool = getRandomSix(themes[theme].filter(item => !used.includes(item.name)));
|
|
354
|
+
const nextUrls = nextPool.map(item => item.src);
|
|
355
|
+
preloadImages(nextUrls, abortController.current?.signal).then(() => {
|
|
356
|
+
nextUrls.forEach(url => preloadedUrls.current.add(url));
|
|
357
|
+
}).catch(() => {
|
|
358
|
+
// Ignore abort errors
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
283
362
|
if (isFirst) {
|
|
284
363
|
setPhase("ready");
|
|
285
|
-
setReadyTime(
|
|
364
|
+
setReadyTime(5); // Увеличено с 3 до 5
|
|
286
365
|
setMemorizeTime(10);
|
|
287
366
|
} else {
|
|
288
367
|
setPhase("memorize");
|
|
@@ -416,6 +495,7 @@ export default function Game({
|
|
|
416
495
|
boxShadow: isMobile ? "none" : "0 0 40px rgba(0,0,0,0.1)",
|
|
417
496
|
margin: isMobile ? "0 auto" : "unset",
|
|
418
497
|
position: "relative", // needed so absolute logo is inside the square
|
|
498
|
+
transform: `scale(${scale})`, // ✅ Применяем масштаб для широких экранов
|
|
419
499
|
}}
|
|
420
500
|
>
|
|
421
501
|
<div
|
|
@@ -442,7 +522,10 @@ export default function Game({
|
|
|
442
522
|
padding: (isMobile && window.innerWidth > window.innerHeight) || (isMobile && window.innerWidth <= 375 && window.innerHeight <= 667) || (window.innerWidth === 896 && window.innerHeight === 414) || (window.innerWidth === 844 && window.innerHeight === 390) || (window.innerWidth === 926 && window.innerHeight === 428) || (window.innerWidth === 932 && window.innerHeight === 430) ? "8px 12px" : "12px 24px",
|
|
443
523
|
fontSize: (isMobile && window.innerWidth > window.innerHeight) || (isMobile && window.innerWidth <= 375 && window.innerHeight <= 667) || (window.innerWidth === 896 && window.innerHeight === 414) || (window.innerWidth === 844 && window.innerHeight === 390) || (window.innerWidth === 926 && window.innerHeight === 428) || (window.innerWidth === 932 && window.innerHeight === 430) ? "12px" : "16px",
|
|
444
524
|
minWidth: (isMobile && window.innerWidth > window.innerHeight) || (isMobile && window.innerWidth <= 375 && window.innerHeight <= 667) || (window.innerWidth === 896 && window.innerHeight === 414) || (window.innerWidth === 844 && window.innerHeight === 390) || (window.innerWidth === 926 && window.innerHeight === 428) || (window.innerWidth === 932 && window.innerHeight === 430) ? "70px" : "auto"
|
|
445
|
-
}} onClick={() =>
|
|
525
|
+
}} onClick={() => {
|
|
526
|
+
setTheme(t as Theme);
|
|
527
|
+
startThemePreload(t as Theme);
|
|
528
|
+
}}>
|
|
446
529
|
{t === "animals" ? "🐶 Animals" : t === "food" ? "🍎 Food" : "🧸 Toys"}
|
|
447
530
|
</button>
|
|
448
531
|
))}
|