be2ea6c4ea
Pointer-Events pan/zoom/tilt under a fixed black ~2pt line with rifle-scope end ticks (≥44×44 hit). AxisPanel becomes Tape line step; session persists tape transform; scale = tick distance / L. sveltedrag unavailable for Svelte 4 — Pointer Events path used.
106 lines
3.1 KiB
Svelte
106 lines
3.1 KiB
Svelte
<script lang="ts">
|
|
import { onMount, onDestroy } from 'svelte';
|
|
import { supported } from './browser';
|
|
import { createCaptureSession, type CaptureSession } from './capture/session';
|
|
import type { Capture } from './capture/capture.types';
|
|
import Resume from './capture/Resume.svelte';
|
|
import BatchPhotos from './capture/BatchPhotos.svelte';
|
|
import PickCalibration from './capture/PickCalibration.svelte';
|
|
import MaskPanel from './capture/MaskPanel.svelte';
|
|
import AxisPanel from './capture/AxisPanel.svelte';
|
|
import MeasurePanel from './capture/MeasurePanel.svelte';
|
|
|
|
const gate = typeof window !== 'undefined' ? supported() : 'window unavailable';
|
|
const session: CaptureSession = createCaptureSession();
|
|
|
|
let cap: Capture = { k: 'batch', photos: [] };
|
|
let busy = false;
|
|
let pngUrl: string | null = null;
|
|
|
|
const unsubCap = session.capture.subscribe((c) => {
|
|
cap = c;
|
|
pngUrl = c.k === 'measure' ? session.getInventoryUrl() : null;
|
|
});
|
|
const unsubBusy = session.busy.subscribe((b) => {
|
|
busy = b;
|
|
});
|
|
|
|
onMount(() => {
|
|
void session.resumeProbe();
|
|
});
|
|
onDestroy(() => {
|
|
unsubCap();
|
|
unsubBusy();
|
|
session.dispose();
|
|
});
|
|
</script>
|
|
|
|
{#if gate}
|
|
<main class="hardstop">
|
|
<h1>Unsupported browser</h1>
|
|
<p>
|
|
SlabUploader needs a modern browser with canvas, workers, and IndexedDB support:
|
|
iOS Safari, Android Chrome, or desktop Chrome, Edge, or Firefox.
|
|
</p>
|
|
<p class="detail">Missing: {gate}</p>
|
|
</main>
|
|
{:else}
|
|
<main class="app">
|
|
{#if cap.k === 'resume'}
|
|
<Resume onContinue={() => session.continueDraft()} onFresh={() => void session.startFresh()} />
|
|
{:else if cap.k === 'batch'}
|
|
<BatchPhotos
|
|
photos={cap.photos}
|
|
onAdd={(f) => void session.addToBatch(f)}
|
|
onRemove={session.removeFromBatch}
|
|
onContinue={session.continueBatch}
|
|
/>
|
|
{:else if cap.k === 'pickCalibration'}
|
|
<PickCalibration
|
|
photos={cap.photos}
|
|
onPick={session.pickCalibration}
|
|
onBack={session.back}
|
|
/>
|
|
{:else if cap.k === 'mask'}
|
|
<MaskPanel
|
|
state={cap}
|
|
{busy}
|
|
setSensitivity={session.setSensitivity}
|
|
setSheet={session.setSheet}
|
|
approveMask={session.approveMask}
|
|
retakes={session.retakes}
|
|
back={session.back}
|
|
/>
|
|
{:else if cap.k === 'axis'}
|
|
<AxisPanel
|
|
state={cap}
|
|
setAxisAngle={session.setAxisAngle}
|
|
setTape={session.setTape}
|
|
approveAxis={session.approveAxis}
|
|
back={session.back}
|
|
/>
|
|
{:else}
|
|
<MeasurePanel
|
|
state={cap}
|
|
setLength={session.setLength}
|
|
setThickness={session.setThickness}
|
|
calculate={session.calculate}
|
|
back={session.back}
|
|
{pngUrl}
|
|
/>
|
|
{/if}
|
|
</main>
|
|
{/if}
|
|
|
|
<style>
|
|
:global(body) {
|
|
margin: 0;
|
|
background: #121212;
|
|
font-family: system-ui, -apple-system, Segoe UI, sans-serif;
|
|
}
|
|
.app, .hardstop { max-width: 720px; margin: 0 auto; min-height: 100vh; }
|
|
.hardstop { padding: 48px 24px; color: #eee; text-align: center; }
|
|
.hardstop h1 { font-size: 1.6rem; }
|
|
.detail { color: #888; font-size: 0.85rem; }
|
|
</style>
|