This commit is contained in:
2025-12-01 17:42:42 +01:00
commit e9b0e9d856
26 changed files with 3438 additions and 0 deletions

207
src/lib/Toolbar.svelte Normal file
View File

@@ -0,0 +1,207 @@
<script lang="ts">
import { Upload, Download, Paintbrush, Trash2, EyeOff } 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));
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>
<div class="zoom">
<input
type="range"
min="0.1"
max="5"
step="0.1"
value={state.viewport.zoom}
oninput={handleZoom}
title="Zoom"
/>
<span class="zoom-label">{zoomPercent}%</span>
</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);
}
.zoom-label {
font-size: 11px;
color: var(--text-dim, #666);
min-width: 36px;
text-align: right;
}
</style>