Integrate CCN 2025-2026 files
This commit is contained in:
252
src/App.svelte
252
src/App.svelte
@ -9,22 +9,15 @@
|
||||
import AudioScope from "./lib/components/audio/AudioScope.svelte";
|
||||
import Spectrogram from "./lib/components/audio/Spectrogram.svelte";
|
||||
import ConfirmDialog from "./lib/components/ui/ConfirmDialog.svelte";
|
||||
import InputDialog from "./lib/components/ui/InputDialog.svelte";
|
||||
import TemplateDialog from "./lib/components/ui/TemplateDialog.svelte";
|
||||
import CsoundReference from "./lib/components/reference/CsoundReference.svelte";
|
||||
import {
|
||||
createCsoundDerivedStores,
|
||||
type LogEntry,
|
||||
type EvalSource,
|
||||
} from "./lib/csound";
|
||||
import { type CsoundProject } from "./lib/project-system";
|
||||
import {
|
||||
templateRegistry,
|
||||
type CsoundTemplate,
|
||||
} from "./lib/templates/template-registry";
|
||||
import { loadLastProjectId } from "./lib/project-system/persistence";
|
||||
import { type File } from "./lib/project-system";
|
||||
import { loadOpenTabs, loadCurrentFileId } from "./lib/project-system/persistence";
|
||||
import { createAppContext, setAppContext } from "./lib/contexts/app-context";
|
||||
import type { ProjectMode } from "./lib/project-system/types";
|
||||
import { themes, applyTheme } from "./lib/themes";
|
||||
import {
|
||||
Save,
|
||||
@ -42,9 +35,9 @@
|
||||
|
||||
const {
|
||||
csound,
|
||||
projectManager,
|
||||
fileManager,
|
||||
editorSettings,
|
||||
projectEditor,
|
||||
editorState,
|
||||
uiState,
|
||||
executionContext,
|
||||
} = appContext;
|
||||
@ -57,45 +50,39 @@
|
||||
let logsUnsubscribe: (() => void) | undefined;
|
||||
|
||||
onMount(async () => {
|
||||
await projectManager.init();
|
||||
await fileManager.init();
|
||||
await editorState.refreshFileCache();
|
||||
|
||||
const lastProjectId = loadLastProjectId();
|
||||
let projectToLoad: CsoundProject | null = null;
|
||||
// Try to restore open tabs
|
||||
const openTabIds = loadOpenTabs();
|
||||
const currentFileId = loadCurrentFileId();
|
||||
|
||||
if (lastProjectId) {
|
||||
const result = await projectManager.getProject(lastProjectId);
|
||||
if (result.success) {
|
||||
projectToLoad = result.data;
|
||||
if (openTabIds.length > 0) {
|
||||
// Restore tabs
|
||||
for (const fileId of openTabIds) {
|
||||
await editorState.openFile(fileId);
|
||||
}
|
||||
}
|
||||
|
||||
if (!projectToLoad) {
|
||||
const allProjectsResult = await projectManager.getAllProjects();
|
||||
if (allProjectsResult.success) {
|
||||
if (allProjectsResult.data.length > 0) {
|
||||
projectToLoad = allProjectsResult.data[0];
|
||||
} else {
|
||||
const classicTemplate = templateRegistry.getById("classic");
|
||||
if (classicTemplate) {
|
||||
const createResult = await projectManager.createProject({
|
||||
title: "Welcome",
|
||||
author: "System",
|
||||
content: classicTemplate.content,
|
||||
tags: [],
|
||||
mode: classicTemplate.mode,
|
||||
});
|
||||
if (createResult.success) {
|
||||
projectToLoad = createResult.data;
|
||||
}
|
||||
}
|
||||
// Switch to last current file
|
||||
if (currentFileId && openTabIds.includes(currentFileId)) {
|
||||
await editorState.switchToFile(currentFileId);
|
||||
}
|
||||
} else {
|
||||
// No tabs to restore, check if we have any files
|
||||
const allFilesResult = await fileManager.getAllFiles();
|
||||
if (allFilesResult.success && allFilesResult.data.length > 0) {
|
||||
// Open the first file
|
||||
await editorState.openFile(allFilesResult.data[0].id);
|
||||
} else {
|
||||
// No files at all, create a welcome file
|
||||
const welcomeContent = '<CsoundSynthesizer>\n<CsOptions>\n-odac\n</CsOptions>\n<CsInstruments>\n\nsr = 44100\nksmps = 32\nnchnls = 2\n0dbfs = 1\n\ninstr 1\n kEnv madsr 0.1, 0.2, 0.7, 0.5\n aOut oscil kEnv * 0.3, 440\n outs aOut, aOut\nendin\n\n</CsInstruments>\n<CsScore>\ni 1 0 2\n</CsScore>\n</CsoundSynthesizer>';
|
||||
const result = await fileManager.createFile({ title: 'Welcome.orc', content: welcomeContent });
|
||||
if (result.success) {
|
||||
await editorState.openFile(result.data.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (projectToLoad) {
|
||||
projectEditor.loadProject(projectToLoad);
|
||||
}
|
||||
|
||||
logsUnsubscribe = csoundLogs.subscribe((logs) => {
|
||||
interpreterLogs = logs;
|
||||
});
|
||||
@ -134,94 +121,65 @@
|
||||
}
|
||||
|
||||
function handleEditorChange(value: string) {
|
||||
projectEditor.setContent(value);
|
||||
editorState.setContent(value);
|
||||
}
|
||||
|
||||
async function handleNewEmptyFile() {
|
||||
const emptyTemplate = templateRegistry.getEmpty();
|
||||
const result = projectEditor.requestSwitch(async () =>
|
||||
await projectEditor.createNew(emptyTemplate.content, emptyTemplate.mode),
|
||||
);
|
||||
|
||||
if (result === "confirm-unsaved") {
|
||||
uiState.showUnsavedChangesDialog();
|
||||
}
|
||||
async function handleNewFile() {
|
||||
await editorState.createNewFile();
|
||||
}
|
||||
|
||||
function handleNewFromTemplate() {
|
||||
uiState.showTemplateDialog();
|
||||
async function handleFileOpen(fileId: string) {
|
||||
await editorState.openFile(fileId);
|
||||
}
|
||||
|
||||
async function handleTemplateSelect(template: CsoundTemplate) {
|
||||
uiState.hideTemplateDialog();
|
||||
|
||||
const result = projectEditor.requestSwitch(async () =>
|
||||
await projectEditor.createNew(template.content, template.mode),
|
||||
);
|
||||
|
||||
if (result === "confirm-unsaved") {
|
||||
uiState.showUnsavedChangesDialog();
|
||||
}
|
||||
async function handleFileDelete(fileId: string) {
|
||||
await editorState.deleteFile(fileId);
|
||||
}
|
||||
|
||||
function handleFileSelect(project: CsoundProject | null) {
|
||||
if (!project) return;
|
||||
|
||||
const result = projectEditor.requestSwitch(() =>
|
||||
projectEditor.loadProject(project),
|
||||
);
|
||||
|
||||
if (result === "confirm-unsaved") {
|
||||
uiState.showUnsavedChangesDialog();
|
||||
}
|
||||
async function handleFileRename(fileId: string, newTitle: string) {
|
||||
await editorState.renameFile(fileId, newTitle);
|
||||
}
|
||||
|
||||
async function handleExecute(code: string, source: EvalSource) {
|
||||
async function handleTabClick(fileId: string) {
|
||||
await editorState.switchToFile(fileId);
|
||||
}
|
||||
|
||||
async function handleTabClose(fileId: string) {
|
||||
await editorState.closeFile(fileId);
|
||||
}
|
||||
|
||||
async function handleExecuteFile() {
|
||||
try {
|
||||
await executionContext.execute(code, source);
|
||||
await executionContext.executeFile();
|
||||
} catch (error) {
|
||||
console.error("Execution error:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleExecuteBlock(code: string) {
|
||||
try {
|
||||
await executionContext.executeBlock(code);
|
||||
} catch (error) {
|
||||
console.error("Execution error:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleExecuteSelection(code: string) {
|
||||
try {
|
||||
await executionContext.executeSelection(code);
|
||||
} catch (error) {
|
||||
console.error("Execution error:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
await projectEditor.save();
|
||||
}
|
||||
|
||||
async function handleSaveAs(title: string) {
|
||||
const success = await projectEditor.saveAs(title);
|
||||
if (success) {
|
||||
uiState.hideSaveAsDialog();
|
||||
}
|
||||
}
|
||||
|
||||
async function handleMetadataUpdate(
|
||||
projectId: string,
|
||||
updates: {
|
||||
title?: string;
|
||||
author?: string;
|
||||
mode?: import("./lib/project-system/types").ProjectMode;
|
||||
},
|
||||
) {
|
||||
await projectEditor.updateMetadata(updates);
|
||||
}
|
||||
|
||||
async function handleSwitchSave() {
|
||||
await projectEditor.confirmSaveAndSwitch();
|
||||
uiState.hideUnsavedChangesDialog();
|
||||
}
|
||||
|
||||
function handleSwitchDiscard() {
|
||||
projectEditor.confirmDiscardAndSwitch();
|
||||
uiState.hideUnsavedChangesDialog();
|
||||
await editorState.save();
|
||||
}
|
||||
|
||||
async function handleShare() {
|
||||
if (!projectEditor.currentProjectId) return;
|
||||
if (!editorState.currentFileId) return;
|
||||
|
||||
const result = await projectManager.exportProjectToUrl(
|
||||
projectEditor.currentProjectId,
|
||||
);
|
||||
const result = await fileManager.exportFilesToUrl([editorState.currentFileId]);
|
||||
if (result.success) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(result.data);
|
||||
@ -262,18 +220,7 @@
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
const mode = projectEditor.currentProject?.mode || "composition";
|
||||
const projectId = projectEditor.currentProjectId;
|
||||
|
||||
if (mode !== executionContext.mode) {
|
||||
executionContext.switchMode(mode).catch(console.error);
|
||||
}
|
||||
|
||||
executionContext.setContentProvider(() => projectEditor.content);
|
||||
|
||||
if (projectId) {
|
||||
executionContext.startSession(projectId).catch(console.error);
|
||||
}
|
||||
executionContext.setContentProvider(() => editorState.content);
|
||||
});
|
||||
|
||||
const panelTabs = [
|
||||
@ -301,12 +248,11 @@
|
||||
|
||||
{#snippet filesTabContent()}
|
||||
<FileBrowser
|
||||
{projectManager}
|
||||
onFileSelect={handleFileSelect}
|
||||
onNewEmptyFile={handleNewEmptyFile}
|
||||
onNewFromTemplate={handleNewFromTemplate}
|
||||
onMetadataUpdate={handleMetadataUpdate}
|
||||
selectedProjectId={projectEditor.currentProjectId}
|
||||
{fileManager}
|
||||
onFileOpen={handleFileOpen}
|
||||
onFileDelete={handleFileDelete}
|
||||
onFileRename={handleFileRename}
|
||||
onNewFile={handleNewFile}
|
||||
/>
|
||||
{/snippet}
|
||||
|
||||
@ -327,27 +273,27 @@
|
||||
</button>
|
||||
<button
|
||||
class="icon-button"
|
||||
onclick={handleNewFromTemplate}
|
||||
title="New from template"
|
||||
onclick={handleNewFile}
|
||||
title="New file"
|
||||
>
|
||||
<FileStack size={18} />
|
||||
</button>
|
||||
<button
|
||||
class="icon-button"
|
||||
onclick={handleSave}
|
||||
disabled={!projectEditor.hasUnsavedChanges}
|
||||
title="Save (Ctrl+S){projectEditor.hasUnsavedChanges
|
||||
disabled={!editorState.hasUnsavedChanges}
|
||||
title="Save (Ctrl+S){editorState.hasUnsavedChanges
|
||||
? ' - unsaved changes'
|
||||
: ''}"
|
||||
class:has-changes={projectEditor.hasUnsavedChanges}
|
||||
class:has-changes={editorState.hasUnsavedChanges}
|
||||
>
|
||||
<Save size={18} />
|
||||
</button>
|
||||
<button
|
||||
onclick={handleShare}
|
||||
class="icon-button"
|
||||
disabled={!projectEditor.currentProjectId}
|
||||
title="Share project"
|
||||
disabled={!editorState.currentFileId}
|
||||
title="Share current file"
|
||||
>
|
||||
<Share2 size={18} />
|
||||
</button>
|
||||
@ -357,7 +303,7 @@
|
||||
class="icon-button evaluate-button"
|
||||
disabled={!$initialized}
|
||||
class:is-running={$running}
|
||||
title="Evaluate (Cmd-E)"
|
||||
title="Evaluate Block (Cmd-E)"
|
||||
>
|
||||
<Play size={18} />
|
||||
</button>
|
||||
@ -417,12 +363,19 @@
|
||||
<div class="editor-area">
|
||||
<EditorWithLogs
|
||||
bind:this={editorWithLogsRef}
|
||||
value={projectEditor.content}
|
||||
value={editorState.content}
|
||||
fileName={editorState.currentFile?.title}
|
||||
onChange={handleEditorChange}
|
||||
onExecute={handleExecute}
|
||||
onExecuteFile={handleExecuteFile}
|
||||
onExecuteBlock={handleExecuteBlock}
|
||||
onExecuteSelection={handleExecuteSelection}
|
||||
logs={interpreterLogs}
|
||||
{editorSettings}
|
||||
mode={projectEditor.currentProject?.mode || 'composition'}
|
||||
openFiles={editorState.openFiles}
|
||||
currentFileId={editorState.currentFileId}
|
||||
unsavedFileIds={new Set(Array.from(editorState.unsavedChanges.keys()))}
|
||||
onTabClick={handleTabClick}
|
||||
onTabClose={handleTabClose}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -520,29 +473,6 @@
|
||||
{/snippet}
|
||||
</ResizablePopup>
|
||||
|
||||
<ConfirmDialog
|
||||
bind:visible={uiState.unsavedChangesDialogVisible}
|
||||
title="Unsaved Changes"
|
||||
message="You have unsaved changes. What would you like to do?"
|
||||
confirmLabel="Save"
|
||||
cancelLabel="Discard"
|
||||
onConfirm={handleSwitchSave}
|
||||
onCancel={handleSwitchDiscard}
|
||||
/>
|
||||
|
||||
<InputDialog
|
||||
bind:visible={uiState.saveAsDialogVisible}
|
||||
title="Save As"
|
||||
label="File name"
|
||||
placeholder="Untitled"
|
||||
onConfirm={handleSaveAs}
|
||||
/>
|
||||
|
||||
<TemplateDialog
|
||||
bind:visible={uiState.templateDialogVisible}
|
||||
onSelect={handleTemplateSelect}
|
||||
onCancel={() => uiState.hideTemplateDialog()}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
Reference in New Issue
Block a user