@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noya-app/noya-file-explorer",
3
- "version": "0.0.16",
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.64",
24
- "@noya-app/noya-icons": "0.1.12",
25
- "@noya-app/noya-multiplayer-react": "0.1.63",
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.24",
28
- "@noya-app/noya-utils": "0.1.6",
29
- "@noya-app/noya-schemas": "0.1.6",
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
  },
@@ -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 { FileMediaItem, MediaItem, MediaMap } from "@noya-app/noya-schemas";
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
- deleteMediaItems,
72
- ExpandedMap,
73
- FileKindFilter,
74
- getDepthMap,
75
- getVisibleItems,
76
- moveMediaInsideFolder,
77
- moveUpAFolder,
78
- renameMediaItemAndDescendantPaths,
79
- updateExpandedMap,
80
- validateMediaItemRename,
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?: 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?: ExpandedMap;
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<ExpandedMap>({});
340
+ const [expandedMap, setExpandedMap] = useState<MediaExpandedMap>({});
328
341
  const visibleItems = useMemo(
329
342
  () =>
330
- getVisibleItems({
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
- () => getDepthMap(rootMediaItem, treeWithTempItem, showAllDescendants),
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 = deleteMediaItems({
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
- [media, setMedia, setSelectedIds, tree, onDidDeleteItems]
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 = validateMediaItemRename({
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
- renameMediaItemAndDescendantPaths({
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 = moveUpAFolder({
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 newMediaMap = await Promise.all(uploadPromises);
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
- ...Object.fromEntries(
537
- newMediaMap.map(({ assetPath, asset }) => [assetPath, asset])
538
- ),
570
+ ...newMediaMap,
539
571
  }
540
572
  );
573
+
574
+ onAssetsUploaded?.(newMediaMap);
541
575
  } catch (error) {
542
- console.error("Failed to upload files:", error);
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
- console.error("Failed to upload file:", error);
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 = moveMediaInsideFolder({
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
- updateExpandedMap({
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
  </>