Beginning of File System integration

This commit is contained in:
2025-10-14 22:30:05 +02:00
parent ece02406bd
commit cec7ed8bd4
12 changed files with 2119 additions and 31 deletions

View File

@ -1,9 +1,15 @@
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import TopBar from './lib/TopBar.svelte';
import EditorWithLogs from './lib/EditorWithLogs.svelte';
import EditorSettings from './lib/EditorSettings.svelte';
import FileBrowser from './lib/FileBrowser.svelte';
import SidePanel from './lib/SidePanel.svelte';
import Popup from './lib/Popup.svelte';
import { csound, csoundLogs, type LogEntry } from './lib/csound';
import { projectManager, type CsoundProject } from './lib/project-system';
import ConfirmDialog from './lib/ConfirmDialog.svelte';
import InputDialog from './lib/InputDialog.svelte';
import {
PanelLeftClose,
PanelLeftOpen,
@ -11,17 +17,54 @@
PanelRightOpen,
PanelBottomClose,
PanelBottomOpen,
LayoutGrid
LayoutGrid,
Save
} from 'lucide-svelte';
let sidePanelVisible = $state(true);
let sidePanelPosition = $state<'left' | 'right' | 'bottom'>('right');
let popupVisible = $state(false);
let editorValue = $state('// Start coding here...\n');
let interpreterLogs = $state<string[]>([]);
let editorValue = $state('<CsoundSynthesizer>\n<CsOptions>\n-odac\n</CsOptions>\n<CsInstruments>\n\nsr = 44100\nksmps = 32\nnchnls = 2\n0dbfs = 1\n\ninstr 1\n aOut oscili 0.5, 440\n outs aOut, aOut\nendin\n\n</CsInstruments>\n<CsScore>\ni 1 0 2\n</CsScore>\n</CsoundSynthesizer>\n');
let interpreterLogs = $state<LogEntry[]>([]);
let sidePanelRef: SidePanel;
let editorRef: EditorWithLogs;
let fileBrowserRef: FileBrowser;
let currentProjectId = $state<string | null>(null);
let hasUnsavedChanges = $state(false);
let isNewUnsavedBuffer = $state(false);
let pendingProject: CsoundProject | null = null;
let showUnsavedDialog = $state(false);
let showSaveAsDialog = $state(false);
const TEMPLATE_CONTENT = '<CsoundSynthesizer>\n<CsOptions>\n-odac\n</CsOptions>\n<CsInstruments>\n\nsr = 44100\nksmps = 32\nnchnls = 2\n0dbfs = 1\n\ninstr 1\n aOut oscili 0.5, 440\n outs aOut, aOut\nendin\n\n</CsInstruments>\n<CsScore>\ni 1 0 2\n</CsScore>\n</CsoundSynthesizer>\n';
onMount(async () => {
await csound.init();
await projectManager.init();
const result = await projectManager.getAllProjects();
if (result.success && result.data.length === 0) {
await projectManager.createProject({
title: 'Template',
author: 'System',
content: TEMPLATE_CONTENT,
tags: []
});
}
const unsubscribe = csoundLogs.subscribe(logs => {
interpreterLogs = logs;
});
return () => {
unsubscribe();
};
});
onDestroy(async () => {
await csound.destroy();
});
function toggleSidePanel() {
sidePanelVisible = !sidePanelVisible;
@ -33,6 +76,121 @@
function handleEditorChange(value: string) {
editorValue = value;
hasUnsavedChanges = true;
}
function handleNewFile() {
if (hasUnsavedChanges) {
pendingProject = null;
showUnsavedDialog = true;
return;
}
createNewBuffer();
}
function createNewBuffer() {
currentProjectId = null;
editorValue = TEMPLATE_CONTENT;
hasUnsavedChanges = false;
isNewUnsavedBuffer = true;
if (editorRef) {
editorRef.setValue(TEMPLATE_CONTENT);
}
}
async function handleFileSelect(project: CsoundProject | null) {
if (hasUnsavedChanges) {
pendingProject = project;
showUnsavedDialog = true;
return;
}
if (project) {
loadProject(project);
}
}
function loadProject(project: CsoundProject) {
currentProjectId = project.id;
editorValue = project.content;
hasUnsavedChanges = false;
isNewUnsavedBuffer = false;
if (editorRef) {
editorRef.setValue(project.content);
}
}
async function saveCurrentProject() {
if (isNewUnsavedBuffer) {
showSaveAsDialog = true;
return;
}
if (!currentProjectId) return;
const result = await projectManager.updateProject({
id: currentProjectId,
content: editorValue
});
if (result.success) {
hasUnsavedChanges = false;
if (fileBrowserRef) {
fileBrowserRef.refresh();
}
}
}
async function handleSaveAs(title: string) {
const finalTitle = title.trim() || 'Untitled';
const result = await projectManager.createProject({
title: finalTitle,
author: 'Anonymous',
content: editorValue,
tags: []
});
if (result.success) {
currentProjectId = result.data.id;
hasUnsavedChanges = false;
isNewUnsavedBuffer = false;
if (fileBrowserRef) {
fileBrowserRef.refresh();
}
}
}
async function handleMetadataUpdate(projectId: string, updates: { title?: string; author?: string }) {
const result = await projectManager.updateProject({
id: projectId,
...updates
});
if (result.success && fileBrowserRef) {
fileBrowserRef.refresh();
}
}
function handleSaveAndSwitch() {
saveCurrentProject().then(() => {
if (pendingProject) {
loadProject(pendingProject);
} else {
createNewBuffer();
}
pendingProject = null;
});
}
function handleDiscardAndSwitch() {
if (pendingProject) {
loadProject(pendingProject);
} else {
createNewBuffer();
}
pendingProject = null;
}
function cyclePanelPosition() {
@ -64,18 +222,26 @@
{/snippet}
{#snippet filesTabContent()}
<h3>File Browser</h3>
<p>Your project files will be listed here.</p>
<ul>
<li>src/</li>
<li>├── App.svelte</li>
<li>├── main.ts</li>
<li>└── lib/</li>
</ul>
<FileBrowser
bind:this={fileBrowserRef}
onFileSelect={handleFileSelect}
onNewFile={handleNewFile}
onMetadataUpdate={handleMetadataUpdate}
selectedProjectId={currentProjectId}
/>
{/snippet}
<div class="app-container">
<TopBar title="oldboy">
<button
class="icon-button"
onclick={saveCurrentProject}
disabled={!hasUnsavedChanges}
title="Save {hasUnsavedChanges ? '(unsaved changes)' : ''}"
class:has-changes={hasUnsavedChanges}
>
<Save size={18} />
</button>
<button onclick={toggleSidePanel} class="icon-button">
{#if sidePanelVisible}
{#if sidePanelPosition === 'left'}
@ -154,6 +320,24 @@
<p>You can drag it around by the header.</p>
<p>It stays on top of everything else.</p>
</Popup>
<ConfirmDialog
bind:visible={showUnsavedDialog}
title="Unsaved Changes"
message="You have unsaved changes. What would you like to do?"
confirmLabel="Save"
cancelLabel="Discard"
onConfirm={handleSaveAndSwitch}
onCancel={handleDiscardAndSwitch}
/>
<InputDialog
bind:visible={showSaveAsDialog}
title="Save As"
label="File name"
placeholder="Untitled"
onConfirm={handleSaveAs}
/>
</div>
<style>
@ -192,11 +376,20 @@
transition: background-color 0.2s;
}
.icon-button:hover {
.icon-button:hover:not(:disabled) {
background-color: #444;
border-color: #646cff;
}
.icon-button:disabled {
opacity: 0.3;
cursor: not-allowed;
}
.icon-button.has-changes {
color: #646cff;
}
h3 {
margin-top: 0;
color: rgba(255, 255, 255, 0.87);