Files
chesscubing/ChessCubing.App/wwwroot/js/chesscubing-interop.js

316 lines
8.7 KiB
JavaScript

(() => {
const assetTokenStorageKey = "chesscubing-arena-asset-token";
let viewportStarted = false;
let audioContext = null;
let authModalMessageHandler = null;
let menuScrollStarted = false;
let menuLastScrollY = 0;
let menuAnimationFrame = 0;
function syncViewportHeight() {
const visibleHeight = window.visualViewport?.height ?? window.innerHeight;
const viewportTopOffset = window.visualViewport?.offsetTop ?? 0;
const viewportHeight = Math.max(
visibleHeight + viewportTopOffset,
window.innerHeight,
document.documentElement.clientHeight,
);
document.documentElement.style.setProperty("--app-visible-height", `${Math.round(visibleHeight)}px`);
document.documentElement.style.setProperty("--app-viewport-top", `${Math.round(viewportTopOffset)}px`);
document.documentElement.style.setProperty("--app-viewport-height", `${Math.round(viewportHeight)}px`);
}
function startViewport() {
if (viewportStarted) {
return;
}
viewportStarted = true;
syncViewportHeight();
window.addEventListener("load", syncViewportHeight);
window.addEventListener("resize", syncViewportHeight);
window.addEventListener("scroll", syncViewportHeight, { passive: true });
window.addEventListener("pageshow", syncViewportHeight);
window.addEventListener("orientationchange", syncViewportHeight);
window.visualViewport?.addEventListener("resize", syncViewportHeight);
window.visualViewport?.addEventListener("scroll", syncViewportHeight);
window.setTimeout(syncViewportHeight, 0);
window.setTimeout(syncViewportHeight, 150);
window.setTimeout(syncViewportHeight, 400);
window.requestAnimationFrame(() => window.requestAnimationFrame(syncViewportHeight));
}
function setMenuHidden(hidden) {
document.body.classList.toggle("site-menu-hidden", hidden);
}
function syncMenuVisibility() {
menuAnimationFrame = 0;
const menu = document.querySelector(".site-menu-shell");
if (!menu) {
setMenuHidden(false);
menuLastScrollY = window.scrollY || window.pageYOffset || 0;
return;
}
const modalIsOpen = document.querySelector(".modal:not(.hidden)");
if (modalIsOpen) {
setMenuHidden(false);
menuLastScrollY = window.scrollY || window.pageYOffset || 0;
return;
}
const currentScrollY = Math.max(window.scrollY || window.pageYOffset || 0, 0);
const delta = currentScrollY - menuLastScrollY;
if (currentScrollY <= 24 || delta < -10) {
setMenuHidden(false);
} else if (delta > 10 && currentScrollY > 120) {
setMenuHidden(true);
}
menuLastScrollY = currentScrollY;
}
function queueMenuVisibilitySync() {
if (menuAnimationFrame) {
return;
}
menuAnimationFrame = window.requestAnimationFrame(syncMenuVisibility);
}
function startMenuScrollTracking() {
if (menuScrollStarted) {
queueMenuVisibilitySync();
return;
}
menuScrollStarted = true;
menuLastScrollY = window.scrollY || window.pageYOffset || 0;
window.addEventListener("scroll", queueMenuVisibilitySync, { passive: true });
window.addEventListener("pageshow", queueMenuVisibilitySync);
window.addEventListener("resize", queueMenuVisibilitySync);
window.addEventListener("hashchange", queueMenuVisibilitySync);
window.setTimeout(queueMenuVisibilitySync, 0);
window.setTimeout(queueMenuVisibilitySync, 150);
}
function getAudioContext() {
const AudioContextClass = window.AudioContext || window.webkitAudioContext;
if (!AudioContextClass) {
return null;
}
if (!audioContext) {
try {
audioContext = new AudioContextClass();
} catch {
return null;
}
}
return audioContext;
}
function primeAudio() {
const context = getAudioContext();
if (!context || context.state === "running") {
return;
}
context.resume().catch(() => undefined);
}
function playCubePhaseAlert() {
primeAudio();
const context = getAudioContext();
if (!context || context.state !== "running") {
return false;
}
const pattern = [
{ frequency: 740, offset: 0, duration: 0.12, gain: 0.035 },
{ frequency: 988, offset: 0.18, duration: 0.12, gain: 0.04 },
{ frequency: 1318, offset: 0.36, duration: 0.2, gain: 0.05 },
];
const startAt = context.currentTime + 0.02;
pattern.forEach(({ frequency, offset, duration, gain }) => {
const oscillator = context.createOscillator();
const envelope = context.createGain();
const toneStartAt = startAt + offset;
oscillator.type = "triangle";
oscillator.frequency.setValueAtTime(frequency, toneStartAt);
envelope.gain.setValueAtTime(0.0001, toneStartAt);
envelope.gain.exponentialRampToValueAtTime(gain, toneStartAt + 0.02);
envelope.gain.exponentialRampToValueAtTime(0.0001, toneStartAt + duration);
oscillator.connect(envelope);
envelope.connect(context.destination);
oscillator.start(toneStartAt);
oscillator.stop(toneStartAt + duration + 0.03);
});
return true;
}
window.chesscubingPage = {
setBodyState(page, bodyClass) {
if (page) {
document.body.dataset.page = page;
} else {
delete document.body.dataset.page;
}
document.body.className = bodyClass || "";
},
};
window.chesscubingViewport = {
start: startViewport,
};
window.chesscubingMenu = {
start: startMenuScrollTracking,
sync: queueMenuVisibilitySync,
};
window.chesscubingStorage = {
getMatchState(storageKey, windowNameKey) {
try {
const raw = window.localStorage.getItem(storageKey);
if (raw) {
return raw;
}
} catch {
}
try {
if (!window.name || !window.name.startsWith(windowNameKey)) {
return null;
}
return window.name.slice(windowNameKey.length);
} catch {
return null;
}
},
setMatchState(storageKey, windowNameKey, value) {
try {
window.name = `${windowNameKey}${value}`;
} catch {
}
try {
window.localStorage.setItem(storageKey, value);
} catch {
}
},
clearMatchState(storageKey, windowNameKey) {
try {
window.localStorage.removeItem(storageKey);
} catch {
}
try {
if (window.name && window.name.startsWith(windowNameKey)) {
window.name = "";
}
} catch {
}
},
};
window.chesscubingAudio = {
prime: primeAudio,
playCubePhaseAlert,
};
window.chesscubingBrowser = {
async forceRefresh(path) {
const refreshToken = `${Date.now()}`;
try {
window.sessionStorage.setItem(assetTokenStorageKey, refreshToken);
} catch {
}
if ("caches" in window) {
try {
const cacheKeys = await window.caches.keys();
await Promise.all(cacheKeys.map((cacheKey) => window.caches.delete(cacheKey)));
} catch {
}
}
if ("serviceWorker" in navigator) {
try {
const registrations = await navigator.serviceWorker.getRegistrations();
await Promise.all(registrations.map((registration) => registration.update().catch(() => undefined)));
await Promise.all(registrations.map((registration) => registration.unregister().catch(() => undefined)));
} catch {
}
}
const targetUrl = new URL(path, window.location.href);
targetUrl.searchParams.set("refresh", refreshToken);
window.location.replace(targetUrl.toString());
},
};
window.chesscubingAuthModal = {
registerListener(dotNetReference) {
if (authModalMessageHandler) {
window.removeEventListener("message", authModalMessageHandler);
}
authModalMessageHandler = (event) => {
if (event.origin !== window.location.origin) {
return;
}
const data = event.data;
if (!data || data.source !== "chesscubing-auth-modal" || typeof data.status !== "string") {
return;
}
dotNetReference.invokeMethodAsync("HandleAuthModalMessage", data.status).catch(() => undefined);
};
window.addEventListener("message", authModalMessageHandler);
},
unregisterListener() {
if (!authModalMessageHandler) {
return;
}
window.removeEventListener("message", authModalMessageHandler);
authModalMessageHandler = null;
},
notifyParent(status) {
if (!window.parent || window.parent === window) {
return;
}
window.parent.postMessage(
{
source: "chesscubing-auth-modal",
status,
},
window.location.origin,
);
},
};
startViewport();
startMenuScrollTracking();
})();