@noya-app/noya-file-explorer 0.0.15 → 0.0.16
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/.turbo/turbo-build.log +13 -13
- package/CHANGELOG.md +14 -0
- package/dist/index.css +124 -10
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +10577 -6
- package/dist/index.d.ts +10577 -6
- package/dist/index.js +241 -121
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +240 -119
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -8
- package/src/MediaCollection.tsx +101 -63
- package/src/index.ts +0 -1
- package/src/utils/files.ts +3 -0
- package/src/utils/handleFileDrop.ts +142 -0
- package/src/formatByteSize.ts +0 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noya-app/noya-file-explorer",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"module": "./dist/index.mjs",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -20,13 +20,13 @@
|
|
|
20
20
|
"dev": "npm run build:main -- --watch"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@noya-app/noya-designsystem": "0.1.
|
|
24
|
-
"@noya-app/noya-icons": "0.1.
|
|
25
|
-
"@noya-app/noya-multiplayer-react": "0.1.
|
|
26
|
-
"@noya-app/noya-keymap": "0.1.
|
|
27
|
-
"@noya-app/react-utils": "0.1.
|
|
28
|
-
"@noya-app/noya-utils": "0.1.
|
|
29
|
-
"@noya-app/noya-schemas": "0.1.
|
|
23
|
+
"@noya-app/noya-designsystem": "0.1.64",
|
|
24
|
+
"@noya-app/noya-icons": "0.1.12",
|
|
25
|
+
"@noya-app/noya-multiplayer-react": "0.1.63",
|
|
26
|
+
"@noya-app/noya-keymap": "0.1.4",
|
|
27
|
+
"@noya-app/react-utils": "0.1.24",
|
|
28
|
+
"@noya-app/noya-utils": "0.1.6",
|
|
29
|
+
"@noya-app/noya-schemas": "0.1.6",
|
|
30
30
|
"imfs": "^0.1.0",
|
|
31
31
|
"browser-fs-access": "0.35.0"
|
|
32
32
|
},
|
package/src/MediaCollection.tsx
CHANGED
|
@@ -13,8 +13,11 @@ import {
|
|
|
13
13
|
FileExplorerEmptyState,
|
|
14
14
|
FileExplorerLayout,
|
|
15
15
|
FileExplorerUploadButton,
|
|
16
|
+
formatByteSize,
|
|
16
17
|
ListView,
|
|
17
18
|
MediaThumbnail,
|
|
19
|
+
MediaThumbnailProps,
|
|
20
|
+
RelativeDropPosition,
|
|
18
21
|
} from "@noya-app/noya-designsystem";
|
|
19
22
|
import {
|
|
20
23
|
DownloadIcon,
|
|
@@ -56,6 +59,7 @@ import {
|
|
|
56
59
|
createMediaFile,
|
|
57
60
|
createMediaFolder,
|
|
58
61
|
createMediaItemTree,
|
|
62
|
+
MediaItemTree,
|
|
59
63
|
PLACEHOLDER_ITEM_NAME,
|
|
60
64
|
rootMediaItem,
|
|
61
65
|
rootMediaItemName,
|
|
@@ -63,7 +67,6 @@ import {
|
|
|
63
67
|
|
|
64
68
|
import { path } from "imfs";
|
|
65
69
|
import React from "react";
|
|
66
|
-
import { formatByteSize } from "./formatByteSize";
|
|
67
70
|
import {
|
|
68
71
|
deleteMediaItems,
|
|
69
72
|
ExpandedMap,
|
|
@@ -76,6 +79,7 @@ import {
|
|
|
76
79
|
updateExpandedMap,
|
|
77
80
|
validateMediaItemRename,
|
|
78
81
|
} from "./utils/files";
|
|
82
|
+
import { handleDataTransfer } from "./utils/handleFileDrop";
|
|
79
83
|
|
|
80
84
|
const extensionToContentType = {
|
|
81
85
|
svg: "image/svg+xml",
|
|
@@ -119,8 +123,10 @@ const MediaThumbnailInternal = memoGeneric(
|
|
|
119
123
|
selected,
|
|
120
124
|
size,
|
|
121
125
|
path: pathProp,
|
|
126
|
+
renderThumbnailIcon,
|
|
122
127
|
}: CollectionThumbnailProps<MediaItem> & {
|
|
123
128
|
path?: string;
|
|
129
|
+
renderThumbnailIcon?: MediaThumbnailProps["renderThumbnailIcon"];
|
|
124
130
|
}) => {
|
|
125
131
|
const asset = useAsset(item.kind === "asset" ? item.assetId : undefined);
|
|
126
132
|
const isRoot = item.id === rootMediaItem.id;
|
|
@@ -152,6 +158,7 @@ const MediaThumbnailInternal = memoGeneric(
|
|
|
152
158
|
selected={selected}
|
|
153
159
|
size={size}
|
|
154
160
|
fileName={fileName}
|
|
161
|
+
renderThumbnailIcon={renderThumbnailIcon}
|
|
155
162
|
/>
|
|
156
163
|
);
|
|
157
164
|
}
|
|
@@ -177,6 +184,7 @@ export type MediaCollectionRef = {
|
|
|
177
184
|
moveUpAFolder: (selectedIds: string[]) => void;
|
|
178
185
|
replace: (selectedItem: MediaItem) => void;
|
|
179
186
|
preview: (selectedItems: MediaItem[]) => void;
|
|
187
|
+
getItemAtIndex: (index: number) => MediaItem | undefined;
|
|
180
188
|
};
|
|
181
189
|
|
|
182
190
|
type MediaCollectionProps = {
|
|
@@ -231,6 +239,8 @@ type MediaCollectionProps = {
|
|
|
231
239
|
>;
|
|
232
240
|
/** @default false */
|
|
233
241
|
sortable?: boolean;
|
|
242
|
+
renderThumbnailIcon?: MediaThumbnailProps["renderThumbnailIcon"];
|
|
243
|
+
onDidDeleteItems?: (items: [string, MediaItem][]) => void;
|
|
234
244
|
} & Pick<
|
|
235
245
|
CollectionProps<MediaItem, MenuAction>,
|
|
236
246
|
| "sortableId"
|
|
@@ -272,6 +282,8 @@ export const MediaCollection = memo(
|
|
|
272
282
|
renderEmptyState,
|
|
273
283
|
sharedDragProps,
|
|
274
284
|
onClickItem,
|
|
285
|
+
renderThumbnailIcon,
|
|
286
|
+
onDidDeleteItems,
|
|
275
287
|
},
|
|
276
288
|
ref
|
|
277
289
|
) {
|
|
@@ -386,15 +398,27 @@ export const MediaCollection = memo(
|
|
|
386
398
|
|
|
387
399
|
const handleDelete = useCallback(
|
|
388
400
|
(selectedIds: string[]) => {
|
|
401
|
+
const deletedItems = Object.entries(media).flatMap(
|
|
402
|
+
([path, item]): [string, MediaItem][] => {
|
|
403
|
+
if (selectedIds.includes(item.id)) {
|
|
404
|
+
return [[path, item]];
|
|
405
|
+
}
|
|
406
|
+
return [];
|
|
407
|
+
}
|
|
408
|
+
);
|
|
409
|
+
|
|
389
410
|
const newMedia = deleteMediaItems({
|
|
390
411
|
selectedIds,
|
|
391
412
|
media,
|
|
392
413
|
tree,
|
|
393
414
|
});
|
|
415
|
+
|
|
394
416
|
setSelectedIds([rootMediaItem.id]);
|
|
395
417
|
setMedia({ name: "Delete items", timestamp: Date.now() }, newMedia);
|
|
418
|
+
|
|
419
|
+
onDidDeleteItems?.(deletedItems);
|
|
396
420
|
},
|
|
397
|
-
[media, setMedia, setSelectedIds, tree]
|
|
421
|
+
[media, setMedia, setSelectedIds, tree, onDidDeleteItems]
|
|
398
422
|
);
|
|
399
423
|
|
|
400
424
|
const onRename = useCallback(
|
|
@@ -485,6 +509,7 @@ export const MediaCollection = memo(
|
|
|
485
509
|
async (selectedId: string) => {
|
|
486
510
|
try {
|
|
487
511
|
const files = await fileOpen({ multiple: true });
|
|
512
|
+
|
|
488
513
|
if (!files || !Array.isArray(files) || files.length === 0) return;
|
|
489
514
|
|
|
490
515
|
const parentPath = tree.idToPathMap.get(selectedId);
|
|
@@ -588,15 +613,13 @@ export const MediaCollection = memo(
|
|
|
588
613
|
|
|
589
614
|
const handleMoveMediaInsideFolder = useCallback(
|
|
590
615
|
(sourceItem: MediaItem, targetItem: MediaItem) => {
|
|
591
|
-
const sourceItemPath = tree.idToPathMap.get(sourceItem.id);
|
|
592
|
-
const targetItemPath = tree.idToPathMap.get(targetItem.id);
|
|
593
|
-
if (!sourceItemPath || !targetItemPath) return;
|
|
594
616
|
const newMedia = moveMediaInsideFolder({
|
|
595
617
|
sourceItemIds: [sourceItem.id],
|
|
596
618
|
targetItemId: targetItem.id,
|
|
597
619
|
media,
|
|
598
620
|
tree,
|
|
599
621
|
});
|
|
622
|
+
|
|
600
623
|
setMedia(
|
|
601
624
|
{
|
|
602
625
|
name: "Move media file inside folder",
|
|
@@ -630,12 +653,12 @@ export const MediaCollection = memo(
|
|
|
630
653
|
],
|
|
631
654
|
[
|
|
632
655
|
onlySingleFolderSelected && {
|
|
633
|
-
title: "
|
|
656
|
+
title: "Upload Files",
|
|
634
657
|
value: "upload",
|
|
635
658
|
icon: <UploadIcon />,
|
|
636
659
|
},
|
|
637
660
|
onlySingleFolderSelected && {
|
|
638
|
-
title: "Add
|
|
661
|
+
title: "Add Folder",
|
|
639
662
|
value: "addFolder",
|
|
640
663
|
icon: <FolderIcon />,
|
|
641
664
|
},
|
|
@@ -768,6 +791,7 @@ export const MediaCollection = memo(
|
|
|
768
791
|
replace: handleReplace,
|
|
769
792
|
preview: handlePreview,
|
|
770
793
|
moveMediaInsideFolder: handleMoveMediaInsideFolder,
|
|
794
|
+
getItemAtIndex: (index) => visibleItems[index],
|
|
771
795
|
}));
|
|
772
796
|
|
|
773
797
|
return (
|
|
@@ -810,6 +834,7 @@ export const MediaCollection = memo(
|
|
|
810
834
|
return "Enter folder name";
|
|
811
835
|
case "asset":
|
|
812
836
|
case "file":
|
|
837
|
+
case "noyaFile":
|
|
813
838
|
return "Enter file name";
|
|
814
839
|
}
|
|
815
840
|
}}
|
|
@@ -832,6 +857,7 @@ export const MediaCollection = memo(
|
|
|
832
857
|
<MediaThumbnailInternal
|
|
833
858
|
{...props}
|
|
834
859
|
path={tree.idToPathMap.get(props.item.id)}
|
|
860
|
+
renderThumbnailIcon={renderThumbnailIcon}
|
|
835
861
|
/>
|
|
836
862
|
)}
|
|
837
863
|
renderAction={renderAction}
|
|
@@ -849,7 +875,7 @@ export const MediaCollection = memo(
|
|
|
849
875
|
renderEmptyState?.() ?? <FileExplorerEmptyState />
|
|
850
876
|
}
|
|
851
877
|
itemRoleDescription="clickable file item"
|
|
852
|
-
getDropTargetParentIndex={(overIndex
|
|
878
|
+
getDropTargetParentIndex={(overIndex) => {
|
|
853
879
|
const item = visibleItems[overIndex];
|
|
854
880
|
const parentIndex = visibleItems.findIndex(
|
|
855
881
|
(i) => i.id === tree.getParentIdForId(item.id)
|
|
@@ -867,32 +893,31 @@ export const MediaCollection = memo(
|
|
|
867
893
|
return false;
|
|
868
894
|
}
|
|
869
895
|
|
|
870
|
-
|
|
871
|
-
const targetItem = visibleItems[targetIndex];
|
|
872
|
-
|
|
873
|
-
if (position !== "inside" || targetItem.kind === "asset") {
|
|
896
|
+
if (sourceListId !== sortableId || targetListId !== sortableId) {
|
|
874
897
|
return false;
|
|
875
898
|
}
|
|
876
|
-
const sourcePath = tree.findPath(
|
|
877
|
-
rootMediaItem,
|
|
878
|
-
(item) => item.id === sourceItem.id
|
|
879
|
-
);
|
|
880
|
-
const targetPath = tree.findPath(
|
|
881
|
-
rootMediaItem,
|
|
882
|
-
(item) => item.id === targetItem.id
|
|
883
|
-
);
|
|
884
899
|
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
if (
|
|
888
|
-
isDeepEqual(sourcePath, targetPath.slice(0, sourcePath.length))
|
|
889
|
-
) {
|
|
890
|
-
return false;
|
|
891
|
-
}
|
|
900
|
+
const sourceItem = visibleItems[sourceIndex];
|
|
901
|
+
const targetItem = visibleItems[targetIndex];
|
|
892
902
|
|
|
893
|
-
return
|
|
903
|
+
return acceptsMediaItemDrop({
|
|
904
|
+
position,
|
|
905
|
+
sourceItem,
|
|
906
|
+
targetItem,
|
|
907
|
+
tree,
|
|
908
|
+
});
|
|
894
909
|
}}
|
|
895
|
-
onMoveItem={({
|
|
910
|
+
onMoveItem={({
|
|
911
|
+
sourceListId,
|
|
912
|
+
sourceIndex,
|
|
913
|
+
targetListId,
|
|
914
|
+
targetIndex,
|
|
915
|
+
position,
|
|
916
|
+
}) => {
|
|
917
|
+
if (sourceListId !== sortableId || targetListId !== sortableId) {
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
|
|
896
921
|
const sourceItem = visibleItems[sourceIndex];
|
|
897
922
|
const targetItem = visibleItems[targetIndex];
|
|
898
923
|
if (position === "inside") {
|
|
@@ -902,40 +927,23 @@ export const MediaCollection = memo(
|
|
|
902
927
|
onFilesDrop={async (event: React.DragEvent<Element>) => {
|
|
903
928
|
event.preventDefault();
|
|
904
929
|
|
|
905
|
-
const
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
if (!rootItemPath) return;
|
|
923
|
-
// Add all the media file references to our state
|
|
924
|
-
setMedia(
|
|
925
|
-
{ name: "Add media files", timestamp: Date.now() },
|
|
926
|
-
{
|
|
927
|
-
...media,
|
|
928
|
-
...Object.fromEntries(
|
|
929
|
-
newMediaItems.map((item) => [
|
|
930
|
-
path.join(rootItemPath, item.name),
|
|
931
|
-
item.asset,
|
|
932
|
-
])
|
|
933
|
-
),
|
|
934
|
-
}
|
|
935
|
-
);
|
|
936
|
-
} catch (error) {
|
|
937
|
-
console.error("Failed to upload dropped files:", error);
|
|
938
|
-
}
|
|
930
|
+
const rootItemPath = tree.idToPathMap.get(rootItemId);
|
|
931
|
+
|
|
932
|
+
if (!rootItemPath) return;
|
|
933
|
+
|
|
934
|
+
const newMedia = await handleDataTransfer({
|
|
935
|
+
dataTransfer: event.dataTransfer,
|
|
936
|
+
rootItemPath,
|
|
937
|
+
media,
|
|
938
|
+
uploadAsset: (file) => assetManager.create(file),
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
if (!newMedia) return;
|
|
942
|
+
|
|
943
|
+
setMedia(
|
|
944
|
+
{ name: "Add media files", timestamp: Date.now() },
|
|
945
|
+
newMedia
|
|
946
|
+
);
|
|
939
947
|
}}
|
|
940
948
|
/>
|
|
941
949
|
</FileExplorerLayout>
|
|
@@ -943,3 +951,33 @@ export const MediaCollection = memo(
|
|
|
943
951
|
);
|
|
944
952
|
})
|
|
945
953
|
);
|
|
954
|
+
|
|
955
|
+
export function acceptsMediaItemDrop(parameters: {
|
|
956
|
+
position: RelativeDropPosition;
|
|
957
|
+
sourceItem: MediaItem;
|
|
958
|
+
targetItem: MediaItem;
|
|
959
|
+
tree: MediaItemTree;
|
|
960
|
+
}) {
|
|
961
|
+
const { position, sourceItem, targetItem, tree } = parameters;
|
|
962
|
+
|
|
963
|
+
if (position !== "inside" || targetItem.kind === "asset") {
|
|
964
|
+
return false;
|
|
965
|
+
}
|
|
966
|
+
const sourcePath = tree.findPath(
|
|
967
|
+
rootMediaItem,
|
|
968
|
+
(item) => item.id === sourceItem.id
|
|
969
|
+
);
|
|
970
|
+
const targetPath = tree.findPath(
|
|
971
|
+
rootMediaItem,
|
|
972
|
+
(item) => item.id === targetItem.id
|
|
973
|
+
);
|
|
974
|
+
|
|
975
|
+
// Don't allow dragging into a descendant
|
|
976
|
+
if (!sourcePath || !targetPath) return false;
|
|
977
|
+
|
|
978
|
+
if (isDeepEqual(sourcePath, targetPath.slice(0, sourcePath.length))) {
|
|
979
|
+
return false;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
return true;
|
|
983
|
+
}
|
package/src/index.ts
CHANGED
package/src/utils/files.ts
CHANGED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Asset, MediaItem, MediaMap } from "@noya-app/noya-schemas";
|
|
2
|
+
import { path } from "imfs";
|
|
3
|
+
import { createMediaAsset, createMediaFolder } from "./mediaItemTree";
|
|
4
|
+
|
|
5
|
+
function isDirectoryEntry(
|
|
6
|
+
entry: FileSystemEntry
|
|
7
|
+
): entry is FileSystemDirectoryEntry {
|
|
8
|
+
return entry.isDirectory;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function isFileEntry(entry: FileSystemEntry): entry is FileSystemFileEntry {
|
|
12
|
+
return entry.isFile;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function handleDataTransfer({
|
|
16
|
+
dataTransfer,
|
|
17
|
+
rootItemPath,
|
|
18
|
+
media,
|
|
19
|
+
uploadAsset,
|
|
20
|
+
}: {
|
|
21
|
+
dataTransfer: DataTransfer;
|
|
22
|
+
rootItemPath: string;
|
|
23
|
+
media: MediaMap;
|
|
24
|
+
uploadAsset: (file: File) => Promise<Asset>;
|
|
25
|
+
}): Promise<MediaMap | undefined> {
|
|
26
|
+
try {
|
|
27
|
+
const dataTransferItems = Array.from(dataTransfer.items ?? []);
|
|
28
|
+
|
|
29
|
+
const supportsEntries = dataTransferItems.some(
|
|
30
|
+
(item) => typeof item?.webkitGetAsEntry === "function"
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
type DroppedFile = { file: File; relativePath: string };
|
|
34
|
+
|
|
35
|
+
const collectFromEntry = async (
|
|
36
|
+
entry: FileSystemEntry,
|
|
37
|
+
basePath: string
|
|
38
|
+
): Promise<DroppedFile[]> => {
|
|
39
|
+
if (!entry) return [];
|
|
40
|
+
if (isFileEntry(entry)) {
|
|
41
|
+
const file: File = await new Promise((resolve) =>
|
|
42
|
+
(entry as FileSystemFileEntry).file((f: File) => resolve(f))
|
|
43
|
+
);
|
|
44
|
+
return [
|
|
45
|
+
{
|
|
46
|
+
file,
|
|
47
|
+
relativePath: path.join(basePath, file.name),
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
if (isDirectoryEntry(entry)) {
|
|
52
|
+
const reader = entry.createReader();
|
|
53
|
+
const readAll = async (): Promise<FileSystemEntry[]> => {
|
|
54
|
+
const all: FileSystemEntry[] = [];
|
|
55
|
+
// readEntries may return partial results; loop until empty
|
|
56
|
+
// eslint-disable-next-line no-constant-condition
|
|
57
|
+
while (true) {
|
|
58
|
+
const entries: FileSystemEntry[] = await new Promise((resolve) =>
|
|
59
|
+
reader.readEntries((ents: FileSystemEntry[]) => resolve(ents))
|
|
60
|
+
);
|
|
61
|
+
if (!entries.length) break;
|
|
62
|
+
all.push(...entries);
|
|
63
|
+
}
|
|
64
|
+
return all;
|
|
65
|
+
};
|
|
66
|
+
const children = await readAll();
|
|
67
|
+
const results = await Promise.all(
|
|
68
|
+
children.map((child) =>
|
|
69
|
+
collectFromEntry(child, path.join(basePath, entry.name))
|
|
70
|
+
)
|
|
71
|
+
);
|
|
72
|
+
return results.flat();
|
|
73
|
+
}
|
|
74
|
+
return [];
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
let dropped: DroppedFile[] = [];
|
|
78
|
+
|
|
79
|
+
if (supportsEntries) {
|
|
80
|
+
const topLevelEntries = dataTransferItems.flatMap((item) => {
|
|
81
|
+
const entry = item.webkitGetAsEntry?.();
|
|
82
|
+
if (!entry) return [];
|
|
83
|
+
return [entry];
|
|
84
|
+
});
|
|
85
|
+
const nested = await Promise.all(
|
|
86
|
+
topLevelEntries.map((entry) => collectFromEntry(entry, ""))
|
|
87
|
+
);
|
|
88
|
+
dropped = nested.flat();
|
|
89
|
+
} else {
|
|
90
|
+
const files = Array.from(dataTransfer.files);
|
|
91
|
+
if (files.length === 0) return;
|
|
92
|
+
dropped = files.map((file) => ({
|
|
93
|
+
file,
|
|
94
|
+
// Best effort: try webkitRelativePath; fall back to name
|
|
95
|
+
relativePath:
|
|
96
|
+
file.webkitRelativePath && file.webkitRelativePath.length > 0
|
|
97
|
+
? file.webkitRelativePath
|
|
98
|
+
: file.name,
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (dropped.length === 0) return;
|
|
103
|
+
|
|
104
|
+
const folderRelativePaths = new Set<string>();
|
|
105
|
+
for (const { relativePath } of dropped) {
|
|
106
|
+
const dir = path.dirname(relativePath);
|
|
107
|
+
if (dir && dir !== ".") {
|
|
108
|
+
const parts = dir.split("/").filter(Boolean);
|
|
109
|
+
let acc = "";
|
|
110
|
+
for (const part of parts) {
|
|
111
|
+
acc = acc ? path.join(acc, part) : part;
|
|
112
|
+
folderRelativePaths.add(acc);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const folderEntries: [string, MediaItem][] = Array.from(folderRelativePaths)
|
|
118
|
+
.map((rel) => path.join(rootItemPath, rel))
|
|
119
|
+
.filter((full) => !media[full])
|
|
120
|
+
.map((full) => [full, createMediaFolder() as MediaItem]);
|
|
121
|
+
|
|
122
|
+
const uploadResults = await Promise.all(
|
|
123
|
+
dropped.map(
|
|
124
|
+
async ({ file, relativePath }): Promise<[string, MediaItem]> => {
|
|
125
|
+
const asset = await uploadAsset(file);
|
|
126
|
+
|
|
127
|
+
return [
|
|
128
|
+
path.join(rootItemPath, relativePath),
|
|
129
|
+
createMediaAsset({ assetId: asset.id }) as MediaItem,
|
|
130
|
+
];
|
|
131
|
+
}
|
|
132
|
+
)
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
...media,
|
|
137
|
+
...Object.fromEntries([...folderEntries, ...uploadResults]),
|
|
138
|
+
};
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error("Failed to upload dropped files:", error);
|
|
141
|
+
}
|
|
142
|
+
}
|
package/src/formatByteSize.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
const byteSizeUnits = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
|
2
|
-
|
|
3
|
-
export function formatByteSize(size: number) {
|
|
4
|
-
const unitIndex = Math.floor(Math.log(size) / Math.log(1024));
|
|
5
|
-
const unit = byteSizeUnits[unitIndex];
|
|
6
|
-
const value = size / Math.pow(1024, unitIndex);
|
|
7
|
-
return `${value.toFixed(1)} ${unit}`;
|
|
8
|
-
}
|