Rewrite website as a Hyprland desktop
This commit is contained in:
1 parent
7c52779cf6
commit
3065ad6092
197 files changed
+5609
-17779
No files matched your search
@@ -0,0 +1,149 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import Icon from '@iconify/svelte';
|
||||
import type { ModelScene } from '#lib/models/model-scene';
|
||||
|
||||
let { modelUrl, modelName }: { modelUrl: string; modelName: string } = $props();
|
||||
|
||||
let container = $state<HTMLDivElement>();
|
||||
let modelScene = $state<ModelScene | null>(null);
|
||||
let status = $state<'loading' | 'ready' | 'error' | 'unsupported'>('loading');
|
||||
let autoRotate = $state(true);
|
||||
let wireframe = $state(false);
|
||||
let brightness = $state(0.6);
|
||||
|
||||
onMount(() => {
|
||||
let disposed = false;
|
||||
let resizeObserver: ResizeObserver | undefined;
|
||||
|
||||
import('#lib/models/model-scene')
|
||||
.then(({ ModelScene }) => {
|
||||
if (disposed || !container) return;
|
||||
modelScene = new ModelScene(container);
|
||||
resizeObserver = new ResizeObserver(() => modelScene?.resize());
|
||||
resizeObserver.observe(container);
|
||||
})
|
||||
.catch(() => (status = 'unsupported'));
|
||||
|
||||
return () => {
|
||||
disposed = true;
|
||||
resizeObserver?.disconnect();
|
||||
modelScene?.dispose();
|
||||
};
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (!modelScene) return;
|
||||
status = 'loading';
|
||||
wireframe = false;
|
||||
modelScene
|
||||
.load(modelUrl)
|
||||
.then(() => (status = 'ready'))
|
||||
.catch(() => (status = 'error'));
|
||||
});
|
||||
|
||||
$effect(() => modelScene?.setAutoRotate(autoRotate));
|
||||
$effect(() => modelScene?.setWireframe(wireframe));
|
||||
$effect(() => modelScene?.setBrightness(brightness));
|
||||
</script>
|
||||
|
||||
<div class="viewer">
|
||||
<div class="toolbar">
|
||||
<span class="model-name">{modelName}</span>
|
||||
<div class="controls">
|
||||
<button class:active={autoRotate} title="Auto rotate" onclick={() => (autoRotate = !autoRotate)}>
|
||||
<Icon icon="mdi:rotate-3d-variant" width="16" />
|
||||
</button>
|
||||
<button class:active={wireframe} title="Wireframe" onclick={() => (wireframe = !wireframe)}>
|
||||
<Icon icon="mdi:cube-outline" width="16" />
|
||||
</button>
|
||||
<button title="Darker" onclick={() => (brightness = Math.max(0, brightness - 0.2))}>
|
||||
<Icon icon="mdi:brightness-5" width="16" />
|
||||
</button>
|
||||
<button title="Brighter" onclick={() => (brightness = Math.min(3, brightness + 0.2))}>
|
||||
<Icon icon="mdi:brightness-7" width="16" />
|
||||
</button>
|
||||
<button title="Reset camera" onclick={() => modelScene?.resetCamera()}>
|
||||
<Icon icon="mdi:camera-retake" width="16" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="canvas-area" bind:this={container} tabindex="0" role="application" aria-label="3D view of {modelName}">
|
||||
{#if status !== 'ready'}
|
||||
<div class="status muted">
|
||||
{#if status === 'loading'}
|
||||
loading model...
|
||||
{:else if status === 'error'}
|
||||
failed to load model
|
||||
{:else}
|
||||
WebGL is not available in this browser
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="hints muted">drag to rotate · scroll to zoom · arrow keys after clicking</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.viewer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid color-mix(in srgb, var(--foreground) 22%, transparent);
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.3rem 0.6rem;
|
||||
border-bottom: 1px solid color-mix(in srgb, var(--foreground) 22%, transparent);
|
||||
}
|
||||
|
||||
.model-name {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.controls button {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background: none;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.controls button:hover {
|
||||
border-color: color-mix(in srgb, var(--foreground) 30%, transparent);
|
||||
}
|
||||
|
||||
.controls button.active {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.canvas-area {
|
||||
position: relative;
|
||||
height: 380px;
|
||||
background: radial-gradient(circle, var(--lighter-background), var(--dark-background));
|
||||
}
|
||||
|
||||
.canvas-area :global(canvas) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.status {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.hints {
|
||||
padding: 0.3rem 0.6rem;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user