3d-arena / src /routes /Viewer.svelte
dylanebert's picture
dylanebert HF staff
viewer topo-only
153165c
<script lang="ts">
import { onMount, onDestroy } from "svelte";
import type { IViewer } from "./viewers/IViewer";
import { createViewer } from "./viewers/ViewerFactory";
import { ArrowLeft, Cube, WatsonHealth3DPrintMesh } from "carbon-icons-svelte";
interface Scene {
name: string;
url: string;
thumbnail: string;
}
export let modelName: string;
export let scene: Scene;
export let onBack: () => void;
let container: HTMLDivElement;
let canvas: HTMLCanvasElement;
let overlay: HTMLDivElement;
let loadingBarFill: HTMLDivElement;
let viewer: IViewer;
async function loadScene() {
overlay.style.display = "flex";
viewer = await createViewer(scene.url, canvas, (progress) => {
loadingBarFill.style.width = `${progress * 100}%`;
});
window.addEventListener("resize", handleResize);
window.addEventListener("keydown", handleKeyDown);
handleResize();
overlay.style.display = "none";
}
function handleResize() {
if (!canvas || !container) return;
requestAnimationFrame(() => {
canvas.width = container.clientWidth;
canvas.height = container.clientHeight;
});
}
function handleKeyDown(e: KeyboardEvent) {
if (e.code === "KeyP") {
capture();
}
}
async function capture() {
const data = await viewer.capture();
if (!data) {
console.error("Failed to capture screenshot");
return;
}
const a = document.createElement("a");
a.href = data;
a.download = `${scene.name}.png`;
a.click();
}
onMount(loadScene);
onDestroy(() => {
viewer?.dispose();
if (typeof window !== "undefined") {
window.removeEventListener("resize", handleResize);
window.removeEventListener("keydown", handleKeyDown);
}
});
</script>
<div class="header">
<div class="back" aria-label="Back" aria-hidden="true" on:click={onBack}>
<ArrowLeft size={24} />
</div>
<div class="spacer" />
<button class="title-button" on:click={loadScene}>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<h2>
<span class="muted" on:click={onBack}>{modelName}/</span>{scene.name.length > 16
? `${scene.name.slice(0, 16)}...`
: scene.name}
</h2>
</button>
<div class="desktop-spacer" />
</div>
<div class="canvas-container" bind:this={container}>
<div bind:this={overlay} class="loading-overlay">
<div class="loading-bar">
<div bind:this={loadingBarFill} class="loading-bar-fill" />
</div>
</div>
<canvas class="viewer-canvas" bind:this={canvas} width={800} height={600}> </canvas>
<div class="stats">
{#if viewer}
<p>vertex count: {viewer.vertexCount}</p>
{/if}
</div>
{#if viewer && !viewer.topoOnly}
<div class="mode-toggle">
<label>
<input
type="radio"
name="modeB"
value="default"
checked
on:change={() => viewer.setRenderMode("default")}
/>
<Cube class="mode-toggle-icon" />
</label>
<label>
<input
type="radio"
name="modeB"
value="wireframe"
on:change={() => viewer.setRenderMode("wireframe")}
/>
<WatsonHealth3DPrintMesh class="mode-toggle-icon" />
</label>
</div>
{/if}
</div>