move footer into layerUI & refactor ActionManager (#729)
This commit is contained in:
parent
88eacc9da7
commit
d79293de06
@ -14,20 +14,16 @@ export class ActionManager implements ActionsManagerInterface {
|
|||||||
|
|
||||||
updater: UpdaterFn;
|
updater: UpdaterFn;
|
||||||
|
|
||||||
resumeHistoryRecording: () => void;
|
|
||||||
|
|
||||||
getAppState: () => AppState;
|
getAppState: () => AppState;
|
||||||
|
|
||||||
getElements: () => readonly ExcalidrawElement[];
|
getElements: () => readonly ExcalidrawElement[];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
updater: UpdaterFn,
|
updater: UpdaterFn,
|
||||||
resumeHistoryRecording: () => void,
|
|
||||||
getAppState: () => AppState,
|
getAppState: () => AppState,
|
||||||
getElements: () => readonly ExcalidrawElement[],
|
getElements: () => readonly ExcalidrawElement[],
|
||||||
) {
|
) {
|
||||||
this.updater = updater;
|
this.updater = updater;
|
||||||
this.resumeHistoryRecording = resumeHistoryRecording;
|
|
||||||
this.getAppState = getAppState;
|
this.getAppState = getAppState;
|
||||||
this.getElements = getElements;
|
this.getElements = getElements;
|
||||||
}
|
}
|
||||||
@ -46,17 +42,18 @@ export class ActionManager implements ActionsManagerInterface {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
return null;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (
|
const commitToHistory =
|
||||||
data[0].commitToHistory &&
|
data[0].commitToHistory &&
|
||||||
data[0].commitToHistory(this.getAppState(), this.getElements())
|
data[0].commitToHistory(this.getAppState(), this.getElements());
|
||||||
) {
|
this.updater(
|
||||||
this.resumeHistoryRecording();
|
data[0].perform(this.getElements(), this.getAppState(), null),
|
||||||
}
|
commitToHistory,
|
||||||
return data[0].perform(this.getElements(), this.getAppState(), null);
|
);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
getContextMenuItems(actionFilter: ActionFilterFn = action => action) {
|
getContextMenuItems(actionFilter: ActionFilterFn = action => action) {
|
||||||
@ -71,14 +68,12 @@ export class ActionManager implements ActionsManagerInterface {
|
|||||||
.map(action => ({
|
.map(action => ({
|
||||||
label: action.contextItemLabel ? t(action.contextItemLabel) : "",
|
label: action.contextItemLabel ? t(action.contextItemLabel) : "",
|
||||||
action: () => {
|
action: () => {
|
||||||
if (
|
const commitToHistory =
|
||||||
action.commitToHistory &&
|
action.commitToHistory &&
|
||||||
action.commitToHistory(this.getAppState(), this.getElements())
|
action.commitToHistory(this.getAppState(), this.getElements());
|
||||||
) {
|
|
||||||
this.resumeHistoryRecording();
|
|
||||||
}
|
|
||||||
this.updater(
|
this.updater(
|
||||||
action.perform(this.getElements(), this.getAppState(), null),
|
action.perform(this.getElements(), this.getAppState(), null),
|
||||||
|
commitToHistory,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
@ -89,15 +84,12 @@ export class ActionManager implements ActionsManagerInterface {
|
|||||||
const action = this.actions[name];
|
const action = this.actions[name];
|
||||||
const PanelComponent = action.PanelComponent!;
|
const PanelComponent = action.PanelComponent!;
|
||||||
const updateData = (formState: any) => {
|
const updateData = (formState: any) => {
|
||||||
if (
|
const commitToHistory =
|
||||||
action.commitToHistory &&
|
action.commitToHistory &&
|
||||||
action.commitToHistory(this.getAppState(), this.getElements()) ===
|
action.commitToHistory(this.getAppState(), this.getElements());
|
||||||
true
|
|
||||||
) {
|
|
||||||
this.resumeHistoryRecording();
|
|
||||||
}
|
|
||||||
this.updater(
|
this.updater(
|
||||||
action.perform(this.getElements(), this.getAppState(), formState),
|
action.perform(this.getElements(), this.getAppState(), formState),
|
||||||
|
commitToHistory,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,8 +3,8 @@ import { ExcalidrawElement } from "../element/types";
|
|||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
|
|
||||||
export type ActionResult = {
|
export type ActionResult = {
|
||||||
elements?: readonly ExcalidrawElement[];
|
elements?: readonly ExcalidrawElement[] | null;
|
||||||
appState?: AppState;
|
appState?: AppState | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ActionFn = (
|
type ActionFn = (
|
||||||
@ -13,7 +13,7 @@ type ActionFn = (
|
|||||||
formData: any,
|
formData: any,
|
||||||
) => ActionResult;
|
) => ActionResult;
|
||||||
|
|
||||||
export type UpdaterFn = (res: ActionResult) => void;
|
export type UpdaterFn = (res: ActionResult, commitToHistory?: boolean) => void;
|
||||||
export type ActionFilterFn = (action: Action) => void;
|
export type ActionFilterFn = (action: Action) => void;
|
||||||
|
|
||||||
export interface Action {
|
export interface Action {
|
||||||
@ -43,7 +43,7 @@ export interface ActionsManagerInterface {
|
|||||||
[keyProp: string]: Action;
|
[keyProp: string]: Action;
|
||||||
};
|
};
|
||||||
registerAction: (action: Action) => void;
|
registerAction: (action: Action) => void;
|
||||||
handleKeyDown: (event: KeyboardEvent) => ActionResult | null;
|
handleKeyDown: (event: KeyboardEvent) => boolean;
|
||||||
getContextMenuItems: (
|
getContextMenuItems: (
|
||||||
actionFilter: ActionFilterFn,
|
actionFilter: ActionFilterFn,
|
||||||
) => { label: string; action: () => void }[];
|
) => { label: string; action: () => void }[];
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { AppState } from "./types";
|
import { AppState } from "./types";
|
||||||
import { getDateTime } from "./utils";
|
import { getDateTime } from "./utils";
|
||||||
import { getLanguage } from "./i18n";
|
|
||||||
|
|
||||||
const DEFAULT_PROJECT_NAME = `excalidraw-${getDateTime()}`;
|
const DEFAULT_PROJECT_NAME = `excalidraw-${getDateTime()}`;
|
||||||
|
|
||||||
@ -29,7 +28,6 @@ export function getDefaultAppState(): AppState {
|
|||||||
name: DEFAULT_PROJECT_NAME,
|
name: DEFAULT_PROJECT_NAME,
|
||||||
isResizing: false,
|
isResizing: false,
|
||||||
selectionElement: null,
|
selectionElement: null,
|
||||||
lng: getLanguage(),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
255
src/index.tsx
255
src/index.tsx
@ -23,7 +23,6 @@ import {
|
|||||||
deleteSelectedElements,
|
deleteSelectedElements,
|
||||||
getElementsWithinSelection,
|
getElementsWithinSelection,
|
||||||
isOverScrollBars,
|
isOverScrollBars,
|
||||||
restoreFromLocalStorage,
|
|
||||||
saveToLocalStorage,
|
saveToLocalStorage,
|
||||||
getElementAtPosition,
|
getElementAtPosition,
|
||||||
createScene,
|
createScene,
|
||||||
@ -32,9 +31,8 @@ import {
|
|||||||
hasStroke,
|
hasStroke,
|
||||||
hasText,
|
hasText,
|
||||||
exportCanvas,
|
exportCanvas,
|
||||||
importFromBackend,
|
|
||||||
addToLoadedScenes,
|
|
||||||
loadedScenes,
|
loadedScenes,
|
||||||
|
loadScene,
|
||||||
calculateScrollCenter,
|
calculateScrollCenter,
|
||||||
loadFromBlob,
|
loadFromBlob,
|
||||||
} from "./scene";
|
} from "./scene";
|
||||||
@ -163,6 +161,7 @@ interface LayerUIProps {
|
|||||||
canvas: HTMLCanvasElement | null;
|
canvas: HTMLCanvasElement | null;
|
||||||
setAppState: any;
|
setAppState: any;
|
||||||
elements: readonly ExcalidrawElement[];
|
elements: readonly ExcalidrawElement[];
|
||||||
|
language: string;
|
||||||
setElements: (elements: readonly ExcalidrawElement[]) => void;
|
setElements: (elements: readonly ExcalidrawElement[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,6 +172,7 @@ const LayerUI = React.memo(
|
|||||||
setAppState,
|
setAppState,
|
||||||
canvas,
|
canvas,
|
||||||
elements,
|
elements,
|
||||||
|
language,
|
||||||
setElements,
|
setElements,
|
||||||
}: LayerUIProps) => {
|
}: LayerUIProps) => {
|
||||||
function renderCanvasActions() {
|
function renderCanvasActions() {
|
||||||
@ -318,56 +318,101 @@ const LayerUI = React.memo(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderIdsDropdown() {
|
||||||
|
const scenes = loadedScenes();
|
||||||
|
if (scenes.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<StoredScenesList
|
||||||
|
scenes={scenes}
|
||||||
|
currentId={appState.selectedId}
|
||||||
|
onChange={async (id, k) =>
|
||||||
|
actionManager.updater(await loadScene(id, k))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FixedSideContainer side="top">
|
<>
|
||||||
<div className="App-menu App-menu_top">
|
<FixedSideContainer side="top">
|
||||||
<Stack.Col gap={4} align="end">
|
<div className="App-menu App-menu_top">
|
||||||
<section
|
<Stack.Col gap={4} align="end">
|
||||||
className="App-right-menu"
|
<section
|
||||||
aria-labelledby="canvas-actions-title"
|
className="App-right-menu"
|
||||||
>
|
aria-labelledby="canvas-actions-title"
|
||||||
<h2 className="visually-hidden" id="canvas-actions-title">
|
>
|
||||||
{t("headings.canvasActions")}
|
<h2 className="visually-hidden" id="canvas-actions-title">
|
||||||
</h2>
|
{t("headings.canvasActions")}
|
||||||
<Island padding={4}>{renderCanvasActions()}</Island>
|
</h2>
|
||||||
</section>
|
<Island padding={4}>{renderCanvasActions()}</Island>
|
||||||
<section
|
</section>
|
||||||
className="App-right-menu"
|
<section
|
||||||
aria-labelledby="selected-shape-title"
|
className="App-right-menu"
|
||||||
>
|
aria-labelledby="selected-shape-title"
|
||||||
<h2 className="visually-hidden" id="selected-shape-title">
|
>
|
||||||
{t("headings.selectedShapeActions")}
|
<h2 className="visually-hidden" id="selected-shape-title">
|
||||||
</h2>
|
{t("headings.selectedShapeActions")}
|
||||||
{renderSelectedShapeActions(elements)}
|
</h2>
|
||||||
</section>
|
{renderSelectedShapeActions(elements)}
|
||||||
</Stack.Col>
|
</section>
|
||||||
<section aria-labelledby="shapes-title">
|
|
||||||
<Stack.Col gap={4} align="start">
|
|
||||||
<Stack.Row gap={1}>
|
|
||||||
<Island padding={1}>
|
|
||||||
<h2 className="visually-hidden" id="shapes-title">
|
|
||||||
{t("headings.shapes")}
|
|
||||||
</h2>
|
|
||||||
<Stack.Row gap={1}>{renderShapesSwitcher()}</Stack.Row>
|
|
||||||
</Island>
|
|
||||||
<LockIcon
|
|
||||||
checked={appState.elementLocked}
|
|
||||||
onChange={() => {
|
|
||||||
setAppState({
|
|
||||||
elementLocked: !appState.elementLocked,
|
|
||||||
elementType: appState.elementLocked
|
|
||||||
? "selection"
|
|
||||||
: appState.elementType,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
title={t("toolBar.lock")}
|
|
||||||
/>
|
|
||||||
</Stack.Row>
|
|
||||||
</Stack.Col>
|
</Stack.Col>
|
||||||
</section>
|
<section aria-labelledby="shapes-title">
|
||||||
<div />
|
<Stack.Col gap={4} align="start">
|
||||||
</div>
|
<Stack.Row gap={1}>
|
||||||
</FixedSideContainer>
|
<Island padding={1}>
|
||||||
|
<h2 className="visually-hidden" id="shapes-title">
|
||||||
|
{t("headings.shapes")}
|
||||||
|
</h2>
|
||||||
|
<Stack.Row gap={1}>{renderShapesSwitcher()}</Stack.Row>
|
||||||
|
</Island>
|
||||||
|
<LockIcon
|
||||||
|
checked={appState.elementLocked}
|
||||||
|
onChange={() => {
|
||||||
|
setAppState({
|
||||||
|
elementLocked: !appState.elementLocked,
|
||||||
|
elementType: appState.elementLocked
|
||||||
|
? "selection"
|
||||||
|
: appState.elementType,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
title={t("toolBar.lock")}
|
||||||
|
/>
|
||||||
|
</Stack.Row>
|
||||||
|
</Stack.Col>
|
||||||
|
</section>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
</FixedSideContainer>
|
||||||
|
<footer role="contentinfo">
|
||||||
|
<HintViewer
|
||||||
|
elementType={appState.elementType}
|
||||||
|
multiMode={appState.multiElement !== null}
|
||||||
|
isResizing={appState.isResizing}
|
||||||
|
elements={elements}
|
||||||
|
/>
|
||||||
|
<LanguageList
|
||||||
|
onChange={lng => {
|
||||||
|
setLanguage(lng);
|
||||||
|
setAppState({});
|
||||||
|
}}
|
||||||
|
languages={languages}
|
||||||
|
currentLanguage={language}
|
||||||
|
/>
|
||||||
|
{renderIdsDropdown()}
|
||||||
|
{appState.scrolledOutside && (
|
||||||
|
<button
|
||||||
|
className="scroll-back-to-content"
|
||||||
|
onClick={() => {
|
||||||
|
setAppState({ ...calculateScrollCenter(elements) });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("buttons.scrollBackToContent")}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</footer>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
(prev, next) => {
|
(prev, next) => {
|
||||||
@ -390,6 +435,7 @@ const LayerUI = React.memo(
|
|||||||
const keys = Object.keys(prevAppState) as (keyof Partial<AppState>)[];
|
const keys = Object.keys(prevAppState) as (keyof Partial<AppState>)[];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
prev.language === next.language &&
|
||||||
prev.elements === next.elements &&
|
prev.elements === next.elements &&
|
||||||
keys.every(k => prevAppState[k] === nextAppState[k])
|
keys.every(k => prevAppState[k] === nextAppState[k])
|
||||||
);
|
);
|
||||||
@ -406,9 +452,6 @@ export class App extends React.Component<any, AppState> {
|
|||||||
super(props);
|
super(props);
|
||||||
this.actionManager = new ActionManager(
|
this.actionManager = new ActionManager(
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
() => {
|
|
||||||
history.resumeRecording();
|
|
||||||
},
|
|
||||||
() => this.state,
|
() => this.state,
|
||||||
() => elements,
|
() => elements,
|
||||||
);
|
);
|
||||||
@ -443,13 +486,22 @@ export class App extends React.Component<any, AppState> {
|
|||||||
this.canvasOnlyActions = [actionSelectAll];
|
this.canvasOnlyActions = [actionSelectAll];
|
||||||
}
|
}
|
||||||
|
|
||||||
private syncActionResult = (res: ActionResult) => {
|
private syncActionResult = (
|
||||||
if (res.elements !== undefined) {
|
res: ActionResult,
|
||||||
|
commitToHistory: boolean = true,
|
||||||
|
) => {
|
||||||
|
if (res.elements) {
|
||||||
elements = res.elements;
|
elements = res.elements;
|
||||||
|
if (commitToHistory) {
|
||||||
|
history.resumeRecording();
|
||||||
|
}
|
||||||
this.setState({});
|
this.setState({});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.appState !== undefined) {
|
if (res.appState) {
|
||||||
|
if (commitToHistory) {
|
||||||
|
history.resumeRecording();
|
||||||
|
}
|
||||||
this.setState({ ...res.appState });
|
this.setState({ ...res.appState });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -478,32 +530,6 @@ export class App extends React.Component<any, AppState> {
|
|||||||
this.saveDebounced.flush();
|
this.saveDebounced.flush();
|
||||||
};
|
};
|
||||||
|
|
||||||
private async loadScene(id: string | null, k: string | undefined) {
|
|
||||||
let data;
|
|
||||||
let selectedId;
|
|
||||||
if (id != null) {
|
|
||||||
// k is the private key used to decrypt the content from the server, take
|
|
||||||
// extra care not to leak it
|
|
||||||
data = await importFromBackend(id, k);
|
|
||||||
addToLoadedScenes(id, k);
|
|
||||||
selectedId = id;
|
|
||||||
window.history.replaceState({}, "Excalidraw", window.location.origin);
|
|
||||||
} else {
|
|
||||||
data = restoreFromLocalStorage();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.elements) {
|
|
||||||
elements = data.elements;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.appState) {
|
|
||||||
history.resumeRecording();
|
|
||||||
this.setState({ ...data.appState, selectedId });
|
|
||||||
} else {
|
|
||||||
this.setState({});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async componentDidMount() {
|
public async componentDidMount() {
|
||||||
document.addEventListener("copy", this.onCopy);
|
document.addEventListener("copy", this.onCopy);
|
||||||
document.addEventListener("paste", this.pasteFromClipboard);
|
document.addEventListener("paste", this.pasteFromClipboard);
|
||||||
@ -523,15 +549,15 @@ export class App extends React.Component<any, AppState> {
|
|||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
// Backwards compatibility with legacy url format
|
// Backwards compatibility with legacy url format
|
||||||
this.loadScene(id, undefined);
|
this.syncActionResult(await loadScene(id));
|
||||||
} else {
|
} else {
|
||||||
const match = window.location.hash.match(
|
const match = window.location.hash.match(
|
||||||
/^#json=([0-9]+),([a-zA-Z0-9_-]+)$/,
|
/^#json=([0-9]+),([a-zA-Z0-9_-]+)$/,
|
||||||
);
|
);
|
||||||
if (match) {
|
if (match) {
|
||||||
this.loadScene(match[1], match[2]);
|
this.syncActionResult(await loadScene(match[1], match[2]));
|
||||||
} else {
|
} else {
|
||||||
this.loadScene(null, undefined);
|
this.syncActionResult(await loadScene(null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -572,13 +598,8 @@ export class App extends React.Component<any, AppState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const actionResult = this.actionManager.handleKeyDown(event);
|
if (this.actionManager.handleKeyDown(event)) {
|
||||||
|
return;
|
||||||
if (actionResult) {
|
|
||||||
this.syncActionResult(actionResult);
|
|
||||||
if (actionResult) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const shape = findShapeByKey(event.key);
|
const shape = findShapeByKey(event.key);
|
||||||
@ -750,6 +771,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
actionManager={this.actionManager}
|
actionManager={this.actionManager}
|
||||||
elements={elements}
|
elements={elements}
|
||||||
setElements={this.setElements}
|
setElements={this.setElements}
|
||||||
|
language={getLanguage()}
|
||||||
/>
|
/>
|
||||||
<main>
|
<main>
|
||||||
<canvas
|
<canvas
|
||||||
@ -1797,10 +1819,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
if (file?.type === "application/json") {
|
if (file?.type === "application/json") {
|
||||||
loadFromBlob(file)
|
loadFromBlob(file)
|
||||||
.then(({ elements, appState }) =>
|
.then(({ elements, appState }) =>
|
||||||
this.syncActionResult({
|
this.syncActionResult({ elements, appState }),
|
||||||
elements,
|
|
||||||
appState,
|
|
||||||
} as ActionResult),
|
|
||||||
)
|
)
|
||||||
.catch(err => console.error(err));
|
.catch(err => console.error(err));
|
||||||
}
|
}
|
||||||
@ -1809,52 +1828,10 @@ export class App extends React.Component<any, AppState> {
|
|||||||
{t("labels.drawingCanvas")}
|
{t("labels.drawingCanvas")}
|
||||||
</canvas>
|
</canvas>
|
||||||
</main>
|
</main>
|
||||||
<footer role="contentinfo">
|
|
||||||
<HintViewer
|
|
||||||
elementType={this.state.elementType}
|
|
||||||
multiMode={this.state.multiElement !== null}
|
|
||||||
isResizing={this.state.isResizing}
|
|
||||||
elements={elements}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<LanguageList
|
|
||||||
onChange={lng => {
|
|
||||||
setLanguage(lng);
|
|
||||||
this.setState({ lng });
|
|
||||||
}}
|
|
||||||
languages={languages}
|
|
||||||
currentLanguage={getLanguage()}
|
|
||||||
/>
|
|
||||||
{this.renderIdsDropdown()}
|
|
||||||
{this.state.scrolledOutside && (
|
|
||||||
<button
|
|
||||||
className="scroll-back-to-content"
|
|
||||||
onClick={() => {
|
|
||||||
this.setState({ ...calculateScrollCenter(elements) });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("buttons.scrollBackToContent")}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</footer>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderIdsDropdown() {
|
|
||||||
const scenes = loadedScenes();
|
|
||||||
if (scenes.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<StoredScenesList
|
|
||||||
scenes={scenes}
|
|
||||||
currentId={this.state.selectedId}
|
|
||||||
onChange={(id, k) => this.loadScene(id, k)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleWheel = (e: WheelEvent) => {
|
private handleWheel = (e: WheelEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const { deltaX, deltaY } = e;
|
const { deltaX, deltaY } = e;
|
||||||
|
@ -123,17 +123,15 @@ export async function loadFromBlob(blob: any) {
|
|||||||
if ("text" in Blob) {
|
if ("text" in Blob) {
|
||||||
contents = await blob.text();
|
contents = await blob.text();
|
||||||
} else {
|
} else {
|
||||||
contents = await (async () => {
|
contents = await new Promise(resolve => {
|
||||||
return new Promise(resolve => {
|
const reader = new FileReader();
|
||||||
const reader = new FileReader();
|
reader.readAsText(blob, "utf8");
|
||||||
reader.readAsText(blob, "utf8");
|
reader.onloadend = () => {
|
||||||
reader.onloadend = () => {
|
if (reader.readyState === FileReader.DONE) {
|
||||||
if (reader.readyState === FileReader.DONE) {
|
resolve(reader.result as string);
|
||||||
resolve(reader.result as string);
|
}
|
||||||
}
|
};
|
||||||
};
|
});
|
||||||
});
|
|
||||||
})();
|
|
||||||
}
|
}
|
||||||
const { elements, appState } = updateAppState(contents);
|
const { elements, appState } = updateAppState(contents);
|
||||||
if (!elements.length) {
|
if (!elements.length) {
|
||||||
@ -488,3 +486,23 @@ export function addToLoadedScenes(id: string, k: string | undefined): void {
|
|||||||
JSON.stringify(scenes),
|
JSON.stringify(scenes),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function loadScene(id: string | null, k?: string) {
|
||||||
|
let data;
|
||||||
|
let selectedId;
|
||||||
|
if (id != null) {
|
||||||
|
// k is the private key used to decrypt the content from the server, take
|
||||||
|
// extra care not to leak it
|
||||||
|
data = await importFromBackend(id, k);
|
||||||
|
addToLoadedScenes(id, k);
|
||||||
|
selectedId = id;
|
||||||
|
window.history.replaceState({}, "Excalidraw", window.location.origin);
|
||||||
|
} else {
|
||||||
|
data = restoreFromLocalStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
elements: data.elements,
|
||||||
|
appState: data.appState && { ...data.appState, selectedId },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -18,6 +18,7 @@ export {
|
|||||||
importFromBackend,
|
importFromBackend,
|
||||||
addToLoadedScenes,
|
addToLoadedScenes,
|
||||||
loadedScenes,
|
loadedScenes,
|
||||||
|
loadScene,
|
||||||
calculateScrollCenter,
|
calculateScrollCenter,
|
||||||
} from "./data";
|
} from "./data";
|
||||||
export {
|
export {
|
||||||
|
@ -28,5 +28,4 @@ export type AppState = {
|
|||||||
name: string;
|
name: string;
|
||||||
selectedId?: string;
|
selectedId?: string;
|
||||||
isResizing: boolean;
|
isResizing: boolean;
|
||||||
lng: string;
|
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user