@noya-app/noya-file-explorer 0.0.2 → 0.0.4
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 +21 -0
- package/dist/index.css +628 -568
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +25738 -13840
- package/dist/index.d.ts +25738 -13840
- package/dist/index.js +424 -138
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +423 -138
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
- package/src/MediaCollection.tsx +128 -44
- package/src/__tests__/renameMediaItemAndDescendantPaths.test.ts +139 -0
- package/src/utils/files.ts +47 -2
- package/tsup.config.ts +1 -3
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.4",
|
|
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.52",
|
|
24
|
+
"@noya-app/noya-icons": "0.1.8",
|
|
25
|
+
"@noya-app/noya-multiplayer-react": "0.1.55",
|
|
26
26
|
"@noya-app/noya-keymap": "0.1.3",
|
|
27
|
-
"@noya-app/react-utils": "0.1.
|
|
27
|
+
"@noya-app/react-utils": "0.1.18",
|
|
28
28
|
"@noya-app/noya-utils": "0.1.5",
|
|
29
|
-
"@noya-app/noya-schemas": "0.1.
|
|
29
|
+
"@noya-app/noya-schemas": "0.1.5",
|
|
30
30
|
"imfs": "^0.1.0",
|
|
31
31
|
"browser-fs-access": "0.35.0"
|
|
32
32
|
},
|
package/src/MediaCollection.tsx
CHANGED
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
useAssetManager,
|
|
33
33
|
useAssets,
|
|
34
34
|
} from "@noya-app/noya-multiplayer-react";
|
|
35
|
-
import { MediaItem, MediaMap } from "@noya-app/noya-schemas";
|
|
35
|
+
import { FileMediaItem, MediaItem, MediaMap } from "@noya-app/noya-schemas";
|
|
36
36
|
import { groupBy, isDeepEqual } from "@noya-app/noya-utils";
|
|
37
37
|
import {
|
|
38
38
|
downloadUrl,
|
|
@@ -71,21 +71,86 @@ import {
|
|
|
71
71
|
getVisibleItems,
|
|
72
72
|
moveMediaInsideFolder,
|
|
73
73
|
moveUpAFolder,
|
|
74
|
+
renameMediaItemAndDescendantPaths,
|
|
74
75
|
updateExpandedMap,
|
|
75
76
|
validateMediaItemRename,
|
|
76
77
|
} from "./utils/files";
|
|
77
78
|
|
|
79
|
+
const extensionToContentType = {
|
|
80
|
+
svg: "image/svg+xml",
|
|
81
|
+
png: "image/png",
|
|
82
|
+
jpeg: "image/jpeg",
|
|
83
|
+
} as const;
|
|
84
|
+
|
|
85
|
+
function encodeFileContentForThumbnail(
|
|
86
|
+
pathProp: string,
|
|
87
|
+
item: FileMediaItem
|
|
88
|
+
): { contentType: string; url?: string } | undefined {
|
|
89
|
+
const extension = path.extname(pathProp).slice(1);
|
|
90
|
+
const contentType =
|
|
91
|
+
extensionToContentType[extension as keyof typeof extensionToContentType];
|
|
92
|
+
|
|
93
|
+
if (contentType) {
|
|
94
|
+
if (item.encoding === "base64") {
|
|
95
|
+
return {
|
|
96
|
+
contentType,
|
|
97
|
+
url: `data:${contentType};base64,${item.content}`,
|
|
98
|
+
};
|
|
99
|
+
} else {
|
|
100
|
+
try {
|
|
101
|
+
return {
|
|
102
|
+
contentType,
|
|
103
|
+
url: `data:${contentType},${encodeURIComponent(item.content)}`,
|
|
104
|
+
};
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.warn("Failed to encode content:", error);
|
|
107
|
+
return { contentType };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
|
|
78
115
|
const MediaThumbnailInternal = memoGeneric(
|
|
79
|
-
({
|
|
116
|
+
({
|
|
117
|
+
item,
|
|
118
|
+
selected,
|
|
119
|
+
size,
|
|
120
|
+
path: pathProp,
|
|
121
|
+
}: CollectionThumbnailProps<MediaItem> & {
|
|
122
|
+
path?: string;
|
|
123
|
+
}) => {
|
|
80
124
|
const asset = useAsset(item.kind === "asset" ? item.assetId : undefined);
|
|
81
125
|
const isRoot = item.id === rootMediaItem.id;
|
|
82
126
|
const isFolder = item.kind === "folder";
|
|
127
|
+
const isFile = item.kind === "file";
|
|
128
|
+
|
|
129
|
+
let contentType: string | undefined;
|
|
130
|
+
let url: string | undefined;
|
|
131
|
+
|
|
132
|
+
if (asset) {
|
|
133
|
+
contentType = asset.contentType;
|
|
134
|
+
url = asset.url;
|
|
135
|
+
} else if (isFile && pathProp) {
|
|
136
|
+
const encoded = encodeFileContentForThumbnail(pathProp, item);
|
|
137
|
+
|
|
138
|
+
if (encoded) {
|
|
139
|
+
contentType = encoded.contentType;
|
|
140
|
+
url = encoded.url;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const fileName = pathProp ? path.basename(pathProp) : undefined;
|
|
145
|
+
|
|
83
146
|
return (
|
|
84
147
|
<MediaThumbnail
|
|
85
|
-
contentType={
|
|
148
|
+
contentType={contentType}
|
|
86
149
|
iconName={isRoot ? "HomeIcon" : isFolder ? "FolderIcon" : undefined}
|
|
87
|
-
url={
|
|
150
|
+
url={url}
|
|
88
151
|
selected={selected}
|
|
152
|
+
size={size}
|
|
153
|
+
fileName={fileName}
|
|
89
154
|
/>
|
|
90
155
|
);
|
|
91
156
|
}
|
|
@@ -120,10 +185,12 @@ type MediaCollectionProps = {
|
|
|
120
185
|
) => void;
|
|
121
186
|
selectedIds?: string[];
|
|
122
187
|
media: MediaMap;
|
|
123
|
-
setMedia
|
|
188
|
+
setMedia?: (
|
|
124
189
|
metadata: Partial<MultiplayerPatchMetadata>,
|
|
125
190
|
media: MediaMap
|
|
126
191
|
) => void;
|
|
192
|
+
/** @default false */
|
|
193
|
+
readOnly?: boolean;
|
|
127
194
|
/** @default "list" */
|
|
128
195
|
viewType?: CollectionViewType;
|
|
129
196
|
/**
|
|
@@ -174,7 +241,8 @@ export const MediaCollection = memo(
|
|
|
174
241
|
onSelectionChange,
|
|
175
242
|
selectedIds: selectedIdsProp,
|
|
176
243
|
media,
|
|
177
|
-
setMedia,
|
|
244
|
+
setMedia: setMediaProp,
|
|
245
|
+
readOnly = false,
|
|
178
246
|
viewType = "list",
|
|
179
247
|
fileKindFilter = "all",
|
|
180
248
|
showRootItem = false,
|
|
@@ -196,18 +264,34 @@ export const MediaCollection = memo(
|
|
|
196
264
|
},
|
|
197
265
|
ref
|
|
198
266
|
) {
|
|
267
|
+
const setMedia = useCallback(
|
|
268
|
+
(...args: Parameters<Extract<typeof setMediaProp, Function>>) => {
|
|
269
|
+
setMediaProp?.(...args);
|
|
270
|
+
},
|
|
271
|
+
[setMediaProp]
|
|
272
|
+
);
|
|
199
273
|
const tree = useMemo(() => createMediaItemTree(media), [media]);
|
|
200
274
|
const [tempItem, setTempItem] = useState<[string, MediaItem] | undefined>(
|
|
201
275
|
undefined
|
|
202
276
|
);
|
|
203
|
-
const
|
|
204
|
-
() =>
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}),
|
|
277
|
+
const mediaWithTempItem = useMemo(
|
|
278
|
+
() => ({
|
|
279
|
+
...media,
|
|
280
|
+
...(tempItem ? { [tempItem[0]]: tempItem[1] } : {}),
|
|
281
|
+
}),
|
|
209
282
|
[media, tempItem]
|
|
210
283
|
);
|
|
284
|
+
const treeWithTempItem = useMemo(
|
|
285
|
+
() => createMediaItemTree(mediaWithTempItem),
|
|
286
|
+
[mediaWithTempItem]
|
|
287
|
+
);
|
|
288
|
+
const temp = useMemo(
|
|
289
|
+
() => ({
|
|
290
|
+
media: mediaWithTempItem,
|
|
291
|
+
tree: treeWithTempItem,
|
|
292
|
+
}),
|
|
293
|
+
[mediaWithTempItem, treeWithTempItem]
|
|
294
|
+
);
|
|
211
295
|
const [selectedIds, setSelectedIds] = useControlledOrUncontrolled<string[]>(
|
|
212
296
|
{
|
|
213
297
|
defaultValue: [],
|
|
@@ -273,14 +357,6 @@ export const MediaCollection = memo(
|
|
|
273
357
|
return itemPath.startsWith(path.dirname(firstSelectedPath));
|
|
274
358
|
});
|
|
275
359
|
|
|
276
|
-
// Convert size prop to grid size if in grid view
|
|
277
|
-
const gridSize = useMemo(() => {
|
|
278
|
-
if (viewType === "grid") {
|
|
279
|
-
return size;
|
|
280
|
-
}
|
|
281
|
-
return "medium";
|
|
282
|
-
}, [viewType, size]);
|
|
283
|
-
|
|
284
360
|
useEffect(() => {
|
|
285
361
|
if (initialExpanded) {
|
|
286
362
|
setExpandedMap(initialExpanded);
|
|
@@ -318,31 +394,29 @@ export const MediaCollection = memo(
|
|
|
318
394
|
selectedItem.id
|
|
319
395
|
);
|
|
320
396
|
if (!selectedItemPath) return;
|
|
321
|
-
const mediaWithTempItem = {
|
|
322
|
-
...media,
|
|
323
|
-
...(tempItem ? { [tempItem[0]]: tempItem[1] } : {}),
|
|
324
|
-
};
|
|
325
397
|
const renameIsValid = validateMediaItemRename({
|
|
326
398
|
basename: newName,
|
|
327
399
|
selectedItemPath,
|
|
328
|
-
media:
|
|
400
|
+
media: temp.media,
|
|
329
401
|
});
|
|
330
402
|
if (!renameIsValid) {
|
|
331
403
|
setTempItem(undefined);
|
|
332
404
|
return;
|
|
333
405
|
}
|
|
334
|
-
const
|
|
335
|
-
|
|
406
|
+
const mediaWithRenamedDescendantPaths =
|
|
407
|
+
renameMediaItemAndDescendantPaths({
|
|
408
|
+
newName,
|
|
409
|
+
selectedItemPath,
|
|
410
|
+
media: temp.media,
|
|
411
|
+
tree: temp.tree,
|
|
412
|
+
});
|
|
336
413
|
setMedia(
|
|
337
414
|
{ name: "Rename media item", timestamp: Date.now() },
|
|
338
|
-
|
|
339
|
-
...mediaClone,
|
|
340
|
-
[path.join(path.dirname(selectedItemPath), newName)]: selectedItem,
|
|
341
|
-
}
|
|
415
|
+
mediaWithRenamedDescendantPaths
|
|
342
416
|
);
|
|
343
417
|
setTempItem(undefined);
|
|
344
418
|
},
|
|
345
|
-
[
|
|
419
|
+
[renamable, setMedia, temp.media, temp.tree, treeWithTempItem.idToPathMap]
|
|
346
420
|
);
|
|
347
421
|
|
|
348
422
|
const handleAddFolder = useCallback(
|
|
@@ -650,10 +724,12 @@ export const MediaCollection = memo(
|
|
|
650
724
|
onSelect={(action) => handleMenuAction(action, selectedMediaItems)}
|
|
651
725
|
selected={selected}
|
|
652
726
|
onOpenChange={onOpenChange}
|
|
727
|
+
variant={viewType === "grid" ? "normal" : "bare"}
|
|
653
728
|
style={{
|
|
654
729
|
backgroundColor: selected
|
|
655
|
-
? cssVars.colors.
|
|
730
|
+
? cssVars.colors.selectedListItemBackground
|
|
656
731
|
: "transparent",
|
|
732
|
+
color: selected ? cssVars.colors.selectedListItemText : undefined,
|
|
657
733
|
}}
|
|
658
734
|
/>
|
|
659
735
|
);
|
|
@@ -662,6 +738,7 @@ export const MediaCollection = memo(
|
|
|
662
738
|
handleMenuAction,
|
|
663
739
|
renderActionProp,
|
|
664
740
|
selectedMediaItems,
|
|
741
|
+
viewType,
|
|
665
742
|
]);
|
|
666
743
|
|
|
667
744
|
useImperativeHandle(ref, () => ({
|
|
@@ -682,12 +759,14 @@ export const MediaCollection = memo(
|
|
|
682
759
|
<FileExplorerLayout
|
|
683
760
|
title={title ?? rootMediaItemName}
|
|
684
761
|
right={
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
762
|
+
!readOnly && (
|
|
763
|
+
<FileExplorerUploadButton
|
|
764
|
+
showUploadButton={showUploadButton}
|
|
765
|
+
onUpload={() => handleUpload(rootMediaItem.id)}
|
|
766
|
+
>
|
|
767
|
+
{right}
|
|
768
|
+
</FileExplorerUploadButton>
|
|
769
|
+
)
|
|
691
770
|
}
|
|
692
771
|
className={className}
|
|
693
772
|
>
|
|
@@ -696,7 +775,7 @@ export const MediaCollection = memo(
|
|
|
696
775
|
scrollable={scrollable}
|
|
697
776
|
items={visibleItems}
|
|
698
777
|
viewType={viewType}
|
|
699
|
-
size={
|
|
778
|
+
size={size}
|
|
700
779
|
getId={(file) => file.id}
|
|
701
780
|
getName={(file) => {
|
|
702
781
|
if (file.id === tempItem?.[1].id) {
|
|
@@ -729,14 +808,19 @@ export const MediaCollection = memo(
|
|
|
729
808
|
onRename={onRename}
|
|
730
809
|
renamable={renamable}
|
|
731
810
|
selectedIds={selectedIds}
|
|
732
|
-
renderThumbnail={(props) =>
|
|
811
|
+
renderThumbnail={(props) => (
|
|
812
|
+
<MediaThumbnailInternal
|
|
813
|
+
{...props}
|
|
814
|
+
path={tree.idToPathMap.get(props.item.id)}
|
|
815
|
+
/>
|
|
816
|
+
)}
|
|
733
817
|
renderAction={renderAction}
|
|
734
818
|
renderDetail={(file, selected) => {
|
|
735
819
|
if (file.kind !== "asset") return null;
|
|
736
820
|
const asset = assets.find((a) => a.id === file.assetId);
|
|
737
821
|
if (!asset) return null;
|
|
738
822
|
return (
|
|
739
|
-
<FileExplorerDetail selected={selected}>
|
|
823
|
+
<FileExplorerDetail selected={selected} size={size}>
|
|
740
824
|
{(asset.size / 1024).toFixed(1)}KB
|
|
741
825
|
</FileExplorerDetail>
|
|
742
826
|
);
|
|
@@ -763,11 +847,11 @@ export const MediaCollection = memo(
|
|
|
763
847
|
if (position !== "inside" || targetItem.kind === "asset") {
|
|
764
848
|
return false;
|
|
765
849
|
}
|
|
766
|
-
const sourcePath = tree.
|
|
850
|
+
const sourcePath = tree.findPath(
|
|
767
851
|
rootMediaItem,
|
|
768
852
|
(item) => item.id === sourceItem.id
|
|
769
853
|
);
|
|
770
|
-
const targetPath = tree.
|
|
854
|
+
const targetPath = tree.findPath(
|
|
771
855
|
rootMediaItem,
|
|
772
856
|
(item) => item.id === targetItem.id
|
|
773
857
|
);
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { MediaMap } from "@noya-app/noya-schemas";
|
|
2
|
+
import { expect, it } from "bun:test";
|
|
3
|
+
import { renameMediaItemAndDescendantPaths } from "../utils/files";
|
|
4
|
+
import {
|
|
5
|
+
createMediaAsset,
|
|
6
|
+
createMediaFolder,
|
|
7
|
+
createMediaItemTree,
|
|
8
|
+
} from "../utils/mediaItemTree";
|
|
9
|
+
|
|
10
|
+
it("should rename a folder with no children", () => {
|
|
11
|
+
const dir1 = createMediaFolder();
|
|
12
|
+
const media: MediaMap = { dir1 };
|
|
13
|
+
|
|
14
|
+
const tree = createMediaItemTree(media);
|
|
15
|
+
|
|
16
|
+
const result = renameMediaItemAndDescendantPaths({
|
|
17
|
+
newName: "renamed-dir",
|
|
18
|
+
selectedItemPath: "dir1",
|
|
19
|
+
media,
|
|
20
|
+
tree,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
expect(result).toEqual({ "renamed-dir": dir1 });
|
|
24
|
+
expect(result).not.toHaveProperty("dir1");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should rename a folder and update paths of its immediate children", () => {
|
|
28
|
+
const dir1 = createMediaFolder();
|
|
29
|
+
const child1 = createMediaAsset({ assetId: "asset1" });
|
|
30
|
+
const child2 = createMediaFolder();
|
|
31
|
+
const media: MediaMap = {
|
|
32
|
+
dir1,
|
|
33
|
+
"dir1/child1": child1,
|
|
34
|
+
"dir1/child2": child2,
|
|
35
|
+
};
|
|
36
|
+
const tree = createMediaItemTree(media);
|
|
37
|
+
|
|
38
|
+
const result = renameMediaItemAndDescendantPaths({
|
|
39
|
+
newName: "renamed-dir",
|
|
40
|
+
selectedItemPath: "dir1",
|
|
41
|
+
media,
|
|
42
|
+
tree,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(result).toEqual({
|
|
46
|
+
"renamed-dir": dir1,
|
|
47
|
+
"renamed-dir/child1": child1,
|
|
48
|
+
"renamed-dir/child2": child2,
|
|
49
|
+
});
|
|
50
|
+
expect(result).not.toHaveProperty("dir1");
|
|
51
|
+
expect(result).not.toHaveProperty("dir1/child1");
|
|
52
|
+
expect(result).not.toHaveProperty("dir1/child2");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should rename a folder and update paths of all nested descendants", () => {
|
|
56
|
+
const dir1 = createMediaFolder();
|
|
57
|
+
const dir2 = createMediaFolder();
|
|
58
|
+
const dir3 = createMediaFolder();
|
|
59
|
+
const asset1 = createMediaAsset({ assetId: "asset1" });
|
|
60
|
+
const media: MediaMap = {
|
|
61
|
+
dir1,
|
|
62
|
+
"dir1/dir2": dir2,
|
|
63
|
+
"dir1/dir2/dir3": dir3,
|
|
64
|
+
"dir1/dir2/dir3/asset1": asset1,
|
|
65
|
+
};
|
|
66
|
+
const tree = createMediaItemTree(media);
|
|
67
|
+
|
|
68
|
+
const result = renameMediaItemAndDescendantPaths({
|
|
69
|
+
newName: "renamed-dir",
|
|
70
|
+
selectedItemPath: "dir1",
|
|
71
|
+
media,
|
|
72
|
+
tree,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
expect(result).toEqual({
|
|
76
|
+
"renamed-dir": dir1,
|
|
77
|
+
"renamed-dir/dir2": dir2,
|
|
78
|
+
"renamed-dir/dir2/dir3": dir3,
|
|
79
|
+
"renamed-dir/dir2/dir3/asset1": asset1,
|
|
80
|
+
});
|
|
81
|
+
expect(result).not.toHaveProperty("dir1");
|
|
82
|
+
expect(result).not.toHaveProperty("dir1/dir2");
|
|
83
|
+
expect(result).not.toHaveProperty("dir1/dir2/dir3");
|
|
84
|
+
expect(result).not.toHaveProperty("dir1/dir2/dir3/asset1");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("should rename a file (asset) correctly", () => {
|
|
88
|
+
const dir1 = createMediaFolder();
|
|
89
|
+
const asset1 = createMediaAsset({ assetId: "asset1" });
|
|
90
|
+
const asset2 = createMediaAsset({ assetId: "asset2" });
|
|
91
|
+
const media: MediaMap = {
|
|
92
|
+
dir1,
|
|
93
|
+
"dir1/asset1": asset1,
|
|
94
|
+
"dir1/asset2": asset2,
|
|
95
|
+
};
|
|
96
|
+
const tree = createMediaItemTree(media);
|
|
97
|
+
|
|
98
|
+
const result = renameMediaItemAndDescendantPaths({
|
|
99
|
+
newName: "renamed-asset.png",
|
|
100
|
+
selectedItemPath: "dir1/asset1",
|
|
101
|
+
media,
|
|
102
|
+
tree,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
expect(result).toEqual({
|
|
106
|
+
dir1,
|
|
107
|
+
"dir1/renamed-asset.png": asset1,
|
|
108
|
+
"dir1/asset2": asset2,
|
|
109
|
+
});
|
|
110
|
+
expect(result).not.toHaveProperty("dir1/asset1");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("should preserve other unrelated paths when renaming", () => {
|
|
114
|
+
const dir1 = createMediaFolder();
|
|
115
|
+
const dir2 = createMediaFolder();
|
|
116
|
+
const asset1 = createMediaAsset({ assetId: "asset1" });
|
|
117
|
+
const asset2 = createMediaAsset({ assetId: "asset2" });
|
|
118
|
+
const media: MediaMap = {
|
|
119
|
+
dir1,
|
|
120
|
+
dir2,
|
|
121
|
+
"dir1/asset1": asset1,
|
|
122
|
+
"dir2/asset2": asset2,
|
|
123
|
+
};
|
|
124
|
+
const tree = createMediaItemTree(media);
|
|
125
|
+
|
|
126
|
+
const result = renameMediaItemAndDescendantPaths({
|
|
127
|
+
newName: "renamed-dir",
|
|
128
|
+
selectedItemPath: "dir1",
|
|
129
|
+
media,
|
|
130
|
+
tree,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
expect(result).toEqual({
|
|
134
|
+
"renamed-dir": dir1,
|
|
135
|
+
"renamed-dir/asset1": asset1,
|
|
136
|
+
dir2,
|
|
137
|
+
"dir2/asset2": asset2,
|
|
138
|
+
});
|
|
139
|
+
});
|
package/src/utils/files.ts
CHANGED
|
@@ -90,6 +90,7 @@ export const validateMediaItemRename = ({
|
|
|
90
90
|
if (newPathExists) return false;
|
|
91
91
|
return true;
|
|
92
92
|
};
|
|
93
|
+
|
|
93
94
|
export const movePathsIntoTarget = ({
|
|
94
95
|
media,
|
|
95
96
|
sourceItemPaths,
|
|
@@ -135,6 +136,7 @@ export const movePathsIntoTarget = ({
|
|
|
135
136
|
}
|
|
136
137
|
return mediaClone;
|
|
137
138
|
};
|
|
139
|
+
|
|
138
140
|
export const moveUpAFolder = ({
|
|
139
141
|
tree,
|
|
140
142
|
media,
|
|
@@ -144,7 +146,7 @@ export const moveUpAFolder = ({
|
|
|
144
146
|
media: MediaMap;
|
|
145
147
|
selectedIds: string[];
|
|
146
148
|
}) => {
|
|
147
|
-
const indexPath = tree.
|
|
149
|
+
const indexPath = tree.findPath(
|
|
148
150
|
rootMediaItem,
|
|
149
151
|
(item) => item.id === selectedIds[0]
|
|
150
152
|
);
|
|
@@ -168,6 +170,7 @@ export const moveUpAFolder = ({
|
|
|
168
170
|
tree,
|
|
169
171
|
});
|
|
170
172
|
};
|
|
173
|
+
|
|
171
174
|
export const getDepthMap = (
|
|
172
175
|
item: MediaItem,
|
|
173
176
|
tree: MediaItemTree,
|
|
@@ -183,6 +186,7 @@ export const getDepthMap = (
|
|
|
183
186
|
});
|
|
184
187
|
return depthMap;
|
|
185
188
|
};
|
|
189
|
+
|
|
186
190
|
export const updateExpandedMap = ({
|
|
187
191
|
item,
|
|
188
192
|
expanded,
|
|
@@ -211,6 +215,7 @@ export const updateExpandedMap = ({
|
|
|
211
215
|
inner(item, expanded);
|
|
212
216
|
return newExpandedMap;
|
|
213
217
|
};
|
|
218
|
+
|
|
214
219
|
export const deleteMediaItems = ({
|
|
215
220
|
selectedIds,
|
|
216
221
|
media,
|
|
@@ -235,6 +240,7 @@ export const deleteMediaItems = ({
|
|
|
235
240
|
Object.entries(media).filter(([key]) => !itemKeysToDelete.has(key))
|
|
236
241
|
);
|
|
237
242
|
};
|
|
243
|
+
|
|
238
244
|
export const moveMediaInsideFolder = ({
|
|
239
245
|
sourceItemIds,
|
|
240
246
|
targetItemId,
|
|
@@ -260,10 +266,11 @@ export const moveMediaInsideFolder = ({
|
|
|
260
266
|
tree,
|
|
261
267
|
});
|
|
262
268
|
};
|
|
269
|
+
|
|
263
270
|
export const getParentDirectories = (mediaMap: MediaMap, folderId: string) => {
|
|
264
271
|
const tree = createMediaItemTree(mediaMap);
|
|
265
272
|
|
|
266
|
-
const indexPath = tree.
|
|
273
|
+
const indexPath = tree.findPath(
|
|
267
274
|
rootMediaItem,
|
|
268
275
|
(item) => item.id === folderId
|
|
269
276
|
);
|
|
@@ -272,3 +279,41 @@ export const getParentDirectories = (mediaMap: MediaMap, folderId: string) => {
|
|
|
272
279
|
|
|
273
280
|
return tree.accessPath(rootMediaItem, indexPath);
|
|
274
281
|
};
|
|
282
|
+
|
|
283
|
+
export const renameMediaItemAndDescendantPaths = ({
|
|
284
|
+
newName,
|
|
285
|
+
selectedItemPath,
|
|
286
|
+
media,
|
|
287
|
+
tree,
|
|
288
|
+
}: {
|
|
289
|
+
newName: string;
|
|
290
|
+
selectedItemPath: string;
|
|
291
|
+
media: MediaMap;
|
|
292
|
+
tree: MediaItemTree;
|
|
293
|
+
}) => {
|
|
294
|
+
const mediaClone = { ...media };
|
|
295
|
+
const selectedItem = mediaClone[selectedItemPath];
|
|
296
|
+
if (!selectedItem) return mediaClone;
|
|
297
|
+
|
|
298
|
+
const parentPath = path.dirname(selectedItemPath);
|
|
299
|
+
const newItemPath = path.join(parentPath, newName);
|
|
300
|
+
|
|
301
|
+
const descendants = tree
|
|
302
|
+
.flat(selectedItem)
|
|
303
|
+
.map((item) => tree.idToPathMap.get(item.id));
|
|
304
|
+
|
|
305
|
+
// Update all descendants' paths
|
|
306
|
+
for (const descendantPath of descendants) {
|
|
307
|
+
if (!descendantPath) continue;
|
|
308
|
+
|
|
309
|
+
// Use the same approach as movePathsIntoTarget
|
|
310
|
+
const newDescendantPath = descendantPath.replace(
|
|
311
|
+
selectedItemPath,
|
|
312
|
+
newItemPath
|
|
313
|
+
);
|
|
314
|
+
mediaClone[newDescendantPath] = mediaClone[descendantPath];
|
|
315
|
+
delete mediaClone[descendantPath];
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return mediaClone;
|
|
319
|
+
};
|
package/tsup.config.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import fixReactVirtualized from "esbuild-plugin-react-virtualized";
|
|
2
1
|
import { defineConfig } from "tsup";
|
|
3
2
|
|
|
4
3
|
export default defineConfig({
|
|
@@ -6,8 +5,7 @@ export default defineConfig({
|
|
|
6
5
|
format: ["cjs", "esm"],
|
|
7
6
|
dts: true,
|
|
8
7
|
sourcemap: true,
|
|
9
|
-
noExternal: ["
|
|
10
|
-
esbuildPlugins: [fixReactVirtualized],
|
|
8
|
+
noExternal: ["@noya-app/noya-designsystem/index.css"],
|
|
11
9
|
// https://github.com/egoist/tsup/issues/619
|
|
12
10
|
splitting: false,
|
|
13
11
|
});
|