This commit is contained in:
parent
39b0160064
commit
2347ecd201
71 changed files with 3779 additions and 1586 deletions
104
src/index.ts
104
src/index.ts
|
|
@ -8,7 +8,9 @@ import { ConfigPane } from './page/config-pane';
|
|||
import { FullScreenHandler } from './page/full-screen-handler';
|
||||
import { MenuHider } from './page/menu-hider';
|
||||
import { activeVibe, applyVibeSettings, resetSettings, settings } from './settings';
|
||||
import { readBrowserStorage, writeBrowserStorage } from './utils/browser-storage';
|
||||
import { DeltaTimeCalculator } from './utils/delta-time-calculator';
|
||||
import { queryRequiredElement, queryRequiredElements } from './utils/dom';
|
||||
import { ErrorHandler, Severity } from './utils/error-handler';
|
||||
import { initializeGpu } from './utils/graphics/initialize-gpu';
|
||||
import { VIBE_PRESETS } from './vibes';
|
||||
|
|
@ -37,10 +39,13 @@ const getMirrorSegmentRatio = (count: number): number =>
|
|||
(count - appConfig.toolbar.mirror.min) /
|
||||
(appConfig.toolbar.mirror.max - appConfig.toolbar.mirror.min);
|
||||
|
||||
const mirrorSegmentNames: Readonly<Record<number, string>> =
|
||||
appConfig.toolbar.mirror.names;
|
||||
|
||||
const formatMirrorSegmentCount = (count: number): string =>
|
||||
count === appConfig.toolbar.mirror.default
|
||||
? 'Mirror off'
|
||||
: `${count} ${appConfig.toolbar.mirror.names[count] ?? 'slices'}`;
|
||||
: `${count} ${mirrorSegmentNames[count] ?? 'slices'}`;
|
||||
|
||||
const renderRuntimeMessage = (
|
||||
container: HTMLElement,
|
||||
|
|
@ -63,40 +68,52 @@ const renderRuntimeMessage = (
|
|||
};
|
||||
|
||||
const elements = {
|
||||
aside: document.querySelector('aside') as HTMLDivElement,
|
||||
infoButton: document.querySelector('button.info') as HTMLButtonElement,
|
||||
infoElement: document.querySelector('.info-page') as HTMLDivElement,
|
||||
minimizeFullScreenButton: document.querySelector(
|
||||
'button.minimize-full-screen'
|
||||
) as HTMLButtonElement,
|
||||
maximizeFullScreenButton: document.querySelector(
|
||||
'button.maximize-full-screen'
|
||||
) as HTMLButtonElement,
|
||||
settingsButton: document.querySelector('button.settings') as HTMLButtonElement,
|
||||
soundButton: document.querySelector('button.sound') as HTMLButtonElement,
|
||||
restartButton: document.querySelector('button.restart') as HTMLButtonElement,
|
||||
canvas: document.querySelector('canvas') as HTMLCanvasElement,
|
||||
eraserPreview: document.querySelector('.eraser-preview') as HTMLDivElement,
|
||||
errorContainer: document.querySelector('.errors-container') as HTMLDivElement,
|
||||
previousVibe: document.querySelector('.previous-vibe') as HTMLButtonElement,
|
||||
nextVibe: document.querySelector('.next-vibe') as HTMLButtonElement,
|
||||
swatches: Array.from(
|
||||
document.querySelectorAll('.color-swatch')
|
||||
) as Array<HTMLButtonElement>,
|
||||
eraserSizeControl: document.querySelector('.eraser-size-control') as HTMLLabelElement,
|
||||
eraserSizeSlider: document.querySelector('.eraser-size-slider') as HTMLInputElement,
|
||||
mirrorSegmentControl: document.querySelector(
|
||||
'.mirror-segment-control'
|
||||
) as HTMLLabelElement,
|
||||
mirrorSegmentSlider: document.querySelector(
|
||||
'.mirror-segment-slider'
|
||||
) as HTMLInputElement,
|
||||
export4k: document.querySelector('.export-4k') as HTMLButtonElement,
|
||||
exportStatus: document.querySelector('.export-status') as HTMLSpanElement,
|
||||
prompt: document.querySelector('.garden-prompt') as HTMLDivElement,
|
||||
aside: queryRequiredElement('aside', HTMLDivElement),
|
||||
infoButton: queryRequiredElement('button.info', HTMLButtonElement),
|
||||
infoElement: queryRequiredElement('.info-page', HTMLDivElement),
|
||||
minimizeFullScreenButton: queryRequiredElement(
|
||||
'button.minimize-full-screen',
|
||||
HTMLButtonElement
|
||||
),
|
||||
maximizeFullScreenButton: queryRequiredElement(
|
||||
'button.maximize-full-screen',
|
||||
HTMLButtonElement
|
||||
),
|
||||
settingsButton: queryRequiredElement('button.settings', HTMLButtonElement),
|
||||
soundButton: queryRequiredElement('button.sound', HTMLButtonElement),
|
||||
restartButton: queryRequiredElement('button.restart', HTMLButtonElement),
|
||||
canvas: queryRequiredElement('canvas', HTMLCanvasElement),
|
||||
eraserPreview: queryRequiredElement('.eraser-preview', HTMLDivElement),
|
||||
errorContainer: queryRequiredElement('.errors-container', HTMLDivElement),
|
||||
previousVibe: queryRequiredElement('.previous-vibe', HTMLButtonElement),
|
||||
nextVibe: queryRequiredElement('.next-vibe', HTMLButtonElement),
|
||||
swatches: queryRequiredElements('.color-swatch', HTMLButtonElement),
|
||||
eraserSizeControl: queryRequiredElement('.eraser-size-control', HTMLLabelElement),
|
||||
eraserSizeSlider: queryRequiredElement('.eraser-size-slider', HTMLInputElement),
|
||||
mirrorSegmentControl: queryRequiredElement(
|
||||
'.mirror-segment-control',
|
||||
HTMLLabelElement
|
||||
),
|
||||
mirrorSegmentSlider: queryRequiredElement(
|
||||
'.mirror-segment-slider',
|
||||
HTMLInputElement
|
||||
),
|
||||
export4k: queryRequiredElement('.export-4k', HTMLButtonElement),
|
||||
exportStatus: queryRequiredElement('.export-status', HTMLSpanElement),
|
||||
prompt: queryRequiredElement('.garden-prompt', HTMLDivElement),
|
||||
loadingIndicator: queryRequiredElement('.loading-indicator', HTMLDivElement),
|
||||
loadingStatus: queryRequiredElement('.loading-status', HTMLDivElement),
|
||||
loadingProgress: queryRequiredElement('.loading-progress', HTMLDivElement),
|
||||
};
|
||||
|
||||
let isAudioMuted = localStorage.getItem(appConfig.storage.audioMutedKey) === '1';
|
||||
const setLoadingStage = (label: string, ratio: number) => {
|
||||
const percent = Math.round(Math.max(0, Math.min(1, ratio)) * 100);
|
||||
elements.loadingStatus.textContent = label;
|
||||
elements.loadingIndicator.style.setProperty('--loading-progress', `${percent}%`);
|
||||
elements.loadingProgress.setAttribute('aria-valuenow', String(percent));
|
||||
};
|
||||
|
||||
let isAudioMuted = readBrowserStorage(appConfig.storage.audioMutedKey) === '1';
|
||||
|
||||
const renderAudioUi = (game: GameLoop | null) => {
|
||||
elements.soundButton.classList.toggle('muted', isAudioMuted);
|
||||
|
|
@ -185,6 +202,7 @@ const main = async () => {
|
|||
ErrorHandler.addOnErrorListener((error, _metadata) => {
|
||||
renderRuntimeMessage(elements.errorContainer, error);
|
||||
if (error.severity === Severity.ERROR) {
|
||||
document.body.classList.remove('is-loading');
|
||||
game?.destroy();
|
||||
shouldStop = true;
|
||||
}
|
||||
|
|
@ -223,7 +241,8 @@ const main = async () => {
|
|||
() =>
|
||||
FullScreenHandler.isInFullScreenMode() &&
|
||||
!configPane.isOpen &&
|
||||
!infoPageHandler.isOpen
|
||||
!infoPageHandler.isOpen,
|
||||
{ persistentElement: elements.settingsButton }
|
||||
);
|
||||
new FullScreenHandler(
|
||||
elements.minimizeFullScreenButton,
|
||||
|
|
@ -232,13 +251,16 @@ const main = async () => {
|
|||
);
|
||||
|
||||
const fontsReady = document.fonts.ready.catch(() => undefined);
|
||||
setLoadingStage('Connecting to GPU…', 0.1);
|
||||
const gpu = await initializeGpu();
|
||||
setLoadingStage('Loading fonts…', 0.4);
|
||||
await fontsReady;
|
||||
setLoadingStage('Compiling shaders…', 0.7);
|
||||
|
||||
elements.restartButton.addEventListener('click', () => game?.destroy());
|
||||
elements.soundButton.addEventListener('click', (event) => {
|
||||
isAudioMuted = !isAudioMuted;
|
||||
localStorage.setItem(appConfig.storage.audioMutedKey, isAudioMuted ? '1' : '0');
|
||||
writeBrowserStorage(appConfig.storage.audioMutedKey, isAudioMuted ? '1' : '0');
|
||||
renderAudioUi(game);
|
||||
if (!isAudioMuted) {
|
||||
game?.startAudio(event.isTrusted);
|
||||
|
|
@ -323,6 +345,7 @@ const main = async () => {
|
|||
renderMirrorSegmentUi();
|
||||
renderAudioUi(game);
|
||||
|
||||
let isFirstStart = true;
|
||||
while (!shouldStop) {
|
||||
game = new GameLoop(elements.canvas, gpu, deltaTimeCalculator, {
|
||||
prompt: elements.prompt,
|
||||
|
|
@ -334,9 +357,18 @@ const main = async () => {
|
|||
renderMirrorSegmentUi();
|
||||
renderAudioUi(game);
|
||||
|
||||
await game.start();
|
||||
const startPromise = game.start();
|
||||
if (isFirstStart) {
|
||||
isFirstStart = false;
|
||||
setLoadingStage('Ready', 1);
|
||||
requestAnimationFrame(() =>
|
||||
requestAnimationFrame(() => document.body.classList.remove('is-loading'))
|
||||
);
|
||||
}
|
||||
await startPromise;
|
||||
}
|
||||
} catch (e) {
|
||||
document.body.classList.remove('is-loading');
|
||||
ErrorHandler.addException(e);
|
||||
console.error(e);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue