Integrate CCN 2025-2026 files

This commit is contained in:
2025-11-13 21:25:52 +01:00
parent 7a0d89735a
commit e1bfb17b3c
26 changed files with 3791 additions and 1071 deletions

View File

@ -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>