This commit is contained in:
2026-01-22 10:51:41 +01:00
parent aa550c96b7
commit d6b9a3dbff
6 changed files with 167 additions and 9 deletions

View File

@@ -4,6 +4,7 @@
import Toolbar from './lib/Toolbar.svelte';
import Editor from './lib/Editor.svelte';
import { state as appState } from './lib/state.svelte';
import { exportBoard } from './lib/io.svelte';
let editingItem = $derived(appState.editingId ? appState.getItem(appState.editingId) : null);
let showEditor = $derived(editingItem || appState.editingGlobal);
@@ -11,6 +12,9 @@
let editorWidth = $state(320);
let isResizing = $state(false);
let interfaceHidden = $state(false);
let exportModalOpen = $state(false);
let exportFilename = $state('board');
let exportInput: HTMLInputElement;
$effect(() => {
if (appState.editingId || appState.editingGlobal) {
@@ -18,6 +22,13 @@
}
});
$effect(() => {
if (exportModalOpen && exportInput) {
exportInput.focus();
exportInput.select();
}
});
function handleResizeStart(e: MouseEvent) {
e.preventDefault();
isResizing = true;
@@ -32,6 +43,28 @@
function handleResizeEnd() {
isResizing = false;
}
function openExportModal() {
exportFilename = 'board';
exportModalOpen = true;
}
async function confirmExport() {
const result = await exportBoard(exportFilename.trim() || 'board');
if (!result.success) {
alert(result.error || 'Export failed');
}
exportModalOpen = false;
}
function cancelExport() {
exportModalOpen = false;
}
function handleExportKeydown(e: KeyboardEvent) {
if (e.key === 'Escape') cancelExport();
if (e.key === 'Enter') confirmExport();
}
</script>
<svelte:head>
@@ -43,7 +76,7 @@
<div class="app" class:resizing={isResizing}>
{#if !interfaceHidden}
<Toolbar onHide={() => { interfaceHidden = true; appState.setLocked(true); }} />
<Toolbar onHide={() => { interfaceHidden = true; appState.setLocked(true); }} onExport={openExportModal} />
{/if}
<div class="workspace">
<div class="canvas-container">
@@ -82,6 +115,32 @@
{/each}
</div>
{/if}
{#if exportModalOpen}
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="modal-overlay" onclick={cancelExport} onkeydown={handleExportKeydown}>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div class="modal" onclick={(e) => e.stopPropagation()}>
<div class="modal-title">Export Board</div>
<div class="modal-field">
<label for="export-filename">Filename</label>
<div class="filename-input">
<input
id="export-filename"
type="text"
bind:value={exportFilename}
bind:this={exportInput}
onkeydown={handleExportKeydown}
/>
<span class="suffix">.bub</span>
</div>
</div>
<div class="modal-actions">
<button onclick={cancelExport}>Cancel</button>
<button class="primary" onclick={confirmExport}>Export</button>
</div>
</div>
</div>
{/if}
</div>
<style>
@@ -138,6 +197,7 @@
background: var(--surface, #282c34);
color: var(--text-dim, #666);
border: 1px solid var(--border, #333);
font-family: inherit;
cursor: pointer;
display: flex;
align-items: center;
@@ -161,4 +221,90 @@
background: var(--accent, #4a9eff);
color: var(--text, #fff);
}
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal {
background: var(--surface, #282c34);
border: 1px solid var(--border, #333);
padding: 16px;
min-width: 280px;
}
.modal-title {
font-size: 14px;
font-weight: bold;
color: var(--text, #fff);
margin-bottom: 16px;
}
.modal-field {
margin-bottom: 16px;
}
.modal-field label {
display: block;
font-size: 12px;
color: var(--text-dim, #666);
margin-bottom: 4px;
}
.filename-input {
display: flex;
align-items: center;
background: var(--surface, #282c34);
border: 1px solid var(--border, #333);
}
.filename-input input {
flex: 1;
background: transparent;
border: none;
padding: 8px;
color: var(--text, #fff);
font-family: inherit;
font-size: 14px;
outline: none;
}
.filename-input .suffix {
padding: 8px;
color: var(--text-dim, #666);
font-size: 14px;
border-left: 1px solid var(--border, #333);
}
.modal-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
}
.modal-actions button {
padding: 6px 12px;
background: var(--surface, #282c34);
color: var(--text-dim, #666);
border: 1px solid var(--border, #333);
font-family: inherit;
cursor: pointer;
font-size: 12px;
}
.modal-actions button:hover {
background: var(--accent, #4a9eff);
color: var(--text, #fff);
}
.modal-actions button.primary {
background: var(--accent, #4a9eff);
color: var(--text, #fff);
}
</style>

View File

@@ -14,6 +14,13 @@
box-sizing: border-box;
}
button,
input,
select,
textarea {
font-family: inherit;
}
:root {
font-family: 'Departure Mono', monospace;
color: var(--text, #fff);

View File

@@ -221,6 +221,7 @@
background: transparent;
border: none;
color: var(--text-dim, #666);
font-family: inherit;
cursor: pointer;
}
@@ -237,6 +238,7 @@
background: transparent;
border: none;
color: var(--text-dim, #666);
font-family: inherit;
cursor: pointer;
font-size: 10px;
text-transform: uppercase;
@@ -251,6 +253,7 @@
background: transparent;
border: none;
color: var(--text-dim, #666);
font-family: inherit;
cursor: pointer;
}

View File

@@ -236,6 +236,7 @@
background: var(--surface, #282c34);
color: var(--text-dim, #666);
border: 1px solid var(--border, #333);
font-family: inherit;
cursor: pointer;
}

View File

@@ -1,18 +1,17 @@
<script lang="ts">
import { Upload, Download, Paintbrush, Trash2, EyeOff, ZoomIn, Combine, Lock, Unlock, Grid3x3 } from 'lucide-svelte';
import { exportBoard, importBoard, mergeBoard } from './io.svelte';
import { importBoard, mergeBoard } from './io.svelte';
import { state } from './state.svelte';
import Palette from './Palette.svelte';
let { onHide }: { onHide?: () => void } = $props();
let { onHide, onExport }: { onHide?: () => void; onExport?: () => void } = $props();
let fileInput: HTMLInputElement;
let mergeFileInput: HTMLInputElement;
async function handleExport() {
const result = await exportBoard();
if (!result.success) {
alert(result.error || 'Export failed');
function handleExport() {
if (onExport) {
onExport();
}
}
@@ -186,6 +185,7 @@
width: 24px;
height: 24px;
padding: 0;
font-family: inherit;
font-size: 11px;
font-weight: bold;
}
@@ -200,6 +200,7 @@
background: var(--surface, #282c34);
color: var(--text-dim, #666);
border: 1px solid var(--border, #333);
font-family: inherit;
cursor: pointer;
}

View File

@@ -2,7 +2,7 @@ import JSZip from 'jszip';
import type { Manifest, AssetStore, Item } from './types';
import { state } from './state.svelte';
export async function exportBoard(): Promise<{
export async function exportBoard(filename = 'board'): Promise<{
success: boolean;
error?: string;
}> {
@@ -51,7 +51,7 @@ export async function exportBoard(): Promise<{
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'board.bub';
a.download = `${filename}.bub`;
a.click();
URL.revokeObjectURL(url);
return { success: true };