Files
buboard/src/lib/Toolbar.svelte
2025-12-01 18:04:56 +01:00

225 lines
4.7 KiB
Svelte

<script lang="ts">
import { Upload, Download, Paintbrush, Trash2, EyeOff, ZoomIn } from 'lucide-svelte';
import { exportBoard, importBoard } from './io';
import { state } from './state.svelte';
import Palette from './Palette.svelte';
let { onHide }: { onHide?: () => void } = $props();
let fileInput: HTMLInputElement;
async function handleExport() {
const result = await exportBoard();
if (!result.success) {
alert(result.error || 'Export failed');
}
}
function handleImportClick() {
fileInput.click();
}
async function handleFileChange(e: Event) {
const input = e.target as HTMLInputElement;
const file = input.files?.[0];
if (!file) return;
const result = await importBoard(file);
if (!result.success) {
alert(result.error || 'Import failed');
}
input.value = '';
}
function handleClear() {
if (confirm('Clear the canvas? This cannot be undone.')) {
state.reset();
}
}
function handleZoom(e: Event) {
const value = parseFloat((e.target as HTMLInputElement).value);
state.setZoom(value);
}
function handleFlagClick(key: string) {
if (state.hasFlag(key)) {
state.gotoFlag(key);
} else {
state.setFlag(key);
}
}
function handleFlagRightClick(e: MouseEvent, key: string) {
e.preventDefault();
state.clearFlag(key);
}
let zoomPercent = $derived(Math.round(state.viewport.zoom * 100));
let coordX = $derived(Math.round(-state.viewport.x / state.viewport.zoom));
let coordY = $derived(Math.round(-state.viewport.y / state.viewport.zoom));
const flagKeys = ['1', '2', '3', '4', '5', '6', '7', '8', '9'];
</script>
<div class="toolbar">
<span class="app-name">Buboard</span>
<Palette />
<div class="flags">
{#each flagKeys as key}
<button
class="flag"
class:filled={state.hasFlag(key)}
onclick={() => handleFlagClick(key)}
oncontextmenu={(e) => handleFlagRightClick(e, key)}
title="Position {key}"
>
{key}
</button>
{/each}
</div>
<div class="spacer"></div>
<span class="coords">{coordX}, {coordY}</span>
<div class="zoom">
<input
type="range"
min="0.1"
max="5"
step="0.1"
value={state.viewport.zoom}
oninput={handleZoom}
title="Zoom"
/>
<button class="zoom-reset" onclick={() => state.resetViewport()} title="Reset view (0, 0 at 100%)">
<ZoomIn size={12} />
<span>{zoomPercent}%</span>
</button>
</div>
<button onclick={() => state.editGlobal(true)} title="Style"><Paintbrush size={14} /></button>
<button onclick={handleClear} title="Clear"><Trash2 size={14} /></button>
<button onclick={handleImportClick} title="Import"><Upload size={14} /></button>
<button onclick={handleExport} title="Export"><Download size={14} /></button>
{#if onHide}
<button onclick={onHide} title="Hide interface"><EyeOff size={14} /></button>
{/if}
<input
bind:this={fileInput}
type="file"
accept=".bub"
onchange={handleFileChange}
style="display: none"
/>
</div>
<style>
.toolbar {
padding: 4px 8px;
display: flex;
gap: 4px;
background: var(--surface, #282c34);
border-bottom: 1px solid var(--border, #333);
flex-shrink: 0;
}
.app-name {
font-size: 12px;
font-weight: bold;
color: var(--text-dim, #666);
display: flex;
align-items: center;
padding: 0 8px 0 4px;
}
.spacer {
flex: 1;
}
.flags {
display: flex;
gap: 2px;
}
.flag {
width: 24px;
height: 24px;
padding: 0;
font-size: 11px;
font-weight: bold;
}
.flag.filled {
background: var(--accent, #4a9eff);
color: var(--text, #fff);
}
button {
padding: 4px 8px;
background: var(--surface, #282c34);
color: var(--text-dim, #666);
border: 1px solid var(--border, #333);
cursor: pointer;
}
button:hover {
background: var(--accent, #4a9eff);
color: var(--text, #fff);
}
.zoom {
display: flex;
align-items: center;
gap: 4px;
}
.zoom input[type='range'] {
-webkit-appearance: none;
appearance: none;
width: 80px;
height: 4px;
background: var(--border, #333);
border: none;
cursor: pointer;
}
.zoom input[type='range']::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 12px;
height: 12px;
background: var(--text-dim, #666);
cursor: pointer;
}
.zoom input[type='range']::-moz-range-thumb {
width: 12px;
height: 12px;
background: var(--text-dim, #666);
border: none;
cursor: pointer;
}
.zoom input[type='range']:hover::-webkit-slider-thumb {
background: var(--accent, #4a9eff);
}
.zoom input[type='range']:hover::-moz-range-thumb {
background: var(--accent, #4a9eff);
}
.coords {
font-size: 11px;
color: var(--text-dim, #666);
display: flex;
align-items: center;
padding: 0 8px;
font-variant-numeric: tabular-nums;
}
.zoom-reset {
display: flex;
align-items: center;
gap: 4px;
padding: 2px 6px;
font-size: 11px;
min-width: 52px;
}
</style>