Manav Rathi 1 год назад
Родитель
Сommit
e92bbdb8c6
2 измененных файлов с 52 добавлено и 4 удалено
  1. 37 4
      web/apps/photos/src/components/Upload/Uploader.tsx
  2. 15 0
      web/packages/utils/array.ts

+ 37 - 4
web/apps/photos/src/components/Upload/Uploader.tsx

@@ -1,6 +1,8 @@
 import { basename } from "@/next/file";
 import log from "@/next/log";
 import type { CollectionMapping, Electron, ZipItem } from "@/next/types/ipc";
+import { firstNonEmpty } from "@/utils/array";
+import { ensure } from "@/utils/ensure";
 import { CustomError } from "@ente/shared/error";
 import { isPromise } from "@ente/shared/utils";
 import DiscFullIcon from "@mui/icons-material/DiscFull";
@@ -324,17 +326,17 @@ export default function Uploader({
 
     // Trigger an upload when any of the dependencies change.
     useEffect(() => {
-        // Re the paths:
+        // About the paths:
         //
         // - These are not necessarily the full paths. In particular, when
         //   running on the browser they'll be the relative paths (at best) or
         //   just the file-name otherwise.
         //
         // - All the paths use POSIX separators. See inline comments.
+        //
         const allItemAndPaths = [
-            // See: [Note: webkitRelativePath]. In particular, they use POSIX
-            // separators.
-            webFiles.map((f) => [f, f.webkitRelativePath ?? f.name]),
+            // Relative path (using POSIX separators) or the file's name.
+            webFiles.map((f) => [f, pathLikeForWebFile(f)]),
             // The paths we get from the desktop app all eventually come either
             // from electron.selectDirectory or electron.pathForFile, both of
             // which return POSIX paths.
@@ -822,6 +824,37 @@ const desktopFilesAndZipItems = async (electron: Electron, files: File[]) => {
     return { fileAndPaths, zipItems };
 };
 
+/**
+ * Return the relative path or name of a File object selected or
+ * drag-and-dropped on the web.
+ *
+ * There are three cases here:
+ *
+ * 1. If the user selects individual file(s), then the returned File objects
+ *    will only have a `name`.
+ *
+ * 2. If the user selects directory(ies), then the returned File objects will
+ *    have a `webkitRelativePath`. For more details, see [Note:
+ *    webkitRelativePath]. In particular, these will POSIX separators.
+ *
+ * 3. If the user drags-and-drops, then the react-dropzone library that we use
+ *    will internally convert `webkitRelativePath` to `path`, but otherwise it
+ *    behaves same as case 2.
+ *    https://github.com/react-dropzone/file-selector/blob/master/src/file.ts#L1214
+ */
+const pathLikeForWebFile = (file: File): string =>
+    ensure(
+        firstNonEmpty([
+            // We need to check first, since path is not a property of
+            // the standard File objects.
+            "path" in file && typeof file.path == "string"
+                ? file.path
+                : undefined,
+            file.webkitRelativePath,
+            file.name,
+        ]),
+    );
+
 // This is used to prompt the user the make upload strategy choice
 interface ImportSuggestion {
     rootFolderName: string;

+ 15 - 0
web/packages/utils/array.ts

@@ -13,3 +13,18 @@ export const shuffled = <T>(xs: T[]) =>
         .map((x) => [Math.random(), x])
         .sort()
         .map(([, x]) => x) as T[];
+
+/**
+ * Return the first non-empty string from the given list of strings.
+ *
+ * This function is needed because the `a ?? b` idiom doesn't do what you'd
+ * expect when a is "". Perhaps the behaviour is wrong, perhaps the expecation
+ * is wrong; this function papers over the differences.
+ *
+ * If none of the strings are non-empty, or if there are no strings in the given
+ * array, return undefined.
+ */
+export const firstNonEmpty = (ss: (string | undefined)[]) => {
+    for (const s of ss) if (s && s.length > 0) return s;
+    return undefined;
+};