isle.pizza/src/App.svelte
Christian Semmler 64be72194e
Some checks are pending
Build / build (push) Waiting to run
Add save game editor (#12)
* WIP

* stuff

* WIP: Interactive 3D score cube for save editor

* Conditionally render ScoreCube to properly clean up WebGL canvas

Only mount the ScoreCube component when on the save-editor page.
This ensures onDestroy is called when navigating away, properly
disposing of the WebGL renderer and removing the canvas from DOM.

* Refactor: Consolidate formats and genericize WDB rendering

- Move parsing/serialization code to src/core/formats/:
  - BinaryReader.js, BinaryWriter.js (shared utilities)
  - SaveGameParser.js, SaveGameSerializer.js
  - PlayersParser.js, PlayersSerializer.js
  - Create formats/index.js as barrel export

- Extract generic WdbModelRenderer from ScoreCubeRenderer:
  - WdbModelRenderer handles D3DRM geometry and paletted textures
  - ScoreCubeRenderer extends it with score-specific logic
  - Prepares for rendering other WDB models in the future

- Keep savegame/constants.js for domain-specific constants
- savegame/index.js remains as high-level API facade

* Save editor UI improvements

- Make save slot cards fixed width (85px) to prevent resizing with name length
- Make save slot cards more compact (smaller icons, padding, font)
- Remove Act selection from Character section
- Remove box-shadow from selected character to fix collapsed section bleed

* Improve score cube lighting to match in-game appearance

- Use flat, even lighting (high ambient + soft front light)
- Remove harsh directional shadows from edges
- Adjust camera position slightly for better framing

* Add spacing below score cube canvas

* Add spinning loader while loading score cube

* Update three.js to 0.182.0 and fix npm audit issues

- Update three.js from 0.170.0 to 0.182.0
- Fix npm audit vulnerabilities (devalue, lodash, svelte)
- Remaining vulns are in dev dependencies (vite, workbox-cli)

* Fix score cube overflow on mobile

Add max-width constraints to prevent the score cube from expanding
its container on narrow viewports while preserving its natural
200x200 size on desktop.

* Add save slot carousel and improve empty states

- Add reusable Carousel component with arrow navigation, drag-to-scroll,
  and click-to-scroll-into-view functionality
- Replace static save slot list with horizontal carousel
- Add empty state with image when no save files exist
- Add prompt state when saves exist but none is selected
- Reset selected slot when entering Save Editor page

* Add February 2026 changelog entry for Save Editor

* Add missing January 2026 changelog entries for Safari and mobile fixes

* Remove unused mission images

* Refactor opfs.js to reduce code duplication

- Consolidate getFileHandle to use getOpfsRoot internally
- Add writeTextFile helper that uses writeBinaryFile
- Extract showToast helper for toast notifications
- Simplify saveConfig to use writeTextFile instead of duplicate worker
- Simplify fileExists and readBinaryFile to use getFileHandle

* Remove unused ScoreColorButton component

* Remove unused UI components
2026-02-01 00:28:16 +01:00

103 lines
3.4 KiB
Svelte

<script>
import { onMount } from 'svelte';
import { currentPage, debugEnabled } from './stores.js';
import { registerServiceWorker, checkCacheStatus } from './core/service-worker.js';
import { setupCanvasEvents } from './core/emscripten.js';
import TopContent from './lib/TopContent.svelte';
import Controls from './lib/Controls.svelte';
import ReadMePage from './lib/ReadMePage.svelte';
import ConfigurePage from './lib/ConfigurePage.svelte';
import FreeStuffPage from './lib/FreeStuffPage.svelte';
import SaveEditorPage from './lib/SaveEditorPage.svelte';
import UpdatePopup from './lib/UpdatePopup.svelte';
import GoodbyePopup from './lib/GoodbyePopup.svelte';
import ConfigToast from './lib/ConfigToast.svelte';
import DebugPanel from './lib/DebugPanel.svelte';
import CanvasWrapper from './lib/CanvasWrapper.svelte';
onMount(async () => {
// Disable browser's automatic scroll restoration
if ('scrollRestoration' in history) {
history.scrollRestoration = 'manual';
}
// Register service worker and initialize
const registration = await registerServiceWorker();
if (registration) {
checkCacheStatus();
}
// Setup canvas events
setupCanvasEvents();
// Initialize history state based on current page
const initialHash = window.location.hash;
if (initialHash) {
// Set up proper history state for the current hash
history.replaceState({ page: 'main' }, '', window.location.pathname);
history.pushState({ page: $currentPage }, '', initialHash);
} else {
history.replaceState({ page: 'main' }, '', window.location.pathname);
}
// Handle browser back/forward
window.addEventListener('popstate', (e) => {
if (e.state && e.state.page && e.state.page !== 'main') {
currentPage.set(e.state.page);
} else {
currentPage.set('main');
}
});
});
// Scroll to top whenever page changes
$: $currentPage, window.scrollTo(0, 0);
</script>
<!-- Audio element outside page routing so it persists across navigation -->
<audio id="install-audio" loop preload="none">
<source src="install.mp3" type="audio/mpeg">
</audio>
<GoodbyePopup />
<UpdatePopup />
<ConfigToast />
<div id="main-container">
<div class="page-wrapper" class:active={$currentPage === 'main'}>
<TopContent />
<Controls />
</div>
<div class="page-wrapper" class:active={$currentPage === 'read-me'}>
<ReadMePage />
</div>
<div class="page-wrapper" class:active={$currentPage === 'configure'}>
<ConfigurePage />
</div>
<div class="page-wrapper" class:active={$currentPage === 'free-stuff'}>
<FreeStuffPage />
</div>
<div class="page-wrapper" class:active={$currentPage === 'save-editor'}>
<SaveEditorPage />
</div>
<div class="footer-disclaimer">
<p>LEGO® and LEGO Island™ are trademarks of The LEGO Group.</p>
<p>This is an unofficial fan project and is not affiliated with or endorsed by The LEGO Group.</p>
</div>
<div class="app-footer">
{#if __BUILD_TIME__}
<p>Last updated: {__BUILD_TIME__}</p>
{:else}
<p><strong>DEVELOPMENT MODE</strong></p>
{/if}
</div>
</div>
<CanvasWrapper />
{#if $debugEnabled}
<DebugPanel />
{/if}