feat: prefer hash when importing libraries & expose importLibrary (#3320)

This commit is contained in:
David Luzar 2021-03-26 18:10:43 +01:00 committed by GitHub
parent 5d26c15daf
commit cf9e29834d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 68 additions and 17 deletions

View File

@ -58,6 +58,8 @@ import {
TAP_TWICE_TIMEOUT, TAP_TWICE_TIMEOUT,
TEXT_TO_CENTER_SNAP_THRESHOLD, TEXT_TO_CENTER_SNAP_THRESHOLD,
TOUCH_CTX_MENU_TIMEOUT, TOUCH_CTX_MENU_TIMEOUT,
URL_HASH_KEYS,
URL_QUERY_KEYS,
ZOOM_STEP, ZOOM_STEP,
} from "../constants"; } from "../constants";
import { loadFromBlob } from "../data"; import { loadFromBlob } from "../data";
@ -278,6 +280,7 @@ export type ExcalidrawImperativeAPI = {
getSceneElements: InstanceType<typeof App>["getSceneElements"]; getSceneElements: InstanceType<typeof App>["getSceneElements"];
getAppState: () => InstanceType<typeof App>["state"]; getAppState: () => InstanceType<typeof App>["state"];
setCanvasOffsets: InstanceType<typeof App>["setCanvasOffsets"]; setCanvasOffsets: InstanceType<typeof App>["setCanvasOffsets"];
importLibrary: InstanceType<typeof App>["importLibraryFromUrl"];
readyPromise: ResolvablePromise<ExcalidrawImperativeAPI>; readyPromise: ResolvablePromise<ExcalidrawImperativeAPI>;
ready: true; ready: true;
}; };
@ -338,6 +341,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
getSceneElements: this.getSceneElements, getSceneElements: this.getSceneElements,
getAppState: () => this.state, getAppState: () => this.state,
setCanvasOffsets: this.setCanvasOffsets, setCanvasOffsets: this.setCanvasOffsets,
importLibrary: this.importLibraryFromUrl,
} as const; } as const;
if (typeof excalidrawRef === "function") { if (typeof excalidrawRef === "function") {
excalidrawRef(api); excalidrawRef(api);
@ -606,7 +610,16 @@ class App extends React.Component<ExcalidrawProps, AppState> {
}; };
private importLibraryFromUrl = async (url: string) => { private importLibraryFromUrl = async (url: string) => {
window.history.replaceState({}, APP_NAME, window.location.origin); if (window.location.hash.includes(URL_HASH_KEYS.addLibrary)) {
const hash = new URLSearchParams(window.location.hash.slice(1));
hash.delete(URL_HASH_KEYS.addLibrary);
window.history.replaceState({}, APP_NAME, `#${hash.toString()}`);
} else if (window.location.search.includes(URL_QUERY_KEYS.addLibrary)) {
const query = new URLSearchParams(window.location.search);
query.delete(URL_QUERY_KEYS.addLibrary);
window.history.replaceState({}, APP_NAME, `?${query.toString()}`);
}
try { try {
const request = await fetch(decodeURIComponent(url)); const request = await fetch(decodeURIComponent(url));
const blob = await request.blob(); const blob = await request.blob();
@ -620,9 +633,11 @@ class App extends React.Component<ExcalidrawProps, AppState> {
) )
) { ) {
await Library.importLibrary(blob); await Library.importLibrary(blob);
this.setState({ // hack to rerender the library items after import
isLibraryOpen: true, if (this.state.isLibraryOpen) {
}); this.setState({ isLibraryOpen: false });
}
this.setState({ isLibraryOpen: true });
} }
} catch (error) { } catch (error) {
window.alert(t("alerts.errorLoadingLibrary")); window.alert(t("alerts.errorLoadingLibrary"));
@ -718,12 +733,18 @@ class App extends React.Component<ExcalidrawProps, AppState> {
commitToHistory: true, commitToHistory: true,
}); });
const addToLibraryUrl = new URLSearchParams(window.location.search).get( const libraryUrl =
"addLibrary", // current
new URLSearchParams(window.location.hash.slice(1)).get(
URL_HASH_KEYS.addLibrary,
) ||
// legacy, kept for compat reasons
new URLSearchParams(window.location.search).get(
URL_QUERY_KEYS.addLibrary,
); );
if (addToLibraryUrl) { if (libraryUrl) {
await this.importLibraryFromUrl(addToLibraryUrl); await this.importLibraryFromUrl(libraryUrl);
} }
}; };

