Spaces:
Configuration error
Configuration error
| // Project-wise File Management System with Local Storage | |
| // Comprehensive project organization and file handling | |
| interface ProjectFile { | |
| id: string; | |
| name: string; | |
| type: 'calculation' | 'report' | 'data' | 'image' | 'document'; | |
| content: any; | |
| size: number; | |
| createdAt: Date; | |
| modifiedAt: Date; | |
| tags: string[]; | |
| metadata: Record<string, any>; | |
| } | |
| interface Project { | |
| id: string; | |
| name: string; | |
| description: string; | |
| createdAt: Date; | |
| modifiedAt: Date; | |
| files: ProjectFile[]; | |
| settings: ProjectSettings; | |
| status: 'active' | 'completed' | 'archived'; | |
| } | |
| interface ProjectSettings { | |
| autoSave: boolean; | |
| backupFrequency: number; // hours | |
| compressionEnabled: boolean; | |
| maxFileSize: number; // MB | |
| allowedFileTypes: string[]; | |
| } | |
| class ProjectManager { | |
| private readonly STORAGE_KEY = 'prithvi_projects'; | |
| private readonly MAX_STORAGE_SIZE = 50 * 1024 * 1024; // 50MB limit | |
| private projects: Map<string, Project> = new Map(); | |
| constructor() { | |
| this.loadProjects(); | |
| this.setupAutoSave(); | |
| } | |
| // Project Management | |
| async createProject(name: string, description: string = ''): Promise<Project> { | |
| try { | |
| if (!name || name.trim().length === 0) { | |
| throw new Error('Project name is required'); | |
| } | |
| if (name.length > 100) { | |
| throw new Error('Project name must be less than 100 characters'); | |
| } | |
| // Check for duplicate names | |
| const existingProject = Array.from(this.projects.values()).find(p => p.name === name); | |
| if (existingProject) { | |
| throw new Error('Project with this name already exists'); | |
| } | |
| const project: Project = { | |
| id: this.generateId(), | |
| name: name.trim(), | |
| description: description.trim(), | |
| createdAt: new Date(), | |
| modifiedAt: new Date(), | |
| files: [], | |
| settings: this.getDefaultSettings(), | |
| status: 'active' | |
| }; | |
| this.projects.set(project.id, project); | |
| await this.saveProjects(); | |
| return project; | |
| } catch (error) { | |
| throw new Error(`Failed to create project: ${error instanceof Error ? error.message : 'Unknown error'}`); | |
| } | |
| } | |
| async updateProject(projectId: string, updates: Partial<Project>): Promise<Project> { | |
| try { | |
| const project = this.projects.get(projectId); | |
| if (!project) { | |
| throw new Error('Project not found'); | |
| } | |
| // Validate updates | |
| if (updates.name !== undefined) { | |
| if (!updates.name || updates.name.trim().length === 0) { | |
| throw new Error('Project name cannot be empty'); | |
| } | |
| if (updates.name.length > 100) { | |
| throw new Error('Project name must be less than 100 characters'); | |
| } | |
| } | |
| const updatedProject = { | |
| ...project, | |
| ...updates, | |
| id: project.id, // Prevent ID changes | |
| modifiedAt: new Date() | |
| }; | |
| this.projects.set(projectId, updatedProject); | |
| await this.saveProjects(); | |
| return updatedProject; | |
| } catch (error) { | |
| throw new Error(`Failed to update project: ${error instanceof Error ? error.message : 'Unknown error'}`); | |
| } | |
| } | |
| async deleteProject(projectId: string): Promise<boolean> { | |
| try { | |
| const project = this.projects.get(projectId); | |
| if (!project) { | |
| throw new Error('Project not found'); | |
| } | |
| this.projects.delete(projectId); | |
| await this.saveProjects(); | |
| return true; | |
| } catch (error) { | |
| throw new Error(`Failed to delete project: ${error instanceof Error ? error.message : 'Unknown error'}`); | |
| } | |
| } | |
| getProject(projectId: string): Project | undefined { | |
| return this.projects.get(projectId); | |
| } | |
| getAllProjects(): Project[] { | |
| return Array.from(this.projects.values()).sort((a, b) => | |
| b.modifiedAt.getTime() - a.modifiedAt.getTime() | |
| ); | |
| } | |
| getProjectsByStatus(status: Project['status']): Project[] { | |
| return this.getAllProjects().filter(p => p.status === status); | |
| } | |
| searchProjects(query: string): Project[] { | |
| if (!query || query.trim().length === 0) { | |
| return this.getAllProjects(); | |
| } | |
| const searchTerm = query.toLowerCase().trim(); | |
| return this.getAllProjects().filter(project => | |
| project.name.toLowerCase().includes(searchTerm) || | |
| project.description.toLowerCase().includes(searchTerm) || | |
| project.files.some(file => | |
| file.name.toLowerCase().includes(searchTerm) || | |
| file.tags.some(tag => tag.toLowerCase().includes(searchTerm)) | |
| ) | |
| ); | |
| } | |
| // File Management | |
| async addFile(projectId: string, file: Omit<ProjectFile, 'id' | 'createdAt' | 'modifiedAt'>): Promise<ProjectFile> { | |
| try { | |
| const project = this.projects.get(projectId); | |
| if (!project) { | |
| throw new Error('Project not found'); | |
| } | |
| // Validate file | |
| this.validateFile(file, project.settings); | |
| // Check storage limits | |
| await this.checkStorageLimit(file.size); | |
| const newFile: ProjectFile = { | |
| ...file, | |
| id: this.generateId(), | |
| createdAt: new Date(), | |
| modifiedAt: new Date() | |
| }; | |
| project.files.push(newFile); | |
| project.modifiedAt = new Date(); | |
| this.projects.set(projectId, project); | |
| await this.saveProjects(); | |
| return newFile; | |
| } catch (error) { | |
| throw new Error(`Failed to add file: ${error instanceof Error ? error.message : 'Unknown error'}`); | |
| } | |
| } | |
| async updateFile(projectId: string, fileId: string, updates: Partial<ProjectFile>): Promise<ProjectFile> { | |
| try { | |
| const project = this.projects.get(projectId); | |
| if (!project) { | |
| throw new Error('Project not found'); | |
| } | |
| const fileIndex = project.files.findIndex(f => f.id === fileId); | |
| if (fileIndex === -1) { | |
| throw new Error('File not found'); | |
| } | |
| const updatedFile = { | |
| ...project.files[fileIndex], | |
| ...updates, | |
| id: fileId, // Prevent ID changes | |
| modifiedAt: new Date() | |
| }; | |
| // Validate updated file | |
| this.validateFile(updatedFile, project.settings); | |
| project.files[fileIndex] = updatedFile; | |
| project.modifiedAt = new Date(); | |
| this.projects.set(projectId, project); | |
| await this.saveProjects(); | |
| return updatedFile; | |
| } catch (error) { | |
| throw new Error(`Failed to update file: ${error instanceof Error ? error.message : 'Unknown error'}`); | |
| } | |
| } | |
| async deleteFile(projectId: string, fileId: string): Promise<boolean> { | |
| try { | |
| const project = this.projects.get(projectId); | |
| if (!project) { | |
| throw new Error('Project not found'); | |
| } | |
| const fileIndex = project.files.findIndex(f => f.id === fileId); | |
| if (fileIndex === -1) { | |
| throw new Error('File not found'); | |
| } | |
| project.files.splice(fileIndex, 1); | |
| project.modifiedAt = new Date(); | |
| this.projects.set(projectId, project); | |
| await this.saveProjects(); | |
| return true; | |
| } catch (error) { | |
| throw new Error(`Failed to delete file: ${error instanceof Error ? error.message : 'Unknown error'}`); | |
| } | |
| } | |
| getFile(projectId: string, fileId: string): ProjectFile | undefined { | |
| const project = this.projects.get(projectId); | |
| return project?.files.find(f => f.id === fileId); | |
| } | |
| getFilesByType(projectId: string, type: ProjectFile['type']): ProjectFile[] { | |
| const project = this.projects.get(projectId); | |
| return project?.files.filter(f => f.type === type) || []; | |
| } | |
| searchFiles(projectId: string, query: string): ProjectFile[] { | |
| const project = this.projects.get(projectId); | |
| if (!project || !query.trim()) { | |
| return project?.files || []; | |
| } | |
| const searchTerm = query.toLowerCase().trim(); | |
| return project.files.filter(file => | |
| file.name.toLowerCase().includes(searchTerm) || | |
| file.tags.some(tag => tag.toLowerCase().includes(searchTerm)) || | |
| (typeof file.content === 'string' && file.content.toLowerCase().includes(searchTerm)) | |
| ); | |
| } | |
| // Data Export/Import | |
| async exportProject(projectId: string): Promise<string> { | |
| try { | |
| const project = this.projects.get(projectId); | |
| if (!project) { | |
| throw new Error('Project not found'); | |
| } | |
| const exportData = { | |
| project, | |
| exportedAt: new Date().toISOString(), | |
| version: '1.0', | |
| metadata: { | |
| totalFiles: project.files.length, | |
| totalSize: this.calculateProjectSize(project) | |
| } | |
| }; | |
| return JSON.stringify(exportData, null, 2); | |
| } catch (error) { | |
| throw new Error(`Failed to export project: ${error instanceof Error ? error.message : 'Unknown error'}`); | |
| } | |
| } | |
| async importProject(importData: string): Promise<Project> { | |
| try { | |
| const data = JSON.parse(importData); | |
| if (!data.project) { | |
| throw new Error('Invalid import data: missing project information'); | |
| } | |
| const project = data.project; | |
| // Validate project structure | |
| if (!project.name || !project.id) { | |
| throw new Error('Invalid project data: missing required fields'); | |
| } | |
| // Generate new ID to avoid conflicts | |
| project.id = this.generateId(); | |
| project.createdAt = new Date(); | |
| project.modifiedAt = new Date(); | |
| // Validate and regenerate file IDs | |
| if (project.files) { | |
| project.files.forEach((file: ProjectFile) => { | |
| file.id = this.generateId(); | |
| file.createdAt = new Date(file.createdAt); | |
| file.modifiedAt = new Date(file.modifiedAt); | |
| }); | |
| } | |
| // Check storage limits | |
| const projectSize = this.calculateProjectSize(project); | |
| await this.checkStorageLimit(projectSize); | |
| this.projects.set(project.id, project); | |
| await this.saveProjects(); | |
| return project; | |
| } catch (error) { | |
| throw new Error(`Failed to import project: ${error instanceof Error ? error.message : 'Unknown error'}`); | |
| } | |
| } | |
| // Storage Management | |
| async getStorageInfo(): Promise<{ | |
| used: number; | |
| available: number; | |
| total: number; | |
| projects: number; | |
| files: number; | |
| }> { | |
| try { | |
| const projects = this.getAllProjects(); | |
| const totalFiles = projects.reduce((sum, p) => sum + p.files.length, 0); | |
| const usedSpace = this.calculateTotalSize(); | |
| return { | |
| used: usedSpace, | |
| available: this.MAX_STORAGE_SIZE - usedSpace, | |
| total: this.MAX_STORAGE_SIZE, | |
| projects: projects.length, | |
| files: totalFiles | |
| }; | |
| } catch (error) { | |
| throw new Error(`Failed to get storage info: ${error instanceof Error ? error.message : 'Unknown error'}`); | |
| } | |
| } | |
| async cleanupStorage(daysOld: number = 30): Promise<{ | |
| deletedProjects: number; | |
| freedSpace: number; | |
| }> { | |
| try { | |
| const cutoffDate = new Date(); | |
| cutoffDate.setDate(cutoffDate.getDate() - daysOld); | |
| let deletedProjects = 0; | |
| let freedSpace = 0; | |
| for (const [projectId, project] of this.projects) { | |
| if (project.status === 'archived' && project.modifiedAt < cutoffDate) { | |
| freedSpace += this.calculateProjectSize(project); | |
| this.projects.delete(projectId); | |
| deletedProjects++; | |
| } | |
| } | |
| if (deletedProjects > 0) { | |
| await this.saveProjects(); | |
| } | |
| return { deletedProjects, freedSpace }; | |
| } catch (error) { | |
| throw new Error(`Failed to cleanup storage: ${error instanceof Error ? error.message : 'Unknown error'}`); | |
| } | |
| } | |
| // Backup and Recovery | |
| async createBackup(): Promise<string> { | |
| try { | |
| const backup = { | |
| projects: Array.from(this.projects.values()), | |
| backupDate: new Date().toISOString(), | |
| version: '1.0' | |
| }; | |
| return JSON.stringify(backup, null, 2); | |
| } catch (error) { | |
| throw new Error(`Failed to create backup: ${error instanceof Error ? error.message : 'Unknown error'}`); | |
| } | |
| } | |
| async restoreFromBackup(backupData: string): Promise<boolean> { | |
| try { | |
| const backup = JSON.parse(backupData); | |
| if (!backup.projects || !Array.isArray(backup.projects)) { | |
| throw new Error('Invalid backup data'); | |
| } | |
| // Clear existing projects | |
| this.projects.clear(); | |
| // Restore projects | |
| backup.projects.forEach((project: Project) => { | |
| // Regenerate dates | |
| project.createdAt = new Date(project.createdAt); | |
| project.modifiedAt = new Date(project.modifiedAt); | |
| if (project.files) { | |
| project.files.forEach((file: ProjectFile) => { | |
| file.createdAt = new Date(file.createdAt); | |
| file.modifiedAt = new Date(file.modifiedAt); | |
| }); | |
| } | |
| this.projects.set(project.id, project); | |
| }); | |
| await this.saveProjects(); | |
| return true; | |
| } catch (error) { | |
| throw new Error(`Failed to restore from backup: ${error instanceof Error ? error.message : 'Unknown error'}`); | |
| } | |
| } | |
| // Private helper methods | |
| private generateId(): string { | |
| return `proj_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; | |
| } | |
| private getDefaultSettings(): ProjectSettings { | |
| return { | |
| autoSave: true, | |
| backupFrequency: 24, | |
| compressionEnabled: true, | |
| maxFileSize: 10, | |
| allowedFileTypes: [ | |
| 'text/plain', 'application/json', 'text/csv', | |
| 'image/jpeg', 'image/png', 'image/gif', | |
| 'application/pdf', 'text/html' | |
| ] | |
| }; | |
| } | |
| private validateFile(file: Partial<ProjectFile>, settings: ProjectSettings): void { | |
| if (!file.name || file.name.trim().length === 0) { | |
| throw new Error('File name is required'); | |
| } | |
| if (file.name.length > 255) { | |
| throw new Error('File name must be less than 255 characters'); | |
| } | |
| if (file.size !== undefined && file.size > settings.maxFileSize * 1024 * 1024) { | |
| throw new Error(`File size exceeds limit of ${settings.maxFileSize}MB`); | |
| } | |
| if (file.tags && file.tags.some(tag => tag.length > 50)) { | |
| throw new Error('Tag length must be less than 50 characters'); | |
| } | |
| } | |
| private calculateProjectSize(project: Project): number { | |
| return project.files.reduce((total, file) => total + file.size, 0); | |
| } | |
| private calculateTotalSize(): number { | |
| let total = 0; | |
| for (const project of this.projects.values()) { | |
| total += this.calculateProjectSize(project); | |
| } | |
| return total; | |
| } | |
| private async checkStorageLimit(additionalSize: number): Promise<void> { | |
| const currentSize = this.calculateTotalSize(); | |
| if (currentSize + additionalSize > this.MAX_STORAGE_SIZE) { | |
| throw new Error(`Storage limit exceeded. Available: ${((this.MAX_STORAGE_SIZE - currentSize) / 1024 / 1024).toFixed(2)}MB, Required: ${(additionalSize / 1024 / 1024).toFixed(2)}MB`); | |
| } | |
| } | |
| private loadProjects(): void { | |
| try { | |
| const data = localStorage.getItem(this.STORAGE_KEY); | |
| if (data) { | |
| const projects = JSON.parse(data); | |
| projects.forEach((project: Project) => { | |
| // Convert date strings back to Date objects | |
| project.createdAt = new Date(project.createdAt); | |
| project.modifiedAt = new Date(project.modifiedAt); | |
| if (project.files) { | |
| project.files.forEach((file: ProjectFile) => { | |
| file.createdAt = new Date(file.createdAt); | |
| file.modifiedAt = new Date(file.modifiedAt); | |
| }); | |
| } | |
| this.projects.set(project.id, project); | |
| }); | |
| } | |
| } catch (error) { | |
| console.error('Failed to load projects from storage:', error); | |
| } | |
| } | |
| private async saveProjects(): Promise<void> { | |
| try { | |
| const projects = Array.from(this.projects.values()); | |
| localStorage.setItem(this.STORAGE_KEY, JSON.stringify(projects)); | |
| } catch (error) { | |
| throw new Error(`Failed to save projects: ${error instanceof Error ? error.message : 'Storage quota exceeded'}`); | |
| } | |
| } | |
| private setupAutoSave(): void { | |
| // Auto-save every 5 minutes | |
| setInterval(() => { | |
| this.saveProjects().catch(error => { | |
| console.error('Auto-save failed:', error); | |
| }); | |
| }, 5 * 60 * 1000); | |
| } | |
| } | |
| export const projectManager = new ProjectManager(); | |
| export { Project, ProjectFile, ProjectSettings }; |