Refactoring
This commit is contained in:
@ -13,7 +13,6 @@
|
||||
import InputDialog from './lib/InputDialog.svelte';
|
||||
import { createCsoundDerivedStores, type LogEntry } from './lib/csound';
|
||||
import { type CsoundProject } from './lib/project-system';
|
||||
import { uiState } from './lib/stores/uiState.svelte';
|
||||
import { DEFAULT_CSOUND_TEMPLATE } from './lib/config/templates';
|
||||
import { createAppContext, setAppContext } from './lib/contexts/app-context';
|
||||
import {
|
||||
@ -32,12 +31,11 @@
|
||||
const appContext = createAppContext();
|
||||
setAppContext(appContext);
|
||||
|
||||
const { csound, projectManager, editorSettings, projectEditor } = appContext;
|
||||
const { csound, projectManager, editorSettings, projectEditor, uiState } = appContext;
|
||||
const csoundDerived = createCsoundDerivedStores(csound);
|
||||
|
||||
let analyserNode = $state<AnalyserNode | null>(null);
|
||||
let interpreterLogs = $state<LogEntry[]>([]);
|
||||
let fileBrowserRef: FileBrowser;
|
||||
|
||||
onMount(async () => {
|
||||
await projectManager.init();
|
||||
@ -79,12 +77,11 @@
|
||||
}
|
||||
|
||||
function handleNewFile() {
|
||||
const needsConfirm = projectEditor.requestSwitch(
|
||||
() => projectEditor.createNew(DEFAULT_CSOUND_TEMPLATE),
|
||||
handleSwitchConfirm
|
||||
const result = projectEditor.requestSwitch(
|
||||
() => projectEditor.createNew(DEFAULT_CSOUND_TEMPLATE)
|
||||
);
|
||||
|
||||
if (needsConfirm) {
|
||||
if (result === 'confirm-unsaved') {
|
||||
uiState.showUnsavedChangesDialog();
|
||||
}
|
||||
}
|
||||
@ -92,12 +89,11 @@
|
||||
function handleFileSelect(project: CsoundProject | null) {
|
||||
if (!project) return;
|
||||
|
||||
const needsConfirm = projectEditor.requestSwitch(
|
||||
() => projectEditor.loadProject(project),
|
||||
handleSwitchConfirm
|
||||
const result = projectEditor.requestSwitch(
|
||||
() => projectEditor.loadProject(project)
|
||||
);
|
||||
|
||||
if (needsConfirm) {
|
||||
if (result === 'confirm-unsaved') {
|
||||
uiState.showUnsavedChangesDialog();
|
||||
}
|
||||
}
|
||||
@ -116,44 +112,36 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const success = await projectEditor.save();
|
||||
if (success) {
|
||||
fileBrowserRef?.refresh();
|
||||
}
|
||||
await projectEditor.save();
|
||||
}
|
||||
|
||||
async function handleSaveAs(title: string) {
|
||||
const success = await projectEditor.saveAs(title);
|
||||
if (success) {
|
||||
fileBrowserRef?.refresh();
|
||||
uiState.hideSaveAsDialog();
|
||||
}
|
||||
}
|
||||
|
||||
async function handleMetadataUpdate(projectId: string, updates: { title?: string; author?: string }) {
|
||||
const success = await projectEditor.updateMetadata(updates);
|
||||
if (success) {
|
||||
fileBrowserRef?.refresh();
|
||||
}
|
||||
await projectEditor.updateMetadata(updates);
|
||||
}
|
||||
|
||||
function handleSwitchConfirm(action: 'save' | 'discard') {
|
||||
if (action === 'save') {
|
||||
if (projectEditor.isNewUnsavedBuffer) {
|
||||
async function handleSwitchSave() {
|
||||
const result = await projectEditor.confirmSaveAndSwitch();
|
||||
|
||||
if (result === 'show-save-as') {
|
||||
uiState.hideUnsavedChangesDialog();
|
||||
uiState.showSaveAsDialog();
|
||||
} else {
|
||||
projectEditor.handleSaveAndSwitch().then(() => {
|
||||
fileBrowserRef?.refresh();
|
||||
uiState.hideUnsavedChangesDialog();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
projectEditor.handleDiscardAndSwitch();
|
||||
uiState.hideUnsavedChangesDialog();
|
||||
}
|
||||
}
|
||||
|
||||
function handleSwitchDiscard() {
|
||||
projectEditor.confirmDiscardAndSwitch();
|
||||
uiState.hideUnsavedChangesDialog();
|
||||
}
|
||||
|
||||
async function handleShare() {
|
||||
if (!projectEditor.currentProjectId) return;
|
||||
|
||||
@ -170,11 +158,15 @@
|
||||
|
||||
$effect(() => {
|
||||
if (uiState.scopePopupVisible || uiState.spectrogramPopupVisible) {
|
||||
const interval = setInterval(() => {
|
||||
analyserNode = csound.getAnalyserNode();
|
||||
}, 100);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
const unsubscribe = csound.onAnalyserNodeCreated((node) => {
|
||||
analyserNode = node;
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
} else {
|
||||
analyserNode = null;
|
||||
}
|
||||
});
|
||||
|
||||
@ -198,7 +190,6 @@
|
||||
|
||||
{#snippet filesTabContent()}
|
||||
<FileBrowser
|
||||
bind:this={fileBrowserRef}
|
||||
{projectManager}
|
||||
onFileSelect={handleFileSelect}
|
||||
onNewFile={handleNewFile}
|
||||
@ -400,8 +391,8 @@
|
||||
message="You have unsaved changes. What would you like to do?"
|
||||
confirmLabel="Save"
|
||||
cancelLabel="Discard"
|
||||
onConfirm={() => handleSwitchConfirm('save')}
|
||||
onCancel={() => handleSwitchConfirm('discard')}
|
||||
onConfirm={handleSwitchSave}
|
||||
onCancel={handleSwitchDiscard}
|
||||
/>
|
||||
|
||||
<InputDialog
|
||||
|
||||
@ -58,8 +58,6 @@
|
||||
]);
|
||||
|
||||
onMount(() => {
|
||||
const settings = $editorSettings;
|
||||
|
||||
const baseExtensions = [
|
||||
highlightActiveLineGutter(),
|
||||
highlightSpecialChars(),
|
||||
@ -86,6 +84,8 @@
|
||||
])
|
||||
];
|
||||
|
||||
const initSettings = $editorSettings;
|
||||
|
||||
editorView = new EditorView({
|
||||
doc: value,
|
||||
extensions: [
|
||||
@ -93,9 +93,9 @@
|
||||
languageExtensions[language],
|
||||
oneDark,
|
||||
evaluateKeymap,
|
||||
lineNumbersCompartment.of(settings.showLineNumbers ? lineNumbers() : []),
|
||||
lineWrappingCompartment.of(settings.enableLineWrapping ? EditorView.lineWrapping : []),
|
||||
vimCompartment.of(settings.vimMode ? vim() : []),
|
||||
lineNumbersCompartment.of(initSettings.showLineNumbers ? lineNumbers() : []),
|
||||
lineWrappingCompartment.of(initSettings.enableLineWrapping ? EditorView.lineWrapping : []),
|
||||
vimCompartment.of(initSettings.vimMode ? vim() : []),
|
||||
EditorView.updateListener.of((update) => {
|
||||
if (update.docChanged && onChange) {
|
||||
onChange(update.state.doc.toString());
|
||||
@ -103,32 +103,29 @@
|
||||
}),
|
||||
EditorView.theme({
|
||||
'&': {
|
||||
fontSize: `${settings.fontSize}px`,
|
||||
fontFamily: settings.fontFamily
|
||||
fontSize: `${initSettings.fontSize}px`,
|
||||
fontFamily: initSettings.fontFamily
|
||||
}
|
||||
})
|
||||
],
|
||||
parent: editorContainer
|
||||
});
|
||||
});
|
||||
|
||||
const unsubscribe = editorSettings.subscribe((newSettings) => {
|
||||
$effect(() => {
|
||||
const settings = $editorSettings;
|
||||
if (!editorView) return;
|
||||
|
||||
editorView.dispatch({
|
||||
effects: [
|
||||
lineNumbersCompartment.reconfigure(newSettings.showLineNumbers ? lineNumbers() : []),
|
||||
lineWrappingCompartment.reconfigure(newSettings.enableLineWrapping ? EditorView.lineWrapping : []),
|
||||
vimCompartment.reconfigure(newSettings.vimMode ? vim() : [])
|
||||
lineNumbersCompartment.reconfigure(settings.showLineNumbers ? lineNumbers() : []),
|
||||
lineWrappingCompartment.reconfigure(settings.enableLineWrapping ? EditorView.lineWrapping : []),
|
||||
vimCompartment.reconfigure(settings.vimMode ? vim() : [])
|
||||
]
|
||||
});
|
||||
|
||||
editorView.dom.style.fontSize = `${newSettings.fontSize}px`;
|
||||
editorView.dom.style.fontFamily = newSettings.fontFamily;
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
editorView.dom.style.fontSize = `${settings.fontSize}px`;
|
||||
editorView.dom.style.fontFamily = settings.fontFamily;
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
|
||||
@ -40,12 +40,17 @@
|
||||
|
||||
onMount(async () => {
|
||||
await loadProjects();
|
||||
|
||||
const unsubscribe = projectManager.onProjectsChanged(() => {
|
||||
loadProjects();
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
});
|
||||
|
||||
async function loadProjects() {
|
||||
loading = true;
|
||||
try {
|
||||
await projectManager.init();
|
||||
const result = await projectManager.getAllProjects();
|
||||
if (result.success) {
|
||||
projects = result.data.sort((a, b) =>
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { getContext, setContext } from 'svelte';
|
||||
import { ProjectManager } from '../project-system/project-manager';
|
||||
import { ProjectDatabase } from '../project-system/db';
|
||||
import { createCsoundStore } from '../csound/store';
|
||||
import { createEditorSettingsStore } from '../stores/editorSettings';
|
||||
import { ProjectEditor } from '../stores/projectEditor.svelte';
|
||||
import { UIState } from '../stores/uiState.svelte';
|
||||
import type { CsoundStore } from '../csound/store';
|
||||
import type { EditorSettingsStore } from '../stores/editorSettings';
|
||||
|
||||
@ -11,17 +13,20 @@ export interface AppContext {
|
||||
csound: CsoundStore;
|
||||
editorSettings: EditorSettingsStore;
|
||||
projectEditor: ProjectEditor;
|
||||
uiState: UIState;
|
||||
}
|
||||
|
||||
const APP_CONTEXT_KEY = Symbol('app-context');
|
||||
|
||||
export function createAppContext(): AppContext {
|
||||
const projectManager = new ProjectManager();
|
||||
const db = new ProjectDatabase();
|
||||
const projectManager = new ProjectManager(db);
|
||||
return {
|
||||
projectManager,
|
||||
csound: createCsoundStore(),
|
||||
editorSettings: createEditorSettingsStore(),
|
||||
projectEditor: new ProjectEditor(projectManager)
|
||||
projectEditor: new ProjectEditor(projectManager),
|
||||
uiState: new UIState()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ export interface CsoundEngineOptions {
|
||||
onMessage?: (message: string) => void;
|
||||
onError?: (error: string) => void;
|
||||
onPerformanceEnd?: () => void;
|
||||
onAnalyserNodeCreated?: (node: AnalyserNode) => void;
|
||||
}
|
||||
|
||||
export class CsoundEngine {
|
||||
@ -145,6 +146,7 @@ export class CsoundEngine {
|
||||
this.scopeNode.connect(this.audioContext.destination);
|
||||
|
||||
this.log('Analyser node created and connected');
|
||||
this.options.onAnalyserNodeCreated?.(this.scopeNode);
|
||||
} catch (error) {
|
||||
console.error('Failed to setup analyser:', error);
|
||||
this.log('Error setting up analyser: ' + error);
|
||||
|
||||
@ -21,6 +21,7 @@ export interface CsoundStore {
|
||||
clearLogs: () => void;
|
||||
getAudioContext: () => AudioContext | null;
|
||||
getAnalyserNode: () => AnalyserNode | null;
|
||||
onAnalyserNodeCreated: (callback: (node: AnalyserNode) => void) => () => void;
|
||||
destroy: () => Promise<void>;
|
||||
}
|
||||
|
||||
@ -34,6 +35,7 @@ export function createCsoundStore(): CsoundStore {
|
||||
const { subscribe, set, update } = writable<CsoundState>(initialState);
|
||||
|
||||
let engine: CsoundEngine | null = null;
|
||||
const analyserNodeListeners: Set<(node: AnalyserNode) => void> = new Set();
|
||||
|
||||
function addLog(message: string, type: 'info' | 'error' = 'info') {
|
||||
update(state => ({
|
||||
@ -51,7 +53,10 @@ export function createCsoundStore(): CsoundStore {
|
||||
try {
|
||||
engine = new CsoundEngine({
|
||||
onMessage: (msg) => addLog(msg, 'info'),
|
||||
onError: (err) => addLog(err, 'error')
|
||||
onError: (err) => addLog(err, 'error'),
|
||||
onAnalyserNodeCreated: (node) => {
|
||||
analyserNodeListeners.forEach(listener => listener(node));
|
||||
}
|
||||
});
|
||||
|
||||
await engine.init();
|
||||
@ -117,11 +122,17 @@ export function createCsoundStore(): CsoundStore {
|
||||
return null;
|
||||
},
|
||||
|
||||
onAnalyserNodeCreated(callback: (node: AnalyserNode) => void): () => void {
|
||||
analyserNodeListeners.add(callback);
|
||||
return () => analyserNodeListeners.delete(callback);
|
||||
},
|
||||
|
||||
async destroy() {
|
||||
if (engine) {
|
||||
await engine.destroy();
|
||||
engine = null;
|
||||
}
|
||||
analyserNodeListeners.clear();
|
||||
set(initialState);
|
||||
}
|
||||
};
|
||||
|
||||
@ -229,5 +229,4 @@ class ProjectDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const projectDb = new ProjectDatabase();
|
||||
export { ProjectDatabase };
|
||||
|
||||
@ -1,8 +1,20 @@
|
||||
import type { CsoundProject, CreateProjectData, UpdateProjectData, Result } from './types';
|
||||
import { projectDb } from './db';
|
||||
import type { ProjectDatabase } from './db';
|
||||
import { compressProject, decompressProject, projectToShareUrl, projectFromShareUrl } from './compression';
|
||||
|
||||
const CSOUND_VERSION = '7.0.0'; // This should be detected from @csound/browser
|
||||
const CSOUND_VERSION = '7.0.0';
|
||||
|
||||
async function wrapResult<T>(fn: () => Promise<T>, errorMsg: string): Promise<Result<T>> {
|
||||
try {
|
||||
const data = await fn();
|
||||
return { success: true, data };
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error : new Error(errorMsg),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a unique ID for a project
|
||||
@ -18,15 +30,54 @@ function getCurrentTimestamp(): string {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an imported/duplicated project with new ID and timestamps
|
||||
*/
|
||||
function createDerivedProject(baseProject: CsoundProject, titleSuffix: string): CsoundProject {
|
||||
const now = getCurrentTimestamp();
|
||||
return {
|
||||
...baseProject,
|
||||
id: generateId(),
|
||||
title: `${baseProject.title} ${titleSuffix}`,
|
||||
dateCreated: now,
|
||||
dateModified: now,
|
||||
saveCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
type ProjectChangeListener = () => void;
|
||||
|
||||
/**
|
||||
* Project Manager - Main API for managing Csound projects
|
||||
*/
|
||||
export class ProjectManager {
|
||||
private db: ProjectDatabase;
|
||||
private changeListeners: Set<ProjectChangeListener> = new Set();
|
||||
|
||||
constructor(db: ProjectDatabase) {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to project changes
|
||||
*/
|
||||
onProjectsChanged(listener: ProjectChangeListener): () => void {
|
||||
this.changeListeners.add(listener);
|
||||
return () => this.changeListeners.delete(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all listeners that projects have changed
|
||||
*/
|
||||
private notifyChange(): void {
|
||||
this.changeListeners.forEach(listener => listener());
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the project manager (initializes database)
|
||||
*/
|
||||
async init(): Promise<void> {
|
||||
await projectDb.init();
|
||||
await this.db.init();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -48,7 +99,8 @@ export class ProjectManager {
|
||||
csoundVersion: CSOUND_VERSION,
|
||||
};
|
||||
|
||||
await projectDb.put(project);
|
||||
await this.db.put(project);
|
||||
this.notifyChange();
|
||||
|
||||
return { success: true, data: project };
|
||||
} catch (error) {
|
||||
@ -64,7 +116,7 @@ export class ProjectManager {
|
||||
*/
|
||||
async getProject(id: string): Promise<Result<CsoundProject>> {
|
||||
try {
|
||||
const project = await projectDb.get(id);
|
||||
const project = await this.db.get(id);
|
||||
|
||||
if (!project) {
|
||||
return {
|
||||
@ -86,15 +138,7 @@ export class ProjectManager {
|
||||
* Get all projects
|
||||
*/
|
||||
async getAllProjects(): Promise<Result<CsoundProject[]>> {
|
||||
try {
|
||||
const projects = await projectDb.getAll();
|
||||
return { success: true, data: projects };
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error : new Error('Failed to get projects'),
|
||||
};
|
||||
}
|
||||
return wrapResult(() => this.db.getAll(), 'Failed to get projects');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -102,7 +146,7 @@ export class ProjectManager {
|
||||
*/
|
||||
async updateProject(data: UpdateProjectData): Promise<Result<CsoundProject>> {
|
||||
try {
|
||||
const existingProject = await projectDb.get(data.id);
|
||||
const existingProject = await this.db.get(data.id);
|
||||
|
||||
if (!existingProject) {
|
||||
return {
|
||||
@ -121,7 +165,8 @@ export class ProjectManager {
|
||||
saveCount: existingProject.saveCount + 1,
|
||||
};
|
||||
|
||||
await projectDb.put(updatedProject);
|
||||
await this.db.put(updatedProject);
|
||||
this.notifyChange();
|
||||
|
||||
return { success: true, data: updatedProject };
|
||||
} catch (error) {
|
||||
@ -136,45 +181,25 @@ export class ProjectManager {
|
||||
* Delete a project
|
||||
*/
|
||||
async deleteProject(id: string): Promise<Result<void>> {
|
||||
try {
|
||||
await projectDb.delete(id);
|
||||
return { success: true, data: undefined };
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error : new Error('Failed to delete project'),
|
||||
};
|
||||
const result = await wrapResult(() => this.db.delete(id), 'Failed to delete project');
|
||||
if (result.success) {
|
||||
this.notifyChange();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search projects by tag
|
||||
*/
|
||||
async getProjectsByTag(tag: string): Promise<Result<CsoundProject[]>> {
|
||||
try {
|
||||
const projects = await projectDb.getByTag(tag);
|
||||
return { success: true, data: projects };
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error : new Error('Failed to search projects by tag'),
|
||||
};
|
||||
}
|
||||
return wrapResult(() => this.db.getByTag(tag), 'Failed to search projects by tag');
|
||||
}
|
||||
|
||||
/**
|
||||
* Search projects by author
|
||||
*/
|
||||
async getProjectsByAuthor(author: string): Promise<Result<CsoundProject[]>> {
|
||||
try {
|
||||
const projects = await projectDb.getByAuthor(author);
|
||||
return { success: true, data: projects };
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error : new Error('Failed to search projects by author'),
|
||||
};
|
||||
}
|
||||
return wrapResult(() => this.db.getByAuthor(author), 'Failed to search projects by author');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -182,7 +207,7 @@ export class ProjectManager {
|
||||
*/
|
||||
async exportProjectToUrl(id: string, baseUrl?: string): Promise<Result<string>> {
|
||||
try {
|
||||
const project = await projectDb.get(id);
|
||||
const project = await this.db.get(id);
|
||||
|
||||
if (!project) {
|
||||
return {
|
||||
@ -207,7 +232,7 @@ export class ProjectManager {
|
||||
*/
|
||||
async exportProjectToCompressed(id: string): Promise<Result<string>> {
|
||||
try {
|
||||
const project = await projectDb.get(id);
|
||||
const project = await this.db.get(id);
|
||||
|
||||
if (!project) {
|
||||
return {
|
||||
@ -233,19 +258,10 @@ export class ProjectManager {
|
||||
async importProjectFromUrl(url: string): Promise<Result<CsoundProject>> {
|
||||
try {
|
||||
const project = projectFromShareUrl(url);
|
||||
const importedProject = createDerivedProject(project, '(imported)');
|
||||
|
||||
// Generate a new ID and reset timestamps
|
||||
const now = getCurrentTimestamp();
|
||||
const importedProject: CsoundProject = {
|
||||
...project,
|
||||
id: generateId(),
|
||||
dateCreated: now,
|
||||
dateModified: now,
|
||||
saveCount: 0,
|
||||
title: `${project.title} (imported)`,
|
||||
};
|
||||
|
||||
await projectDb.put(importedProject);
|
||||
await this.db.put(importedProject);
|
||||
this.notifyChange();
|
||||
|
||||
return { success: true, data: importedProject };
|
||||
} catch (error) {
|
||||
@ -265,19 +281,10 @@ export class ProjectManager {
|
||||
data: compressedData,
|
||||
version: 1,
|
||||
});
|
||||
const importedProject = createDerivedProject(project, '(imported)');
|
||||
|
||||
// Generate a new ID and reset timestamps
|
||||
const now = getCurrentTimestamp();
|
||||
const importedProject: CsoundProject = {
|
||||
...project,
|
||||
id: generateId(),
|
||||
dateCreated: now,
|
||||
dateModified: now,
|
||||
saveCount: 0,
|
||||
title: `${project.title} (imported)`,
|
||||
};
|
||||
|
||||
await projectDb.put(importedProject);
|
||||
await this.db.put(importedProject);
|
||||
this.notifyChange();
|
||||
|
||||
return { success: true, data: importedProject };
|
||||
} catch (error) {
|
||||
@ -293,7 +300,7 @@ export class ProjectManager {
|
||||
*/
|
||||
async duplicateProject(id: string): Promise<Result<CsoundProject>> {
|
||||
try {
|
||||
const originalProject = await projectDb.get(id);
|
||||
const originalProject = await this.db.get(id);
|
||||
|
||||
if (!originalProject) {
|
||||
return {
|
||||
@ -302,17 +309,10 @@ export class ProjectManager {
|
||||
};
|
||||
}
|
||||
|
||||
const now = getCurrentTimestamp();
|
||||
const duplicatedProject: CsoundProject = {
|
||||
...originalProject,
|
||||
id: generateId(),
|
||||
title: `${originalProject.title} (copy)`,
|
||||
dateCreated: now,
|
||||
dateModified: now,
|
||||
saveCount: 0,
|
||||
};
|
||||
const duplicatedProject = createDerivedProject(originalProject, '(copy)');
|
||||
|
||||
await projectDb.put(duplicatedProject);
|
||||
await this.db.put(duplicatedProject);
|
||||
this.notifyChange();
|
||||
|
||||
return { success: true, data: duplicatedProject };
|
||||
} catch (error) {
|
||||
@ -327,14 +327,10 @@ export class ProjectManager {
|
||||
* Clear all projects (use with caution!)
|
||||
*/
|
||||
async clearAllProjects(): Promise<Result<void>> {
|
||||
try {
|
||||
await projectDb.clear();
|
||||
return { success: true, data: undefined };
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error : new Error('Failed to clear projects'),
|
||||
};
|
||||
}
|
||||
const result = await wrapResult(() => this.db.clear(), 'Failed to clear projects');
|
||||
if (result.success) {
|
||||
this.notifyChange();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
const STORAGE_KEY = 'editorSettings';
|
||||
|
||||
export interface EditorSettings {
|
||||
fontSize: number;
|
||||
fontFamily: string;
|
||||
@ -26,7 +28,7 @@ export interface EditorSettingsStore {
|
||||
}
|
||||
|
||||
export function createEditorSettingsStore(): EditorSettingsStore {
|
||||
const stored = typeof localStorage !== 'undefined' ? localStorage.getItem('editorSettings') : null;
|
||||
const stored = typeof localStorage !== 'undefined' ? localStorage.getItem(STORAGE_KEY) : null;
|
||||
const initial = stored ? { ...defaultSettings, ...JSON.parse(stored) } : defaultSettings;
|
||||
|
||||
const { subscribe, set, update } = writable<EditorSettings>(initial);
|
||||
@ -35,7 +37,7 @@ export function createEditorSettingsStore(): EditorSettingsStore {
|
||||
subscribe,
|
||||
set: (value: EditorSettings) => {
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.setItem('editorSettings', JSON.stringify(value));
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(value));
|
||||
}
|
||||
set(value);
|
||||
},
|
||||
@ -43,7 +45,7 @@ export function createEditorSettingsStore(): EditorSettingsStore {
|
||||
update((current) => {
|
||||
const newValue = updater(current);
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.setItem('editorSettings', JSON.stringify(newValue));
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(newValue));
|
||||
}
|
||||
return newValue;
|
||||
});
|
||||
@ -52,7 +54,7 @@ export function createEditorSettingsStore(): EditorSettingsStore {
|
||||
update((current) => {
|
||||
const newValue = { ...current, ...partial };
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.setItem('editorSettings', JSON.stringify(newValue));
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(newValue));
|
||||
}
|
||||
return newValue;
|
||||
});
|
||||
|
||||
@ -123,32 +123,33 @@ export class ProjectEditor {
|
||||
return false;
|
||||
}
|
||||
|
||||
requestSwitch(action: () => void, onConfirm: (action: 'save' | 'discard') => void) {
|
||||
requestSwitch(action: () => void): 'proceed' | 'confirm-unsaved' {
|
||||
if (!this.state.hasUnsavedChanges) {
|
||||
action();
|
||||
return false;
|
||||
return 'proceed';
|
||||
}
|
||||
|
||||
this.pendingAction = action;
|
||||
this.confirmCallback = onConfirm;
|
||||
return true;
|
||||
return 'confirm-unsaved';
|
||||
}
|
||||
|
||||
async handleSaveAndSwitch(): Promise<void> {
|
||||
async confirmSaveAndSwitch(): Promise<'show-save-as' | 'done'> {
|
||||
if (this.state.isNewUnsavedBuffer) {
|
||||
this.confirmCallback?.('save');
|
||||
return;
|
||||
return 'show-save-as';
|
||||
}
|
||||
|
||||
await this.save();
|
||||
this.pendingAction?.();
|
||||
this.pendingAction = null;
|
||||
this.confirmCallback = null;
|
||||
return 'done';
|
||||
}
|
||||
|
||||
handleDiscardAndSwitch(): void {
|
||||
confirmDiscardAndSwitch(): void {
|
||||
this.pendingAction?.();
|
||||
this.pendingAction = null;
|
||||
this.confirmCallback = null;
|
||||
}
|
||||
|
||||
cancelSwitch(): void {
|
||||
this.pendingAction = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
type PanelPosition = 'left' | 'right' | 'bottom';
|
||||
|
||||
class UIState {
|
||||
export class UIState {
|
||||
sidePanelVisible = $state(true);
|
||||
sidePanelPosition = $state<PanelPosition>('right');
|
||||
|
||||
@ -60,5 +60,3 @@ class UIState {
|
||||
this.saveAsDialogVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
export const uiState = new UIState();
|
||||
|
||||
Reference in New Issue
Block a user