fixes
This commit is contained in:
148
src/App.svelte
148
src/App.svelte
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
background: var(--surface, #282c34);
|
||||
color: var(--text-dim, #666);
|
||||
border: 1px solid var(--border, #333);
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user