@noya-app/noya-file-explorer 0.0.15 → 0.0.17
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 +27 -0
- package/dist/index.css +916 -843
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +11207 -45
- package/dist/index.d.ts +11207 -45
- package/dist/index.js +1361 -160
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1399 -156
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -8
- package/src/MediaCollection.tsx +156 -97
- package/src/ResourceExplorer.tsx +1141 -0
- package/src/__tests__/deleteMediaItems.test.ts +7 -7
- package/src/__tests__/getDepthMap.test.ts +7 -7
- package/src/__tests__/getParentDirectories.test.ts +6 -6
- package/src/__tests__/getVisibleItems.test.ts +9 -9
- package/src/__tests__/moveMediaInsideFolder.test.ts +11 -11
- package/src/__tests__/movePathsIntoTarget.test.ts +9 -9
- package/src/__tests__/moveUpAFolder.test.ts +7 -7
- package/src/__tests__/renameMediaItemAndDescendantPaths.test.ts +6 -6
- package/src/__tests__/updateExpandedMap.test.ts +8 -8
- package/src/__tests__/validateMediaItemRename.test.ts +11 -11
- package/src/index.ts +2 -1
- package/src/utils/files.ts +28 -37
- package/src/utils/handleFileDrop.ts +165 -0
- package/src/utils/resourceUtils.ts +329 -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.17",
|
|
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.65",
|
|
24
|
+
"@noya-app/noya-icons": "0.1.13",
|
|
25
|
+
"@noya-app/noya-multiplayer-react": "0.1.64",
|
|
26
|
+
"@noya-app/noya-keymap": "0.1.4",
|
|
27
|
+
"@noya-app/react-utils": "0.1.25",
|
|
28
|
+
"@noya-app/noya-utils": "0.1.7",
|
|
29
|
+
"@noya-app/noya-schemas": "0.1.7",
|
|
30
30
|
"imfs": "^0.1.0",
|
|
31
31
|
"browser-fs-access": "0.35.0"
|
|
32
32
|
},
|
package/src/MediaCollection.tsx
CHANGED
|
@@ -13,8 +13,12 @@ import {
|
|
|
13
13
|
FileExplorerEmptyState,
|
|
14
14
|
FileExplorerLayout,
|
|
15
15
|
FileExplorerUploadButton,
|
|
16
|
+
formatByteSize,
|
|
16
17
|
ListView,
|
|
17
18
|
MediaThumbnail,
|
|
19
|
+
MediaThumbnailProps,
|
|
20
|
+
RelativeDropPosition,
|
|
21
|
+
useOpenConfirmationDialog,
|
|
18
22
|
} from "@noya-app/noya-designsystem";
|
|
19
23
|
import {
|
|
20
24
|
DownloadIcon,
|
|
@@ -32,7 +36,12 @@ import {
|
|
|
32
36
|
useAssetManager,
|
|
33
37
|
useAssets,
|
|
34
38
|
} from "@noya-app/noya-multiplayer-react";
|
|
35
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
AssetMediaItem,
|
|
41
|
+
FileMediaItem,
|
|
42
|
+
MediaItem,
|
|
43
|
+
MediaMap,
|
|
44
|
+
} from "@noya-app/noya-schemas";
|
|
36
45
|
import { groupBy, isDeepEqual } from "@noya-app/noya-utils";
|
|
37
46
|
import {
|
|
38
47
|
downloadUrl,
|
|
@@ -56,6 +65,7 @@ import {
|
|
|
56
65
|
createMediaFile,
|
|
57
66
|
createMediaFolder,
|
|
58
67
|
createMediaItemTree,
|
|
68
|
+
MediaItemTree,
|
|
59
69
|
PLACEHOLDER_ITEM_NAME,
|
|
60
70
|
rootMediaItem,
|
|
61
71
|
rootMediaItemName,
|
|
@@ -63,18 +73,17 @@ import {
|
|
|
63
73
|
|
|
64
74
|
import { path } from "imfs";
|
|
65
75
|
import React from "react";
|
|
66
|
-
import { formatByteSize } from "./formatByteSize";
|
|
67
76
|
import {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
mediaDeleteMediaItems,
|
|
78
|
+
MediaExpandedMap,
|
|
79
|
+
MediaFileKindFilter,
|
|
80
|
+
mediaGetDepthMap,
|
|
81
|
+
mediaGetVisibleItems,
|
|
82
|
+
mediaMoveMediaInsideFolder,
|
|
83
|
+
mediaMoveUpAFolder,
|
|
84
|
+
mediaRenameMediaItemAndDescendantPaths,
|
|
85
|
+
mediaUpdateExpandedMap,
|
|
86
|
+
mediaValidateMediaItemRename,
|
|
78
87
|
} from "./utils/files";
|
|
79
88
|
|
|
80
89
|
const extensionToContentType = {
|
|
@@ -119,8 +128,10 @@ const MediaThumbnailInternal = memoGeneric(
|
|
|
119
128
|
selected,
|
|
120
129
|
size,
|
|
121
130
|
path: pathProp,
|
|
131
|
+
renderThumbnailIcon,
|
|
122
132
|
}: CollectionThumbnailProps<MediaItem> & {
|
|
123
133
|
path?: string;
|
|
134
|
+
renderThumbnailIcon?: MediaThumbnailProps["renderThumbnailIcon"];
|
|
124
135
|
}) => {
|
|
125
136
|
const asset = useAsset(item.kind === "asset" ? item.assetId : undefined);
|
|
126
137
|
const isRoot = item.id === rootMediaItem.id;
|
|
@@ -129,10 +140,14 @@ const MediaThumbnailInternal = memoGeneric(
|
|
|
129
140
|
|
|
130
141
|
let contentType: string | undefined;
|
|
131
142
|
let url: string | undefined;
|
|
143
|
+
let width: number | undefined;
|
|
144
|
+
let height: number | undefined;
|
|
132
145
|
|
|
133
146
|
if (asset) {
|
|
134
147
|
contentType = asset.contentType;
|
|
135
148
|
url = asset.url;
|
|
149
|
+
width = asset.width ?? undefined;
|
|
150
|
+
height = asset.height ?? undefined;
|
|
136
151
|
} else if (isFile && pathProp) {
|
|
137
152
|
const encoded = encodeFileContentForThumbnail(pathProp, item);
|
|
138
153
|
|
|
@@ -143,6 +158,7 @@ const MediaThumbnailInternal = memoGeneric(
|
|
|
143
158
|
}
|
|
144
159
|
|
|
145
160
|
const fileName = pathProp ? path.basename(pathProp) : undefined;
|
|
161
|
+
const dimensions = width && height ? { width, height } : undefined;
|
|
146
162
|
|
|
147
163
|
return (
|
|
148
164
|
<MediaThumbnail
|
|
@@ -152,6 +168,8 @@ const MediaThumbnailInternal = memoGeneric(
|
|
|
152
168
|
selected={selected}
|
|
153
169
|
size={size}
|
|
154
170
|
fileName={fileName}
|
|
171
|
+
renderThumbnailIcon={renderThumbnailIcon}
|
|
172
|
+
dimensions={dimensions}
|
|
155
173
|
/>
|
|
156
174
|
);
|
|
157
175
|
}
|
|
@@ -177,6 +195,7 @@ export type MediaCollectionRef = {
|
|
|
177
195
|
moveUpAFolder: (selectedIds: string[]) => void;
|
|
178
196
|
replace: (selectedItem: MediaItem) => void;
|
|
179
197
|
preview: (selectedItems: MediaItem[]) => void;
|
|
198
|
+
getItemAtIndex: (index: number) => MediaItem | undefined;
|
|
180
199
|
};
|
|
181
200
|
|
|
182
201
|
type MediaCollectionProps = {
|
|
@@ -199,7 +218,7 @@ type MediaCollectionProps = {
|
|
|
199
218
|
*
|
|
200
219
|
* @default "all"
|
|
201
220
|
* */
|
|
202
|
-
fileKindFilter?:
|
|
221
|
+
fileKindFilter?: MediaFileKindFilter;
|
|
203
222
|
/**
|
|
204
223
|
* Whether to show the root item
|
|
205
224
|
*
|
|
@@ -207,7 +226,7 @@ type MediaCollectionProps = {
|
|
|
207
226
|
* */
|
|
208
227
|
showRootItem?: boolean;
|
|
209
228
|
/** Whether to expand all directories by default */
|
|
210
|
-
initialExpanded?:
|
|
229
|
+
initialExpanded?: MediaExpandedMap;
|
|
211
230
|
/**
|
|
212
231
|
* Callback for when an item is double-clicked
|
|
213
232
|
*/
|
|
@@ -231,6 +250,9 @@ type MediaCollectionProps = {
|
|
|
231
250
|
>;
|
|
232
251
|
/** @default false */
|
|
233
252
|
sortable?: boolean;
|
|
253
|
+
renderThumbnailIcon?: MediaThumbnailProps["renderThumbnailIcon"];
|
|
254
|
+
onDidDeleteItems?: (items: [string, MediaItem][]) => void;
|
|
255
|
+
onAssetsUploaded?: (mediaMap: Record<string, AssetMediaItem>) => void;
|
|
234
256
|
} & Pick<
|
|
235
257
|
CollectionProps<MediaItem, MenuAction>,
|
|
236
258
|
| "sortableId"
|
|
@@ -272,6 +294,9 @@ export const MediaCollection = memo(
|
|
|
272
294
|
renderEmptyState,
|
|
273
295
|
sharedDragProps,
|
|
274
296
|
onClickItem,
|
|
297
|
+
renderThumbnailIcon,
|
|
298
|
+
onDidDeleteItems,
|
|
299
|
+
onAssetsUploaded,
|
|
275
300
|
},
|
|
276
301
|
ref
|
|
277
302
|
) {
|
|
@@ -312,10 +337,10 @@ export const MediaCollection = memo(
|
|
|
312
337
|
);
|
|
313
338
|
const assetManager = useAssetManager();
|
|
314
339
|
const assets = useAssets();
|
|
315
|
-
const [expandedMap, setExpandedMap] = useState<
|
|
340
|
+
const [expandedMap, setExpandedMap] = useState<MediaExpandedMap>({});
|
|
316
341
|
const visibleItems = useMemo(
|
|
317
342
|
() =>
|
|
318
|
-
|
|
343
|
+
mediaGetVisibleItems({
|
|
319
344
|
expandedMap,
|
|
320
345
|
fileKindFilter: fileKindFilter,
|
|
321
346
|
rootItemId,
|
|
@@ -333,7 +358,8 @@ export const MediaCollection = memo(
|
|
|
333
358
|
]
|
|
334
359
|
);
|
|
335
360
|
const depthMap = useMemo(
|
|
336
|
-
() =>
|
|
361
|
+
() =>
|
|
362
|
+
mediaGetDepthMap(rootMediaItem, treeWithTempItem, showAllDescendants),
|
|
337
363
|
[treeWithTempItem, showAllDescendants]
|
|
338
364
|
);
|
|
339
365
|
const collectionRef = useRef<CollectionRef>(null);
|
|
@@ -384,17 +410,46 @@ export const MediaCollection = memo(
|
|
|
384
410
|
[expandedMap, expandable]
|
|
385
411
|
);
|
|
386
412
|
|
|
413
|
+
const openConfirmationDialog = useOpenConfirmationDialog();
|
|
414
|
+
|
|
387
415
|
const handleDelete = useCallback(
|
|
388
|
-
(selectedIds: string[]) => {
|
|
389
|
-
const
|
|
416
|
+
async (selectedIds: string[]) => {
|
|
417
|
+
const ok = await openConfirmationDialog({
|
|
418
|
+
title: "Delete items",
|
|
419
|
+
description:
|
|
420
|
+
"Are you sure you want to delete these items? This action cannot be undone.",
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
if (!ok) return;
|
|
424
|
+
|
|
425
|
+
const deletedItems = Object.entries(media).flatMap(
|
|
426
|
+
([path, item]): [string, MediaItem][] => {
|
|
427
|
+
if (selectedIds.includes(item.id)) {
|
|
428
|
+
return [[path, item]];
|
|
429
|
+
}
|
|
430
|
+
return [];
|
|
431
|
+
}
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
const newMedia = mediaDeleteMediaItems({
|
|
390
435
|
selectedIds,
|
|
391
436
|
media,
|
|
392
437
|
tree,
|
|
393
438
|
});
|
|
439
|
+
|
|
394
440
|
setSelectedIds([rootMediaItem.id]);
|
|
395
441
|
setMedia({ name: "Delete items", timestamp: Date.now() }, newMedia);
|
|
442
|
+
|
|
443
|
+
onDidDeleteItems?.(deletedItems);
|
|
396
444
|
},
|
|
397
|
-
[
|
|
445
|
+
[
|
|
446
|
+
media,
|
|
447
|
+
setMedia,
|
|
448
|
+
setSelectedIds,
|
|
449
|
+
tree,
|
|
450
|
+
onDidDeleteItems,
|
|
451
|
+
openConfirmationDialog,
|
|
452
|
+
]
|
|
398
453
|
);
|
|
399
454
|
|
|
400
455
|
const onRename = useCallback(
|
|
@@ -404,7 +459,7 @@ export const MediaCollection = memo(
|
|
|
404
459
|
selectedItem.id
|
|
405
460
|
);
|
|
406
461
|
if (!selectedItemPath) return;
|
|
407
|
-
const renameIsValid =
|
|
462
|
+
const renameIsValid = mediaValidateMediaItemRename({
|
|
408
463
|
basename: newName,
|
|
409
464
|
selectedItemPath,
|
|
410
465
|
media: temp.media,
|
|
@@ -414,7 +469,7 @@ export const MediaCollection = memo(
|
|
|
414
469
|
return;
|
|
415
470
|
}
|
|
416
471
|
const mediaWithRenamedDescendantPaths =
|
|
417
|
-
|
|
472
|
+
mediaRenameMediaItemAndDescendantPaths({
|
|
418
473
|
newName,
|
|
419
474
|
selectedItemPath,
|
|
420
475
|
media: temp.media,
|
|
@@ -468,7 +523,7 @@ export const MediaCollection = memo(
|
|
|
468
523
|
|
|
469
524
|
const handleMoveUpAFolder = useCallback(
|
|
470
525
|
(selectedIds: string[]) => {
|
|
471
|
-
const newMedia =
|
|
526
|
+
const newMedia = mediaMoveUpAFolder({
|
|
472
527
|
tree,
|
|
473
528
|
media,
|
|
474
529
|
selectedIds,
|
|
@@ -485,6 +540,7 @@ export const MediaCollection = memo(
|
|
|
485
540
|
async (selectedId: string) => {
|
|
486
541
|
try {
|
|
487
542
|
const files = await fileOpen({ multiple: true });
|
|
543
|
+
|
|
488
544
|
if (!files || !Array.isArray(files) || files.length === 0) return;
|
|
489
545
|
|
|
490
546
|
const parentPath = tree.idToPathMap.get(selectedId);
|
|
@@ -502,24 +558,31 @@ export const MediaCollection = memo(
|
|
|
502
558
|
|
|
503
559
|
setIsUploading(true);
|
|
504
560
|
|
|
505
|
-
const
|
|
561
|
+
const uploadedAssets = await Promise.all(uploadPromises);
|
|
562
|
+
const newMediaMap = Object.fromEntries(
|
|
563
|
+
uploadedAssets.map(({ assetPath, asset }) => [assetPath, asset])
|
|
564
|
+
);
|
|
506
565
|
|
|
507
566
|
setMedia(
|
|
508
567
|
{ name: "Add media items", timestamp: Date.now() },
|
|
509
568
|
{
|
|
510
569
|
...media,
|
|
511
|
-
...
|
|
512
|
-
newMediaMap.map(({ assetPath, asset }) => [assetPath, asset])
|
|
513
|
-
),
|
|
570
|
+
...newMediaMap,
|
|
514
571
|
}
|
|
515
572
|
);
|
|
573
|
+
|
|
574
|
+
onAssetsUploaded?.(newMediaMap);
|
|
516
575
|
} catch (error) {
|
|
517
|
-
|
|
576
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
577
|
+
// Ignore user-cancelled file picker
|
|
578
|
+
} else {
|
|
579
|
+
console.error("Failed to upload files:", error);
|
|
580
|
+
}
|
|
518
581
|
} finally {
|
|
519
582
|
setIsUploading(false);
|
|
520
583
|
}
|
|
521
584
|
},
|
|
522
|
-
[tree.idToPathMap, setMedia, media, assetManager]
|
|
585
|
+
[tree.idToPathMap, setMedia, media, assetManager, onAssetsUploaded]
|
|
523
586
|
);
|
|
524
587
|
|
|
525
588
|
const handleDownload = useCallback(
|
|
@@ -573,7 +636,11 @@ export const MediaCollection = memo(
|
|
|
573
636
|
}
|
|
574
637
|
);
|
|
575
638
|
} catch (error) {
|
|
576
|
-
|
|
639
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
640
|
+
// Ignore user-cancelled file picker
|
|
641
|
+
} else {
|
|
642
|
+
console.error("Failed to upload files:", error);
|
|
643
|
+
}
|
|
577
644
|
}
|
|
578
645
|
},
|
|
579
646
|
[media, setMedia, assetManager, tree]
|
|
@@ -588,15 +655,13 @@ export const MediaCollection = memo(
|
|
|
588
655
|
|
|
589
656
|
const handleMoveMediaInsideFolder = useCallback(
|
|
590
657
|
(sourceItem: MediaItem, targetItem: MediaItem) => {
|
|
591
|
-
const
|
|
592
|
-
const targetItemPath = tree.idToPathMap.get(targetItem.id);
|
|
593
|
-
if (!sourceItemPath || !targetItemPath) return;
|
|
594
|
-
const newMedia = moveMediaInsideFolder({
|
|
658
|
+
const newMedia = mediaMoveMediaInsideFolder({
|
|
595
659
|
sourceItemIds: [sourceItem.id],
|
|
596
660
|
targetItemId: targetItem.id,
|
|
597
661
|
media,
|
|
598
662
|
tree,
|
|
599
663
|
});
|
|
664
|
+
|
|
600
665
|
setMedia(
|
|
601
666
|
{
|
|
602
667
|
name: "Move media file inside folder",
|
|
@@ -630,12 +695,12 @@ export const MediaCollection = memo(
|
|
|
630
695
|
],
|
|
631
696
|
[
|
|
632
697
|
onlySingleFolderSelected && {
|
|
633
|
-
title: "
|
|
698
|
+
title: "Upload Files",
|
|
634
699
|
value: "upload",
|
|
635
700
|
icon: <UploadIcon />,
|
|
636
701
|
},
|
|
637
702
|
onlySingleFolderSelected && {
|
|
638
|
-
title: "Add
|
|
703
|
+
title: "Add Folder",
|
|
639
704
|
value: "addFolder",
|
|
640
705
|
icon: <FolderIcon />,
|
|
641
706
|
},
|
|
@@ -717,7 +782,7 @@ export const MediaCollection = memo(
|
|
|
717
782
|
const handleSetExpanded = useCallback(
|
|
718
783
|
(item: MediaItem, expanded: boolean) => {
|
|
719
784
|
setExpandedMap((prev) =>
|
|
720
|
-
|
|
785
|
+
mediaUpdateExpandedMap({
|
|
721
786
|
item,
|
|
722
787
|
expanded,
|
|
723
788
|
expandable,
|
|
@@ -768,6 +833,7 @@ export const MediaCollection = memo(
|
|
|
768
833
|
replace: handleReplace,
|
|
769
834
|
preview: handlePreview,
|
|
770
835
|
moveMediaInsideFolder: handleMoveMediaInsideFolder,
|
|
836
|
+
getItemAtIndex: (index) => visibleItems[index],
|
|
771
837
|
}));
|
|
772
838
|
|
|
773
839
|
return (
|
|
@@ -810,6 +876,7 @@ export const MediaCollection = memo(
|
|
|
810
876
|
return "Enter folder name";
|
|
811
877
|
case "asset":
|
|
812
878
|
case "file":
|
|
879
|
+
case "noyaFile":
|
|
813
880
|
return "Enter file name";
|
|
814
881
|
}
|
|
815
882
|
}}
|
|
@@ -832,6 +899,7 @@ export const MediaCollection = memo(
|
|
|
832
899
|
<MediaThumbnailInternal
|
|
833
900
|
{...props}
|
|
834
901
|
path={tree.idToPathMap.get(props.item.id)}
|
|
902
|
+
renderThumbnailIcon={renderThumbnailIcon}
|
|
835
903
|
/>
|
|
836
904
|
)}
|
|
837
905
|
renderAction={renderAction}
|
|
@@ -849,7 +917,7 @@ export const MediaCollection = memo(
|
|
|
849
917
|
renderEmptyState?.() ?? <FileExplorerEmptyState />
|
|
850
918
|
}
|
|
851
919
|
itemRoleDescription="clickable file item"
|
|
852
|
-
getDropTargetParentIndex={(overIndex
|
|
920
|
+
getDropTargetParentIndex={(overIndex) => {
|
|
853
921
|
const item = visibleItems[overIndex];
|
|
854
922
|
const parentIndex = visibleItems.findIndex(
|
|
855
923
|
(i) => i.id === tree.getParentIdForId(item.id)
|
|
@@ -867,79 +935,70 @@ export const MediaCollection = memo(
|
|
|
867
935
|
return false;
|
|
868
936
|
}
|
|
869
937
|
|
|
870
|
-
|
|
871
|
-
const targetItem = visibleItems[targetIndex];
|
|
872
|
-
|
|
873
|
-
if (position !== "inside" || targetItem.kind === "asset") {
|
|
938
|
+
if (sourceListId !== sortableId || targetListId !== sortableId) {
|
|
874
939
|
return false;
|
|
875
940
|
}
|
|
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
941
|
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
if (
|
|
888
|
-
isDeepEqual(sourcePath, targetPath.slice(0, sourcePath.length))
|
|
889
|
-
) {
|
|
890
|
-
return false;
|
|
891
|
-
}
|
|
942
|
+
const sourceItem = visibleItems[sourceIndex];
|
|
943
|
+
const targetItem = visibleItems[targetIndex];
|
|
892
944
|
|
|
893
|
-
return
|
|
945
|
+
return acceptsMediaItemDrop({
|
|
946
|
+
position,
|
|
947
|
+
sourceItem,
|
|
948
|
+
targetItem,
|
|
949
|
+
tree,
|
|
950
|
+
});
|
|
894
951
|
}}
|
|
895
|
-
onMoveItem={({
|
|
952
|
+
onMoveItem={({
|
|
953
|
+
sourceListId,
|
|
954
|
+
sourceIndex,
|
|
955
|
+
targetListId,
|
|
956
|
+
targetIndex,
|
|
957
|
+
position,
|
|
958
|
+
}) => {
|
|
959
|
+
if (sourceListId !== sortableId || targetListId !== sortableId) {
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
|
|
896
963
|
const sourceItem = visibleItems[sourceIndex];
|
|
897
964
|
const targetItem = visibleItems[targetIndex];
|
|
898
965
|
if (position === "inside") {
|
|
899
966
|
handleMoveMediaInsideFolder(sourceItem, targetItem);
|
|
900
967
|
}
|
|
901
968
|
}}
|
|
902
|
-
onFilesDrop={async (event: React.DragEvent<Element>) => {
|
|
903
|
-
event.preventDefault();
|
|
904
|
-
|
|
905
|
-
const files = Array.from(event.dataTransfer.files);
|
|
906
|
-
if (files.length === 0) return;
|
|
907
|
-
|
|
908
|
-
try {
|
|
909
|
-
// Upload all dropped files
|
|
910
|
-
const uploadPromises = files.map(async (file) => {
|
|
911
|
-
const asset = await assetManager.create(file);
|
|
912
|
-
return {
|
|
913
|
-
asset: createMediaAsset({
|
|
914
|
-
assetId: asset.id,
|
|
915
|
-
}),
|
|
916
|
-
name: file.name,
|
|
917
|
-
};
|
|
918
|
-
});
|
|
919
|
-
|
|
920
|
-
const newMediaItems = await Promise.all(uploadPromises);
|
|
921
|
-
const rootItemPath = tree.idToPathMap.get(rootItemId);
|
|
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
|
-
}
|
|
939
|
-
}}
|
|
940
969
|
/>
|
|
941
970
|
</FileExplorerLayout>
|
|
942
971
|
</>
|
|
943
972
|
);
|
|
944
973
|
})
|
|
945
974
|
);
|
|
975
|
+
|
|
976
|
+
export function acceptsMediaItemDrop(parameters: {
|
|
977
|
+
position: RelativeDropPosition;
|
|
978
|
+
sourceItem: MediaItem;
|
|
979
|
+
targetItem: MediaItem;
|
|
980
|
+
tree: MediaItemTree;
|
|
981
|
+
}) {
|
|
982
|
+
const { position, sourceItem, targetItem, tree } = parameters;
|
|
983
|
+
|
|
984
|
+
if (position !== "inside" || targetItem.kind === "asset") {
|
|
985
|
+
return false;
|
|
986
|
+
}
|
|
987
|
+
const sourcePath = tree.findPath(
|
|
988
|
+
rootMediaItem,
|
|
989
|
+
(item) => item.id === sourceItem.id
|
|
990
|
+
);
|
|
991
|
+
const targetPath = tree.findPath(
|
|
992
|
+
rootMediaItem,
|
|
993
|
+
(item) => item.id === targetItem.id
|
|
994
|
+
);
|
|
995
|
+
|
|
996
|
+
// Don't allow dragging into a descendant
|
|
997
|
+
if (!sourcePath || !targetPath) return false;
|
|
998
|
+
|
|
999
|
+
if (isDeepEqual(sourcePath, targetPath.slice(0, sourcePath.length))) {
|
|
1000
|
+
return false;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
return true;
|
|
1004
|
+
}
|