344 lines
8.6 KiB
TypeScript
344 lines
8.6 KiB
TypeScript
import type { CsoundProject, CreateProjectData, UpdateProjectData, Result } from './types';
|
|
import { projectDb } from './db';
|
|
import { compressProject, decompressProject, projectToShareUrl, projectFromShareUrl } from './compression';
|
|
|
|
const CSOUND_VERSION = '7.0.0'; // This should be detected from @csound/browser
|
|
|
|
/**
|
|
* Generate a unique ID for a project
|
|
*/
|
|
function generateId(): string {
|
|
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
}
|
|
|
|
/**
|
|
* Get the current ISO timestamp
|
|
*/
|
|
function getCurrentTimestamp(): string {
|
|
return new Date().toISOString();
|
|
}
|
|
|
|
/**
|
|
* Project Manager - Main API for managing Csound projects
|
|
*/
|
|
export class ProjectManager {
|
|
/**
|
|
* Initialize the project manager (initializes database)
|
|
*/
|
|
async init(): Promise<void> {
|
|
await projectDb.init();
|
|
}
|
|
|
|
/**
|
|
* Create a new project
|
|
*/
|
|
async createProject(data: CreateProjectData): Promise<Result<CsoundProject>> {
|
|
try {
|
|
const now = getCurrentTimestamp();
|
|
|
|
const project: CsoundProject = {
|
|
id: generateId(),
|
|
title: data.title,
|
|
author: data.author,
|
|
dateCreated: now,
|
|
dateModified: now,
|
|
saveCount: 0,
|
|
content: data.content || '',
|
|
tags: data.tags || [],
|
|
csoundVersion: CSOUND_VERSION,
|
|
};
|
|
|
|
await projectDb.put(project);
|
|
|
|
return { success: true, data: project };
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error : new Error('Failed to create project'),
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a project by ID
|
|
*/
|
|
async getProject(id: string): Promise<Result<CsoundProject>> {
|
|
try {
|
|
const project = await projectDb.get(id);
|
|
|
|
if (!project) {
|
|
return {
|
|
success: false,
|
|
error: new Error(`Project not found: ${id}`),
|
|
};
|
|
}
|
|
|
|
return { success: true, data: project };
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error : new Error('Failed to get project'),
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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'),
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update an existing project
|
|
*/
|
|
async updateProject(data: UpdateProjectData): Promise<Result<CsoundProject>> {
|
|
try {
|
|
const existingProject = await projectDb.get(data.id);
|
|
|
|
if (!existingProject) {
|
|
return {
|
|
success: false,
|
|
error: new Error(`Project not found: ${data.id}`),
|
|
};
|
|
}
|
|
|
|
const updatedProject: CsoundProject = {
|
|
...existingProject,
|
|
...(data.title !== undefined && { title: data.title }),
|
|
...(data.author !== undefined && { author: data.author }),
|
|
...(data.content !== undefined && { content: data.content }),
|
|
...(data.tags !== undefined && { tags: data.tags }),
|
|
dateModified: getCurrentTimestamp(),
|
|
saveCount: existingProject.saveCount + 1,
|
|
};
|
|
|
|
await projectDb.put(updatedProject);
|
|
|
|
return { success: true, data: updatedProject };
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error : new Error('Failed to update project'),
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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'),
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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'),
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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'),
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Export a project to a shareable URL
|
|
*/
|
|
async exportProjectToUrl(id: string, baseUrl?: string): Promise<Result<string>> {
|
|
try {
|
|
const project = await projectDb.get(id);
|
|
|
|
if (!project) {
|
|
return {
|
|
success: false,
|
|
error: new Error(`Project not found: ${id}`),
|
|
};
|
|
}
|
|
|
|
const url = projectToShareUrl(project, baseUrl);
|
|
|
|
return { success: true, data: url };
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error : new Error('Failed to export project'),
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Export a project to compressed data (for copying to clipboard, etc.)
|
|
*/
|
|
async exportProjectToCompressed(id: string): Promise<Result<string>> {
|
|
try {
|
|
const project = await projectDb.get(id);
|
|
|
|
if (!project) {
|
|
return {
|
|
success: false,
|
|
error: new Error(`Project not found: ${id}`),
|
|
};
|
|
}
|
|
|
|
const compressed = compressProject(project);
|
|
|
|
return { success: true, data: compressed.data };
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error : new Error('Failed to export project'),
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Import a project from a URL
|
|
*/
|
|
async importProjectFromUrl(url: string): Promise<Result<CsoundProject>> {
|
|
try {
|
|
const project = projectFromShareUrl(url);
|
|
|
|
// 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);
|
|
|
|
return { success: true, data: importedProject };
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error : new Error('Failed to import project'),
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Import a project from compressed data
|
|
*/
|
|
async importProjectFromCompressed(compressedData: string): Promise<Result<CsoundProject>> {
|
|
try {
|
|
const project = decompressProject({
|
|
data: compressedData,
|
|
version: 1,
|
|
});
|
|
|
|
// 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);
|
|
|
|
return { success: true, data: importedProject };
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error : new Error('Failed to import project'),
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Duplicate a project
|
|
*/
|
|
async duplicateProject(id: string): Promise<Result<CsoundProject>> {
|
|
try {
|
|
const originalProject = await projectDb.get(id);
|
|
|
|
if (!originalProject) {
|
|
return {
|
|
success: false,
|
|
error: new Error(`Project not found: ${id}`),
|
|
};
|
|
}
|
|
|
|
const now = getCurrentTimestamp();
|
|
const duplicatedProject: CsoundProject = {
|
|
...originalProject,
|
|
id: generateId(),
|
|
title: `${originalProject.title} (copy)`,
|
|
dateCreated: now,
|
|
dateModified: now,
|
|
saveCount: 0,
|
|
};
|
|
|
|
await projectDb.put(duplicatedProject);
|
|
|
|
return { success: true, data: duplicatedProject };
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error : new Error('Failed to duplicate project'),
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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'),
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
// Export singleton instance
|
|
export const projectManager = new ProjectManager();
|