stuff
This commit is contained in:
@ -133,10 +133,10 @@
|
|||||||
projectEditor.setContent(value);
|
projectEditor.setContent(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleNewEmptyFile() {
|
async function handleNewEmptyFile() {
|
||||||
const emptyTemplate = templateRegistry.getEmpty();
|
const emptyTemplate = templateRegistry.getEmpty();
|
||||||
const result = projectEditor.requestSwitch(() =>
|
const result = projectEditor.requestSwitch(async () =>
|
||||||
projectEditor.createNew(emptyTemplate.content),
|
await projectEditor.createNew(emptyTemplate.content, emptyTemplate.mode),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result === "confirm-unsaved") {
|
if (result === "confirm-unsaved") {
|
||||||
@ -148,11 +148,11 @@
|
|||||||
uiState.showTemplateDialog();
|
uiState.showTemplateDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTemplateSelect(template: CsoundTemplate) {
|
async function handleTemplateSelect(template: CsoundTemplate) {
|
||||||
uiState.hideTemplateDialog();
|
uiState.hideTemplateDialog();
|
||||||
|
|
||||||
const result = projectEditor.requestSwitch(() =>
|
const result = projectEditor.requestSwitch(async () =>
|
||||||
projectEditor.createNew(template.content),
|
await projectEditor.createNew(template.content, template.mode),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result === "confirm-unsaved") {
|
if (result === "confirm-unsaved") {
|
||||||
@ -181,11 +181,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleSave() {
|
async function handleSave() {
|
||||||
if (projectEditor.isNewUnsavedBuffer) {
|
|
||||||
uiState.showSaveAsDialog();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await projectEditor.save();
|
await projectEditor.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,14 +203,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleSwitchSave() {
|
async function handleSwitchSave() {
|
||||||
const result = await projectEditor.confirmSaveAndSwitch();
|
await projectEditor.confirmSaveAndSwitch();
|
||||||
|
uiState.hideUnsavedChangesDialog();
|
||||||
if (result === "show-save-as") {
|
|
||||||
uiState.hideUnsavedChangesDialog();
|
|
||||||
uiState.showSaveAsDialog();
|
|
||||||
} else {
|
|
||||||
uiState.hideUnsavedChangesDialog();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSwitchDiscard() {
|
function handleSwitchDiscard() {
|
||||||
|
|||||||
@ -28,7 +28,23 @@
|
|||||||
let isResizing = $state(false);
|
let isResizing = $state(false);
|
||||||
let startPos = $state(0);
|
let startPos = $state(0);
|
||||||
let startWidth = $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) {
|
function handleResizeStart(e: MouseEvent) {
|
||||||
isResizing = true;
|
isResizing = true;
|
||||||
@ -62,7 +78,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
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;
|
activeTab = tabs[0].id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,6 +96,12 @@
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (activeTab) {
|
||||||
|
saveActiveTab(activeTab);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export function toggle() {
|
export function toggle() {
|
||||||
visible = !visible;
|
visible = !visible;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import type { CsoundProject, ProjectManager } from '../project-system';
|
import type { CsoundProject, ProjectManager } from '../project-system';
|
||||||
import { saveLastProjectId } from '../project-system/persistence';
|
import { saveLastProjectId } from '../project-system/persistence';
|
||||||
|
import type { ProjectMode } from '../project-system/types';
|
||||||
|
|
||||||
interface ProjectEditorState {
|
interface ProjectEditorState {
|
||||||
currentProject: CsoundProject | null;
|
currentProject: CsoundProject | null;
|
||||||
content: string;
|
content: string;
|
||||||
hasUnsavedChanges: boolean;
|
hasUnsavedChanges: boolean;
|
||||||
isNewUnsavedBuffer: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ProjectEditor {
|
export class ProjectEditor {
|
||||||
@ -14,8 +14,7 @@ export class ProjectEditor {
|
|||||||
private state = $state<ProjectEditorState>({
|
private state = $state<ProjectEditorState>({
|
||||||
currentProject: null,
|
currentProject: null,
|
||||||
content: '',
|
content: '',
|
||||||
hasUnsavedChanges: false,
|
hasUnsavedChanges: false
|
||||||
isNewUnsavedBuffer: false
|
|
||||||
});
|
});
|
||||||
|
|
||||||
private pendingAction: (() => void) | null = null;
|
private pendingAction: (() => void) | null = null;
|
||||||
@ -37,14 +36,31 @@ export class ProjectEditor {
|
|||||||
return this.state.hasUnsavedChanges;
|
return this.state.hasUnsavedChanges;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isNewUnsavedBuffer() {
|
|
||||||
return this.state.isNewUnsavedBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
get currentProjectId() {
|
get currentProjectId() {
|
||||||
return this.state.currentProject?.id ?? null;
|
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) {
|
setContent(content: string) {
|
||||||
this.state.content = content;
|
this.state.content = content;
|
||||||
this.state.hasUnsavedChanges = true;
|
this.state.hasUnsavedChanges = true;
|
||||||
@ -54,23 +70,32 @@ export class ProjectEditor {
|
|||||||
this.state.currentProject = project;
|
this.state.currentProject = project;
|
||||||
this.state.content = project.content;
|
this.state.content = project.content;
|
||||||
this.state.hasUnsavedChanges = false;
|
this.state.hasUnsavedChanges = false;
|
||||||
this.state.isNewUnsavedBuffer = false;
|
|
||||||
saveLastProjectId(project.id);
|
saveLastProjectId(project.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
createNew(template: string) {
|
async createNew(content: string, mode: ProjectMode = 'composition'): Promise<boolean> {
|
||||||
this.state.currentProject = null;
|
const title = await this.generateUnnamedTitle();
|
||||||
this.state.content = template;
|
|
||||||
this.state.hasUnsavedChanges = false;
|
const result = await this.projectManager.createProject({
|
||||||
this.state.isNewUnsavedBuffer = true;
|
title,
|
||||||
saveLastProjectId(null);
|
author: 'Anonymous',
|
||||||
|
content,
|
||||||
|
tags: [],
|
||||||
|
mode
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
this.state.currentProject = result.data;
|
||||||
|
this.state.content = result.data.content;
|
||||||
|
this.state.hasUnsavedChanges = false;
|
||||||
|
saveLastProjectId(result.data.id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async save(): Promise<boolean> {
|
async save(): Promise<boolean> {
|
||||||
if (this.state.isNewUnsavedBuffer) {
|
|
||||||
throw new Error('Cannot save new buffer without title. Use saveAs instead.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.state.currentProject) {
|
if (!this.state.currentProject) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -91,24 +116,22 @@ export class ProjectEditor {
|
|||||||
return false;
|
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 finalTitle = title.trim() || 'Untitled';
|
||||||
|
|
||||||
const isLiveCodingTemplate = this.state.content.includes('Live Coding Template');
|
const result = await this.projectManager.updateProject({
|
||||||
|
id: this.state.currentProject.id,
|
||||||
const result = await this.projectManager.createProject({
|
|
||||||
title: finalTitle,
|
title: finalTitle,
|
||||||
author,
|
content: this.state.content
|
||||||
content: this.state.content,
|
|
||||||
tags: [],
|
|
||||||
mode: isLiveCodingTemplate ? 'livecoding' : 'composition'
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success && result.data) {
|
||||||
this.state.currentProject = result.data;
|
this.state.currentProject = result.data;
|
||||||
this.state.hasUnsavedChanges = false;
|
this.state.hasUnsavedChanges = false;
|
||||||
this.state.isNewUnsavedBuffer = false;
|
|
||||||
saveLastProjectId(result.data.id);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,15 +166,10 @@ export class ProjectEditor {
|
|||||||
return 'confirm-unsaved';
|
return 'confirm-unsaved';
|
||||||
}
|
}
|
||||||
|
|
||||||
async confirmSaveAndSwitch(): Promise<'show-save-as' | 'done'> {
|
async confirmSaveAndSwitch(): Promise<void> {
|
||||||
if (this.state.isNewUnsavedBuffer) {
|
|
||||||
return 'show-save-as';
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.save();
|
await this.save();
|
||||||
this.pendingAction?.();
|
this.pendingAction?.();
|
||||||
this.pendingAction = null;
|
this.pendingAction = null;
|
||||||
return 'done';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
confirmDiscardAndSwitch(): void {
|
confirmDiscardAndSwitch(): void {
|
||||||
|
|||||||
@ -71,24 +71,15 @@ const LIVECODING_TEMPLATE: CsoundTemplate = {
|
|||||||
id: 'livecoding',
|
id: 'livecoding',
|
||||||
name: 'Live Coding',
|
name: 'Live Coding',
|
||||||
mode: 'livecoding',
|
mode: 'livecoding',
|
||||||
content: `; LIVE CODING MODE
|
content: `gaReverb init 0
|
||||||
; 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
|
|
||||||
|
|
||||||
instr 1
|
instr 1
|
||||||
kFreq chnget "freq"
|
kFreq chnget "freq"
|
||||||
kFreq = (kFreq == 0 ? p4 : kFreq)
|
kFreq = (kFreq == 0 ? p4 : kFreq)
|
||||||
kAmp = p5
|
kAmp = p5
|
||||||
|
|
||||||
kEnv linsegr 0, 0.01, 1, 0.1, 0.7, 0.2, 0
|
kEnv linsegr 0, 0.01, 1, 0.1, 0.7, 0.2, 0
|
||||||
aOsc vco2 kAmp * kEnv, kFreq
|
aOsc vco2 kAmp * kEnv, kFreq
|
||||||
|
|
||||||
aFilt moogladder aOsc, 2000, 0.3
|
aFilt moogladder aOsc, 2000, 0.3
|
||||||
|
|
||||||
outs aFilt, aFilt
|
outs aFilt, aFilt
|
||||||
gaReverb = gaReverb + aFilt * 0.3
|
gaReverb = gaReverb + aFilt * 0.3
|
||||||
endin
|
endin
|
||||||
@ -96,12 +87,9 @@ endin
|
|||||||
instr 2
|
instr 2
|
||||||
iFreq = p4
|
iFreq = p4
|
||||||
iAmp = p5
|
iAmp = p5
|
||||||
|
|
||||||
kEnv linsegr 0, 0.005, 1, 0.05, 0.5, 0.1, 0
|
kEnv linsegr 0, 0.005, 1, 0.05, 0.5, 0.1, 0
|
||||||
aOsc vco2 iAmp * kEnv, iFreq, 10
|
aOsc vco2 iAmp * kEnv, iFreq, 10
|
||||||
|
|
||||||
aFilt butterlp aOsc, 800
|
aFilt butterlp aOsc, 800
|
||||||
|
|
||||||
outs aFilt, aFilt
|
outs aFilt, aFilt
|
||||||
endin
|
endin
|
||||||
|
|
||||||
@ -115,10 +103,6 @@ endin
|
|||||||
i 99 0 -1
|
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
|
i 1 0 2 440 0.3
|
||||||
|
|
||||||
; Arpeggio
|
; 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
|
i 2 1.5 0.5 130.81 0.4
|
||||||
|
|
||||||
; Long note for channel control
|
; 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 = 440
|
||||||
|
|
||||||
freq = 554.37
|
freq = 554.37
|
||||||
|
|||||||
Reference in New Issue
Block a user