mirror of
https://github.com/excalidraw/excalidraw.git
synced 2024-11-10 11:35:52 +01:00
feat: support importing scene from url (#2726)
This commit is contained in:
parent
9b58efd363
commit
beffc290fd
@ -5,8 +5,9 @@ import { CanvasError } from "../errors";
|
|||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { calculateScrollCenter } from "../scene";
|
import { calculateScrollCenter } from "../scene";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
|
import { isValidExcalidrawData } from "./json";
|
||||||
import { restore } from "./restore";
|
import { restore } from "./restore";
|
||||||
import { ImportedDataState, LibraryData } from "./types";
|
import { LibraryData } from "./types";
|
||||||
|
|
||||||
const parseFileContents = async (blob: Blob | File) => {
|
const parseFileContents = async (blob: Blob | File) => {
|
||||||
let contents: string;
|
let contents: string;
|
||||||
@ -85,8 +86,8 @@ export const loadFromBlob = async (
|
|||||||
) => {
|
) => {
|
||||||
const contents = await parseFileContents(blob);
|
const contents = await parseFileContents(blob);
|
||||||
try {
|
try {
|
||||||
const data: ImportedDataState = JSON.parse(contents);
|
const data = JSON.parse(contents);
|
||||||
if (data.type !== "excalidraw") {
|
if (!isValidExcalidrawData(data)) {
|
||||||
throw new Error(t("alerts.couldNotLoadInvalidFile"));
|
throw new Error(t("alerts.couldNotLoadInvalidFile"));
|
||||||
}
|
}
|
||||||
const result = restore(
|
const result = restore(
|
||||||
|
@ -6,6 +6,7 @@ import { ExcalidrawElement } from "../element/types";
|
|||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
import { loadFromBlob } from "./blob";
|
import { loadFromBlob } from "./blob";
|
||||||
import { Library } from "./library";
|
import { Library } from "./library";
|
||||||
|
import { ImportedDataState } from "./types";
|
||||||
|
|
||||||
export const serializeAsJSON = (
|
export const serializeAsJSON = (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
@ -53,6 +54,19 @@ export const loadFromJSON = async (localAppState: AppState) => {
|
|||||||
return loadFromBlob(blob, localAppState);
|
return loadFromBlob(blob, localAppState);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isValidExcalidrawData = (data?: {
|
||||||
|
type?: any;
|
||||||
|
elements?: any;
|
||||||
|
appState?: any;
|
||||||
|
}): data is ImportedDataState => {
|
||||||
|
return (
|
||||||
|
data?.type === "excalidraw" &&
|
||||||
|
(!data.elements ||
|
||||||
|
(Array.isArray(data.elements) &&
|
||||||
|
(!data.appState || typeof data.appState === "object")))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const isValidLibrary = (json: any) => {
|
export const isValidLibrary = (json: any) => {
|
||||||
return (
|
return (
|
||||||
typeof json === "object" &&
|
typeof json === "object" &&
|
||||||
|
@ -13,6 +13,7 @@ import { ExcalidrawImperativeAPI } from "../components/App";
|
|||||||
import { ErrorDialog } from "../components/ErrorDialog";
|
import { ErrorDialog } from "../components/ErrorDialog";
|
||||||
import { TopErrorBoundary } from "../components/TopErrorBoundary";
|
import { TopErrorBoundary } from "../components/TopErrorBoundary";
|
||||||
import { APP_NAME, EVENT, TITLE_TIMEOUT, VERSION_TIMEOUT } from "../constants";
|
import { APP_NAME, EVENT, TITLE_TIMEOUT, VERSION_TIMEOUT } from "../constants";
|
||||||
|
import { loadFromBlob } from "../data/blob";
|
||||||
import { DataState, ImportedDataState } from "../data/types";
|
import { DataState, ImportedDataState } from "../data/types";
|
||||||
import {
|
import {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
@ -69,9 +70,10 @@ const initializeScene = async (opts: {
|
|||||||
}): Promise<ImportedDataState | null> => {
|
}): Promise<ImportedDataState | null> => {
|
||||||
const searchParams = new URLSearchParams(window.location.search);
|
const searchParams = new URLSearchParams(window.location.search);
|
||||||
const id = searchParams.get("id");
|
const id = searchParams.get("id");
|
||||||
const jsonMatch = window.location.hash.match(
|
const jsonBackendMatch = window.location.hash.match(
|
||||||
/^#json=([0-9]+),([a-zA-Z0-9_-]+)$/,
|
/^#json=([0-9]+),([a-zA-Z0-9_-]+)$/,
|
||||||
);
|
);
|
||||||
|
const externalUrlMatch = window.location.hash.match(/^#url=(.*)$/);
|
||||||
|
|
||||||
const initialData = importFromLocalStorage();
|
const initialData = importFromLocalStorage();
|
||||||
|
|
||||||
@ -82,7 +84,7 @@ const initializeScene = async (opts: {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let roomLinkData = getCollaborationLinkData(window.location.href);
|
let roomLinkData = getCollaborationLinkData(window.location.href);
|
||||||
const isExternalScene = !!(id || jsonMatch || roomLinkData);
|
const isExternalScene = !!(id || jsonBackendMatch || roomLinkData);
|
||||||
if (isExternalScene) {
|
if (isExternalScene) {
|
||||||
if (
|
if (
|
||||||
// don't prompt if scene is empty
|
// don't prompt if scene is empty
|
||||||
@ -95,8 +97,12 @@ const initializeScene = async (opts: {
|
|||||||
// Backwards compatibility with legacy url format
|
// Backwards compatibility with legacy url format
|
||||||
if (id) {
|
if (id) {
|
||||||
scene = await loadScene(id, null, initialData);
|
scene = await loadScene(id, null, initialData);
|
||||||
} else if (jsonMatch) {
|
} else if (jsonBackendMatch) {
|
||||||
scene = await loadScene(jsonMatch[1], jsonMatch[2], initialData);
|
scene = await loadScene(
|
||||||
|
jsonBackendMatch[1],
|
||||||
|
jsonBackendMatch[2],
|
||||||
|
initialData,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
scene.scrollToCenter = true;
|
scene.scrollToCenter = true;
|
||||||
if (!roomLinkData) {
|
if (!roomLinkData) {
|
||||||
@ -119,7 +125,28 @@ const initializeScene = async (opts: {
|
|||||||
roomLinkData = null;
|
roomLinkData = null;
|
||||||
window.history.replaceState({}, APP_NAME, window.location.origin);
|
window.history.replaceState({}, APP_NAME, window.location.origin);
|
||||||
}
|
}
|
||||||
|
} else if (externalUrlMatch) {
|
||||||
|
window.history.replaceState({}, APP_NAME, window.location.origin);
|
||||||
|
|
||||||
|
const url = externalUrlMatch[1];
|
||||||
|
try {
|
||||||
|
const request = await fetch(window.decodeURIComponent(url));
|
||||||
|
const data = await loadFromBlob(await request.blob(), null);
|
||||||
|
if (
|
||||||
|
!scene.elements.length ||
|
||||||
|
window.confirm(t("alerts.loadSceneOverridePrompt"))
|
||||||
|
) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
appState: {
|
||||||
|
errorMessage: t("alerts.invalidSceneUrl"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (roomLinkData) {
|
if (roomLinkData) {
|
||||||
return opts.collabAPI.initializeSocketClient(roomLinkData);
|
return opts.collabAPI.initializeSocketClient(roomLinkData);
|
||||||
} else if (scene) {
|
} else if (scene) {
|
||||||
|
@ -142,6 +142,7 @@
|
|||||||
"confirmAddLibrary": "This will add {{numShapes}} shape(s) to your library. Are you sure?",
|
"confirmAddLibrary": "This will add {{numShapes}} shape(s) to your library. Are you sure?",
|
||||||
"imageDoesNotContainScene": "Importing images isn't supported at the moment.\n\nDid you want to import a scene? This image does not seem to contain any scene data. Have you enabled this during export?",
|
"imageDoesNotContainScene": "Importing images isn't supported at the moment.\n\nDid you want to import a scene? This image does not seem to contain any scene data. Have you enabled this during export?",
|
||||||
"cannotRestoreFromImage": "Scene couldn't be restored from this image file",
|
"cannotRestoreFromImage": "Scene couldn't be restored from this image file",
|
||||||
|
"invalidSceneUrl": "Couldn't import scene from the supplied URL. It's either malformed, or doesn't contain valid Excalidraw JSON data.",
|
||||||
"resetLibrary": "This will clear your library. Are you sure?"
|
"resetLibrary": "This will clear your library. Are you sure?"
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
|
Loading…
Reference in New Issue
Block a user