stuff
This commit is contained in:
@ -133,10 +133,10 @@
|
||||
projectEditor.setContent(value);
|
||||
}
|
||||
|
||||
function handleNewEmptyFile() {
|
||||
async function handleNewEmptyFile() {
|
||||
const emptyTemplate = templateRegistry.getEmpty();
|
||||
const result = projectEditor.requestSwitch(() =>
|
||||
projectEditor.createNew(emptyTemplate.content),
|
||||
const result = projectEditor.requestSwitch(async () =>
|
||||
await projectEditor.createNew(emptyTemplate.content, emptyTemplate.mode),
|
||||
);
|
||||
|
||||
if (result === "confirm-unsaved") {
|
||||
@ -148,11 +148,11 @@
|
||||
uiState.showTemplateDialog();
|
||||
}
|
||||
|
||||
function handleTemplateSelect(template: CsoundTemplate) {
|
||||
async function handleTemplateSelect(template: CsoundTemplate) {
|
||||
uiState.hideTemplateDialog();
|
||||
|
||||
const result = projectEditor.requestSwitch(() =>
|
||||
projectEditor.createNew(template.content),
|
||||
const result = projectEditor.requestSwitch(async () =>
|
||||
await projectEditor.createNew(template.content, template.mode),
|
||||
);
|
||||
|
||||
if (result === "confirm-unsaved") {
|
||||
@ -181,11 +181,6 @@
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
if (projectEditor.isNewUnsavedBuffer) {
|
||||
uiState.showSaveAsDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
await projectEditor.save();
|
||||
}
|
||||
|
||||
@ -208,14 +203,8 @@
|
||||
}
|
||||
|
||||
async function handleSwitchSave() {
|
||||
const result = await projectEditor.confirmSaveAndSwitch();
|
||||
|
||||
if (result === "show-save-as") {
|
||||
await projectEditor.confirmSaveAndSwitch();
|
||||
uiState.hideUnsavedChangesDialog();
|
||||
uiState.showSaveAsDialog();
|
||||
} else {
|
||||
uiState.hideUnsavedChangesDialog();
|
||||
}
|
||||
}
|
||||
|
||||
function handleSwitchDiscard() {
|
||||
|
||||
@ -28,7 +28,23 @@
|
||||
let isResizing = $state(false);
|
||||
let startPos = $state(0);
|
||||
let startWidth = $state(0);
|
||||
let activeTab = $state(tabs[0]?.id || '');
|
||||
let activeTab = $state('');
|
||||
|
||||
function loadActiveTab(): string | null {
|
||||
try {
|
||||
return localStorage.getItem('sidePanelActiveTab');
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function saveActiveTab(tabId: string) {
|
||||
try {
|
||||
localStorage.setItem('sidePanelActiveTab', tabId);
|
||||
} catch {
|
||||
// Ignore localStorage errors
|
||||
}
|
||||
}
|
||||
|
||||
function handleResizeStart(e: MouseEvent) {
|
||||
isResizing = true;
|
||||
@ -62,7 +78,12 @@
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (tabs.length > 0 && !activeTab) {
|
||||
const savedTab = loadActiveTab();
|
||||
const savedTabExists = tabs.some(tab => tab.id === savedTab);
|
||||
|
||||
if (savedTabExists) {
|
||||
activeTab = savedTab!;
|
||||
} else if (tabs.length > 0 && !activeTab) {
|
||||
activeTab = tabs[0].id;
|
||||
}
|
||||
|
||||
@ -75,6 +96,12 @@
|
||||
};
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (activeTab) {
|
||||
saveActiveTab(activeTab);
|
||||
}
|
||||
});
|
||||
|
||||
export function toggle() {
|
||||
visible = !visible;
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import type { CsoundProject, ProjectManager } from '../project-system';
|
||||
import { saveLastProjectId } from '../project-system/persistence';
|
||||
import type { ProjectMode } from '../project-system/types';
|
||||
|
||||
interface ProjectEditorState {
|
||||
currentProject: CsoundProject | null;
|
||||
content: string;
|
||||
hasUnsavedChanges: boolean;
|
||||
isNewUnsavedBuffer: boolean;
|
||||
}
|
||||
|
||||
export class ProjectEditor {
|
||||
@ -14,8 +14,7 @@ export class ProjectEditor {
|
||||
private state = $state<ProjectEditorState>({
|
||||
currentProject: null,
|
||||
content: '',
|
||||
hasUnsavedChanges: false,
|
||||
isNewUnsavedBuffer: false
|
||||
hasUnsavedChanges: false
|
||||
});
|
||||
|
||||
private pendingAction: (() => void) | null = null;
|
||||
@ -37,14 +36,31 @@ export class ProjectEditor {
|
||||
return this.state.hasUnsavedChanges;
|
||||
}
|
||||
|
||||
get isNewUnsavedBuffer() {
|
||||
return this.state.isNewUnsavedBuffer;
|
||||
}
|
||||
|
||||
get currentProjectId() {
|
||||
return this.state.currentProject?.id ?? null;
|
||||
}
|
||||
|
||||
private async generateUnnamedTitle(): Promise<string> {
|
||||
const result = await this.projectManager.getAllProjects();
|
||||
if (!result.success) {
|
||||
return 'Unnamed-1';
|
||||
}
|
||||
|
||||
const projects = result.data;
|
||||
const unnamedPattern = /^Unnamed-(\d+)$/;
|
||||
const unnamedNumbers = projects
|
||||
.map(p => p.title.match(unnamedPattern)?.[1])
|
||||
.filter((n): n is string => n !== undefined)
|
||||
.map(n => parseInt(n, 10));
|
||||
|
||||
if (unnamedNumbers.length === 0) {
|
||||
return 'Unnamed-1';
|
||||
}
|
||||
|
||||
const maxNumber = Math.max(...unnamedNumbers);
|
||||
return `Unnamed-${maxNumber + 1}`;
|
||||
}
|
||||
|
||||
setContent(content: string) {
|
||||
this.state.content = content;
|
||||
this.state.hasUnsavedChanges = true;
|
||||
@ -54,23 +70,32 @@ export class ProjectEditor {
|
||||
this.state.currentProject = project;
|
||||
this.state.content = project.content;
|
||||
this.state.hasUnsavedChanges = false;
|
||||
this.state.isNewUnsavedBuffer = false;
|
||||
saveLastProjectId(project.id);
|
||||
}
|
||||
|
||||
createNew(template: string) {
|
||||
this.state.currentProject = null;
|
||||
this.state.content = template;
|
||||
async createNew(content: string, mode: ProjectMode = 'composition'): Promise<boolean> {
|
||||
const title = await this.generateUnnamedTitle();
|
||||
|
||||
const result = await this.projectManager.createProject({
|
||||
title,
|
||||
author: 'Anonymous',
|
||||
content,
|
||||
tags: [],
|
||||
mode
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
this.state.currentProject = result.data;
|
||||
this.state.content = result.data.content;
|
||||
this.state.hasUnsavedChanges = false;
|
||||
this.state.isNewUnsavedBuffer = true;
|
||||
saveLastProjectId(null);
|
||||
saveLastProjectId(result.data.id);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async save(): Promise<boolean> {
|
||||
if (this.state.isNewUnsavedBuffer) {
|
||||
throw new Error('Cannot save new buffer without title. Use saveAs instead.');
|
||||
}
|
||||
|
||||
if (!this.state.currentProject) {
|
||||
return false;
|
||||
}
|
||||
@ -91,24 +116,22 @@ export class ProjectEditor {
|
||||
return false;
|
||||
}
|
||||
|
||||
async saveAs(title: string, author: string = 'Anonymous'): Promise<boolean> {
|
||||
async saveAs(title: string): Promise<boolean> {
|
||||
if (!this.state.currentProject) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const finalTitle = title.trim() || 'Untitled';
|
||||
|
||||
const isLiveCodingTemplate = this.state.content.includes('Live Coding Template');
|
||||
|
||||
const result = await this.projectManager.createProject({
|
||||
const result = await this.projectManager.updateProject({
|
||||
id: this.state.currentProject.id,
|
||||
title: finalTitle,
|
||||
author,
|
||||
content: this.state.content,
|
||||
tags: [],
|
||||
mode: isLiveCodingTemplate ? 'livecoding' : 'composition'
|
||||
content: this.state.content
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
if (result.success && result.data) {
|
||||
this.state.currentProject = result.data;
|
||||
this.state.hasUnsavedChanges = false;
|
||||
this.state.isNewUnsavedBuffer = false;
|
||||
saveLastProjectId(result.data.id);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -143,15 +166,10 @@ export class ProjectEditor {
|
||||
return 'confirm-unsaved';
|
||||
}
|
||||
|
||||
async confirmSaveAndSwitch(): Promise<'show-save-as' | 'done'> {
|
||||
if (this.state.isNewUnsavedBuffer) {
|
||||
return 'show-save-as';
|
||||
}
|
||||
|
||||
async confirmSaveAndSwitch(): Promise<void> {
|
||||
await this.save();
|
||||
this.pendingAction?.();
|
||||
this.pendingAction = null;
|
||||
return 'done';
|
||||
}
|
||||
|
||||
confirmDiscardAndSwitch(): void {
|
||||
|
||||
@ -71,24 +71,15 @@ const LIVECODING_TEMPLATE: CsoundTemplate = {
|
||||
id: 'livecoding',
|
||||
name: 'Live Coding',
|
||||
mode: 'livecoding',
|
||||
content: `; LIVE CODING MODE
|
||||
; Engine auto-initializes on first evaluation (Ctrl+E)
|
||||
; Evaluate instruments/opcodes with Ctrl+E to define them
|
||||
; Evaluate score events (i-statements) to trigger sounds
|
||||
; Press Ctrl+. to stop all audio
|
||||
|
||||
gaReverb init 0
|
||||
content: `gaReverb init 0
|
||||
|
||||
instr 1
|
||||
kFreq chnget "freq"
|
||||
kFreq = (kFreq == 0 ? p4 : kFreq)
|
||||
kAmp = p5
|
||||
|
||||
kEnv linsegr 0, 0.01, 1, 0.1, 0.7, 0.2, 0
|
||||
aOsc vco2 kAmp * kEnv, kFreq
|
||||
|
||||
aFilt moogladder aOsc, 2000, 0.3
|
||||
|
||||
outs aFilt, aFilt
|
||||
gaReverb = gaReverb + aFilt * 0.3
|
||||
endin
|
||||
@ -96,12 +87,9 @@ endin
|
||||
instr 2
|
||||
iFreq = p4
|
||||
iAmp = p5
|
||||
|
||||
kEnv linsegr 0, 0.005, 1, 0.05, 0.5, 0.1, 0
|
||||
aOsc vco2 iAmp * kEnv, iFreq, 10
|
||||
|
||||
aFilt butterlp aOsc, 800
|
||||
|
||||
outs aFilt, aFilt
|
||||
endin
|
||||
|
||||
@ -115,10 +103,6 @@ endin
|
||||
i 99 0 -1
|
||||
|
||||
|
||||
; === LIVE CODING EXAMPLES ===
|
||||
; Select a block and press Ctrl+E to evaluate
|
||||
|
||||
; Basic note
|
||||
i 1 0 2 440 0.3
|
||||
|
||||
; Arpeggio
|
||||
@ -134,9 +118,8 @@ i 2 1.0 0.5 164.81 0.4
|
||||
i 2 1.5 0.5 130.81 0.4
|
||||
|
||||
; Long note for channel control
|
||||
i 1 0 30 440 0.3
|
||||
i 1 0 10 440 0.3
|
||||
|
||||
; Change frequency while playing (select and evaluate)
|
||||
freq = 440
|
||||
|
||||
freq = 554.37
|
||||
|
||||
Reference in New Issue
Block a user