View File

@ -179,7 +179,7 @@ const LibraryMenuItems = ({
<a <a
href={`https://libraries.excalidraw.com?target=${ href={`https://libraries.excalidraw.com?target=${
window.name || "_blank" window.name || "_blank"
}&referrer=${referrer}`} }&referrer=${referrer}&useHash=true`}
target="_excalidraw_libraries" target="_excalidraw_libraries"
> >
{t("labels.libraries")} {t("labels.libraries")}

View File

@ -116,3 +116,11 @@ export const MODES = {
}; };
export const THEME_FILTER = cssVariables.themeFilter; export const THEME_FILTER = cssVariables.themeFilter;
export const URL_QUERY_KEYS = {
addLibrary: "addLibrary",
} as const;
export const URL_HASH_KEYS = {
addLibrary: "addLibrary",
} as const;

View File

@ -12,7 +12,13 @@ import { getDefaultAppState } from "../appState";
import { ExcalidrawImperativeAPI } from "../components/App"; 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,
URL_HASH_KEYS,
VERSION_TIMEOUT,
} from "../constants";
import { loadFromBlob } from "../data/blob"; import { loadFromBlob } from "../data/blob";
import { DataState, ImportedDataState } from "../data/types"; import { DataState, ImportedDataState } from "../data/types";
import { import {
@ -213,12 +219,25 @@ function ExcalidrawWrapper() {
initialStatePromiseRef.current.promise.resolve(scene); initialStatePromiseRef.current.promise.resolve(scene);
}); });
const onHashChange = (_: HashChangeEvent) => { const onHashChange = (event: HashChangeEvent) => {
event.preventDefault();
const libraryUrl = new URLSearchParams(window.location.hash.slice(1)).get(
URL_HASH_KEYS.addLibrary,
);
if (libraryUrl) {
// If hash changed and it contains library url, import it and replace
// the url to its previous state (important in case of collaboration
// and similar).
// Using history API won't trigger another hashchange.
window.history.replaceState({}, "", event.oldURL);
excalidrawAPI.importLibrary(libraryUrl);
} else {
initializeScene({ collabAPI }).then((scene) => { initializeScene({ collabAPI }).then((scene) => {
if (scene) { if (scene) {
excalidrawAPI.updateScene(scene); excalidrawAPI.updateScene(scene);
} }
}); });
}
}; };
const titleTimeout = setTimeout( const titleTimeout = setTimeout(

View File

@ -18,6 +18,8 @@ Please add the latest change on the top under the correct section.
### Features ### Features
- #### BREAKING CHANGE
Use `location.hash` when importing libraries to fix installation issues. This will require host apps to add a `hashchange` listener and call the newly exposed `excalidrawAPI.importLibrary(url)` API when applicable [#3320](https://github.com/excalidraw/excalidraw/pull/3320).
- Append `location.pathname` to `libraryReturnUrl` default url [#3325](https://github.com/excalidraw/excalidraw/pull/3325). - Append `location.pathname` to `libraryReturnUrl` default url [#3325](https://github.com/excalidraw/excalidraw/pull/3325).
## 0.5.0 (2021-03-21) ## 0.5.0 (2021-03-21)

View File

@ -473,6 +473,7 @@ You can pass a `ref` when you want to access some excalidraw APIs. We expose the
| history | `{ clear: () => void }` | This is the history API. `history.clear()` will clear the history | | history | `{ clear: () => void }` | This is the history API. `history.clear()` will clear the history |
| setScrollToContent | <pre> (<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement[]</a>) => void </pre> | Scroll to the nearest element to center | | setScrollToContent | <pre> (<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement[]</a>) => void </pre> | Scroll to the nearest element to center |
| setCanvasOffsets | `() => void` | Updates the offsets for the Excalidraw component so that the coordinates are computed correctly (for example the cursor position). You should call this API when your app changes the dimensions/position of the Excalidraw container, such as when toggling a sidebar. You don't have to call this when the position is changed on page scroll (we handled that ourselves). | | setCanvasOffsets | `() => void` | Updates the offsets for the Excalidraw component so that the coordinates are computed correctly (for example the cursor position). You should call this API when your app changes the dimensions/position of the Excalidraw container, such as when toggling a sidebar. You don't have to call this when the position is changed on page scroll (we handled that ourselves). |
| importLibrary | `(url: string) => void` | Imports library from given URL. You should call this on `hashchange`, passing the `addLibrary` value if you detect it. |
#### `readyPromise` #### `readyPromise`