Skip to content

Commit 23a9e23

Browse files
committed
clean up persisted file cache handling and dragger state
1 parent a6bbce9 commit 23a9e23

File tree

3 files changed

+231
-73
lines changed

3 files changed

+231
-73
lines changed

client/src/App.js

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,44 @@
1-
import { useEffect, useState } from 'react'
2-
import localforage from 'localforage'
1+
import { useContext, useEffect, useState } from 'react'
32
import './App.css'
43
import Views from './views/Views'
5-
import { ContextWrapper } from './contexts/GlobalContext'
4+
import { AppContext, ContextWrapper } from './contexts/GlobalContext'
65
import { YamlContextWrapper } from './contexts/YamlContext'
76

8-
const FILE_CACHE_KEYS = [
9-
'files',
10-
'fileList',
11-
'imageFileList',
12-
'labelFileList',
13-
'currentImage',
14-
'currentLabel',
15-
'inputImage',
16-
'inputLabel'
17-
]
18-
19-
function App () {
7+
function CacheBootstrapper ({ children }) {
8+
const { resetFileState } = useContext(AppContext)
209
const [isCacheCleared, setIsCacheCleared] = useState(false)
2110

2211
useEffect(() => {
23-
const clearFileCache = async () => {
24-
try {
25-
await Promise.all(
26-
FILE_CACHE_KEYS.map((key) => localforage.removeItem(key))
27-
)
28-
} catch (error) {
29-
console.error('Failed to clear file cache on startup:', error)
30-
} finally {
12+
let isMounted = true
13+
const clearCache = async () => {
14+
await resetFileState()
15+
if (isMounted) {
3116
setIsCacheCleared(true)
3217
}
3318
}
3419

35-
clearFileCache()
36-
}, [])
20+
clearCache()
21+
return () => {
22+
isMounted = false
23+
}
24+
}, [resetFileState])
3725

3826
if (!isCacheCleared) {
3927
return null
4028
}
4129

30+
return children
31+
}
32+
33+
function App () {
4234
return (
4335
<ContextWrapper>
4436
<YamlContextWrapper>
45-
<div className='App'>
46-
<Views />
47-
</div>
37+
<CacheBootstrapper>
38+
<div className='App'>
39+
<Views />
40+
</div>
41+
</CacheBootstrapper>
4842
</YamlContextWrapper>
4943
</ContextWrapper>
5044
)

client/src/components/Dragger.js

Lines changed: 114 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// global FileReader
2-
import React, { useContext, useState, useEffect } from 'react'
2+
import React, { useContext, useState, useEffect, useRef, useCallback, useMemo } from 'react'
33
import { Button, Input, message, Modal, Space, Upload } from 'antd'
44
import { InboxOutlined } from '@ant-design/icons'
55
import { AppContext } from '../contexts/GlobalContext'
@@ -34,12 +34,52 @@ const enrichFileMetadata = (uploadFile) => {
3434
if (folderPath) {
3535
enhancedFile.folderPath = folderPath
3636
}
37+
if (!enhancedFile.originalName) {
38+
const derivedOriginalName =
39+
uploadFile?.originFileObj?.name ||
40+
uploadFile?.name ||
41+
enhancedFile.name
42+
if (derivedOriginalName) {
43+
enhancedFile.originalName = derivedOriginalName
44+
}
45+
}
3746
return enhancedFile
3847
}
3948

4049
export function Dragger () {
4150
const context = useContext(AppContext)
42-
const { setFiles, files } = context
51+
const {
52+
setFiles,
53+
files,
54+
setFileList,
55+
setImageFileList,
56+
setLabelFileList,
57+
resetFileState
58+
} = context
59+
const objectUrlMapRef = useRef(new Map())
60+
61+
const revokeObjectUrl = useCallback((uid) => {
62+
const url = objectUrlMapRef.current.get(uid)
63+
if (url) {
64+
URL.revokeObjectURL(url)
65+
objectUrlMapRef.current.delete(uid)
66+
}
67+
}, [])
68+
69+
const revokeAllObjectUrls = useCallback(() => {
70+
objectUrlMapRef.current.forEach((url) => URL.revokeObjectURL(url))
71+
objectUrlMapRef.current.clear()
72+
}, [])
73+
74+
const filesByUid = useMemo(() => {
75+
const map = new Map()
76+
if (Array.isArray(files)) {
77+
files.forEach((file) => {
78+
map.set(file.uid, file)
79+
})
80+
}
81+
return map
82+
}, [files])
4383

4484
// const getBase64 = (file) =>
4585
// new Promise((resolve, reject) => {
@@ -64,9 +104,11 @@ export function Dragger () {
64104
} else if (status === 'error') {
65105
console.log('error')
66106
message.error(`${info.file.name} file upload failed.`)
107+
revokeObjectUrl(info.file.uid)
67108
} else if (status === 'removed') {
68109
console.log(info.fileList)
69110
setFiles(info.fileList.map(enrichFileMetadata))
111+
revokeObjectUrl(info.file.uid)
70112
}
71113
}
72114

@@ -114,49 +156,86 @@ export function Dragger () {
114156
const fetchFile = async (file) => {
115157
try {
116158
if (fileType === 'Label') {
117-
context.setLabelFileList((prevLabelList) => [...prevLabelList, file])
159+
setLabelFileList((prevLabelList) => [...prevLabelList, file])
118160
} else if (fileType === 'Image') {
119-
context.setImageFileList((prevImageList) => [...prevImageList, file])
161+
setImageFileList((prevImageList) => [...prevImageList, file])
120162
}
121163
} catch (error) {
122164
console.error(error)
123165
}
124166
}
125167

126-
const handleSubmit = (type) => {
127-
console.log('submitting path', previewFileFolderPath)
168+
const handleSubmit = () => {
169+
if (!fileUID) return
170+
const targetFile = filesByUid.get(fileUID)
171+
if (!targetFile) return
172+
173+
const updates = {}
128174
if (previewFileFolderPath !== '') {
129-
context.files.find(
130-
(targetFile) => targetFile.uid === fileUID
131-
).folderPath = previewFileFolderPath
175+
updates.folderPath = previewFileFolderPath
132176
setPreviewFileFolderPath('')
133177
}
134178
if (value !== '') {
135-
context.files.find((targetFile) => targetFile.uid === fileUID).name =
136-
value
137-
context.fileList.find(
138-
(targetFile) => targetFile.value === fileUID
139-
).label = value
179+
updates.name = value
140180
setValue('')
141181
}
142-
fetchFile(context.files.find((targetFile) => targetFile.uid === fileUID))
182+
183+
const updatedFile = Object.keys(updates).length > 0
184+
? { ...targetFile, ...updates }
185+
: targetFile
186+
187+
if (Object.keys(updates).length > 0) {
188+
setFiles((prevFiles) =>
189+
prevFiles.map((file) =>
190+
file.uid === fileUID ? { ...file, ...updates } : file
191+
)
192+
)
193+
194+
if (updates.name) {
195+
setFileList((prevList) =>
196+
prevList.map((entry) =>
197+
entry.value === fileUID ? { ...entry, label: updates.name } : entry
198+
)
199+
)
200+
}
201+
}
202+
203+
fetchFile(updatedFile)
143204
setPreviewOpen(false)
144205
}
145206

146207
const handleClearCache = async () => {
147-
context.setFileList([])
148-
context.setImageFileList([])
149-
context.setLabelFileList([])
150-
message.success('File list cleared successfully.')
208+
try {
209+
revokeAllObjectUrls()
210+
await resetFileState()
211+
message.success('File cache cleared successfully.')
212+
} catch (error) {
213+
console.error('Failed to clear file cache:', error)
214+
message.error('Failed to clear file cache.')
215+
}
151216
}
152217

153218
const handleRevert = () => {
154-
const oldName = context.files.find((targetFile) => targetFile.uid === fileUID)
155-
.originFileObj.name
156-
context.files.find((targetFile) => targetFile.uid === fileUID).name =
157-
oldName
158-
context.fileList.find((targetFile) => targetFile.value === fileUID).label =
159-
oldName
219+
if (!fileUID) {
220+
setPreviewOpen(false)
221+
return
222+
}
223+
const targetFile = filesByUid.get(fileUID)
224+
const oldName = targetFile?.originFileObj?.name || targetFile?.originalName
225+
if (!oldName) {
226+
setPreviewOpen(false)
227+
return
228+
}
229+
setFiles((prevFiles) =>
230+
prevFiles.map((file) =>
231+
file.uid === fileUID ? { ...file, name: oldName } : file
232+
)
233+
)
234+
setFileList((prevList) =>
235+
prevList.map((entry) =>
236+
entry.value === fileUID ? { ...entry, label: oldName } : entry
237+
)
238+
)
160239
setPreviewOpen(false)
161240
}
162241

@@ -214,13 +293,9 @@ export function Dragger () {
214293
setPreviewOpen(true)
215294
setPreviewImage(file.thumbUrl)
216295
setPreviewTitle(file.name || file.url.substring(file.url.lastIndexOf('/') + 1))
217-
if (
218-
context.files.find(targetFile => targetFile.uid === file.uid) &&
219-
context.files.find(targetFile => targetFile.uid === file.uid).folderPath) {
220-
setPreviewFileFolderPath(
221-
context.files.find(targetFile => targetFile.uid === file.uid)
222-
.folderPath
223-
)
296+
const targetFile = filesByUid.get(file.uid)
297+
if (targetFile?.folderPath) {
298+
setPreviewFileFolderPath(targetFile.folderPath)
224299
} else {
225300
const originPath =
226301
file?.originFileObj?.path ||
@@ -231,22 +306,12 @@ export function Dragger () {
231306
}
232307
}
233308

234-
// Solved the "clear the cache" button for image loading is not reachable when the image preview files are loaded.
235309
const listItemStyle = {
236310
display: 'inline-block',
237311
width: '185px',
238312
height: 'auto',
239313
verticalAlign: 'top'
240314
}
241-
useEffect(() => {
242-
// Get all elements with the class name "ant-upload-list-item-container"
243-
const uploadListItemContainers = document.querySelectorAll('.ant-upload-list-item-container')
244-
245-
// Apply styles to each element
246-
uploadListItemContainers.forEach((element) => {
247-
Object.assign(element.style, listItemStyle)
248-
})
249-
})
250315

251316
// when click or drag file to this area to upload, below function will be deployed.
252317
const handleBeforeUpload = (file) => {
@@ -262,10 +327,17 @@ export function Dragger () {
262327
})
263328
} else {
264329
file.thumbUrl = URL.createObjectURL(file)
330+
objectUrlMapRef.current.set(file.uid, file.thumbUrl)
265331
}
266332
return true // Allow the upload
267333
}
268334

335+
useEffect(() => {
336+
return () => {
337+
revokeAllObjectUrls()
338+
}
339+
}, [revokeAllObjectUrls])
340+
269341
return (
270342
<>
271343
<Upload.Dragger

0 commit comments

Comments
 (0)