@noya-app/noya-file-explorer 0.0.16 → 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 +13 -0
- package/dist/index.css +853 -894
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +624 -33
- package/dist/index.d.ts +624 -33
- package/dist/index.js +1346 -265
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1384 -262
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -7
- package/src/MediaCollection.tsx +74 -53
- 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 -0
- package/src/utils/files.ts +25 -37
- package/src/utils/handleFileDrop.ts +38 -15
- package/src/utils/resourceUtils.ts +329 -0
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.
|
|
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
26
|
"@noya-app/noya-keymap": "0.1.4",
|
|
27
|
-
"@noya-app/react-utils": "0.1.
|
|
28
|
-
"@noya-app/noya-utils": "0.1.
|
|
29
|
-
"@noya-app/noya-schemas": "0.1.
|
|
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
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
MediaThumbnail,
|
|
19
19
|
MediaThumbnailProps,
|
|
20
20
|
RelativeDropPosition,
|
|
21
|
+
useOpenConfirmationDialog,
|
|
21
22
|
} from "@noya-app/noya-designsystem";
|
|
22
23
|
import {
|
|
23
24
|
DownloadIcon,
|
|
@@ -35,7 +36,12 @@ import {
|
|
|
35
36
|
useAssetManager,
|
|
36
37
|
useAssets,
|
|
37
38
|
} from "@noya-app/noya-multiplayer-react";
|
|
38
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
AssetMediaItem,
|
|
41
|
+
FileMediaItem,
|
|
42
|
+
MediaItem,
|
|
43
|
+
MediaMap,
|
|
44
|
+
} from "@noya-app/noya-schemas";
|
|
39
45
|
import { groupBy, isDeepEqual } from "@noya-app/noya-utils";
|
|
40
46
|
import {
|
|
41
47
|
downloadUrl,
|
|
@@ -68,18 +74,17 @@ import {
|
|
|
68
74
|
import { path } from "imfs";
|
|
69
75
|
import React from "react";
|
|
70
76
|
import {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
mediaDeleteMediaItems,
|
|
78
|
+
MediaExpandedMap,
|
|
79
|
+
MediaFileKindFilter,
|
|
80
|
+
mediaGetDepthMap,
|
|
81
|
+
mediaGetVisibleItems,
|
|
82
|
+
mediaMoveMediaInsideFolder,
|
|
83
|
+
mediaMoveUpAFolder,
|
|
84
|
+
mediaRenameMediaItemAndDescendantPaths,
|
|
85
|
+
mediaUpdateExpandedMap,
|
|
86
|
+
mediaValidateMediaItemRename,
|
|
81
87
|
} from "./utils/files";
|
|
82
|
-
import { handleDataTransfer } from "./utils/handleFileDrop";
|
|
83
88
|
|
|
84
89
|
const extensionToContentType = {
|
|
85
90
|
svg: "image/svg+xml",
|
|
@@ -135,10 +140,14 @@ const MediaThumbnailInternal = memoGeneric(
|
|
|
135
140
|
|
|
136
141
|
let contentType: string | undefined;
|
|
137
142
|
let url: string | undefined;
|
|
143
|
+
let width: number | undefined;
|
|
144
|
+
let height: number | undefined;
|
|
138
145
|
|
|
139
146
|
if (asset) {
|
|
140
147
|
contentType = asset.contentType;
|
|
141
148
|
url = asset.url;
|
|
149
|
+
width = asset.width ?? undefined;
|
|
150
|
+
height = asset.height ?? undefined;
|
|
142
151
|
} else if (isFile && pathProp) {
|
|
143
152
|
const encoded = encodeFileContentForThumbnail(pathProp, item);
|
|
144
153
|
|
|
@@ -149,6 +158,7 @@ const MediaThumbnailInternal = memoGeneric(
|
|
|
149
158
|
}
|
|
150
159
|
|
|
151
160
|
const fileName = pathProp ? path.basename(pathProp) : undefined;
|
|
161
|
+
const dimensions = width && height ? { width, height } : undefined;
|
|
152
162
|
|
|
153
163
|
return (
|
|
154
164
|
<MediaThumbnail
|
|
@@ -159,6 +169,7 @@ const MediaThumbnailInternal = memoGeneric(
|
|
|
159
169
|
size={size}
|
|
160
170
|
fileName={fileName}
|
|
161
171
|
renderThumbnailIcon={renderThumbnailIcon}
|
|
172
|
+
dimensions={dimensions}
|
|
162
173
|
/>
|
|
163
174
|
);
|
|
164
175
|
}
|
|
@@ -207,7 +218,7 @@ type MediaCollectionProps = {
|
|
|
207
218
|
*
|
|
208
219
|
* @default "all"
|
|
209
220
|
* */
|
|
210
|
-
fileKindFilter?:
|
|
221
|
+
fileKindFilter?: MediaFileKindFilter;
|
|
211
222
|
/**
|
|
212
223
|
* Whether to show the root item
|
|
213
224
|
*
|
|
@@ -215,7 +226,7 @@ type MediaCollectionProps = {
|
|
|
215
226
|
* */
|
|
216
227
|
showRootItem?: boolean;
|
|
217
228
|
/** Whether to expand all directories by default */
|
|
218
|
-
initialExpanded?:
|
|
229
|
+
initialExpanded?: MediaExpandedMap;
|
|
219
230
|
/**
|
|
220
231
|
* Callback for when an item is double-clicked
|
|
221
232
|
*/
|
|
@@ -241,6 +252,7 @@ type MediaCollectionProps = {
|
|
|
241
252
|
sortable?: boolean;
|
|
242
253
|
renderThumbnailIcon?: MediaThumbnailProps["renderThumbnailIcon"];
|
|
243
254
|
onDidDeleteItems?: (items: [string, MediaItem][]) => void;
|
|
255
|
+
onAssetsUploaded?: (mediaMap: Record<string, AssetMediaItem>) => void;
|
|
244
256
|
} & Pick<
|
|
245
257
|
CollectionProps<MediaItem, MenuAction>,
|
|
246
258
|
| "sortableId"
|
|
@@ -284,6 +296,7 @@ export const MediaCollection = memo(
|
|
|
284
296
|
onClickItem,
|
|
285
297
|
renderThumbnailIcon,
|
|
286
298
|
onDidDeleteItems,
|
|
299
|
+
onAssetsUploaded,
|
|
287
300
|
},
|
|
288
301
|
ref
|
|
289
302
|
) {
|
|
@@ -324,10 +337,10 @@ export const MediaCollection = memo(
|
|
|
324
337
|
);
|
|
325
338
|
const assetManager = useAssetManager();
|
|
326
339
|
const assets = useAssets();
|
|
327
|
-
const [expandedMap, setExpandedMap] = useState<
|
|
340
|
+
const [expandedMap, setExpandedMap] = useState<MediaExpandedMap>({});
|
|
328
341
|
const visibleItems = useMemo(
|
|
329
342
|
() =>
|
|
330
|
-
|
|
343
|
+
mediaGetVisibleItems({
|
|
331
344
|
expandedMap,
|
|
332
345
|
fileKindFilter: fileKindFilter,
|
|
333
346
|
rootItemId,
|
|
@@ -345,7 +358,8 @@ export const MediaCollection = memo(
|
|
|
345
358
|
]
|
|
346
359
|
);
|
|
347
360
|
const depthMap = useMemo(
|
|
348
|
-
() =>
|
|
361
|
+
() =>
|
|
362
|
+
mediaGetDepthMap(rootMediaItem, treeWithTempItem, showAllDescendants),
|
|
349
363
|
[treeWithTempItem, showAllDescendants]
|
|
350
364
|
);
|
|
351
365
|
const collectionRef = useRef<CollectionRef>(null);
|
|
@@ -396,8 +410,18 @@ export const MediaCollection = memo(
|
|
|
396
410
|
[expandedMap, expandable]
|
|
397
411
|
);
|
|
398
412
|
|
|
413
|
+
const openConfirmationDialog = useOpenConfirmationDialog();
|
|
414
|
+
|
|
399
415
|
const handleDelete = useCallback(
|
|
400
|
-
(selectedIds: string[]) => {
|
|
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
|
+
|
|
401
425
|
const deletedItems = Object.entries(media).flatMap(
|
|
402
426
|
([path, item]): [string, MediaItem][] => {
|
|
403
427
|
if (selectedIds.includes(item.id)) {
|
|
@@ -407,7 +431,7 @@ export const MediaCollection = memo(
|
|
|
407
431
|
}
|
|
408
432
|
);
|
|
409
433
|
|
|
410
|
-
const newMedia =
|
|
434
|
+
const newMedia = mediaDeleteMediaItems({
|
|
411
435
|
selectedIds,
|
|
412
436
|
media,
|
|
413
437
|
tree,
|
|
@@ -418,7 +442,14 @@ export const MediaCollection = memo(
|
|
|
418
442
|
|
|
419
443
|
onDidDeleteItems?.(deletedItems);
|
|
420
444
|
},
|
|
421
|
-
[
|
|
445
|
+
[
|
|
446
|
+
media,
|
|
447
|
+
setMedia,
|
|
448
|
+
setSelectedIds,
|
|
449
|
+
tree,
|
|
450
|
+
onDidDeleteItems,
|
|
451
|
+
openConfirmationDialog,
|
|
452
|
+
]
|
|
422
453
|
);
|
|
423
454
|
|
|
424
455
|
const onRename = useCallback(
|
|
@@ -428,7 +459,7 @@ export const MediaCollection = memo(
|
|
|
428
459
|
selectedItem.id
|
|
429
460
|
);
|
|
430
461
|
if (!selectedItemPath) return;
|
|
431
|
-
const renameIsValid =
|
|
462
|
+
const renameIsValid = mediaValidateMediaItemRename({
|
|
432
463
|
basename: newName,
|
|
433
464
|
selectedItemPath,
|
|
434
465
|
media: temp.media,
|
|
@@ -438,7 +469,7 @@ export const MediaCollection = memo(
|
|
|
438
469
|
return;
|
|
439
470
|
}
|
|
440
471
|
const mediaWithRenamedDescendantPaths =
|
|
441
|
-
|
|
472
|
+
mediaRenameMediaItemAndDescendantPaths({
|
|
442
473
|
newName,
|
|
443
474
|
selectedItemPath,
|
|
444
475
|
media: temp.media,
|
|
@@ -492,7 +523,7 @@ export const MediaCollection = memo(
|
|
|
492
523
|
|
|
493
524
|
const handleMoveUpAFolder = useCallback(
|
|
494
525
|
(selectedIds: string[]) => {
|
|
495
|
-
const newMedia =
|
|
526
|
+
const newMedia = mediaMoveUpAFolder({
|
|
496
527
|
tree,
|
|
497
528
|
media,
|
|
498
529
|
selectedIds,
|
|
@@ -527,24 +558,31 @@ export const MediaCollection = memo(
|
|
|
527
558
|
|
|
528
559
|
setIsUploading(true);
|
|
529
560
|
|
|
530
|
-
const
|
|
561
|
+
const uploadedAssets = await Promise.all(uploadPromises);
|
|
562
|
+
const newMediaMap = Object.fromEntries(
|
|
563
|
+
uploadedAssets.map(({ assetPath, asset }) => [assetPath, asset])
|
|
564
|
+
);
|
|
531
565
|
|
|
532
566
|
setMedia(
|
|
533
567
|
{ name: "Add media items", timestamp: Date.now() },
|
|
534
568
|
{
|
|
535
569
|
...media,
|
|
536
|
-
...
|
|
537
|
-
newMediaMap.map(({ assetPath, asset }) => [assetPath, asset])
|
|
538
|
-
),
|
|
570
|
+
...newMediaMap,
|
|
539
571
|
}
|
|
540
572
|
);
|
|
573
|
+
|
|
574
|
+
onAssetsUploaded?.(newMediaMap);
|
|
541
575
|
} catch (error) {
|
|
542
|
-
|
|
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
|
+
}
|
|
543
581
|
} finally {
|
|
544
582
|
setIsUploading(false);
|
|
545
583
|
}
|
|
546
584
|
},
|
|
547
|
-
[tree.idToPathMap, setMedia, media, assetManager]
|
|
585
|
+
[tree.idToPathMap, setMedia, media, assetManager, onAssetsUploaded]
|
|
548
586
|
);
|
|
549
587
|
|
|
550
588
|
const handleDownload = useCallback(
|
|
@@ -598,7 +636,11 @@ export const MediaCollection = memo(
|
|
|
598
636
|
}
|
|
599
637
|
);
|
|
600
638
|
} catch (error) {
|
|
601
|
-
|
|
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
|
+
}
|
|
602
644
|
}
|
|
603
645
|
},
|
|
604
646
|
[media, setMedia, assetManager, tree]
|
|
@@ -613,7 +655,7 @@ export const MediaCollection = memo(
|
|
|
613
655
|
|
|
614
656
|
const handleMoveMediaInsideFolder = useCallback(
|
|
615
657
|
(sourceItem: MediaItem, targetItem: MediaItem) => {
|
|
616
|
-
const newMedia =
|
|
658
|
+
const newMedia = mediaMoveMediaInsideFolder({
|
|
617
659
|
sourceItemIds: [sourceItem.id],
|
|
618
660
|
targetItemId: targetItem.id,
|
|
619
661
|
media,
|
|
@@ -740,7 +782,7 @@ export const MediaCollection = memo(
|
|
|
740
782
|
const handleSetExpanded = useCallback(
|
|
741
783
|
(item: MediaItem, expanded: boolean) => {
|
|
742
784
|
setExpandedMap((prev) =>
|
|
743
|
-
|
|
785
|
+
mediaUpdateExpandedMap({
|
|
744
786
|
item,
|
|
745
787
|
expanded,
|
|
746
788
|
expandable,
|
|
@@ -924,27 +966,6 @@ export const MediaCollection = memo(
|
|
|
924
966
|
handleMoveMediaInsideFolder(sourceItem, targetItem);
|
|
925
967
|
}
|
|
926
968
|
}}
|
|
927
|
-
onFilesDrop={async (event: React.DragEvent<Element>) => {
|
|
928
|
-
event.preventDefault();
|
|
929
|
-
|
|
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
|
-
);
|
|
947
|
-
}}
|
|
948
969
|
/>
|
|
949
970
|
</FileExplorerLayout>
|
|
950
971
|
</>
|