Beginning of File System integration
This commit is contained in:
217
src/App.svelte
217
src/App.svelte
@ -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);
|
||||
|
||||
Reference in New Issue
Block a user