@stoker-platform/web-app 0.5.140 → 0.5.142
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/src/Files.tsx +280 -71
- package/src/utils/prepareFile.ts +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @stoker-platform/web-app
|
|
2
2
|
|
|
3
|
+
## 0.5.142
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- feat: add bulk file rename feature
|
|
8
|
+
|
|
9
|
+
## 0.5.141
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- fix: apply maxImageWidth processing to image file rename operation
|
|
14
|
+
|
|
3
15
|
## 0.5.140
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/package.json
CHANGED
package/src/Files.tsx
CHANGED
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
Edit,
|
|
39
39
|
Trash2,
|
|
40
40
|
Folder,
|
|
41
|
-
File,
|
|
41
|
+
File as FileIcon,
|
|
42
42
|
ChevronRight,
|
|
43
43
|
ArrowLeft,
|
|
44
44
|
Plus,
|
|
@@ -135,7 +135,7 @@ const FileImageThumbnail = ({ storage, fullPath, fileName, pathPrefix }: FileIma
|
|
|
135
135
|
return <div className="h-12 w-12 shrink-0 rounded-md border bg-muted animate-pulse" aria-hidden />
|
|
136
136
|
}
|
|
137
137
|
if (phase === "fallback" || !url) {
|
|
138
|
-
return <
|
|
138
|
+
return <FileIcon className="h-5 w-5 shrink-0 text-gray-500" aria-hidden />
|
|
139
139
|
}
|
|
140
140
|
return (
|
|
141
141
|
<div className="h-12 w-12 shrink-0 rounded-md flex items-center justify-center">
|
|
@@ -178,10 +178,15 @@ export const RecordFiles = ({ collection, record }: FilesProps) => {
|
|
|
178
178
|
const [loading, setLoading] = useState(false)
|
|
179
179
|
const [editingFile, setEditingFile] = useState<string | null>(null)
|
|
180
180
|
const [newFileName, setNewFileName] = useState("")
|
|
181
|
+
const [bulkRenameMode, setBulkRenameMode] = useState(false)
|
|
182
|
+
const [bulkRenameNames, setBulkRenameNames] = useState<Record<string, string>>({})
|
|
183
|
+
const [bulkRenameInProgress, setBulkRenameInProgress] = useState(false)
|
|
181
184
|
const [creatingFolder, setCreatingFolder] = useState(false)
|
|
182
185
|
const [newFolderName, setNewFolderName] = useState("")
|
|
183
186
|
const [deletingFiles, setDeletingFiles] = useState<Set<string>>(new Set())
|
|
184
187
|
const [renamingFiles, setRenamingFiles] = useState<Set<string>>(new Set())
|
|
188
|
+
const [updatingPermissions, setUpdatingPermissions] = useState<Set<string>>(new Set())
|
|
189
|
+
const pendingFileOpsRef = useRef(0)
|
|
185
190
|
const { setIsRouteLoading } = useRouteLoading()
|
|
186
191
|
|
|
187
192
|
const [showFilenameDialog, setShowFilenameDialog] = useState(false)
|
|
@@ -213,6 +218,56 @@ export const RecordFiles = ({ collection, record }: FilesProps) => {
|
|
|
213
218
|
}, 150)
|
|
214
219
|
}, [])
|
|
215
220
|
|
|
221
|
+
const beginFileOperation = useCallback(() => {
|
|
222
|
+
if (pendingFileOpsRef.current === 0) {
|
|
223
|
+
setIsRouteLoading("+", location.pathname, true)
|
|
224
|
+
}
|
|
225
|
+
pendingFileOpsRef.current++
|
|
226
|
+
}, [location.pathname, setIsRouteLoading])
|
|
227
|
+
|
|
228
|
+
const endFileOperation = useCallback(() => {
|
|
229
|
+
pendingFileOpsRef.current = Math.max(0, pendingFileOpsRef.current - 1)
|
|
230
|
+
if (pendingFileOpsRef.current === 0) {
|
|
231
|
+
setIsRouteLoading("-", location.pathname)
|
|
232
|
+
}
|
|
233
|
+
}, [location.pathname, setIsRouteLoading])
|
|
234
|
+
|
|
235
|
+
const finishDeleteOperation = useCallback(
|
|
236
|
+
(fileName: string) => {
|
|
237
|
+
setDeletingFiles((prev) => {
|
|
238
|
+
const next = new Set(prev)
|
|
239
|
+
next.delete(fileName)
|
|
240
|
+
return next
|
|
241
|
+
})
|
|
242
|
+
endFileOperation()
|
|
243
|
+
},
|
|
244
|
+
[endFileOperation],
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
const finishRenameOperation = useCallback(
|
|
248
|
+
(fileName: string) => {
|
|
249
|
+
setRenamingFiles((prev) => {
|
|
250
|
+
const next = new Set(prev)
|
|
251
|
+
next.delete(fileName)
|
|
252
|
+
return next
|
|
253
|
+
})
|
|
254
|
+
endFileOperation()
|
|
255
|
+
},
|
|
256
|
+
[endFileOperation],
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
const finishPermissionsOperation = useCallback(
|
|
260
|
+
(fileName: string) => {
|
|
261
|
+
setUpdatingPermissions((prev) => {
|
|
262
|
+
const next = new Set(prev)
|
|
263
|
+
next.delete(fileName)
|
|
264
|
+
return next
|
|
265
|
+
})
|
|
266
|
+
endFileOperation()
|
|
267
|
+
},
|
|
268
|
+
[endFileOperation],
|
|
269
|
+
)
|
|
270
|
+
|
|
216
271
|
const basePath = record ? `${tenantId}/${record.Collection_Path.join("/")}/${record.id}` : ""
|
|
217
272
|
|
|
218
273
|
const totalPages = Math.ceil(items.length / itemsPerPage)
|
|
@@ -224,6 +279,12 @@ export const RecordFiles = ({ collection, record }: FilesProps) => {
|
|
|
224
279
|
setCurrentPage(1)
|
|
225
280
|
}, [items.length])
|
|
226
281
|
|
|
282
|
+
useEffect(() => {
|
|
283
|
+
setBulkRenameMode(false)
|
|
284
|
+
setBulkRenameNames({})
|
|
285
|
+
setBulkRenameInProgress(false)
|
|
286
|
+
}, [currentPath])
|
|
287
|
+
|
|
227
288
|
useEffect(() => {
|
|
228
289
|
const interval = setInterval(() => {
|
|
229
290
|
setUploadProgress((prev) =>
|
|
@@ -572,8 +633,11 @@ export const RecordFiles = ({ collection, record }: FilesProps) => {
|
|
|
572
633
|
async (permissions: FilePermissions) => {
|
|
573
634
|
if (!selectedFileForPermissions || !record || !currentUser) return
|
|
574
635
|
|
|
636
|
+
const fileName = selectedFileForPermissions.name
|
|
637
|
+
|
|
575
638
|
try {
|
|
576
|
-
|
|
639
|
+
beginFileOperation()
|
|
640
|
+
setUpdatingPermissions((prev) => new Set(prev).add(fileName))
|
|
577
641
|
|
|
578
642
|
const targetRef = selectedFileForPermissions.isFolder
|
|
579
643
|
? ref(storage, `${selectedFileForPermissions.fullPath}/.placeholder`)
|
|
@@ -651,12 +715,12 @@ export const RecordFiles = ({ collection, record }: FilesProps) => {
|
|
|
651
715
|
variant: "destructive",
|
|
652
716
|
})
|
|
653
717
|
} finally {
|
|
654
|
-
|
|
718
|
+
finishPermissionsOperation(fileName)
|
|
655
719
|
setShowUpdatePermissionsDialog(false)
|
|
656
720
|
setSelectedFileForPermissions(null)
|
|
657
721
|
}
|
|
658
722
|
},
|
|
659
|
-
[selectedFileForPermissions, record,
|
|
723
|
+
[selectedFileForPermissions, record, currentPath, currentUser, beginFileOperation, finishPermissionsOperation],
|
|
660
724
|
)
|
|
661
725
|
|
|
662
726
|
const handleUpdatePermissionsCancel = useCallback(() => {
|
|
@@ -730,7 +794,7 @@ export const RecordFiles = ({ collection, record }: FilesProps) => {
|
|
|
730
794
|
if (!itemToDelete || !record) return
|
|
731
795
|
|
|
732
796
|
try {
|
|
733
|
-
|
|
797
|
+
beginFileOperation()
|
|
734
798
|
setDeletingFiles((prev) => new Set(prev).add(itemToDelete.name))
|
|
735
799
|
|
|
736
800
|
if (itemToDelete.isFolder) {
|
|
@@ -757,16 +821,11 @@ export const RecordFiles = ({ collection, record }: FilesProps) => {
|
|
|
757
821
|
variant: "destructive",
|
|
758
822
|
})
|
|
759
823
|
} finally {
|
|
760
|
-
|
|
761
|
-
const newSet = new Set(prev)
|
|
762
|
-
newSet.delete(itemToDelete.name)
|
|
763
|
-
return newSet
|
|
764
|
-
})
|
|
765
|
-
setIsRouteLoading("-", location.pathname)
|
|
824
|
+
finishDeleteOperation(itemToDelete.name)
|
|
766
825
|
setShowDeleteDialog(false)
|
|
767
826
|
setItemToDelete(null)
|
|
768
827
|
}
|
|
769
|
-
}, [itemToDelete, currentPath,
|
|
828
|
+
}, [itemToDelete, currentPath, record, beginFileOperation, finishDeleteOperation])
|
|
770
829
|
|
|
771
830
|
const handleDeleteCancel = useCallback(() => {
|
|
772
831
|
setShowDeleteDialog(false)
|
|
@@ -774,30 +833,56 @@ export const RecordFiles = ({ collection, record }: FilesProps) => {
|
|
|
774
833
|
}, [])
|
|
775
834
|
|
|
776
835
|
const handleEditName = useCallback(
|
|
777
|
-
async (item: StorageItem, newName: string) => {
|
|
836
|
+
async (item: StorageItem, newName: string, options?: { skipReload?: boolean }) => {
|
|
778
837
|
if (!record) return
|
|
779
838
|
if (newName === item.name) {
|
|
780
839
|
setEditingFile(null)
|
|
781
840
|
return
|
|
782
841
|
}
|
|
783
842
|
|
|
784
|
-
|
|
843
|
+
let finalName = newName.trim()
|
|
844
|
+
const validationError = validateStorageName(finalName)
|
|
785
845
|
if (validationError) {
|
|
786
846
|
toast({ title: "Invalid file name", description: validationError, variant: "destructive" })
|
|
787
847
|
return
|
|
788
848
|
}
|
|
789
849
|
|
|
790
850
|
try {
|
|
791
|
-
|
|
851
|
+
beginFileOperation()
|
|
792
852
|
setRenamingFiles((prev) => new Set(prev).add(item.name))
|
|
793
|
-
const pathParts = item.fullPath.split("/")
|
|
794
|
-
pathParts.pop()
|
|
795
|
-
const newPath = [...pathParts, newName].join("/")
|
|
796
853
|
|
|
797
854
|
const originalRef = ref(storage, item.fullPath)
|
|
798
855
|
const downloadURL = await getDownloadURL(originalRef)
|
|
799
856
|
const metadata = await getMetadata(originalRef)
|
|
800
857
|
|
|
858
|
+
const response = await fetch(downloadURL)
|
|
859
|
+
const blob = await response.blob()
|
|
860
|
+
|
|
861
|
+
let uploadContent: Blob = blob
|
|
862
|
+
if (fileOptions?.maxImageWidth) {
|
|
863
|
+
const sourceFile = new File([blob], finalName, {
|
|
864
|
+
type: blob.type || metadata.contentType || "",
|
|
865
|
+
})
|
|
866
|
+
const prepared = await prepareFile(sourceFile, finalName, fileOptions, true)
|
|
867
|
+
uploadContent = prepared.file
|
|
868
|
+
if (prepared.filename !== finalName) {
|
|
869
|
+
const preparedValidationError = validateStorageName(prepared.filename)
|
|
870
|
+
if (preparedValidationError) {
|
|
871
|
+
toast({
|
|
872
|
+
title: "Invalid file name",
|
|
873
|
+
description: preparedValidationError,
|
|
874
|
+
variant: "destructive",
|
|
875
|
+
})
|
|
876
|
+
return
|
|
877
|
+
}
|
|
878
|
+
finalName = prepared.filename
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
const pathParts = item.fullPath.split("/")
|
|
883
|
+
pathParts.pop()
|
|
884
|
+
const newPath = [...pathParts, finalName].join("/")
|
|
885
|
+
|
|
801
886
|
try {
|
|
802
887
|
await runHooks("preFileUpdate", globalConfig, customization, {
|
|
803
888
|
record,
|
|
@@ -807,17 +892,14 @@ export const RecordFiles = ({ collection, record }: FilesProps) => {
|
|
|
807
892
|
return
|
|
808
893
|
}
|
|
809
894
|
|
|
810
|
-
const response = await fetch(downloadURL)
|
|
811
|
-
const blob = await response.blob()
|
|
812
|
-
|
|
813
895
|
const newRef = ref(storage, newPath)
|
|
814
|
-
await uploadBytesResumable(newRef,
|
|
896
|
+
await uploadBytesResumable(newRef, uploadContent, { customMetadata: metadata.customMetadata })
|
|
815
897
|
|
|
816
898
|
await deleteObject(originalRef)
|
|
817
899
|
|
|
818
900
|
toast({
|
|
819
901
|
title: "File renamed",
|
|
820
|
-
description: `${item.name} has been renamed to ${
|
|
902
|
+
description: `${item.name} has been renamed to ${finalName}`,
|
|
821
903
|
})
|
|
822
904
|
|
|
823
905
|
try {
|
|
@@ -831,7 +913,9 @@ export const RecordFiles = ({ collection, record }: FilesProps) => {
|
|
|
831
913
|
|
|
832
914
|
setEditingFile(null)
|
|
833
915
|
|
|
834
|
-
|
|
916
|
+
if (!options?.skipReload) {
|
|
917
|
+
loadDirectory(currentPath)
|
|
918
|
+
}
|
|
835
919
|
} catch (error) {
|
|
836
920
|
console.error((error as FirebaseError).message)
|
|
837
921
|
toast({
|
|
@@ -840,33 +924,29 @@ export const RecordFiles = ({ collection, record }: FilesProps) => {
|
|
|
840
924
|
variant: "destructive",
|
|
841
925
|
})
|
|
842
926
|
} finally {
|
|
843
|
-
|
|
844
|
-
const newSet = new Set(prev)
|
|
845
|
-
newSet.delete(item.name)
|
|
846
|
-
return newSet
|
|
847
|
-
})
|
|
848
|
-
setIsRouteLoading("-", location.pathname)
|
|
927
|
+
finishRenameOperation(item.name)
|
|
849
928
|
}
|
|
850
929
|
},
|
|
851
|
-
[currentPath],
|
|
930
|
+
[currentPath, fileOptions, beginFileOperation, finishRenameOperation, loadDirectory],
|
|
852
931
|
)
|
|
853
932
|
|
|
854
933
|
const navigateToFolder = useCallback(
|
|
855
934
|
(folderName: string) => {
|
|
935
|
+
if (bulkRenameInProgress) return
|
|
856
936
|
const newPath = currentPath ? `${currentPath}/${folderName}` : folderName
|
|
857
937
|
loadDirectory(newPath)
|
|
858
938
|
},
|
|
859
|
-
[currentPath],
|
|
939
|
+
[currentPath, bulkRenameInProgress],
|
|
860
940
|
)
|
|
861
941
|
|
|
862
942
|
const navigateUp = useCallback(() => {
|
|
863
|
-
if (!currentPath) return
|
|
943
|
+
if (!currentPath || bulkRenameInProgress) return
|
|
864
944
|
|
|
865
945
|
const pathParts = currentPath.split("/")
|
|
866
946
|
pathParts.pop()
|
|
867
947
|
const newPath = pathParts.join("/")
|
|
868
948
|
loadDirectory(newPath)
|
|
869
|
-
}, [currentPath])
|
|
949
|
+
}, [currentPath, bulkRenameInProgress])
|
|
870
950
|
|
|
871
951
|
const getPathBreadcrumbs = useCallback(() => {
|
|
872
952
|
if (!currentPath) return []
|
|
@@ -947,6 +1027,59 @@ export const RecordFiles = ({ collection, record }: FilesProps) => {
|
|
|
947
1027
|
setShowFolderPermissionsDialog(false)
|
|
948
1028
|
}, [])
|
|
949
1029
|
|
|
1030
|
+
const canEditFile = useCallback(
|
|
1031
|
+
(item: StorageItem) => {
|
|
1032
|
+
if (item.isFolder) return false
|
|
1033
|
+
return (
|
|
1034
|
+
(permissions?.Role && item.metadata?.update?.includes(permissions.Role)) ||
|
|
1035
|
+
(currentUser && item.metadata?.createdBy === currentUser.uid)
|
|
1036
|
+
)
|
|
1037
|
+
},
|
|
1038
|
+
[permissions, currentUser],
|
|
1039
|
+
)
|
|
1040
|
+
|
|
1041
|
+
const startBulkRename = useCallback(() => {
|
|
1042
|
+
const editableFiles = items.filter(canEditFile)
|
|
1043
|
+
if (editableFiles.length === 0) return
|
|
1044
|
+
setEditingFile(null)
|
|
1045
|
+
setBulkRenameNames(Object.fromEntries(editableFiles.map((item) => [item.name, item.name])))
|
|
1046
|
+
setBulkRenameMode(true)
|
|
1047
|
+
}, [items, canEditFile])
|
|
1048
|
+
|
|
1049
|
+
const exitBulkRename = useCallback(() => {
|
|
1050
|
+
setBulkRenameMode(false)
|
|
1051
|
+
setBulkRenameNames({})
|
|
1052
|
+
setBulkRenameInProgress(false)
|
|
1053
|
+
}, [])
|
|
1054
|
+
|
|
1055
|
+
const cancelBulkRename = useCallback(() => {
|
|
1056
|
+
if (bulkRenameInProgress) return
|
|
1057
|
+
exitBulkRename()
|
|
1058
|
+
}, [bulkRenameInProgress, exitBulkRename])
|
|
1059
|
+
|
|
1060
|
+
const handleRenameAll = useCallback(async () => {
|
|
1061
|
+
const filesToRename = items
|
|
1062
|
+
.filter((item) => canEditFile(item) && bulkRenameNames[item.name] !== item.name)
|
|
1063
|
+
.map((item) => ({ item, newName: bulkRenameNames[item.name] }))
|
|
1064
|
+
|
|
1065
|
+
if (filesToRename.length === 0) {
|
|
1066
|
+
exitBulkRename()
|
|
1067
|
+
return
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
setBulkRenameInProgress(true)
|
|
1071
|
+
try {
|
|
1072
|
+
await Promise.all(
|
|
1073
|
+
filesToRename.map(({ item, newName }) => handleEditName(item, newName, { skipReload: true })),
|
|
1074
|
+
)
|
|
1075
|
+
await loadDirectory(currentPath)
|
|
1076
|
+
} finally {
|
|
1077
|
+
exitBulkRename()
|
|
1078
|
+
}
|
|
1079
|
+
}, [items, bulkRenameNames, canEditFile, handleEditName, exitBulkRename, loadDirectory, currentPath])
|
|
1080
|
+
|
|
1081
|
+
const editableFileCount = items.filter(canEditFile).length
|
|
1082
|
+
|
|
950
1083
|
let borderClass = "border-primary/40"
|
|
951
1084
|
let textClass = "text-primary/50"
|
|
952
1085
|
if (isDragOver) {
|
|
@@ -965,7 +1098,13 @@ export const RecordFiles = ({ collection, record }: FilesProps) => {
|
|
|
965
1098
|
|
|
966
1099
|
<div className="flex items-center space-x-2 mb-4">
|
|
967
1100
|
{currentPath && (
|
|
968
|
-
<Button
|
|
1101
|
+
<Button
|
|
1102
|
+
variant="outline"
|
|
1103
|
+
size="sm"
|
|
1104
|
+
onClick={navigateUp}
|
|
1105
|
+
disabled={bulkRenameInProgress}
|
|
1106
|
+
className="flex items-center space-x-1"
|
|
1107
|
+
>
|
|
969
1108
|
<ArrowLeft className="h-4 w-4" />
|
|
970
1109
|
<span>Back</span>
|
|
971
1110
|
</Button>
|
|
@@ -996,7 +1135,7 @@ export const RecordFiles = ({ collection, record }: FilesProps) => {
|
|
|
996
1135
|
>
|
|
997
1136
|
<div className={cn("text-center font-bold", textClass)}>Drop files here</div>
|
|
998
1137
|
</div>
|
|
999
|
-
<div className="flex items-center space-x-2
|
|
1138
|
+
<div className="flex flex-col gap-2 mt-4 sm:flex-row sm:items-center sm:gap-0 sm:space-x-2">
|
|
1000
1139
|
<input
|
|
1001
1140
|
type="file"
|
|
1002
1141
|
multiple
|
|
@@ -1010,15 +1149,52 @@ export const RecordFiles = ({ collection, record }: FilesProps) => {
|
|
|
1010
1149
|
cursor-pointer"
|
|
1011
1150
|
onChange={handleFileUpload}
|
|
1012
1151
|
/>
|
|
1013
|
-
<
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1152
|
+
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-0 sm:space-x-2 shrink-0">
|
|
1153
|
+
<Button
|
|
1154
|
+
variant="outline"
|
|
1155
|
+
size="sm"
|
|
1156
|
+
onClick={() => setCreatingFolder(true)}
|
|
1157
|
+
disabled={bulkRenameMode}
|
|
1158
|
+
className="flex items-center space-x-1 whitespace-nowrap"
|
|
1159
|
+
>
|
|
1160
|
+
<Plus className="h-4 w-4" />
|
|
1161
|
+
<span>New Folder</span>
|
|
1162
|
+
</Button>
|
|
1163
|
+
{bulkRenameMode ? (
|
|
1164
|
+
<>
|
|
1165
|
+
<Button
|
|
1166
|
+
size="sm"
|
|
1167
|
+
onClick={handleRenameAll}
|
|
1168
|
+
disabled={bulkRenameInProgress}
|
|
1169
|
+
className="flex items-center space-x-1 whitespace-nowrap"
|
|
1170
|
+
>
|
|
1171
|
+
<Edit className="h-4 w-4" />
|
|
1172
|
+
<span>Rename All</span>
|
|
1173
|
+
</Button>
|
|
1174
|
+
<Button
|
|
1175
|
+
variant="outline"
|
|
1176
|
+
size="sm"
|
|
1177
|
+
onClick={cancelBulkRename}
|
|
1178
|
+
disabled={bulkRenameInProgress}
|
|
1179
|
+
className="whitespace-nowrap"
|
|
1180
|
+
>
|
|
1181
|
+
Cancel
|
|
1182
|
+
</Button>
|
|
1183
|
+
</>
|
|
1184
|
+
) : (
|
|
1185
|
+
editableFileCount > 0 && (
|
|
1186
|
+
<Button
|
|
1187
|
+
variant="outline"
|
|
1188
|
+
size="sm"
|
|
1189
|
+
onClick={startBulkRename}
|
|
1190
|
+
className="flex items-center space-x-2 whitespace-nowrap"
|
|
1191
|
+
>
|
|
1192
|
+
<Edit className="h-4 w-4" />
|
|
1193
|
+
<span>Rename Files</span>
|
|
1194
|
+
</Button>
|
|
1195
|
+
)
|
|
1196
|
+
)}
|
|
1197
|
+
</div>
|
|
1022
1198
|
</div>
|
|
1023
1199
|
|
|
1024
1200
|
{uploadProgress.length > 0 && (
|
|
@@ -1107,7 +1283,8 @@ export const RecordFiles = ({ collection, record }: FilesProps) => {
|
|
|
1107
1283
|
{currentItems.map((item, index) => {
|
|
1108
1284
|
const isDeleting = deletingFiles.has(item.name)
|
|
1109
1285
|
const isRenaming = renamingFiles.has(item.name)
|
|
1110
|
-
const
|
|
1286
|
+
const isUpdatingPermissions = updatingPermissions.has(item.name)
|
|
1287
|
+
const isDisabled = isDeleting || isRenaming || isUpdatingPermissions
|
|
1111
1288
|
|
|
1112
1289
|
return (
|
|
1113
1290
|
<div
|
|
@@ -1132,43 +1309,71 @@ export const RecordFiles = ({ collection, record }: FilesProps) => {
|
|
|
1132
1309
|
}
|
|
1133
1310
|
/>
|
|
1134
1311
|
) : (
|
|
1135
|
-
<
|
|
1312
|
+
<FileIcon className="h-5 w-5 shrink-0 text-gray-500" />
|
|
1136
1313
|
)}
|
|
1137
1314
|
|
|
1138
|
-
{
|
|
1315
|
+
{(bulkRenameMode && canEditFile(item)) ||
|
|
1316
|
+
(editingFile === item.name && !isDisabled) ? (
|
|
1139
1317
|
<div className="flex items-center space-x-2 flex-1">
|
|
1140
1318
|
<Input
|
|
1141
|
-
value={
|
|
1142
|
-
|
|
1319
|
+
value={
|
|
1320
|
+
bulkRenameMode
|
|
1321
|
+
? bulkRenameNames[item.name]
|
|
1322
|
+
: newFileName
|
|
1323
|
+
}
|
|
1324
|
+
disabled={bulkRenameMode && bulkRenameInProgress}
|
|
1325
|
+
onChange={(event) => {
|
|
1326
|
+
if (bulkRenameMode) {
|
|
1327
|
+
setBulkRenameNames((prev) => ({
|
|
1328
|
+
...prev,
|
|
1329
|
+
[item.name]: event.target.value,
|
|
1330
|
+
}))
|
|
1331
|
+
} else {
|
|
1332
|
+
setNewFileName(event.target.value)
|
|
1333
|
+
}
|
|
1334
|
+
}}
|
|
1143
1335
|
className="flex-1"
|
|
1144
1336
|
onKeyDown={(e) => {
|
|
1145
|
-
if (
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1337
|
+
if (!bulkRenameMode) {
|
|
1338
|
+
if (e.key === "Enter") {
|
|
1339
|
+
handleEditName(item, newFileName)
|
|
1340
|
+
} else if (e.key === "Escape") {
|
|
1341
|
+
setEditingFile(null)
|
|
1342
|
+
}
|
|
1149
1343
|
}
|
|
1150
1344
|
}}
|
|
1151
1345
|
/>
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1346
|
+
{!bulkRenameMode && (
|
|
1347
|
+
<>
|
|
1348
|
+
<Button
|
|
1349
|
+
size="sm"
|
|
1350
|
+
onClick={() => handleEditName(item, newFileName)}
|
|
1351
|
+
>
|
|
1352
|
+
Save
|
|
1353
|
+
</Button>
|
|
1354
|
+
<Button
|
|
1355
|
+
size="sm"
|
|
1356
|
+
variant="outline"
|
|
1357
|
+
onClick={() => setEditingFile(null)}
|
|
1358
|
+
>
|
|
1359
|
+
Cancel
|
|
1360
|
+
</Button>
|
|
1361
|
+
</>
|
|
1362
|
+
)}
|
|
1165
1363
|
</div>
|
|
1166
1364
|
) : (
|
|
1167
1365
|
<div className="flex-1 min-w-0">
|
|
1168
1366
|
{item.isFolder ? (
|
|
1169
1367
|
<button
|
|
1368
|
+
type="button"
|
|
1170
1369
|
onClick={() => navigateToFolder(item.name)}
|
|
1171
|
-
|
|
1370
|
+
disabled={bulkRenameInProgress}
|
|
1371
|
+
className={cn(
|
|
1372
|
+
"text-left block w-full",
|
|
1373
|
+
bulkRenameInProgress
|
|
1374
|
+
? "cursor-not-allowed opacity-50"
|
|
1375
|
+
: "hover:underline",
|
|
1376
|
+
)}
|
|
1172
1377
|
>
|
|
1173
1378
|
{item.name}
|
|
1174
1379
|
</button>
|
|
@@ -1179,7 +1384,10 @@ export const RecordFiles = ({ collection, record }: FilesProps) => {
|
|
|
1179
1384
|
)}
|
|
1180
1385
|
</div>
|
|
1181
1386
|
|
|
1182
|
-
{!(
|
|
1387
|
+
{!(
|
|
1388
|
+
(bulkRenameMode && canEditFile(item)) ||
|
|
1389
|
+
(editingFile === item.name && !isDisabled)
|
|
1390
|
+
) && (
|
|
1183
1391
|
<div className="flex items-center space-x-2">
|
|
1184
1392
|
{!item.isFolder && (
|
|
1185
1393
|
<>
|
|
@@ -1199,10 +1407,11 @@ export const RecordFiles = ({ collection, record }: FilesProps) => {
|
|
|
1199
1407
|
size="sm"
|
|
1200
1408
|
variant="outline"
|
|
1201
1409
|
onClick={() => {
|
|
1410
|
+
cancelBulkRename()
|
|
1202
1411
|
setEditingFile(item.name)
|
|
1203
1412
|
setNewFileName(item.name)
|
|
1204
1413
|
}}
|
|
1205
|
-
disabled={isDisabled}
|
|
1414
|
+
disabled={isDisabled || bulkRenameMode}
|
|
1206
1415
|
>
|
|
1207
1416
|
<Edit className="h-4 w-4" />
|
|
1208
1417
|
</Button>
|
package/src/utils/prepareFile.ts
CHANGED
|
@@ -33,6 +33,7 @@ export const prepareFile = async (
|
|
|
33
33
|
file: File,
|
|
34
34
|
preferredFileName: string,
|
|
35
35
|
fileOptions: FileOptions,
|
|
36
|
+
rename: boolean = false,
|
|
36
37
|
): Promise<{ file: File; filename: string }> => {
|
|
37
38
|
if (!file.type.startsWith("image/") || file.type === "image/svg+xml") {
|
|
38
39
|
return { file, filename: preferredFileName }
|
|
@@ -107,7 +108,7 @@ export const prepareFile = async (
|
|
|
107
108
|
blob.size >= file.size &&
|
|
108
109
|
!didDownscale &&
|
|
109
110
|
((usePngOutput && file.type === "image/png") || (!usePngOutput && file.type === "image/jpeg"))
|
|
110
|
-
if (noBenefit) {
|
|
111
|
+
if (noBenefit && !rename) {
|
|
111
112
|
return { file, filename: preferredFileName }
|
|
112
113
|
}
|
|
113
114
|
|