Rename Space to Project, add desktop document creation

This commit is contained in:
2026-07-20 17:48:14 -04:00
parent 5d3681bd3f
commit 2cfa4afe92
21 changed files with 494 additions and 428 deletions
+103
View File
@@ -0,0 +1,103 @@
<script lang="ts">
import Icon from '@iconify/svelte';
interface ProjectFile {
id: string;
path: string;
kind: string;
}
let {
files = [],
activeFileId = '',
entrypoint = 'main.typ',
readOnly = false,
onSelect,
onCreate,
onUpload,
onRename,
onDelete,
onSetEntry
}: {
files?: ProjectFile[];
activeFileId?: string;
entrypoint?: string;
readOnly?: boolean;
onSelect: (file: ProjectFile) => void;
onCreate: (path: string) => void;
onUpload: (fileList: FileList) => void;
onRename: (file: ProjectFile, path: string) => void;
onDelete: (file: ProjectFile) => void;
onSetEntry: (file: ProjectFile) => void;
} = $props();
let fileInput: HTMLInputElement = $state()!;
function iconFor(path: string): string {
const lower = path.toLowerCase();
if (lower.endsWith('.typ')) return 'mdi:language-markdown-outline';
if (lower.endsWith('.toml')) return 'mdi:cog-outline';
if (lower.endsWith('.bib')) return 'mdi:book-open-variant';
if (lower.endsWith('.png') || lower.endsWith('.jpg') || lower.endsWith('.jpeg') || lower.endsWith('.svg') || lower.endsWith('.gif')) return 'mdi:image-outline';
if (lower.endsWith('.ttf') || lower.endsWith('.otf')) return 'mdi:format-font';
return 'mdi:file-outline';
}
function handleCreate() {
const path = prompt('New file path (e.g. chapter.typ, refs.bib):');
if (path && path.trim()) onCreate(path.trim());
}
function handleRename(file: ProjectFile) {
const path = prompt('Rename file to:', file.path);
if (path && path.trim() && path.trim() !== file.path) onRename(file, path.trim());
}
</script>
<div class="h-full flex flex-col bg-[var(--theme-bg)] border-r border-gray-200 dark:border-white/10">
<div class="flex items-center justify-between px-3 py-2 border-b border-gray-200 dark:border-white/10">
<span class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">Files</span>
{#if !readOnly}
<div class="flex items-center gap-1">
<button onclick={handleCreate} title="New file" class="p-1 rounded hover:bg-gray-100 dark:hover:bg-white/10 text-gray-600 dark:text-gray-300">
<Icon icon="mdi:file-plus-outline" class="text-lg" />
</button>
<button onclick={() => fileInput.click()} title="Upload file" class="p-1 rounded hover:bg-gray-100 dark:hover:bg-white/10 text-gray-600 dark:text-gray-300">
<Icon icon="mdi:upload" class="text-lg" />
</button>
<input bind:this={fileInput} type="file" multiple class="hidden" onchange={(e) => { const t = e.target as HTMLInputElement; if (t.files) onUpload(t.files); t.value = ''; }} />
</div>
{/if}
</div>
<div class="flex-1 overflow-y-auto py-1">
{#each files as file (file.id)}
<div class="group flex items-center gap-1 px-2 py-1.5 text-sm cursor-pointer {activeFileId === file.id ? 'bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300' : 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-white/5'}">
<button class="flex items-center gap-2 flex-1 min-w-0 text-left" onclick={() => onSelect(file)}>
<Icon icon={iconFor(file.path)} class="text-base flex-shrink-0" />
<span class="truncate">{file.path}</span>
{#if file.path === entrypoint}
<span title="Entrypoint">
<Icon icon="mdi:star" class="text-amber-500 text-xs flex-shrink-0" />
</span>
{/if}
</button>
{#if !readOnly}
<div class="flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity">
{#if file.kind === 'text' && file.path.toLowerCase().endsWith('.typ') && file.path !== entrypoint}
<button onclick={() => onSetEntry(file)} title="Set as entrypoint" class="p-0.5 rounded hover:bg-gray-200 dark:hover:bg-white/10 text-gray-500">
<Icon icon="mdi:star-outline" class="text-sm" />
</button>
{/if}
<button onclick={() => handleRename(file)} title="Rename" class="p-0.5 rounded hover:bg-gray-200 dark:hover:bg-white/10 text-gray-500">
<Icon icon="mdi:pencil-outline" class="text-sm" />
</button>
<button onclick={() => onDelete(file)} title="Delete" class="p-0.5 rounded hover:bg-red-100 dark:hover:bg-red-900/30 text-gray-500 hover:text-red-600">
<Icon icon="mdi:trash-can-outline" class="text-sm" />
</button>
</div>
{/if}
</div>
{/each}
</div>
</div>