TypeScript · 7064 bytes Raw Blame History
1 import { defineStore } from 'pinia'
2 import { ref, computed } from 'vue'
3 import { apiClient } from '@/services/api'
4 import type { DirectoryListing, FileItem, UploadResponse } from '@shared/types'
5
6 interface UploadProgress {
7 fileId: string
8 filename: string
9 progress: number
10 status: 'uploading' | 'completed' | 'error'
11 error?: string
12 }
13
14 export const useFilesStore = defineStore('files', () => {
15 // State
16 const currentListing = ref<DirectoryListing | null>(null)
17 const currentPath = ref<string>('/')
18 const loading = ref(false)
19 const error = ref<string | null>(null)
20 const uploads = ref<Map<string, UploadProgress>>(new Map())
21 const selectedFiles = ref<Set<string>>(new Set())
22 const viewMode = ref<'grid' | 'list'>('grid')
23 const sortBy = ref<'name' | 'size' | 'date'>('name')
24 const sortOrder = ref<'asc' | 'desc'>('asc')
25
26 // Getters
27 const sortedFiles = computed(() => {
28 if (!currentListing.value) return []
29
30 const files = [...currentListing.value.files]
31
32 files.sort((a, b) => {
33 let comparison = 0
34
35 switch (sortBy.value) {
36 case 'name':
37 comparison = a.name.localeCompare(b.name)
38 break
39 case 'size':
40 comparison = a.size - b.size
41 break
42 case 'date':
43 comparison = new Date(a.lastModified).getTime() - new Date(b.lastModified).getTime()
44 break
45 }
46
47 return sortOrder.value === 'asc' ? comparison : -comparison
48 })
49
50 // Always put directories first
51 return files.sort((a, b) => {
52 if (a.type === 'directory' && b.type === 'file') return -1
53 if (a.type === 'file' && b.type === 'directory') return 1
54 return 0
55 })
56 })
57
58 const activeUploads = computed(() =>
59 Array.from(uploads.value.values()).filter(upload => upload.status === 'uploading')
60 )
61
62 const hasSelection = computed(() => selectedFiles.value.size > 0)
63
64 // Actions
65 async function loadDirectory(path: string = '/'): Promise<void> {
66 loading.value = true
67 error.value = null
68
69 try {
70 currentListing.value = await apiClient.listFiles(path)
71 currentPath.value = path
72 } catch (err: any) {
73 error.value = err.response?.data?.message || 'Failed to load directory'
74 throw err
75 } finally {
76 loading.value = false
77 }
78 }
79
80 async function uploadFiles(
81 files: File[],
82 path: string = currentPath.value,
83 options: { encrypted?: boolean } = {}
84 ): Promise<void> {
85 const uploadPromises = files.map(file => uploadFile(file, path, options))
86 await Promise.allSettled(uploadPromises)
87 }
88
89 async function uploadFile(
90 file: File,
91 path: string = currentPath.value,
92 options: { encrypted?: boolean } = {}
93 ): Promise<UploadResponse> {
94 const uploadId = crypto.randomUUID()
95
96 // Add to uploads tracking
97 uploads.value.set(uploadId, {
98 fileId: uploadId,
99 filename: file.name,
100 progress: 0,
101 status: 'uploading',
102 })
103
104 try {
105 const result = await apiClient.uploadFile(file, path, {
106 ...options,
107 onProgress: (progress) => {
108 const upload = uploads.value.get(uploadId)
109 if (upload) {
110 upload.progress = progress
111 }
112 },
113 })
114
115 // Mark as completed
116 const upload = uploads.value.get(uploadId)
117 if (upload) {
118 upload.status = 'completed'
119 upload.fileId = result.fileId
120 }
121
122 // Refresh directory listing if we're in the same path
123 if (path === currentPath.value) {
124 await loadDirectory(currentPath.value)
125 }
126
127 return result
128 } catch (err: any) {
129 // Mark as error
130 const upload = uploads.value.get(uploadId)
131 if (upload) {
132 upload.status = 'error'
133 upload.error = err.response?.data?.message || 'Upload failed'
134 }
135 throw err
136 }
137 }
138
139 async function downloadFile(file: FileItem): Promise<void> {
140 try {
141 const blob = await apiClient.downloadFile(file.id)
142
143 // Create download link
144 const url = window.URL.createObjectURL(blob)
145 const link = document.createElement('a')
146 link.href = url
147 link.download = file.name
148 document.body.appendChild(link)
149 link.click()
150 document.body.removeChild(link)
151 window.URL.revokeObjectURL(url)
152 } catch (err: any) {
153 error.value = err.response?.data?.message || 'Download failed'
154 throw err
155 }
156 }
157
158 async function deleteFile(fileId: string): Promise<void> {
159 try {
160 await apiClient.deleteFile(fileId)
161
162 // Refresh directory listing
163 await loadDirectory(currentPath.value)
164 } catch (err: any) {
165 error.value = err.response?.data?.message || 'Delete failed'
166 throw err
167 }
168 }
169
170 async function deleteSelectedFiles(): Promise<void> {
171 if (!hasSelection.value) return
172
173 const deletePromises = Array.from(selectedFiles.value).map(fileId =>
174 apiClient.deleteFile(fileId)
175 )
176
177 try {
178 await Promise.allSettled(deletePromises)
179 clearSelection()
180 await loadDirectory(currentPath.value)
181 } catch (err: any) {
182 error.value = err.response?.data?.message || 'Delete failed'
183 throw err
184 }
185 }
186
187 function toggleFileSelection(fileId: string): void {
188 if (selectedFiles.value.has(fileId)) {
189 selectedFiles.value.delete(fileId)
190 } else {
191 selectedFiles.value.add(fileId)
192 }
193 }
194
195 function selectAllFiles(): void {
196 if (!currentListing.value) return
197
198 currentListing.value.files.forEach(file => {
199 selectedFiles.value.add(file.id)
200 })
201 }
202
203 function clearSelection(): void {
204 selectedFiles.value.clear()
205 }
206
207 function removeUpload(uploadId: string): void {
208 uploads.value.delete(uploadId)
209 }
210
211 function clearCompletedUploads(): void {
212 for (const [id, upload] of uploads.value.entries()) {
213 if (upload.status === 'completed' || upload.status === 'error') {
214 uploads.value.delete(id)
215 }
216 }
217 }
218
219 function setViewMode(mode: 'grid' | 'list'): void {
220 viewMode.value = mode
221 localStorage.setItem('files_view_mode', mode)
222 }
223
224 function setSortBy(field: 'name' | 'size' | 'date'): void {
225 if (sortBy.value === field) {
226 sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc'
227 } else {
228 sortBy.value = field
229 sortOrder.value = 'asc'
230 }
231 }
232
233 function clearError(): void {
234 error.value = null
235 }
236
237 // Initialize view mode from localStorage
238 const savedViewMode = localStorage.getItem('files_view_mode') as 'grid' | 'list'
239 if (savedViewMode) {
240 viewMode.value = savedViewMode
241 }
242
243 return {
244 // State
245 currentListing,
246 currentPath,
247 loading,
248 error,
249 uploads,
250 selectedFiles,
251 viewMode,
252 sortBy,
253 sortOrder,
254
255 // Getters
256 sortedFiles,
257 activeUploads,
258 hasSelection,
259
260 // Actions
261 loadDirectory,
262 uploadFiles,
263 uploadFile,
264 downloadFile,
265 deleteFile,
266 deleteSelectedFiles,
267 toggleFileSelection,
268 selectAllFiles,
269 clearSelection,
270 removeUpload,
271 clearCompletedUploads,
272 setViewMode,
273 setSortBy,
274 clearError,
275 }
276 })