Cleaning
This commit is contained in:
@ -13,19 +13,22 @@
|
||||
import { css } from '@codemirror/lang-css';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
import { vim } from '@replit/codemirror-vim';
|
||||
import { editorSettings } from './stores/editorSettings';
|
||||
import { csound } from './csound';
|
||||
import type { EditorSettingsStore } from './stores/editorSettings';
|
||||
|
||||
interface Props {
|
||||
initialValue?: string;
|
||||
value: string;
|
||||
language?: 'javascript' | 'html' | 'css';
|
||||
onChange?: (value: string) => void;
|
||||
onExecute?: (code: string) => void;
|
||||
editorSettings: EditorSettingsStore;
|
||||
}
|
||||
|
||||
let {
|
||||
initialValue = '',
|
||||
value = '',
|
||||
language = 'javascript',
|
||||
onChange
|
||||
onChange,
|
||||
onExecute,
|
||||
editorSettings
|
||||
}: Props = $props();
|
||||
|
||||
let editorContainer: HTMLDivElement;
|
||||
@ -45,10 +48,10 @@
|
||||
{
|
||||
key: 'Mod-e',
|
||||
run: (view) => {
|
||||
const code = view.state.doc.toString();
|
||||
csound.evaluate(code).catch(err => {
|
||||
console.error('Evaluation error:', err);
|
||||
});
|
||||
if (onExecute) {
|
||||
const code = view.state.doc.toString();
|
||||
onExecute(code);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -84,7 +87,7 @@
|
||||
];
|
||||
|
||||
editorView = new EditorView({
|
||||
doc: initialValue,
|
||||
doc: value,
|
||||
extensions: [
|
||||
...baseExtensions,
|
||||
languageExtensions[language],
|
||||
@ -134,17 +137,13 @@
|
||||
}
|
||||
});
|
||||
|
||||
export function getValue(): string {
|
||||
return editorView?.state.doc.toString() || '';
|
||||
}
|
||||
|
||||
export function setValue(value: string): void {
|
||||
if (editorView) {
|
||||
$effect(() => {
|
||||
if (editorView && value !== editorView.state.doc.toString()) {
|
||||
editorView.dispatch({
|
||||
changes: { from: 0, to: editorView.state.doc.length, insert: value }
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="editor-wrapper">
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { editorSettings } from './stores/editorSettings';
|
||||
import { getAppContext } from './contexts/app-context';
|
||||
|
||||
const { editorSettings } = getAppContext();
|
||||
|
||||
let settings = $state($editorSettings);
|
||||
|
||||
|
||||
@ -2,22 +2,26 @@
|
||||
import { onMount } from 'svelte';
|
||||
import Editor from './Editor.svelte';
|
||||
import LogPanel from './LogPanel.svelte';
|
||||
import type { EditorSettingsStore } from './stores/editorSettings';
|
||||
|
||||
interface Props {
|
||||
initialValue?: string;
|
||||
value: string;
|
||||
language?: 'javascript' | 'html' | 'css';
|
||||
onChange?: (value: string) => void;
|
||||
onExecute?: (code: string) => void;
|
||||
logs?: string[];
|
||||
editorSettings: EditorSettingsStore;
|
||||
}
|
||||
|
||||
let {
|
||||
initialValue = '',
|
||||
value = '',
|
||||
language = 'javascript',
|
||||
onChange,
|
||||
logs = []
|
||||
onExecute,
|
||||
logs = [],
|
||||
editorSettings
|
||||
}: Props = $props();
|
||||
|
||||
let editorRef: Editor;
|
||||
let logPanelRef: LogPanel;
|
||||
|
||||
let editorHeight = $state(70);
|
||||
@ -60,22 +64,16 @@
|
||||
};
|
||||
});
|
||||
|
||||
export function getValue(): string {
|
||||
return editorRef?.getValue() || '';
|
||||
}
|
||||
|
||||
export function setValue(value: string): void {
|
||||
editorRef?.setValue(value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="editor-with-logs">
|
||||
<div class="editor-section" style="height: {editorHeight}%;">
|
||||
<Editor
|
||||
bind:this={editorRef}
|
||||
{initialValue}
|
||||
{value}
|
||||
{language}
|
||||
{onChange}
|
||||
{onExecute}
|
||||
{editorSettings}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,18 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { File, Plus, Trash2 } from 'lucide-svelte';
|
||||
import { projectManager, type CsoundProject } from './project-system';
|
||||
import type { CsoundProject, ProjectManager } from './project-system';
|
||||
import ConfirmDialog from './ConfirmDialog.svelte';
|
||||
import InputDialog from './InputDialog.svelte';
|
||||
|
||||
interface Props {
|
||||
projectManager: ProjectManager;
|
||||
onFileSelect?: (project: CsoundProject | null) => void;
|
||||
onNewFile?: () => void;
|
||||
onMetadataUpdate?: (projectId: string, updates: { title?: string; author?: string }) => void;
|
||||
selectedProjectId?: string | null;
|
||||
}
|
||||
|
||||
let { onFileSelect, onNewFile, onMetadataUpdate, selectedProjectId = null }: Props = $props();
|
||||
let { projectManager, onFileSelect, onNewFile, onMetadataUpdate, selectedProjectId = null }: Props = $props();
|
||||
|
||||
let projects = $state<CsoundProject[]>([]);
|
||||
let loading = $state(true);
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { Copy, Trash2, Search, Pause, Play } from 'lucide-svelte';
|
||||
import { csound } from './csound';
|
||||
import { getAppContext } from './contexts/app-context';
|
||||
import type { LogEntry } from './csound';
|
||||
import { onMount, tick } from 'svelte';
|
||||
import { tick } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
logs?: LogEntry[];
|
||||
@ -10,6 +10,8 @@
|
||||
|
||||
let { logs = [] }: Props = $props();
|
||||
|
||||
const { csound } = getAppContext();
|
||||
|
||||
let searchQuery = $state('');
|
||||
let autoFollow = $state(true);
|
||||
let searchVisible = $state(false);
|
||||
|
||||
33
src/lib/config/templates.ts
Normal file
33
src/lib/config/templates.ts
Normal file
@ -0,0 +1,33 @@
|
||||
export const DEFAULT_CSOUND_TEMPLATE = `<CsoundSynthesizer>
|
||||
<CsOptions>
|
||||
</CsOptions>
|
||||
<CsInstruments>
|
||||
|
||||
sr = 44100
|
||||
ksmps = 32
|
||||
nchnls = 2
|
||||
0dbfs = 1
|
||||
|
||||
instr 1
|
||||
iFreq = p4
|
||||
iAmp = p5
|
||||
|
||||
; ADSR envelope
|
||||
kEnv madsr 0.01, 0.1, 0.6, 0.2
|
||||
|
||||
; Sine wave oscillator
|
||||
aOsc oscili iAmp * kEnv, iFreq
|
||||
|
||||
outs aOsc, aOsc
|
||||
endin
|
||||
|
||||
</CsInstruments>
|
||||
<CsScore>
|
||||
; Arpeggio: C4 E4 G4 C5
|
||||
i 1 0.0 0.5 261.63 0.3
|
||||
i 1 0.5 0.5 329.63 0.3
|
||||
i 1 1.0 0.5 392.00 0.3
|
||||
i 1 1.5 0.5 523.25 0.3
|
||||
</CsScore>
|
||||
</CsoundSynthesizer>
|
||||
`;
|
||||
38
src/lib/contexts/app-context.ts
Normal file
38
src/lib/contexts/app-context.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { getContext, setContext } from 'svelte';
|
||||
import { ProjectManager } from '../project-system/project-manager';
|
||||
import { createCsoundStore } from '../csound/store';
|
||||
import { createEditorSettingsStore } from '../stores/editorSettings';
|
||||
import { ProjectEditor } from '../stores/projectEditor.svelte';
|
||||
import type { CsoundStore } from '../csound/store';
|
||||
import type { EditorSettingsStore } from '../stores/editorSettings';
|
||||
|
||||
export interface AppContext {
|
||||
projectManager: ProjectManager;
|
||||
csound: CsoundStore;
|
||||
editorSettings: EditorSettingsStore;
|
||||
projectEditor: ProjectEditor;
|
||||
}
|
||||
|
||||
const APP_CONTEXT_KEY = Symbol('app-context');
|
||||
|
||||
export function createAppContext(): AppContext {
|
||||
const projectManager = new ProjectManager();
|
||||
return {
|
||||
projectManager,
|
||||
csound: createCsoundStore(),
|
||||
editorSettings: createEditorSettingsStore(),
|
||||
projectEditor: new ProjectEditor(projectManager)
|
||||
};
|
||||
}
|
||||
|
||||
export function setAppContext(context: AppContext): void {
|
||||
setContext<AppContext>(APP_CONTEXT_KEY, context);
|
||||
}
|
||||
|
||||
export function getAppContext(): AppContext {
|
||||
const context = getContext<AppContext>(APP_CONTEXT_KEY);
|
||||
if (!context) {
|
||||
throw new Error('AppContext not found. Did you forget to call setAppContext?');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
export { CsoundEngine } from './engine';
|
||||
export type { CsoundEngineOptions } from './engine';
|
||||
export { csound, csoundLogs, csoundInitialized, csoundRunning } from './store';
|
||||
export type { LogEntry } from './store';
|
||||
export { createCsoundStore, createCsoundDerivedStores } from './store';
|
||||
export type { LogEntry, CsoundStore } from './store';
|
||||
|
||||
@ -13,7 +13,18 @@ interface CsoundState {
|
||||
logs: LogEntry[];
|
||||
}
|
||||
|
||||
function createCsoundStore() {
|
||||
export interface CsoundStore {
|
||||
subscribe: (run: (value: CsoundState) => void) => () => void;
|
||||
init: () => Promise<void>;
|
||||
evaluate: (code: string) => Promise<void>;
|
||||
stop: () => Promise<void>;
|
||||
clearLogs: () => void;
|
||||
getAudioContext: () => AudioContext | null;
|
||||
getAnalyserNode: () => AnalyserNode | null;
|
||||
destroy: () => Promise<void>;
|
||||
}
|
||||
|
||||
export function createCsoundStore(): CsoundStore {
|
||||
const initialState: CsoundState = {
|
||||
initialized: false,
|
||||
running: false,
|
||||
@ -116,8 +127,10 @@ function createCsoundStore() {
|
||||
};
|
||||
}
|
||||
|
||||
export const csound = createCsoundStore();
|
||||
|
||||
export const csoundLogs = derived(csound, $csound => $csound.logs);
|
||||
export const csoundInitialized = derived(csound, $csound => $csound.initialized);
|
||||
export const csoundRunning = derived(csound, $csound => $csound.running);
|
||||
export function createCsoundDerivedStores(csound: CsoundStore) {
|
||||
return {
|
||||
logs: derived(csound, $csound => $csound.logs),
|
||||
initialized: derived(csound, $csound => $csound.initialized),
|
||||
running: derived(csound, $csound => $csound.running)
|
||||
};
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@ export type {
|
||||
} from './types';
|
||||
|
||||
// Export main API
|
||||
export { ProjectManager, projectManager } from './project-manager';
|
||||
export { ProjectManager } from './project-manager';
|
||||
|
||||
// Export database (for advanced usage)
|
||||
export { projectDb } from './db';
|
||||
|
||||
@ -338,6 +338,3 @@ export class ProjectManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const projectManager = new ProjectManager();
|
||||
|
||||
@ -18,8 +18,15 @@ const defaultSettings: EditorSettings = {
|
||||
vimMode: false
|
||||
};
|
||||
|
||||
function createEditorSettings() {
|
||||
const stored = localStorage.getItem('editorSettings');
|
||||
export interface EditorSettingsStore {
|
||||
subscribe: (run: (value: EditorSettings) => void) => () => void;
|
||||
set: (value: EditorSettings) => void;
|
||||
update: (updater: (value: EditorSettings) => EditorSettings) => void;
|
||||
updatePartial: (partial: Partial<EditorSettings>) => void;
|
||||
}
|
||||
|
||||
export function createEditorSettingsStore(): EditorSettingsStore {
|
||||
const stored = typeof localStorage !== 'undefined' ? localStorage.getItem('editorSettings') : null;
|
||||
const initial = stored ? { ...defaultSettings, ...JSON.parse(stored) } : defaultSettings;
|
||||
|
||||
const { subscribe, set, update } = writable<EditorSettings>(initial);
|
||||
@ -27,24 +34,28 @@ function createEditorSettings() {
|
||||
return {
|
||||
subscribe,
|
||||
set: (value: EditorSettings) => {
|
||||
localStorage.setItem('editorSettings', JSON.stringify(value));
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.setItem('editorSettings', JSON.stringify(value));
|
||||
}
|
||||
set(value);
|
||||
},
|
||||
update: (updater: (value: EditorSettings) => EditorSettings) => {
|
||||
update((current) => {
|
||||
const newValue = updater(current);
|
||||
localStorage.setItem('editorSettings', JSON.stringify(newValue));
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.setItem('editorSettings', JSON.stringify(newValue));
|
||||
}
|
||||
return newValue;
|
||||
});
|
||||
},
|
||||
updatePartial: (partial: Partial<EditorSettings>) => {
|
||||
update((current) => {
|
||||
const newValue = { ...current, ...partial };
|
||||
localStorage.setItem('editorSettings', JSON.stringify(newValue));
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.setItem('editorSettings', JSON.stringify(newValue));
|
||||
}
|
||||
return newValue;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const editorSettings = createEditorSettings();
|
||||
|
||||
154
src/lib/stores/projectEditor.svelte.ts
Normal file
154
src/lib/stores/projectEditor.svelte.ts
Normal file
@ -0,0 +1,154 @@
|
||||
import type { CsoundProject, ProjectManager } from '../project-system';
|
||||
|
||||
interface ProjectEditorState {
|
||||
currentProject: CsoundProject | null;
|
||||
content: string;
|
||||
hasUnsavedChanges: boolean;
|
||||
isNewUnsavedBuffer: boolean;
|
||||
}
|
||||
|
||||
export class ProjectEditor {
|
||||
private projectManager: ProjectManager;
|
||||
|
||||
private state = $state<ProjectEditorState>({
|
||||
currentProject: null,
|
||||
content: '',
|
||||
hasUnsavedChanges: false,
|
||||
isNewUnsavedBuffer: false
|
||||
});
|
||||
|
||||
private pendingAction: (() => void) | null = null;
|
||||
private confirmCallback: ((action: 'save' | 'discard') => void) | null = null;
|
||||
|
||||
constructor(projectManager: ProjectManager) {
|
||||
this.projectManager = projectManager;
|
||||
}
|
||||
|
||||
get currentProject() {
|
||||
return this.state.currentProject;
|
||||
}
|
||||
|
||||
get content() {
|
||||
return this.state.content;
|
||||
}
|
||||
|
||||
get hasUnsavedChanges() {
|
||||
return this.state.hasUnsavedChanges;
|
||||
}
|
||||
|
||||
get isNewUnsavedBuffer() {
|
||||
return this.state.isNewUnsavedBuffer;
|
||||
}
|
||||
|
||||
get currentProjectId() {
|
||||
return this.state.currentProject?.id ?? null;
|
||||
}
|
||||
|
||||
setContent(content: string) {
|
||||
this.state.content = content;
|
||||
this.state.hasUnsavedChanges = true;
|
||||
}
|
||||
|
||||
loadProject(project: CsoundProject) {
|
||||
this.state.currentProject = project;
|
||||
this.state.content = project.content;
|
||||
this.state.hasUnsavedChanges = false;
|
||||
this.state.isNewUnsavedBuffer = false;
|
||||
}
|
||||
|
||||
createNew(template: string) {
|
||||
this.state.currentProject = null;
|
||||
this.state.content = template;
|
||||
this.state.hasUnsavedChanges = false;
|
||||
this.state.isNewUnsavedBuffer = true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const result = await this.projectManager.updateProject({
|
||||
id: this.state.currentProject.id,
|
||||
content: this.state.content
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
this.state.hasUnsavedChanges = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async saveAs(title: string, author: string = 'Anonymous'): Promise<boolean> {
|
||||
const finalTitle = title.trim() || 'Untitled';
|
||||
|
||||
const result = await this.projectManager.createProject({
|
||||
title: finalTitle,
|
||||
author,
|
||||
content: this.state.content,
|
||||
tags: []
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
this.state.currentProject = result.data;
|
||||
this.state.hasUnsavedChanges = false;
|
||||
this.state.isNewUnsavedBuffer = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async updateMetadata(updates: { title?: string; author?: string }): Promise<boolean> {
|
||||
if (!this.state.currentProject) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const result = await this.projectManager.updateProject({
|
||||
id: this.state.currentProject.id,
|
||||
...updates
|
||||
});
|
||||
|
||||
if (result.success && result.data) {
|
||||
this.state.currentProject = result.data;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
requestSwitch(action: () => void, onConfirm: (action: 'save' | 'discard') => void) {
|
||||
if (!this.state.hasUnsavedChanges) {
|
||||
action();
|
||||
return false;
|
||||
}
|
||||
|
||||
this.pendingAction = action;
|
||||
this.confirmCallback = onConfirm;
|
||||
return true;
|
||||
}
|
||||
|
||||
async handleSaveAndSwitch(): Promise<void> {
|
||||
if (this.state.isNewUnsavedBuffer) {
|
||||
this.confirmCallback?.('save');
|
||||
return;
|
||||
}
|
||||
|
||||
await this.save();
|
||||
this.pendingAction?.();
|
||||
this.pendingAction = null;
|
||||
this.confirmCallback = null;
|
||||
}
|
||||
|
||||
handleDiscardAndSwitch(): void {
|
||||
this.pendingAction?.();
|
||||
this.pendingAction = null;
|
||||
this.confirmCallback = null;
|
||||
}
|
||||
}
|
||||
64
src/lib/stores/uiState.svelte.ts
Normal file
64
src/lib/stores/uiState.svelte.ts
Normal file
@ -0,0 +1,64 @@
|
||||
type PanelPosition = 'left' | 'right' | 'bottom';
|
||||
|
||||
class UIState {
|
||||
sidePanelVisible = $state(true);
|
||||
sidePanelPosition = $state<PanelPosition>('right');
|
||||
|
||||
scopePopupVisible = $state(false);
|
||||
spectrogramPopupVisible = $state(false);
|
||||
sharePopupVisible = $state(false);
|
||||
audioPermissionPopupVisible = $state(true);
|
||||
unsavedChangesDialogVisible = $state(false);
|
||||
saveAsDialogVisible = $state(false);
|
||||
|
||||
shareUrl = $state('');
|
||||
|
||||
toggleSidePanel() {
|
||||
this.sidePanelVisible = !this.sidePanelVisible;
|
||||
}
|
||||
|
||||
cyclePanelPosition() {
|
||||
if (this.sidePanelPosition === 'right') {
|
||||
this.sidePanelPosition = 'left';
|
||||
} else if (this.sidePanelPosition === 'left') {
|
||||
this.sidePanelPosition = 'bottom';
|
||||
} else {
|
||||
this.sidePanelPosition = 'right';
|
||||
}
|
||||
}
|
||||
|
||||
openScope() {
|
||||
this.scopePopupVisible = true;
|
||||
}
|
||||
|
||||
openSpectrogram() {
|
||||
this.spectrogramPopupVisible = true;
|
||||
}
|
||||
|
||||
showShare(url: string) {
|
||||
this.shareUrl = url;
|
||||
this.sharePopupVisible = true;
|
||||
}
|
||||
|
||||
closeAudioPermission() {
|
||||
this.audioPermissionPopupVisible = false;
|
||||
}
|
||||
|
||||
showUnsavedChangesDialog() {
|
||||
this.unsavedChangesDialogVisible = true;
|
||||
}
|
||||
|
||||
hideUnsavedChangesDialog() {
|
||||
this.unsavedChangesDialogVisible = false;
|
||||
}
|
||||
|
||||
showSaveAsDialog() {
|
||||
this.saveAsDialogVisible = true;
|
||||
}
|
||||
|
||||
hideSaveAsDialog() {
|
||||
this.saveAsDialogVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
export const uiState = new UIState();
|
||||
Reference in New Issue
Block a user