Преглед на файлове

feat: Implement the Web Share Target API (#3230)

* Use the web share target API

* Make requested changes

* Remove line

* Add application/json back

* Add application/vnd.excalidraw+json

* Add 'POST' check back

* Make requested changes

* Update src/appState.ts

Co-authored-by: Thomas Steiner <tomac@google.com>

* Update test

* Override initializeScene

* Use Excalidraw MIME type

* Minor fixes

* More MIME type tweaks

* More permissive file open

* Be overpermissive in file open

Co-authored-by: Thomas Steiner <tomac@google.com>
Co-authored-by: tomayac <steiner.thomas@gmail.com>
Arun преди 4 години
родител
ревизия
b9e70ec666
променени са 6 файла, в които са добавени 428 реда и са изтрити 407 реда
  1. 1 1
      package.json
  2. 14 1
      public/manifest.json
  3. 41 16
      src/components/App.tsx
  4. 16 4
      src/data/json.ts
  5. 17 0
      src/service-worker.js
  6. 339 385
      yarn.lock

+ 1 - 1
package.json

@@ -27,7 +27,7 @@
     "@types/react": "17.0.2",
     "@types/react-dom": "17.0.1",
     "@types/socket.io-client": "1.4.35",
-    "browser-fs-access": "0.14.1",
+    "browser-fs-access": "0.14.2",
     "clsx": "1.1.1",
     "firebase": "8.2.10",
     "i18next-browser-languagedetector": "6.0.1",

+ 14 - 1
public/manifest.json

@@ -26,5 +26,18 @@
       }
     }
   ],
-  "capture_links": "new_client"
+  "capture_links": "new_client",
+  "share_target": {
+    "action": "/web-share-target",
+    "method": "POST",
+    "enctype": "multipart/form-data",
+    "params": {
+      "files": [
+        {
+          "name": "file",
+          "accept": ["application/vnd.excalidraw+json", "application/json", ".excalidraw"]
+        }
+      ]
+    }
+  }
 }

+ 41 - 16
src/components/App.tsx

@@ -737,11 +737,16 @@ class App extends React.Component<ExcalidrawProps, AppState> {
     this.scene.addCallback(this.onSceneUpdated);
     this.addEventListeners();
 
-    // optim to avoid extra render on init
-    if (
+    const searchParams = new URLSearchParams(window.location.search.slice(1));
+
+    if (searchParams.has("web-share-target")) {
+      // Obtain a file that was shared via the Web Share Target API.
+      this.restoreFileFromShare();
+    } else if (
       typeof this.props.offsetLeft === "number" &&
       typeof this.props.offsetTop === "number"
     ) {
+      // Optimization to avoid extra render on init.
       this.initializeScene();
     } else {
       this.setState(this.getCanvasOffsets(this.props), () => {
@@ -1278,6 +1283,22 @@ class App extends React.Component<ExcalidrawProps, AppState> {
     this.setState({ toastMessage: null });
   };
 
+  restoreFileFromShare = async () => {
+    try {
+      const webShareTargetCache = await caches.open("web-share-target");
+
+      const file = await webShareTargetCache.match("shared-file");
+      if (file) {
+        const blob = await file.blob();
+        this.loadFileToCanvas(blob);
+        await webShareTargetCache.delete("shared-file");
+        window.history.replaceState(null, APP_NAME, window.location.pathname);
+      }
+    } catch (error) {
+      this.setState({ errorMessage: error.message });
+    }
+  };
+
   public updateScene = withBatchedUpdates((sceneData: SceneData) => {
     if (sceneData.commitToHistory) {
       history.resumeRecording();
@@ -3576,20 +3597,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
           console.warn(error.name, error.message);
         }
       }
-      loadFromBlob(file, this.state)
-        .then(({ elements, appState }) =>
-          this.syncActionResult({
-            elements,
-            appState: {
-              ...(appState || this.state),
-              isLoading: false,
-            },
-            commitToHistory: true,
-          }),
-        )
-        .catch((error) => {
-          this.setState({ isLoading: false, errorMessage: error.message });
-        });
+      this.loadFileToCanvas(file);
     } else if (
       file?.type === MIME_TYPES.excalidrawlib ||
       file?.name.endsWith(".excalidrawlib")
@@ -3609,6 +3617,23 @@ class App extends React.Component<ExcalidrawProps, AppState> {
     }
   };
 
+  loadFileToCanvas = (file: Blob) => {
+    loadFromBlob(file, this.state)
+      .then(({ elements, appState }) =>
+        this.syncActionResult({
+          elements,
+          appState: {
+            ...(appState || this.state),
+            isLoading: false,
+          },
+          commitToHistory: true,
+        }),
+      )
+      .catch((error) => {
+        this.setState({ isLoading: false, errorMessage: error.message });
+      });
+  };
+
   private handleCanvasContextMenu = (
     event: React.PointerEvent<HTMLCanvasElement>,
   ) => {

+ 16 - 4
src/data/json.ts

@@ -30,13 +30,13 @@ export const saveAsJSON = async (
 ) => {
   const serialized = serializeAsJSON(elements, appState);
   const blob = new Blob([serialized], {
-    type: "application/json",
+    type: MIME_TYPES.excalidraw,
   });
 
   const fileHandle = await fileSave(
     blob,
     {
-      fileName: appState.name,
+      fileName: `${appState.name}.excalidraw`,
       description: "Excalidraw file",
       extensions: [".excalidraw"],
     },
@@ -48,8 +48,17 @@ export const saveAsJSON = async (
 export const loadFromJSON = async (localAppState: AppState) => {
   const blob = await fileOpen({
     description: "Excalidraw files",
+    // ToDo: Be over-permissive until https://bugs.webkit.org/show_bug.cgi?id=34442
+    // gets resolved. Else, iOS users cannot open `.excalidraw` files.
+    /*
     extensions: [".json", ".excalidraw", ".png", ".svg"],
-    mimeTypes: ["application/json", "image/png", "image/svg+xml"],
+    mimeTypes: [
+      MIME_TYPES.excalidraw,
+      "application/json",
+      "image/png",
+      "image/svg+xml",
+    ],
+    */
   });
   return loadFromBlob(blob, localAppState);
 };
@@ -101,8 +110,11 @@ export const saveLibraryAsJSON = async () => {
 export const importLibraryFromJSON = async () => {
   const blob = await fileOpen({
     description: "Excalidraw library files",
+    // ToDo: Be over-permissive until https://bugs.webkit.org/show_bug.cgi?id=34442
+    // gets resolved. Else, iOS users cannot open `.excalidraw` files.
+    /*
     extensions: [".json", ".excalidrawlib"],
-    mimeTypes: ["application/json"],
+    */
   });
   Library.importLibrary(blob);
 };

+ 17 - 0
src/service-worker.js

@@ -47,3 +47,20 @@ workbox.routing.registerRoute(
     plugins: [new workbox.expiration.Plugin({ maxEntries: 10 })],
   }),
 );
+
+self.addEventListener("fetch", (event) => {
+  if (
+    event.request.method === "POST" &&
+    event.request.url.endsWith("/web-share-target")
+  ) {
+    return event.respondWith(
+      (async () => {
+        const formData = await event.request.formData();
+        const file = formData.get("file");
+        const webShareTargetCache = await caches.open("web-share-target");
+        await webShareTargetCache.put("shared-file", new Response(file));
+        return Response.redirect("/?web-share-target", 303);
+      })(),
+    );
+  }
+});

Файловите разлики са ограничени, защото са твърде много
+ 339 - 385
yarn.lock


Някои файлове не бяха показани, защото твърде много файлове са промени