feat(player): dev-skin через URL #skin= + убран beforeunload
All checks were successful
All checks were successful
LOCAL DEV: при запуске на localhost плеер берёт скин из: 1) hash-параметра #skin=<id> (сайт 3000 передаёт его при play-ticket через buildPlayerUrl(gameId, ticket, selectedSkin)) 2) localStorage самого плеера (rublox_selected_skin) 3) дефолт skin_y-bot Это нужно потому что: - localStorage на :5173 (плеер) и :3000 (сайт) — РАЗНЫЕ хранилища - В прод-БД пока legacy-скины (skin_sigma-labubu и др.), пока feature-flag RUBLOX_NEW_SKINS_AVAILABLE=false плеер не должен в неё лезть локально PROD: только БД (rublox_equipped_skin) — поведение не меняется. Также убран beforeunload-prompt: системное окно браузера невозможно стилизовать (Chrome игнорирует кастомный текст с 2017), а уродливое окно мешало. Случайное закрытие вкладки теперь без подтверждения.
This commit is contained in:
parent
8047cd366c
commit
dbdd61b4d6
@ -311,36 +311,26 @@ const KubikonPlayer = () => {
|
|||||||
return () => { active = false; };
|
return () => { active = false; };
|
||||||
}, [projectId]);
|
}, [projectId]);
|
||||||
|
|
||||||
// Перехват Ctrl-комбинаций которые ломают игру (Ctrl+W = закрыть вкладку,
|
// Перехват Ctrl-комбинаций которые ломают игру (Ctrl+W, Ctrl+R, Ctrl+T,
|
||||||
// Ctrl+R = reload, Ctrl+T/N — мешают). Большинство браузеров блокирует
|
// Ctrl+N). Большинство браузеров блокирует отмену системных шорткатов,
|
||||||
// отмену системных шорткатов, но beforeunload даёт пользователю шанс
|
// но мы пробуем preventDefault — иногда срабатывает.
|
||||||
// подтвердить выход. Также превентим preventDefault на keydown для
|
//
|
||||||
// случаев когда фокус НЕ на window-уровне (Chrome иногда позволяет).
|
// 2026-06-14: beforeunload-подтверждение убрано по решению UX — системное
|
||||||
|
// окно браузера невозможно стилизовать (с 2017 Chrome игнорирует кастомный
|
||||||
|
// текст), а уродливое модальное мешает. Случайное закрытие вкладки теперь
|
||||||
|
// просто закрывает игру без вопроса.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onKey = (e) => {
|
const onKey = (e) => {
|
||||||
if (!e.ctrlKey && !e.metaKey) return;
|
if (!e.ctrlKey && !e.metaKey) return;
|
||||||
// Список «опасных» в игре сочетаний — превентим
|
|
||||||
const dangerousCodes = ['KeyW', 'KeyR', 'KeyT', 'KeyN'];
|
const dangerousCodes = ['KeyW', 'KeyR', 'KeyT', 'KeyN'];
|
||||||
if (dangerousCodes.includes(e.code)) {
|
if (dangerousCodes.includes(e.code)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const onBeforeUnload = (e) => {
|
|
||||||
// Если юзер сам нажал «Покинуть» в меню — пропускаем без
|
|
||||||
// подтверждения. Флаг ставит exitPlayer().
|
|
||||||
if (window.__rubloxExplicitExit) return undefined;
|
|
||||||
// Случайное закрытие вкладки (Ctrl+W, X-кнопка) — показываем
|
|
||||||
// подтверждение чтобы не потерять прогресс игры.
|
|
||||||
e.preventDefault();
|
|
||||||
e.returnValue = '';
|
|
||||||
return '';
|
|
||||||
};
|
|
||||||
window.addEventListener('keydown', onKey, { capture: true });
|
window.addEventListener('keydown', onKey, { capture: true });
|
||||||
window.addEventListener('beforeunload', onBeforeUnload);
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('keydown', onKey, { capture: true });
|
window.removeEventListener('keydown', onKey, { capture: true });
|
||||||
window.removeEventListener('beforeunload', onBeforeUnload);
|
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -592,29 +582,39 @@ const KubikonPlayer = () => {
|
|||||||
// Этот же skinFolder уйдёт в мультиплеер как modelType,
|
// Этот же skinFolder уйдёт в мультиплеер как modelType,
|
||||||
// чтобы соперники видели наш реальный скин.
|
// чтобы соперники видели наш реальный скин.
|
||||||
//
|
//
|
||||||
// LOCAL DEV (localhost): сначала пробуем localStorage
|
// LOCAL DEV (localhost): берём скин из URL #skin=<id>
|
||||||
// ('rublox_selected_skin' — тот же ключ что в rublox-site),
|
// (передаётся сайтом 3000 при нажатии «Играть»), потом из
|
||||||
// чтобы юзер мог тестить выбор скинов без записи в прод-БД.
|
// localStorage самого плеера, потом дефолт. В БД НЕ лезем —
|
||||||
|
// прод-БД хранит легаси скины (skin_sigma-labubu и др.),
|
||||||
|
// которые мы не хотим грузить локально.
|
||||||
|
//
|
||||||
|
// PROD: только БД (rublox_equipped_skin).
|
||||||
let mySkin = 'skin_y-bot';
|
let mySkin = 'skin_y-bot';
|
||||||
const isLocalDev = (typeof window !== 'undefined'
|
const isLocalDev = (typeof window !== 'undefined'
|
||||||
&& (window.location.hostname === 'localhost'
|
&& (window.location.hostname === 'localhost'
|
||||||
|| window.location.hostname === '127.0.0.1'));
|
|| window.location.hostname === '127.0.0.1'));
|
||||||
if (isLocalDev) {
|
if (isLocalDev) {
|
||||||
try {
|
try {
|
||||||
const localPick = localStorage.getItem('rublox_selected_skin');
|
// 1) hash-параметр #skin=<id> (от сайта при play-ticket)
|
||||||
if (localPick && typeof localPick === 'string') {
|
const m = window.location.hash.match(/[#&]skin=([\w-]+)/);
|
||||||
mySkin = localPick;
|
if (m && m[1]) {
|
||||||
console.log('[KubikonPlayer] local-dev skin:', mySkin);
|
mySkin = m[1];
|
||||||
|
console.log('[KubikonPlayer] local-dev skin (URL):', mySkin);
|
||||||
|
} else {
|
||||||
|
// 2) localStorage самого плеера
|
||||||
|
const localPick = localStorage.getItem('rublox_selected_skin');
|
||||||
|
if (localPick && typeof localPick === 'string') {
|
||||||
|
mySkin = localPick;
|
||||||
|
console.log('[KubikonPlayer] local-dev skin (LS):', mySkin);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
} else if (userId) {
|
||||||
if (mySkin === 'skin_y-bot' && userId) {
|
|
||||||
try {
|
try {
|
||||||
const skinRes = await Kubikon3DApi.getEquippedSkin(userId);
|
const skinRes = await Kubikon3DApi.getEquippedSkin(userId);
|
||||||
const sf = skinRes?.data?.skin_folder;
|
const sf = skinRes?.data?.skin_folder;
|
||||||
if (sf && typeof sf === 'string') mySkin = sf;
|
if (sf && typeof sf === 'string') mySkin = sf;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Сеть/ошибка — играем с дефолтным скином, не блокируем.
|
|
||||||
console.warn('[KubikonPlayer] equipped-skin load failed', e);
|
console.warn('[KubikonPlayer] equipped-skin load failed', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user