mxivideo/src/stores/useProjectStore.ts

265 lines
6.8 KiB
TypeScript

/**
* Project Store
* Manages project state and operations
*/
import { create } from 'zustand'
import { ProjectInfo, VideoTrack, AudioTrack } from '../services/tauri'
import { ProjectService } from '../services/tauri'
interface ProjectState {
// Current project
currentProject: ProjectInfo | null
// Project list
projects: ProjectInfo[]
// Loading states
isLoading: boolean
isSaving: boolean
// Error state
error: string | null
// Actions
createProject: (name: string, description?: string) => Promise<void>
loadProject: (projectPath: string) => Promise<void>
saveProject: () => Promise<void>
closeProject: () => void
// Track management
addVideoTrack: (track: VideoTrack) => void
addAudioTrack: (track: AudioTrack) => void
removeVideoTrack: (trackId: string) => void
removeAudioTrack: (trackId: string) => void
updateVideoTrack: (trackId: string, updates: Partial<VideoTrack>) => void
updateAudioTrack: (trackId: string, updates: Partial<AudioTrack>) => void
// Timeline management
setTimelinePosition: (position: number) => void
setTimelineZoom: (zoom: number) => void
setTimelineDuration: (duration: number) => void
// Utility actions
setError: (error: string | null) => void
clearError: () => void
}
export const useProjectStore = create<ProjectState>((set, get) => ({
// Initial state
currentProject: null,
projects: [],
isLoading: false,
isSaving: false,
error: null,
// Project operations
createProject: async (name: string, description = '') => {
set({ isLoading: true, error: null })
try {
const project = await ProjectService.createProject(name, description)
set({
currentProject: project,
isLoading: false
})
} catch (error) {
set({
error: error instanceof Error ? error.message : 'Failed to create project',
isLoading: false
})
}
},
loadProject: async (projectPath: string) => {
set({ isLoading: true, error: null })
try {
const project = await ProjectService.loadProject(projectPath)
set({
currentProject: project,
isLoading: false
})
} catch (error) {
set({
error: error instanceof Error ? error.message : 'Failed to load project',
isLoading: false
})
}
},
saveProject: async () => {
const { currentProject } = get()
if (!currentProject) return
set({ isSaving: true, error: null })
try {
await ProjectService.saveProject(currentProject)
set({
currentProject: {
...currentProject,
modified_at: new Date().toISOString()
},
isSaving: false
})
} catch (error) {
set({
error: error instanceof Error ? error.message : 'Failed to save project',
isSaving: false
})
}
},
closeProject: () => {
set({ currentProject: null })
},
// Track management
addVideoTrack: (track: VideoTrack) => {
const { currentProject } = get()
if (!currentProject) return
const updatedProject = {
...currentProject,
video_tracks: [...currentProject.video_tracks, track],
modified_at: new Date().toISOString()
}
set({ currentProject: updatedProject })
},
addAudioTrack: (track: AudioTrack) => {
const { currentProject } = get()
if (!currentProject) return
const updatedProject = {
...currentProject,
audio_tracks: [...currentProject.audio_tracks, track],
modified_at: new Date().toISOString()
}
set({ currentProject: updatedProject })
},
removeVideoTrack: (trackId: string) => {
const { currentProject } = get()
if (!currentProject) return
const updatedProject = {
...currentProject,
video_tracks: currentProject.video_tracks.filter(track => track.id !== trackId),
modified_at: new Date().toISOString()
}
set({ currentProject: updatedProject })
},
removeAudioTrack: (trackId: string) => {
const { currentProject } = get()
if (!currentProject) return
const updatedProject = {
...currentProject,
audio_tracks: currentProject.audio_tracks.filter(track => track.id !== trackId),
modified_at: new Date().toISOString()
}
set({ currentProject: updatedProject })
},
updateVideoTrack: (trackId: string, updates: Partial<VideoTrack>) => {
const { currentProject } = get()
if (!currentProject) return
const updatedProject = {
...currentProject,
video_tracks: currentProject.video_tracks.map(track =>
track.id === trackId ? { ...track, ...updates } : track
),
modified_at: new Date().toISOString()
}
set({ currentProject: updatedProject })
},
updateAudioTrack: (trackId: string, updates: Partial<AudioTrack>) => {
const { currentProject } = get()
if (!currentProject) return
const updatedProject = {
...currentProject,
audio_tracks: currentProject.audio_tracks.map(track =>
track.id === trackId ? { ...track, ...updates } : track
),
modified_at: new Date().toISOString()
}
set({ currentProject: updatedProject })
},
// Timeline management
setTimelinePosition: (position: number) => {
const { currentProject } = get()
if (!currentProject) return
const updatedProject = {
...currentProject,
timeline: {
...currentProject.timeline,
position
}
}
set({ currentProject: updatedProject })
},
setTimelineZoom: (zoom: number) => {
const { currentProject } = get()
if (!currentProject) return
const updatedProject = {
...currentProject,
timeline: {
...currentProject.timeline,
zoom
}
}
set({ currentProject: updatedProject })
},
setTimelineDuration: (duration: number) => {
const { currentProject } = get()
if (!currentProject) return
const updatedProject = {
...currentProject,
timeline: {
...currentProject.timeline,
duration
}
}
set({ currentProject: updatedProject })
},
// Utility actions
setError: (error: string | null) => {
set({ error })
},
clearError: () => {
set({ error: null })
}
}))
// Selectors for easier access to specific state
export const useCurrentProject = () => useProjectStore(state => state.currentProject)
export const useProjectLoading = () => useProjectStore(state => state.isLoading)
export const useProjectSaving = () => useProjectStore(state => state.isSaving)
export const useProjectError = () => useProjectStore(state => state.error)
export const useVideoTracks = () => useProjectStore(state => state.currentProject?.video_tracks || [])
export const useAudioTracks = () => useProjectStore(state => state.currentProject?.audio_tracks || [])
export const useTimeline = () => useProjectStore(state => state.currentProject?.timeline || { duration: 0, zoom: 1, position: 0 })