Compare commits

..

15 Commits

Author SHA1 Message Date
dwelle
2bf886c941 show autosave checkbox only if fileHandle exists 2021-04-06 21:34:18 +02:00
dwelle
6215256787 Merge branch 'master' into kb/auto-save-support
# Conflicts:
#	src/tests/__snapshots__/regressionTests.test.tsx.snap
2021-04-06 21:30:48 +02:00
dwelle
35d195e891 update snaps 2021-04-04 15:32:55 +02:00
dwelle
9d3d7f3500 update copy 2021-04-04 15:16:29 +02:00
dwelle
0b32757085 prevent autosave from prompting on failure & granularize error messages 2021-04-04 15:13:56 +02:00
dwelle
6442a45bd4 Merge branch 'master' into kb/auto-save-support 2021-04-04 14:47:38 +02:00
dwelle
d7a015cb3a autoSaveautosave 2021-03-26 21:49:43 +01:00
dwelle
f68404fbed handle error properly and display error message 2021-03-26 21:42:27 +01:00
kbariotis
01f5914a82 auto save only when its supported 2021-03-22 21:59:10 +02:00
kbariotis
5e1e16c150 Merge branch 'master' into kb/auto-save-support 2021-03-22 21:56:30 +02:00
kbariotis
14537cbaba update tests 2021-03-20 16:26:22 +02:00
kbariotis
92ac11c49d add eror handler, update snapshots 2021-03-20 16:22:57 +02:00
kbariotis
90d68b3e0b move to export dialog 2021-03-20 16:01:06 +02:00
kbariotis
006aad052d update tests 2021-02-14 23:55:17 +02:00
kbariotis
98a7707e26 initial commit 2021-02-14 23:42:54 +02:00
42 changed files with 513 additions and 494 deletions

View File

@@ -19,18 +19,18 @@
]
},
"dependencies": {
"@sentry/browser": "6.2.5",
"@sentry/integrations": "6.2.5",
"@sentry/browser": "6.2.2",
"@sentry/integrations": "6.2.1",
"@testing-library/jest-dom": "5.11.10",
"@testing-library/react": "11.2.6",
"@testing-library/react": "11.2.5",
"@types/jest": "26.0.22",
"@types/react": "17.0.3",
"@types/react-dom": "17.0.3",
"@types/react-dom": "17.0.2",
"@types/socket.io-client": "1.4.36",
"browser-fs-access": "0.16.4",
"browser-fs-access": "0.16.2",
"clsx": "1.1.1",
"firebase": "8.3.2",
"i18next-browser-languagedetector": "6.1.0",
"firebase": "8.2.10",
"i18next-browser-languagedetector": "6.0.1",
"lodash.throttle": "4.1.1",
"nanoid": "3.1.22",
"open-color": "1.8.0",
@@ -46,7 +46,7 @@
"roughjs": "4.3.1",
"sass": "1.32.8",
"socket.io-client": "2.3.1",
"typescript": "4.2.4"
"typescript": "4.2.3"
},
"devDependencies": {
"@excalidraw/eslint-config": "1.0.0",
@@ -56,7 +56,7 @@
"@types/resize-observer-browser": "0.1.5",
"eslint-config-prettier": "8.1.0",
"eslint-plugin-prettier": "3.3.1",
"firebase-tools": "9.9.0",
"firebase-tools": "9.6.1",
"husky": "4.3.8",
"jest-canvas-mock": "2.3.1",
"lint-staged": "10.5.4",

View File

@@ -8,7 +8,7 @@ import { getCommonBounds, getNonDeletedElements } from "../element";
import { newElementWith } from "../element/mutateElement";
import { ExcalidrawElement } from "../element/types";
import { t } from "../i18n";
import { useIsMobile } from "../components/App";
import { useIsMobile } from "../is-mobile";
import { CODES, KEYS } from "../keys";
import { getNormalizedZoom, getSelectedElements } from "../scene";
import { centerScrollOn } from "../scene/scroll";

View File

@@ -8,7 +8,7 @@ import { Tooltip } from "../components/Tooltip";
import { DarkModeToggle, Appearence } from "../components/DarkModeToggle";
import { loadFromJSON, saveAsJSON } from "../data";
import { t } from "../i18n";
import { useIsMobile } from "../components/App";
import { useIsMobile } from "../is-mobile";
import { KEYS } from "../keys";
import { register } from "./register";
import { supported } from "browser-fs-access";
@@ -238,3 +238,37 @@ export const actionExportWithDarkMode = register({
</div>
),
});
export const actionToggleAutosave = register({
name: "toggleAutosave",
perform(elements, appState) {
trackEvent("toggle", "autosave");
return {
appState: {
...appState,
autosave: !appState.autosave,
},
commitToHistory: false,
};
},
PanelComponent: ({ appState, updateData }) =>
supported && appState.fileHandle ? (
<label style={{ display: "flex" }}>
<input
type="checkbox"
checked={appState.autosave}
onChange={(event) => updateData(event.target.checked)}
/>{" "}
{t("labels.toggleAutosave")}
<Tooltip
label={t("labels.toggleAutosave_details")}
position="above"
long={true}
>
<div className="TooltipIcon">{questionCircle}</div>
</Tooltip>
</label>
) : (
<></>
),
});

View File

@@ -33,6 +33,7 @@ export { actionFinalize } from "./actionFinalize";
export {
actionChangeProjectName,
actionChangeExportBackground,
actionToggleAutosave,
actionSaveScene,
actionSaveAsScene,
actionLoadScene,

View File

@@ -51,6 +51,7 @@ export type ActionName =
| "changeOpacity"
| "changeFontSize"
| "toggleCanvasMenu"
| "toggleAutosave"
| "toggleEditMenu"
| "undo"
| "redo"

View File

@@ -13,6 +13,7 @@ export const getDefaultAppState = (): Omit<
"offsetTop" | "offsetLeft" | "width" | "height"
> => {
return {
autosave: false,
theme: "light",
collaborators: new Map(),
currentChartType: "bar",
@@ -90,6 +91,7 @@ const APP_STATE_STORAGE_CONF = (<
>(
config: { [K in keyof T]: K extends keyof AppState ? T[K] : never },
) => config)({
autosave: { browser: true, export: false },
theme: { browser: true, export: false },
collaborators: { browser: false, export: false },
currentChartType: { browser: true, export: false },

View File

@@ -14,13 +14,6 @@ type ElementsClipboard = {
elements: ExcalidrawElement[];
};
export interface ClipboardData {
spreadsheet?: Spreadsheet;
elements?: readonly ExcalidrawElement[];
text?: string;
errorMessage?: string;
}
let CLIPBOARD = "";
let PREFER_APP_CLIPBOARD = false;
@@ -117,7 +110,12 @@ const getSystemClipboard = async (
*/
export const parseClipboard = async (
event: ClipboardEvent | null,
): Promise<ClipboardData> => {
): Promise<{
spreadsheet?: Spreadsheet;
elements?: readonly ExcalidrawElement[];
text?: string;
errorMessage?: string;
}> => {
const systemClipboard = await getSystemClipboard(event);
// if system clipboard empty, couldn't be resolved, or contains previously

View File

@@ -3,7 +3,7 @@ import { ActionManager } from "../actions/manager";
import { getNonDeletedElements } from "../element";
import { ExcalidrawElement } from "../element/types";
import { t } from "../i18n";
import { useIsMobile } from "../components/App";
import { useIsMobile } from "../is-mobile";
import {
canChangeSharpness,
canHaveArrowheads,

View File

@@ -1,5 +1,5 @@
import { Point, simplify } from "points-on-curve";
import React, { useContext } from "react";
import React from "react";
import { RoughCanvas } from "roughjs/bin/canvas";
import rough from "roughjs/bin/rough";
import clsx from "clsx";
@@ -46,7 +46,6 @@ import {
CURSOR_TYPE,
DEFAULT_UI_OPTIONS,
DEFAULT_VERTICAL_ALIGN,
DETECT_POSITION_CHANGE_INTERVAL,
DRAGGING_THRESHOLD,
ELEMENT_SHIFT_TRANSLATE_AMOUNT,
ELEMENT_TRANSLATE_AMOUNT,
@@ -55,11 +54,9 @@ import {
GRID_SIZE,
LINE_CONFIRM_THRESHOLD,
MIME_TYPES,
MQ_MAX_HEIGHT_LANDSCAPE,
MQ_MAX_WIDTH_LANDSCAPE,
MQ_MAX_WIDTH_PORTRAIT,
POINTER_BUTTON,
SCROLL_TIMEOUT,
AUTO_SAVE_TIMEOUT,
TAP_TWICE_TIMEOUT,
TEXT_TO_CENTER_SNAP_THRESHOLD,
TOUCH_CTX_MENU_TIMEOUT,
@@ -68,7 +65,7 @@ import {
ZOOM_STEP,
} from "../constants";
import { loadFromBlob } from "../data";
import { isValidLibrary } from "../data/json";
import { saveAsJSON, isValidLibrary } from "../data/json";
import { Library } from "../data/library";
import { restore } from "../data/restore";
import {
@@ -169,7 +166,6 @@ import { AppProps, AppState, Gesture, GestureEvent, SceneData } from "../types";
import {
debounce,
distance,
getNearestScrollableContainer,
isInputLike,
isToolIcon,
isWritableElement,
@@ -183,15 +179,13 @@ import {
viewportCoordsToSceneCoords,
withBatchedUpdates,
} from "../utils";
import { isMobile } from "../is-mobile";
import ContextMenu, { ContextMenuOption } from "./ContextMenu";
import LayerUI from "./LayerUI";
import { Stats } from "./Stats";
import { Toast } from "./Toast";
import { actionToggleViewMode } from "../actions/actionToggleViewMode";
export const IsMobileContext = React.createContext(false);
export const useIsMobile = () => useContext(IsMobileContext);
const { history } = createHistory();
let didTapTwice: boolean = false;
@@ -293,9 +287,6 @@ class App extends React.Component<AppProps, AppState> {
rc: RoughCanvas | null = null;
unmounted: boolean = false;
actionManager: ActionManager;
isMobile = false;
detachIsMobileMqHandler?: () => void;
private excalidrawContainerRef = React.createRef<HTMLDivElement>();
public static defaultProps: Partial<AppProps> = {
@@ -305,9 +296,6 @@ class App extends React.Component<AppProps, AppState> {
private scene: Scene;
private resizeObserver: ResizeObserver | undefined;
private nearestScrollableContainer: HTMLElement | Document | undefined;
private detectPositionIntervalId: NodeJS.Timeout | undefined;
constructor(props: AppProps) {
super(props);
const defaultAppState = getDefaultAppState();
@@ -450,64 +438,60 @@ class App extends React.Component<AppProps, AppState> {
<div
className={clsx("excalidraw", {
"excalidraw--view-mode": viewModeEnabled,
"excalidraw--mobile": this.isMobile,
})}
ref={this.excalidrawContainerRef}
onDrop={this.handleAppOnDrop}
>
<IsMobileContext.Provider value={this.isMobile}>
<LayerUI
canvas={this.canvas}
<LayerUI
canvas={this.canvas}
appState={this.state}
setAppState={this.setAppState}
actionManager={this.actionManager}
elements={this.scene.getElements()}
onCollabButtonClick={onCollabButtonClick}
onLockToggle={this.toggleLock}
onInsertElements={(elements) =>
this.addElementsFromPasteOrLibrary(
elements,
DEFAULT_PASTE_X,
DEFAULT_PASTE_Y,
)
}
zenModeEnabled={zenModeEnabled}
toggleZenMode={this.toggleZenMode}
langCode={getLanguage().code}
isCollaborating={this.props.isCollaborating || false}
onExportToBackend={onExportToBackend}
renderCustomFooter={renderFooter}
viewModeEnabled={viewModeEnabled}
showExitZenModeBtn={
typeof this.props?.zenModeEnabled === "undefined" && zenModeEnabled
}
showThemeBtn={
typeof this.props?.theme === "undefined" &&
this.props.UIOptions.canvasActions.theme
}
libraryReturnUrl={this.props.libraryReturnUrl}
UIOptions={this.props.UIOptions}
/>
<div className="excalidraw-textEditorContainer" />
<div className="excalidraw-contextMenuContainer" />
{this.state.showStats && (
<Stats
appState={this.state}
setAppState={this.setAppState}
actionManager={this.actionManager}
elements={this.scene.getElements()}
onCollabButtonClick={onCollabButtonClick}
onLockToggle={this.toggleLock}
onInsertElements={(elements) =>
this.addElementsFromPasteOrLibrary(
elements,
DEFAULT_PASTE_X,
DEFAULT_PASTE_Y,
)
}
zenModeEnabled={zenModeEnabled}
toggleZenMode={this.toggleZenMode}
langCode={getLanguage().code}
isCollaborating={this.props.isCollaborating || false}
onExportToBackend={onExportToBackend}
renderCustomFooter={renderFooter}
viewModeEnabled={viewModeEnabled}
showExitZenModeBtn={
typeof this.props?.zenModeEnabled === "undefined" &&
zenModeEnabled
}
showThemeBtn={
typeof this.props?.theme === "undefined" &&
this.props.UIOptions.canvasActions.theme
}
libraryReturnUrl={this.props.libraryReturnUrl}
UIOptions={this.props.UIOptions}
onClose={this.toggleStats}
renderCustomStats={renderCustomStats}
/>
<div className="excalidraw-textEditorContainer" />
<div className="excalidraw-contextMenuContainer" />
{this.state.showStats && (
<Stats
appState={this.state}
setAppState={this.setAppState}
elements={this.scene.getElements()}
onClose={this.toggleStats}
renderCustomStats={renderCustomStats}
/>
)}
{this.state.toastMessage !== null && (
<Toast
message={this.state.toastMessage}
clearToast={this.clearToast}
/>
)}
<main>{this.renderCanvas()}</main>
</IsMobileContext.Provider>
)}
{this.state.toastMessage !== null && (
<Toast
message={this.state.toastMessage}
clearToast={this.clearToast}
/>
)}
<main>{this.renderCanvas()}</main>
</div>
);
}
@@ -791,38 +775,12 @@ class App extends React.Component<AppProps, AppState> {
this.scene.addCallback(this.onSceneUpdated);
this.addEventListeners();
if (this.props.detectPosition) {
this.detectPositionIntervalId = setInterval(
this.updateOffsetsIfChanged,
DETECT_POSITION_CHANGE_INTERVAL,
);
}
if ("ResizeObserver" in window && this.excalidrawContainerRef?.current) {
this.resizeObserver = new ResizeObserver(() => {
// compute isMobile state
// ---------------------------------------------------------------------
const {
width,
height,
} = this.excalidrawContainerRef.current!.getBoundingClientRect();
this.isMobile =
width < MQ_MAX_WIDTH_PORTRAIT ||
(height < MQ_MAX_HEIGHT_LANDSCAPE && width < MQ_MAX_WIDTH_LANDSCAPE);
// refresh offsets
// ---------------------------------------------------------------------
this.updateDOMRect();
});
this.resizeObserver?.observe(this.excalidrawContainerRef.current);
} else if (window.matchMedia) {
const mediaQuery = window.matchMedia(
`(max-width: ${MQ_MAX_WIDTH_PORTRAIT}px), (max-height: ${MQ_MAX_HEIGHT_LANDSCAPE}px) and (max-width: ${MQ_MAX_WIDTH_LANDSCAPE}px)`,
);
const handler = () => (this.isMobile = mediaQuery.matches);
mediaQuery.addListener(handler);
this.detachIsMobileMqHandler = () => mediaQuery.removeListener(handler);
}
const searchParams = new URLSearchParams(window.location.search.slice(1));
if (searchParams.has("web-share-target")) {
@@ -839,9 +797,6 @@ class App extends React.Component<AppProps, AppState> {
this.removeEventListeners();
this.scene.destroy();
clearTimeout(touchTimeout);
if (this.detectPositionIntervalId) {
clearInterval(this.detectPositionIntervalId);
}
touchTimeout = 0;
}
@@ -856,10 +811,6 @@ class App extends React.Component<AppProps, AppState> {
document.removeEventListener(EVENT.COPY, this.onCopy);
document.removeEventListener(EVENT.PASTE, this.pasteFromClipboard);
document.removeEventListener(EVENT.CUT, this.onCut);
this.nearestScrollableContainer?.removeEventListener(
EVENT.SCROLL,
this.onScroll,
);
document.removeEventListener(EVENT.KEYDOWN, this.onKeyDown, false);
document.removeEventListener(
@@ -889,8 +840,6 @@ class App extends React.Component<AppProps, AppState> {
this.onGestureEnd as any,
false,
);
this.detachIsMobileMqHandler?.();
}
private addEventListeners() {
@@ -926,15 +875,8 @@ class App extends React.Component<AppProps, AppState> {
document.addEventListener(EVENT.PASTE, this.pasteFromClipboard);
document.addEventListener(EVENT.CUT, this.onCut);
if (this.props.detectScroll) {
this.nearestScrollableContainer = getNearestScrollableContainer(
this.excalidrawContainerRef.current!,
);
this.nearestScrollableContainer.addEventListener(
EVENT.SCROLL,
this.onScroll,
);
}
document.addEventListener(EVENT.SCROLL, this.onScroll);
window.addEventListener(EVENT.RESIZE, this.onResize, false);
window.addEventListener(EVENT.UNLOAD, this.onUnload, false);
window.addEventListener(EVENT.BLUR, this.onBlur, false);
@@ -982,6 +924,13 @@ class App extends React.Component<AppProps, AppState> {
.querySelector(".excalidraw")
?.classList.toggle("theme--dark", this.state.theme === "dark");
if (this.state.autosave && this.state.fileHandle && supported) {
this.autosaveLocalSceneDebounced(
this.scene.getElementsIncludingDeleted(),
this.state,
);
}
if (
this.state.editingLinearElement &&
!this.state.selectedElementIds[this.state.editingLinearElement.elementId]
@@ -1075,7 +1024,7 @@ class App extends React.Component<AppProps, AppState> {
},
{
renderOptimizations: true,
renderScrollbars: !this.isMobile,
renderScrollbars: !isMobile(),
},
);
if (scrollBars) {
@@ -1104,7 +1053,7 @@ class App extends React.Component<AppProps, AppState> {
}
}
private updateOffsetsIfChanged = () => {
private onScroll = debounce(() => {
const { offsetTop, offsetLeft } = this.getCanvasOffsets();
this.setState((state) => {
if (state.offsetLeft === offsetLeft && state.offsetTop === offsetTop) {
@@ -1112,9 +1061,38 @@ class App extends React.Component<AppProps, AppState> {
}
return { offsetTop, offsetLeft };
});
};
}, SCROLL_TIMEOUT);
private onScroll = debounce(this.updateOffsetsIfChanged, SCROLL_TIMEOUT);
private autosaveLocalSceneDebounced = debounce(
async (elements: readonly ExcalidrawElement[], state: AppState) => {
if (this.state.autosave && this.state.fileHandle && supported) {
try {
await saveAsJSON(
elements,
state,
// only if fileHandle valid
true,
);
} catch (error) {
this.setState({
autosave: false,
toastMessage:
error.name === "NotAllowedError"
? t("toast.autosaveFailed_notAllowed")
: error.name === "NotFoundError"
? t("toast.autosaveFailed_notFound")
: t("toast.autosaveFailed"),
});
// shouldn't happen, so let's log it
if (!["NotAllowedError", "NotFoundError"].includes(error.name)) {
console.error(error);
}
}
}
},
AUTO_SAVE_TIMEOUT,
);
// Copy/paste
@@ -1216,11 +1194,6 @@ class App extends React.Component<AppProps, AppState> {
return;
}
const data = await parseClipboard(event);
if (this.props.onPaste) {
if (await this.props.onPaste(data, event)) {
return;
}
}
if (data.errorMessage) {
this.setState({ errorMessage: data.errorMessage });
} else if (data.spreadsheet) {
@@ -3877,6 +3850,8 @@ class App extends React.Component<AppProps, AppState> {
const separator = "separator";
const _isMobile = isMobile();
const elements = this.scene.getElements();
const options: ContextMenuOption[] = [];
@@ -3913,7 +3888,7 @@ class App extends React.Component<AppProps, AppState> {
ContextMenu.push({
options: [
this.isMobile &&
_isMobile &&
navigator.clipboard && {
name: "paste",
perform: (elements, appStates) => {
@@ -3924,7 +3899,7 @@ class App extends React.Component<AppProps, AppState> {
},
contextItemLabel: "labels.paste",
},
this.isMobile && navigator.clipboard && separator,
_isMobile && navigator.clipboard && separator,
probablySupportsClipboardBlob &&
elements.length > 0 &&
actionCopyAsPng,
@@ -3967,9 +3942,9 @@ class App extends React.Component<AppProps, AppState> {
ContextMenu.push({
options: [
this.isMobile && actionCut,
this.isMobile && navigator.clipboard && actionCopy,
this.isMobile &&
_isMobile && actionCut,
_isMobile && navigator.clipboard && actionCopy,
_isMobile &&
navigator.clipboard && {
name: "paste",
perform: (elements, appStates) => {
@@ -3980,7 +3955,7 @@ class App extends React.Component<AppProps, AppState> {
},
contextItemLabel: "labels.paste",
},
this.isMobile && separator,
_isMobile && separator,
...options,
separator,
actionCopyStyles,

View File

@@ -2,7 +2,7 @@ import React from "react";
import clsx from "clsx";
import { ToolButton } from "./ToolButton";
import { t } from "../i18n";
import { useIsMobile } from "../components/App";
import { useIsMobile } from "../is-mobile";
import { users } from "./icons";
import "./CollabButton.scss";

View File

@@ -218,7 +218,7 @@
left: 2px;
}
@include isMobile {
@media #{$is-mobile-query} {
display: none;
}
}

View File

@@ -76,7 +76,7 @@
z-index: 1;
}
@include isMobile {
@media #{$is-mobile-query} {
.context-menu-option {
display: block;

View File

@@ -31,7 +31,7 @@
padding: 0 16px 16px;
}
@include isMobile {
@media #{$is-mobile-query} {
.Dialog {
--metric: calc(var(--space-factor) * 4);
--inset-left: #{"max(var(--metric), var(--sal))"};

View File

@@ -2,7 +2,7 @@ import clsx from "clsx";
import React, { useEffect } from "react";
import { useCallbackRefState } from "../hooks/useCallbackRefState";
import { t } from "../i18n";
import { useIsMobile } from "../components/App";
import { useIsMobile } from "../is-mobile";
import { KEYS } from "../keys";
import "./Dialog.scss";
import { back, close } from "./icons";

View File

@@ -55,7 +55,7 @@
}
}
@include isMobile {
@media #{$is-mobile-query} {
.ExportDialog {
display: flex;
flex-direction: column;

View File

@@ -6,7 +6,7 @@ import { canvasToBlob } from "../data/blob";
import { NonDeletedExcalidrawElement } from "../element/types";
import { CanvasError } from "../errors";
import { t } from "../i18n";
import { useIsMobile } from "../components/App";
import { useIsMobile } from "../is-mobile";
import { getSelectedElements, isSomeElementSelected } from "../scene";
import { exportToCanvas, getExportSize } from "../scene/export";
import { AppState } from "../types";
@@ -202,6 +202,7 @@ const ExportModal = ({
})}
</Stack.Row>
</div>
{actionManager.renderAction("toggleAutosave")}
{actionManager.renderAction("changeExportBackground")}
{someElementIsSelected && (
<div>

View File

@@ -19,7 +19,7 @@ $wide-viewport-width: 1000px;
color: $oc-gray-6;
font-size: 0.8rem;
@include isMobile {
@media #{$is-mobile-query} {
position: static;
padding-right: 2em;
}

View File

@@ -111,7 +111,7 @@
:root[dir="rtl"] & {
left: 2px;
}
@include isMobile {
@media #{$is-mobile-query} {
display: none;
}
}

View File

@@ -14,7 +14,7 @@ import { Library } from "../data/library";
import { isTextElement, showSelectedShapeActions } from "../element";
import { NonDeletedExcalidrawElement } from "../element/types";
import { Language, t } from "../i18n";
import { useIsMobile } from "../components/App";
import { useIsMobile } from "../is-mobile";
import { calculateScrollCenter, getSelectedElements } from "../scene";
import { ExportType } from "../scene/types";
import {

View File

@@ -4,7 +4,7 @@ import React, { useEffect, useRef, useState } from "react";
import { close } from "../components/icons";
import { MIME_TYPES } from "../constants";
import { t } from "../i18n";
import { useIsMobile } from "../components/App";
import { useIsMobile } from "../is-mobile";
import { exportToSvg } from "../scene/export";
import { LibraryItem } from "../types";
import "./LibraryUnit.scss";

View File

@@ -52,7 +52,7 @@
border-radius: 6px;
box-sizing: border-box;
@include isMobile {
@media #{$is-mobile-query} {
max-width: 100%;
border: 0;
border-radius: 0;
@@ -82,7 +82,7 @@
}
}
@include isMobile {
@media #{$is-mobile-query} {
.Modal {
padding: 0;
}

View File

@@ -1,10 +1,9 @@
import "./Modal.scss";
import React, { useState, useLayoutEffect, useRef } from "react";
import React, { useState, useLayoutEffect } from "react";
import { createPortal } from "react-dom";
import clsx from "clsx";
import { KEYS } from "../keys";
import { useIsMobile } from "../components/App";
export const Modal = (props: {
className?: string;
@@ -49,16 +48,6 @@ export const Modal = (props: {
const useBodyRoot = () => {
const [div, setDiv] = useState<HTMLDivElement | null>(null);
const isMobile = useIsMobile();
const isMobileRef = useRef(isMobile);
isMobileRef.current = isMobile;
useLayoutEffect(() => {
if (div) {
div.classList.toggle("excalidraw--mobile", isMobile);
}
}, [div, isMobile]);
useLayoutEffect(() => {
const isDarkTheme = !!document
.querySelector(".excalidraw")
@@ -66,7 +55,6 @@ const useBodyRoot = () => {
const div = document.createElement("div");
div.classList.add("excalidraw", "excalidraw-modal-container");
div.classList.toggle("excalidraw--mobile", isMobileRef.current);
if (isDarkTheme) {
div.classList.add("theme--dark");

View File

@@ -2,7 +2,7 @@
.excalidraw {
.PasteChartDialog {
@include isMobile {
@media #{$is-mobile-query} {
.Island {
display: flex;
flex-direction: column;
@@ -13,7 +13,7 @@
align-items: center;
justify-content: space-around;
flex-wrap: wrap;
@include isMobile {
@media #{$is-mobile-query} {
flex-direction: column;
justify-content: center;
}

View File

@@ -2,7 +2,7 @@ import React from "react";
import { getCommonBounds } from "../element/bounds";
import { NonDeletedExcalidrawElement } from "../element/types";
import { t } from "../i18n";
import { useIsMobile } from "../components/App";
import { useIsMobile } from "../is-mobile";
import { getTargetElements } from "../scene";
import { AppState, ExcalidrawProps } from "../types";
import { close } from "./icons";

View File

@@ -10,7 +10,7 @@
cursor: default;
left: 50%;
margin-left: -150px;
padding: 4px 0;
padding: 8px;
position: absolute;
text-align: center;
width: 300px;

View File

@@ -193,7 +193,7 @@
margin-left: 5px;
margin-top: 1px;
@include isMobile {
@media #{$is-mobile-query} {
display: none;
}
}

View File

@@ -101,8 +101,9 @@ export const TOUCH_CTX_MENU_TIMEOUT = 500;
export const TITLE_TIMEOUT = 10000;
export const TOAST_TIMEOUT = 5000;
export const VERSION_TIMEOUT = 30000;
export const AUTO_SAVE_TIMEOUT = 500;
export const SCROLL_TIMEOUT = 100;
export const DETECT_POSITION_CHANGE_INTERVAL = 500;
export const ZOOM_STEP = 0.1;
// Report a user inactive after IDLE_THRESHOLD milliseconds
@@ -137,7 +138,3 @@ export const DEFAULT_UI_OPTIONS: AppProps["UIOptions"] = {
theme: true,
},
};
export const MQ_MAX_WIDTH_PORTRAIT = 730;
export const MQ_MAX_WIDTH_LANDSCAPE = 1000;
export const MQ_MAX_HEIGHT_LANDSCAPE = 500;

View File

@@ -480,7 +480,7 @@
}
}
@include isMobile {
@media #{$is-mobile-query} {
aside {
display: none;
}

View File

@@ -1,13 +1,10 @@
@import "open-color/open-color.scss";
@mixin isMobile() {
@at-root .excalidraw--mobile#{&} {
@content;
}
}
// keep up to date with is-mobile.tsx
$is-mobile-query: "(max-width: 600px), (max-height: 500px) and (max-width: 1000px)";
$theme-filter: "invert(93%) hue-rotate(180deg)";
:export {
isMobileQuery: unquote($is-mobile-query);
themeFilter: unquote($theme-filter);
}

View File

@@ -27,6 +27,7 @@ export const serializeAsJSON = (
export const saveAsJSON = async (
elements: readonly ExcalidrawElement[],
appState: AppState,
onlyIfFileHandleValid = false,
) => {
const serialized = serializeAsJSON(elements, appState);
const blob = new Blob([serialized], {
@@ -41,6 +42,7 @@ export const saveAsJSON = async (
extensions: [".excalidraw"],
},
appState.fileHandle,
onlyIfFileHandleValid,
);
return { fileHandle };
};

View File

@@ -32,13 +32,13 @@
display: flex;
align-items: center;
justify-content: center;
@include isMobile {
@media #{$is-mobile-query} {
flex-direction: column;
align-items: stretch;
}
}
@include isMobile {
@media #{$is-mobile-query} {
.RoomDialog-usernameLabel {
font-weight: bold;
}
@@ -51,7 +51,7 @@
min-width: 0;
flex: 1 1 auto;
margin-inline-start: 1em;
@include isMobile {
@media #{$is-mobile-query} {
margin-top: 0.5em;
margin-inline-start: 0;
}

View File

@@ -324,7 +324,6 @@ const ExcalidrawWrapper = () => {
renderFooter={renderFooter}
langCode={langCode}
renderCustomStats={renderCustomStats}
detectScroll={false}
/>
{excalidrawAPI && <CollabWrapper excalidrawAPI={excalidrawAPI} />}
{errorMessage && (

37
src/is-mobile.tsx Normal file
View File

@@ -0,0 +1,37 @@
import React, { useState, useEffect, useRef, useContext } from "react";
import variables from "./css/variables.module.scss";
const context = React.createContext(false);
const getIsMobileMatcher = () => {
return window.matchMedia
? window.matchMedia(variables.isMobileQuery)
: (({
matches: false,
addListener: () => {},
removeListener: () => {},
} as any) as MediaQueryList);
};
export const IsMobileProvider = ({
children,
}: {
children: React.ReactNode;
}) => {
const query = useRef<MediaQueryList>();
if (!query.current) {
query.current = getIsMobileMatcher();
}
const [isMobile, setMobile] = useState(query.current.matches);
useEffect(() => {
const handler = () => setMobile(query.current!.matches);
query.current!.addListener(handler);
return () => query.current!.removeListener(handler);
}, []);
return <context.Provider value={isMobile}>{children}</context.Provider>;
};
export const isMobile = () => getIsMobileMatcher().matches;
export const useIsMobile = () => useContext(context);

View File

@@ -78,6 +78,8 @@
"ungroup": "Ungroup selection",
"collaborators": "Collaborators",
"showGrid": "Show grid",
"toggleAutosave": "Autosave to current file",
"toggleAutosave_details": "Automatically save changes when working on an existing file.",
"addToLibrary": "Add to library",
"removeFromLibrary": "Remove from library",
"libraryLoadingMessage": "Loading library…",
@@ -249,6 +251,9 @@
"width": "Width"
},
"toast": {
"autosaveFailed_notAllowed": "Autosave was disabled.",
"autosaveFailed_notFound": "Autosave failed.\nIt seems the file no longer exists.",
"autosaveFailed": "Autosave failed.",
"copyStyles": "Copied styles.",
"copyToClipboard": "Copied to clipboard.",
"copyToClipboardAsPng": "Copied {{exportSelection}} to clipboard as PNG\n({{exportColorScheme}})",

View File

@@ -11,20 +11,6 @@ The change should be grouped under one of the below section and must contain PR
Please add the latest change on the top under the correct section.
-->
## Unreleased
## Excalidraw API
- Support detecting position of the component and recompute offsets when the position changes [#3428](https://github.com/excalidraw/excalidraw/pull/3428). Disabled by default. You can enable this by setting [`detectPosition`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#detectPosition) to `true`.
- Recompute offsets on `scroll` of the nearest scrollable container [#3408](https://github.com/excalidraw/excalidraw/pull/3408). This can be disabled by setting [`detectScroll`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#detectScroll) to `false`.
- Add `onPaste` prop to handle custom clipboard behaviours [#3420](https://github.com/excalidraw/excalidraw/pull/3420).
## Excalidraw Library
### Features
- App now breaks into mobile view using the component dimensions, not viewport dimensions. This fixes a case where the app would break sooner than necessary when the component's size is smaller than viewport [#3414](https://github.com/excalidraw/excalidraw/pull/3414).
## 0.6.0 (2021-04-04)
## Excalidraw API

View File

@@ -364,9 +364,6 @@ To view the full example visit :point_down:
| [`theme`](#theme) | `light` or `dark` | | The theme of the Excalidraw component |
| [`name`](#name) | string | | Name of the drawing |
| [`UIOptions`](#UIOptions) | <pre>{ canvasActions: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L208"> CanvasActions<a/> }</pre> | [DEFAULT UI OPTIONS](https://github.com/excalidraw/excalidraw/blob/master/src/constants.ts#L129) | To customise UI options. Currently we support customising [`canvas actions`](#canvasActions) |
| [`onPaste`](#onPaste) | <pre>(data: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/clipboard.ts#L17">ClipboardData</a>, event: ClipboardEvent &#124; null) => boolean</pre> | | Callback to be triggered if passed when the something is pasted in to the scene |
| [`detectScroll`](#detectScroll) | boolean | true | Indicates whether to recompute the offsets when nearest ancestor is scrolled. |
| [`detectPosition`](#detectPosition) | boolean | false | Indicates whether to recompute the offsets when position of the Excalidraw component is updated. |
### Dimensions of Excalidraw
@@ -442,8 +439,8 @@ You can pass a `ref` when you want to access some excalidraw APIs. We expose the
| getAppState | <pre> () => <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L37">AppState</a></pre> | Returns current appState |
| 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 |
| [`refresh`](#refresh) | `() => void` | Recomputes the offsets for the Excalidraw component. |
| [`importLibrary`](#importlibrary) | `(url: string, token?: string) => void` | Imports library from given URL. |
| refresh | `() => void` | Updates the offsets for the Excalidraw component so that the coordinates are computed correctly (for example the cursor position). You don't have to call this when the position is changed on page scroll or when the excalidraw container resizes (we handle that ourselves). For any other cases if the position of excalidraw is updated (example due to scroll on parent container and not page scroll) you should call this API. |
| [importLibrary](#importlibrary) | `(url: string, token?: string) => void` | Imports library from given URL |
| setToastMessage | `(message: string) => void` | This API can be used to show the toast with custom message. |
#### `readyPromise`
@@ -531,7 +528,7 @@ This prop controls Excalidraw's theme. When supplied, the value takes precedence
This prop sets the name of the drawing which will be used when exporting the drawing. When supplied, the value takes precedence over `intialData.appState.name`, the `name` will be fully controlled by host app and the users won't be able to edit from within Excalidraw.
#### `UIOptions`
### `UIOptions`
This prop can be used to customise UI of Excalidraw. Currently we support customising only [`canvasActions`](#canvasActions). It accepts the below parameters
@@ -539,7 +536,7 @@ This prop can be used to customise UI of Excalidraw. Currently we support custom
{ canvasActions: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L208"> CanvasActions<a/> }
</pre>
##### canvasActions
#### canvasActions
| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
@@ -551,26 +548,10 @@ This prop can be used to customise UI of Excalidraw. Currently we support custom
| `saveScene` | boolean | true | Implies whether to show `Save button` |
| `theme` | boolean | true | Implies whether to show `Theme toggle` |
#### `onPaste`
This callback is triggered if passed when something is pasted into the scene. You can use this callback in case you want to do something additional when the paste event occurs.
<pre>
(data: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/clipboard.ts#L17">ClipboardData</a>, event: ClipboardEvent &#124; null) => boolean
</pre>
This callback must return a `boolean` value or a [promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise) which resolves to a boolean value.
In case you want to prevent the excalidraw paste action you must return `true`, it will stop the native excalidraw clipboard management flow (nothing will be pasted into the scene).
### Does it support collaboration ?
No Excalidraw package doesn't come with collaboration, since this would have different implementations on the consumer so we expose the API's which you can use to communicate with Excalidraw as mentioned above. If you are interested in understanding how Excalidraw does it you can check it [here](https://github.com/excalidraw/excalidraw/blob/master/src/excalidraw-app/index.tsx).
### refresh
Recomputes the offsets for the Excalidraw component so that the coordinates are computed correctly (for example the cursor position). You don't have to call this when the position is changed due to scrolling on the nearest scrollable parent or when the excalidraw container resizes (we handle that ourselves). For any other cases if the position of excalidraw is updated (for example due to multiple scrolls or add / removal of elements in flex container) you can enable [`detectPosition`](#detectposition) or handle it manually by calling this API.
### importLibrary
Imports library from given URL. You should call this on `hashchange`, passing the `addLibrary` value if you detect it as shown below. Optionally pass a CSRF `token` to skip prompting during installation (retrievable via `token` key from the url coming from [https://libraries.excalidraw.com](https://libraries.excalidraw.com/)).
@@ -593,18 +574,6 @@ useEffect(() => {
Try out the [Demo](#Demo) to see it in action.
### detectScroll
Indicates whether Excalidraw should listen for `scroll` event on the nearest scrollable container in the DOM tree and recompute the coordinates (e.g. to correctly handle the cursor) when the component's position changes. You can disable this when you either know this doesn't affect your app or you want to take care of it yourself (calling the [`refresh()`](#refresh) method).
### detectPosition
Indicates whether the coordinates should be recomputed (e.g. to correctly handle the cursor) when the component's position changes. This is disabled by default.
Resizing and handling nearest scrollable parent ([detectScroll](#detectScroll)) is already handled so if there is any other case where position gets updated is where you will want to enable this prop (eg multiple scroll containers, or position updating due to addition/removal of elements in flex container).
You might want to disable [detectScroll](#detectScroll) when you enable this prop.
### Extra API's
#### `getSceneVersion`

View File

@@ -8,6 +8,7 @@ import "../../css/app.scss";
import "../../css/styles.scss";
import { ExcalidrawAPIRefValue, ExcalidrawProps } from "../../types";
import { IsMobileProvider } from "../../is-mobile";
import { defaultLang } from "../../i18n";
import { DEFAULT_UI_OPTIONS } from "../../constants";
@@ -29,9 +30,6 @@ const Excalidraw = (props: ExcalidrawProps) => {
theme,
name,
renderCustomStats,
onPaste,
detectScroll = true,
detectPosition = false,
} = props;
const canvasActions = props.UIOptions?.canvasActions;
@@ -63,28 +61,27 @@ const Excalidraw = (props: ExcalidrawProps) => {
return (
<InitializeApp langCode={langCode}>
<App
onChange={onChange}
initialData={initialData}
excalidrawRef={excalidrawRef}
onCollabButtonClick={onCollabButtonClick}
isCollaborating={isCollaborating}
onPointerUpdate={onPointerUpdate}
onExportToBackend={onExportToBackend}
renderFooter={renderFooter}
langCode={langCode}
viewModeEnabled={viewModeEnabled}
zenModeEnabled={zenModeEnabled}
gridModeEnabled={gridModeEnabled}
libraryReturnUrl={libraryReturnUrl}
theme={theme}
name={name}
renderCustomStats={renderCustomStats}
UIOptions={UIOptions}
onPaste={onPaste}
detectScroll={detectScroll}
detectPosition={detectPosition}
/>
<IsMobileProvider>
<App
onChange={onChange}
initialData={initialData}
excalidrawRef={excalidrawRef}
onCollabButtonClick={onCollabButtonClick}
isCollaborating={isCollaborating}
onPointerUpdate={onPointerUpdate}
onExportToBackend={onExportToBackend}
renderFooter={renderFooter}
langCode={langCode}
viewModeEnabled={viewModeEnabled}
zenModeEnabled={zenModeEnabled}
gridModeEnabled={gridModeEnabled}
libraryReturnUrl={libraryReturnUrl}
theme={theme}
name={name}
renderCustomStats={renderCustomStats}
UIOptions={UIOptions}
/>
</IsMobileProvider>
</InitializeApp>
);
};
@@ -106,6 +103,11 @@ const areEqual = (
);
};
Excalidraw.defaultProps = {
lanCode: defaultLang.code,
UIOptions: DEFAULT_UI_OPTIONS,
};
const forwardedRefComp = forwardRef<
ExcalidrawAPIRefValue,
PublicExcalidrawProps

View File

@@ -2,6 +2,7 @@
exports[`given element A and group of elements B and given both are selected when user clicks on B, on pointer up only elements from B should be selected: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -463,6 +464,7 @@ exports[`given element A and group of elements B and given both are selected whe
exports[`given element A and group of elements B and given both are selected when user shift-clicks on B, on pointer up only element A should be selected: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -930,6 +932,7 @@ exports[`given element A and group of elements B and given both are selected whe
exports[`regression tests Cmd/Ctrl-click exclusively select element under pointer: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -1706,6 +1709,7 @@ exports[`regression tests Cmd/Ctrl-click exclusively select element under pointe
exports[`regression tests Drags selected element when hitting only bounding box and keeps element selected: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -1910,6 +1914,7 @@ exports[`regression tests Drags selected element when hitting only bounding box
exports[`regression tests adjusts z order when grouping: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -2368,6 +2373,7 @@ exports[`regression tests adjusts z order when grouping: [end of test] number of
exports[`regression tests alt-drag duplicates an element: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -2621,6 +2627,7 @@ exports[`regression tests alt-drag duplicates an element: [end of test] number o
exports[`regression tests arrow keys: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -2785,6 +2792,7 @@ exports[`regression tests arrow keys: [end of test] number of renders 1`] = `20`
exports[`regression tests can drag element that covers another element, while another elem is selected: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -3262,6 +3270,7 @@ exports[`regression tests can drag element that covers another element, while an
exports[`regression tests change the properties of a shape: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "#fa5252",
@@ -3498,6 +3507,7 @@ exports[`regression tests change the properties of a shape: [end of test] number
exports[`regression tests click on an element and drag it: [dragged] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -3702,6 +3712,7 @@ exports[`regression tests click on an element and drag it: [dragged] number of r
exports[`regression tests click on an element and drag it: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -3946,6 +3957,7 @@ exports[`regression tests click on an element and drag it: [end of test] number
exports[`regression tests click to select a shape: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -4198,6 +4210,7 @@ exports[`regression tests click to select a shape: [end of test] number of rende
exports[`regression tests click-drag to select a group: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -4559,6 +4572,7 @@ exports[`regression tests click-drag to select a group: [end of test] number of
exports[`regression tests deselects group of selected elements on pointer down when pointer doesn't hit any element: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -4854,6 +4868,7 @@ exports[`regression tests deselects group of selected elements on pointer down w
exports[`regression tests deselects group of selected elements on pointer up when pointer hits common bounding box without hitting any element: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -5161,6 +5176,7 @@ exports[`regression tests deselects group of selected elements on pointer up whe
exports[`regression tests deselects selected element on pointer down when pointer doesn't hit any element: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -5369,6 +5385,7 @@ exports[`regression tests deselects selected element on pointer down when pointe
exports[`regression tests deselects selected element, on pointer up, when click hits element bounding box but doesn't hit the element: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -5555,6 +5572,7 @@ exports[`regression tests deselects selected element, on pointer up, when click
exports[`regression tests double click to edit a group: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -6008,6 +6026,7 @@ exports[`regression tests double click to edit a group: [end of test] number of
exports[`regression tests drags selected elements from point inside common bounding box that doesn't hit any element and keeps elements selected after dragging: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -6326,6 +6345,7 @@ exports[`regression tests drags selected elements from point inside common bound
exports[`regression tests draw every type of shape: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -8360,6 +8380,7 @@ exports[`regression tests draw every type of shape: [end of test] number of rend
exports[`regression tests given a group of selected elements with an element that is not selected inside the group common bounding box when element that is not selected is clicked should switch selection to not selected element on pointer up: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -8722,6 +8743,7 @@ exports[`regression tests given a group of selected elements with an element tha
exports[`regression tests given a selected element A and a not selected element B with higher z-index than A and given B partialy overlaps A when there's a shift-click on the overlapped section B is added to the selection: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "#fa5252",
@@ -8977,6 +8999,7 @@ exports[`regression tests given a selected element A and a not selected element
exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when clicking intersection between A and B B should be selected on pointer up: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "#fa5252",
@@ -9230,6 +9253,7 @@ exports[`regression tests given selected element A with lower z-index than unsel
exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when dragging on intersection between A and B A should be dragged and keep being selected: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "#fa5252",
@@ -9545,6 +9569,7 @@ exports[`regression tests given selected element A with lower z-index than unsel
exports[`regression tests key 2 selects rectangle tool: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -9709,6 +9734,7 @@ exports[`regression tests key 2 selects rectangle tool: [end of test] number of
exports[`regression tests key 3 selects diamond tool: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -9873,6 +9899,7 @@ exports[`regression tests key 3 selects diamond tool: [end of test] number of re
exports[`regression tests key 4 selects ellipse tool: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -10037,6 +10064,7 @@ exports[`regression tests key 4 selects ellipse tool: [end of test] number of re
exports[`regression tests key 5 selects arrow tool: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -10231,6 +10259,7 @@ exports[`regression tests key 5 selects arrow tool: [end of test] number of rend
exports[`regression tests key 6 selects line tool: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -10425,6 +10454,7 @@ exports[`regression tests key 6 selects line tool: [end of test] number of rende
exports[`regression tests key 7 selects draw tool: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -10619,6 +10649,7 @@ exports[`regression tests key 7 selects draw tool: [end of test] number of rende
exports[`regression tests key a selects arrow tool: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -10813,6 +10844,7 @@ exports[`regression tests key a selects arrow tool: [end of test] number of rend
exports[`regression tests key d selects diamond tool: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -10977,6 +11009,7 @@ exports[`regression tests key d selects diamond tool: [end of test] number of re
exports[`regression tests key e selects ellipse tool: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -11141,6 +11174,7 @@ exports[`regression tests key e selects ellipse tool: [end of test] number of re
exports[`regression tests key l selects line tool: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -11335,6 +11369,7 @@ exports[`regression tests key l selects line tool: [end of test] number of rende
exports[`regression tests key r selects rectangle tool: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -11499,6 +11534,7 @@ exports[`regression tests key r selects rectangle tool: [end of test] number of
exports[`regression tests key x selects draw tool: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -11693,6 +11729,7 @@ exports[`regression tests key x selects draw tool: [end of test] number of rende
exports[`regression tests make a group and duplicate it: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -12409,6 +12446,7 @@ exports[`regression tests make a group and duplicate it: [end of test] number of
exports[`regression tests noop interaction after undo shouldn't create history entry: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -12662,6 +12700,7 @@ exports[`regression tests noop interaction after undo shouldn't create history e
exports[`regression tests pinch-to-zoom works: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -12764,6 +12803,7 @@ exports[`regression tests pinch-to-zoom works: [end of test] number of renders 1
exports[`regression tests rerenders UI on language change: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -12864,6 +12904,7 @@ exports[`regression tests rerenders UI on language change: [end of test] number
exports[`regression tests shift click on selected element should deselect it on pointer up: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -13031,6 +13072,7 @@ exports[`regression tests shift click on selected element should deselect it on
exports[`regression tests shift-click to multiselect, then drag: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -13352,6 +13394,7 @@ exports[`regression tests shift-click to multiselect, then drag: [end of test] n
exports[`regression tests should show fill icons when element has non transparent background: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "#fa5252",
@@ -13555,6 +13598,7 @@ exports[`regression tests should show fill icons when element has non transparen
exports[`regression tests single-clicking on a subgroup of a selected group should not alter selection: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -14376,6 +14420,7 @@ exports[`regression tests single-clicking on a subgroup of a selected group shou
exports[`regression tests spacebar + drag scrolls the canvas: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -14476,6 +14521,7 @@ exports[`regression tests spacebar + drag scrolls the canvas: [end of test] numb
exports[`regression tests supports nested groups: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -15208,6 +15254,7 @@ exports[`regression tests supports nested groups: [end of test] number of render
exports[`regression tests switches from group of selected elements to another element on pointer down: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -15613,6 +15660,7 @@ exports[`regression tests switches from group of selected elements to another el
exports[`regression tests switches selected element on pointer down: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -15908,6 +15956,7 @@ exports[`regression tests switches selected element on pointer down: [end of tes
exports[`regression tests two-finger scroll works: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -16010,6 +16059,7 @@ exports[`regression tests two-finger scroll works: [end of test] number of rende
exports[`regression tests undo/redo drawing an element: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -16508,6 +16558,7 @@ exports[`regression tests undo/redo drawing an element: [end of test] number of
exports[`regression tests updates fontSize & fontFamily appState: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
@@ -16608,6 +16659,7 @@ exports[`regression tests updates fontSize & fontFamily appState: [end of test]
exports[`regression tests zoom hotkeys: [end of test] appState 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",

View File

@@ -2,6 +2,7 @@
exports[`exportToSvg with default arguments 1`] = `
Object {
"autosave": false,
"collaborators": Map {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",

View File

@@ -20,7 +20,6 @@ import { ExcalidrawImperativeAPI } from "./components/App";
import type { ResolvablePromise } from "./utils";
import { Spreadsheet } from "./charts";
import { Language } from "./i18n";
import { ClipboardData } from "./clipboard";
export type Point = Readonly<RoughPoint>;
@@ -40,6 +39,7 @@ export type Collaborator = {
};
export type AppState = {
autosave: boolean;
isLoading: boolean;
errorMessage: string | null;
draggingElement: NonDeletedExcalidrawElement | null;
@@ -178,10 +178,6 @@ export interface ExcalidrawProps {
appState: AppState,
canvas: HTMLCanvasElement | null,
) => void;
onPaste?: (
data: ClipboardData,
event: ClipboardEvent | null,
) => Promise<boolean> | boolean;
renderFooter?: (isMobile: boolean) => JSX.Element;
langCode?: Language["code"];
viewModeEnabled?: boolean;
@@ -195,8 +191,6 @@ export interface ExcalidrawProps {
appState: AppState,
) => JSX.Element;
UIOptions?: UIOptions;
detectScroll?: boolean;
detectPosition?: boolean;
}
export type SceneData = {
@@ -230,6 +224,4 @@ export type AppProps = ExcalidrawProps & {
UIOptions: {
canvasActions: Required<CanvasActions>;
};
detectScroll: boolean;
detectPosition: boolean;
};

View File

@@ -406,24 +406,3 @@ export const supportsEmoji = () => {
ctx.fillText("😀", 0, 0);
return ctx.getImageData(offset, offset, 1, 1).data[0] !== 0;
};
export const getNearestScrollableContainer = (
element: HTMLElement,
): HTMLElement | Document => {
let parent = element.parentElement;
while (parent) {
if (parent === document.body) {
return document;
}
const { overflowY } = window.getComputedStyle(parent);
const hasScrollableContent = parent.scrollHeight > parent.clientHeight;
if (
hasScrollableContent &&
(overflowY === "auto" || overflowY === "scroll")
) {
return parent;
}
parent = parent.parentElement;
}
return document;
};

397
yarn.lock
View File

@@ -1088,33 +1088,31 @@
version "0.4.0"
resolved "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.4.0.tgz"
"@firebase/analytics@0.6.7":
version "0.6.7"
resolved "https://registry.yarnpkg.com/@firebase/analytics/-/analytics-0.6.7.tgz#9c6d3fc478555829e43ecd7fb4dd4884ad1b9c7d"
integrity sha512-ObnFDewIqiamvU7UKDx+0jfLrD3LyqEIsXZdjnGQhY/xc10HFH0jp23lOzb39CWf/399X+xMMJ3Uj51VyHwbJQ==
"@firebase/analytics@0.6.4":
version "0.6.4"
resolved "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.6.4.tgz"
dependencies:
"@firebase/analytics-types" "0.4.0"
"@firebase/component" "0.3.1"
"@firebase/installations" "0.4.23"
"@firebase/component" "0.2.0"
"@firebase/installations" "0.4.20"
"@firebase/logger" "0.2.6"
"@firebase/util" "0.4.1"
tslib "^2.1.0"
"@firebase/util" "0.3.4"
tslib "^1.11.1"
"@firebase/app-types@0.6.1":
version "0.6.1"
resolved "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz"
"@firebase/app@0.6.18":
version "0.6.18"
resolved "https://registry.yarnpkg.com/@firebase/app/-/app-0.6.18.tgz#68df65fee9929d5ace7a25fcd522888c0858bb2b"
integrity sha512-eBThPc4QGHy/FC+oHZsnp4Qk6oksYTZ10B4jXaVH1lCS5eUSKvV1TIzAtpkPzMp2huS/qBz411r1tkQUv5vKcw==
"@firebase/app@0.6.15":
version "0.6.15"
resolved "https://registry.npmjs.org/@firebase/app/-/app-0.6.15.tgz"
dependencies:
"@firebase/app-types" "0.6.1"
"@firebase/component" "0.3.1"
"@firebase/component" "0.2.0"
"@firebase/logger" "0.2.6"
"@firebase/util" "0.4.1"
"@firebase/util" "0.3.4"
dom-storage "2.1.0"
tslib "^2.1.0"
tslib "^1.11.1"
xmlhttprequest "1.8.0"
"@firebase/auth-interop-types@0.1.5":
@@ -1131,13 +1129,12 @@
dependencies:
"@firebase/auth-types" "0.10.2"
"@firebase/component@0.3.1":
version "0.3.1"
resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.3.1.tgz#91e32bd4426ad10f6bbe86b178780e8715171986"
integrity sha512-8ACaB772bWwZRE47aVEYzld+jlDPgvHnLZoiVtG6BzygonVnKzwXo0wK6wcRzCbx4kun7G/gXYM0gUMkqvKtRA==
"@firebase/component@0.2.0":
version "0.2.0"
resolved "https://registry.npmjs.org/@firebase/component/-/component-0.2.0.tgz"
dependencies:
"@firebase/util" "0.4.1"
tslib "^2.1.0"
"@firebase/util" "0.3.4"
tslib "^1.11.1"
"@firebase/database-types@0.7.0":
version "0.7.0"
@@ -1145,68 +1142,63 @@
dependencies:
"@firebase/app-types" "0.6.1"
"@firebase/database@0.9.7":
version "0.9.7"
resolved "https://registry.yarnpkg.com/@firebase/database/-/database-0.9.7.tgz#42a03c18cd705e95ad2c8fcc739616eecb8e1dfa"
integrity sha512-JUm6CnUxFRuyWvzTAzv/Mo/KYwLtUezpNGa4AzbhbdS8t3ewprc/7ARFErpv95cIM5MgiiPcLOC5F+mLDmrQwA==
"@firebase/database@0.9.4":
version "0.9.4"
resolved "https://registry.npmjs.org/@firebase/database/-/database-0.9.4.tgz"
dependencies:
"@firebase/auth-interop-types" "0.1.5"
"@firebase/component" "0.3.1"
"@firebase/component" "0.2.0"
"@firebase/database-types" "0.7.0"
"@firebase/logger" "0.2.6"
"@firebase/util" "0.4.1"
"@firebase/util" "0.3.4"
faye-websocket "0.11.3"
tslib "^2.1.0"
tslib "^1.11.1"
"@firebase/firestore-types@2.2.0":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@firebase/firestore-types/-/firestore-types-2.2.0.tgz#9a3f3f2906232c3b4a726d988a6ef077f35f9093"
integrity sha512-5kZZtQ32FIRJP1029dw+ZVNRCclKOErHv1+Xn0pw/5Fq3dxroA/ZyFHqDu+uV52AyWHhNLjCqX43ibm4YqOzRw==
"@firebase/firestore-types@2.1.0":
version "2.1.0"
resolved "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-2.1.0.tgz"
"@firebase/firestore@2.2.2":
version "2.2.2"
resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-2.2.2.tgz#7f4fb103600c983d762748f658d7bead20827584"
integrity sha512-tFB0gRZcYQ8y9WBO5cSCij8pspF4vv2NdUkG8qWKG9cx2ccXnjo3qiQWRkoLuJGPaicCOGt11c08KvNSy/zfDA==
"@firebase/firestore@2.1.7":
version "2.1.7"
resolved "https://registry.npmjs.org/@firebase/firestore/-/firestore-2.1.7.tgz"
dependencies:
"@firebase/component" "0.3.1"
"@firebase/firestore-types" "2.2.0"
"@firebase/component" "0.2.0"
"@firebase/firestore-types" "2.1.0"
"@firebase/logger" "0.2.6"
"@firebase/util" "0.4.1"
"@firebase/util" "0.3.4"
"@firebase/webchannel-wrapper" "0.4.1"
"@grpc/grpc-js" "^1.0.0"
"@grpc/proto-loader" "^0.5.0"
node-fetch "2.6.1"
tslib "^2.1.0"
tslib "^1.11.1"
"@firebase/functions-types@0.4.0":
version "0.4.0"
resolved "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.4.0.tgz"
"@firebase/functions@0.6.5":
version "0.6.5"
resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.6.5.tgz#2302dff2ffe4257a61ab1b810166284d8d63906d"
integrity sha512-8T/BKscHJhzQ7cM9Kn2Hcs8mkA1Zypzvo4b0mue7hRm6W/vzDMsgTiAUk7j7H1HEEf1Saw58h2tlQBg2rdDHPQ==
"@firebase/functions@0.6.2":
version "0.6.2"
resolved "https://registry.npmjs.org/@firebase/functions/-/functions-0.6.2.tgz"
dependencies:
"@firebase/component" "0.3.1"
"@firebase/component" "0.2.0"
"@firebase/functions-types" "0.4.0"
"@firebase/messaging-types" "0.5.0"
node-fetch "2.6.1"
tslib "^2.1.0"
tslib "^1.11.1"
"@firebase/installations-types@0.3.4":
version "0.3.4"
resolved "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.3.4.tgz"
"@firebase/installations@0.4.23":
version "0.4.23"
resolved "https://registry.yarnpkg.com/@firebase/installations/-/installations-0.4.23.tgz#e1013f52d6b8cc1ce2faebb0e2474a5fee33e0de"
integrity sha512-vULPhK0DbDcXL0utJ8Td8+x5ArpUjSbCarz5ttR+u3Xsn1sEC6EX2Tlmua6csqNnBU/VpMo1bopWOvCVyX9jYA==
"@firebase/installations@0.4.20":
version "0.4.20"
resolved "https://registry.npmjs.org/@firebase/installations/-/installations-0.4.20.tgz"
dependencies:
"@firebase/component" "0.3.1"
"@firebase/component" "0.2.0"
"@firebase/installations-types" "0.3.4"
"@firebase/util" "0.4.1"
"@firebase/util" "0.3.4"
idb "3.0.2"
tslib "^2.1.0"
tslib "^1.11.1"
"@firebase/logger@0.2.6":
version "0.2.6"
@@ -1216,33 +1208,31 @@
version "0.5.0"
resolved "https://registry.npmjs.org/@firebase/messaging-types/-/messaging-types-0.5.0.tgz"
"@firebase/messaging@0.7.7":
version "0.7.7"
resolved "https://registry.yarnpkg.com/@firebase/messaging/-/messaging-0.7.7.tgz#ef9e1258f05dd80981b8a1b170352efe28936d0b"
integrity sha512-osS61riot7Kg3YPuQWGqxOHos+IXOrTvTdchFOU/HVxenwmXteOpepEeNC3PZvudnYSKoI/w6voo5+E5yUyftw==
"@firebase/messaging@0.7.4":
version "0.7.4"
resolved "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.7.4.tgz"
dependencies:
"@firebase/component" "0.3.1"
"@firebase/installations" "0.4.23"
"@firebase/component" "0.2.0"
"@firebase/installations" "0.4.20"
"@firebase/messaging-types" "0.5.0"
"@firebase/util" "0.4.1"
"@firebase/util" "0.3.4"
idb "3.0.2"
tslib "^2.1.0"
tslib "^1.11.1"
"@firebase/performance-types@0.0.13":
version "0.0.13"
resolved "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.0.13.tgz"
"@firebase/performance@0.4.9":
version "0.4.9"
resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.4.9.tgz#f25306b81c9887a223b100ac308454c8f6036a46"
integrity sha512-2BozmCAbvL4iFZwHE+9xSrdl3sJeF1/l8X2Ci4n8n+vwZjQbhq5pHPSZXLVT78i23V3XM14eS4SUJVqNL/QkRw==
"@firebase/performance@0.4.6":
version "0.4.6"
resolved "https://registry.npmjs.org/@firebase/performance/-/performance-0.4.6.tgz"
dependencies:
"@firebase/component" "0.3.1"
"@firebase/installations" "0.4.23"
"@firebase/component" "0.2.0"
"@firebase/installations" "0.4.20"
"@firebase/logger" "0.2.6"
"@firebase/performance-types" "0.0.13"
"@firebase/util" "0.4.1"
tslib "^2.1.0"
"@firebase/util" "0.3.4"
tslib "^1.11.1"
"@firebase/polyfill@0.3.36":
version "0.3.36"
@@ -1256,38 +1246,35 @@
version "0.1.9"
resolved "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.1.9.tgz"
"@firebase/remote-config@0.1.34":
version "0.1.34"
resolved "https://registry.yarnpkg.com/@firebase/remote-config/-/remote-config-0.1.34.tgz#3156dd2d3e94ae047aff206f0933cb529505f965"
integrity sha512-4dXdRjwuTH8lckmF8bPYCq0P/fM3NLV9QAF98Anft7f/0ZZNAucyQpvlK8KP7IRBZcllXq1Rla4THCNFtrLLOA==
"@firebase/remote-config@0.1.31":
version "0.1.31"
resolved "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.1.31.tgz"
dependencies:
"@firebase/component" "0.3.1"
"@firebase/installations" "0.4.23"
"@firebase/component" "0.2.0"
"@firebase/installations" "0.4.20"
"@firebase/logger" "0.2.6"
"@firebase/remote-config-types" "0.1.9"
"@firebase/util" "0.4.1"
tslib "^2.1.0"
"@firebase/util" "0.3.4"
tslib "^1.11.1"
"@firebase/storage-types@0.3.13":
version "0.3.13"
resolved "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.3.13.tgz"
"@firebase/storage@0.4.6":
version "0.4.6"
resolved "https://registry.yarnpkg.com/@firebase/storage/-/storage-0.4.6.tgz#485b6297a0863a8455e2c1d3b162509c215ea9a5"
integrity sha512-nXhLuPKGJlty2whW56T5/Kpr/3O+cKSB5YcCcRKUO8eBu/1VvIswPgipWFaIpgZ3hkXJqaNzYLYpTdIf1UPWrQ==
"@firebase/storage@0.4.3":
version "0.4.3"
resolved "https://registry.npmjs.org/@firebase/storage/-/storage-0.4.3.tgz"
dependencies:
"@firebase/component" "0.3.1"
"@firebase/component" "0.2.0"
"@firebase/storage-types" "0.3.13"
"@firebase/util" "0.4.1"
tslib "^2.1.0"
"@firebase/util" "0.3.4"
tslib "^1.11.1"
"@firebase/util@0.4.1":
version "0.4.1"
resolved "https://registry.yarnpkg.com/@firebase/util/-/util-0.4.1.tgz#fe76cf0238901dc5455b341cf02e298e7bf68df4"
integrity sha512-XhYCOwq4AH+YeQBEnDQvigz50WiiBU4LnJh2+//VMt4J2Ybsk0eTgUHNngUzXsmp80EJrwal3ItODg55q1ajWg==
"@firebase/util@0.3.4":
version "0.3.4"
resolved "https://registry.npmjs.org/@firebase/util/-/util-0.3.4.tgz"
dependencies:
tslib "^2.1.0"
tslib "^1.11.1"
"@firebase/webchannel-wrapper@0.4.1":
version "0.4.1"
@@ -1695,66 +1682,70 @@
estree-walker "^1.0.1"
picomatch "^2.2.2"
"@sentry/browser@6.2.5":
version "6.2.5"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.2.5.tgz#35e259e16521d26f348a06b31eb495e0033111d6"
integrity sha512-nlvaE+D7oaj4MxoY9ikw+krQDOjftnDYJQnOwOraXPk7KYM6YwmkakLuE+x/AkaH3FQVTQF330VAa9d6SWETlA==
"@sentry/browser@6.2.2":
version "6.2.2"
resolved "https://registry.npmjs.org/@sentry/browser/-/browser-6.2.2.tgz"
dependencies:
"@sentry/core" "6.2.5"
"@sentry/types" "6.2.5"
"@sentry/utils" "6.2.5"
"@sentry/core" "6.2.2"
"@sentry/types" "6.2.2"
"@sentry/utils" "6.2.2"
tslib "^1.9.3"
"@sentry/core@6.2.5":
version "6.2.5"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-6.2.5.tgz#e75093f8598becc0a4a0be927f32f7ac49e8588f"
integrity sha512-I+AkgIFO6sDUoHQticP6I27TT3L+i6TUS03in3IEtpBcSeP2jyhlxI8l/wdA7gsBqUPdQ4GHOOaNgtFIcr8qag==
"@sentry/core@6.2.2":
version "6.2.2"
resolved "https://registry.npmjs.org/@sentry/core/-/core-6.2.2.tgz"
dependencies:
"@sentry/hub" "6.2.5"
"@sentry/minimal" "6.2.5"
"@sentry/types" "6.2.5"
"@sentry/utils" "6.2.5"
"@sentry/hub" "6.2.2"
"@sentry/minimal" "6.2.2"
"@sentry/types" "6.2.2"
"@sentry/utils" "6.2.2"
tslib "^1.9.3"
"@sentry/hub@6.2.5":
version "6.2.5"
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-6.2.5.tgz#324cae0c90d736cd1032e94104bf3f18becec4d6"
integrity sha512-YlEFdEhcfqpl2HC+/dWXBsBJEljyMzFS7LRRjCk8QANcOdp9PhwQjwebUB4/ulOBjHPP2WZk7fBBd/IKDasTUg==
"@sentry/hub@6.2.2":
version "6.2.2"
resolved "https://registry.npmjs.org/@sentry/hub/-/hub-6.2.2.tgz"
dependencies:
"@sentry/types" "6.2.5"
"@sentry/utils" "6.2.5"
"@sentry/types" "6.2.2"
"@sentry/utils" "6.2.2"
tslib "^1.9.3"
"@sentry/integrations@6.2.5":
version "6.2.5"
resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-6.2.5.tgz#37cac11b486779707d62751da36aaaefbb44951a"
integrity sha512-4LOgO8lSeGaRV4w1Y03YWtTqrZdm56ciD7k0GLhv+PcFLpiu0exsS1XSs/9vET5LB5GtIgBTeJNNbxVFvvmv8g==
"@sentry/integrations@6.2.1":
version "6.2.1"
resolved "https://registry.npmjs.org/@sentry/integrations/-/integrations-6.2.1.tgz"
dependencies:
"@sentry/types" "6.2.5"
"@sentry/utils" "6.2.5"
"@sentry/types" "6.2.1"
"@sentry/utils" "6.2.1"
localforage "^1.8.1"
tslib "^1.9.3"
"@sentry/minimal@6.2.5":
version "6.2.5"
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-6.2.5.tgz#3e963e868bfa68e97581403521fd4e09a8965b02"
integrity sha512-RKP4Qx3p7Cv0oX1cPKAkNVFYM7p2k1t32cNk1+rrVQS4hwlJ7Eg6m6fsqsO+85jd6Ne/FnyYsfo9cDD3ImTlWQ==
"@sentry/minimal@6.2.2":
version "6.2.2"
resolved "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.2.2.tgz"
dependencies:
"@sentry/hub" "6.2.5"
"@sentry/types" "6.2.5"
"@sentry/hub" "6.2.2"
"@sentry/types" "6.2.2"
tslib "^1.9.3"
"@sentry/types@6.2.5":
version "6.2.5"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.2.5.tgz#34b75285b149e0b9bc5fd54fcc2c445d978c7f2e"
integrity sha512-1Sux6CLYrV9bETMsGP/HuLFLouwKoX93CWzG8BjMueW+Di0OGxZphYjXrGuDs8xO8bAKEVGCHgVQdcB2jevS0w==
"@sentry/types@6.2.1":
version "6.2.1"
resolved "https://registry.npmjs.org/@sentry/types/-/types-6.2.1.tgz"
"@sentry/utils@6.2.5":
version "6.2.5"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.2.5.tgz#be90d056b09ed1216097d7a29e3e81ba39238e1b"
integrity sha512-fJoLUZHrd5MPylV1dT4qL74yNFDl1Ur/dab+pKNSyvnHPnbZ/LRM7aJ8VaRY/A7ZdpRowU+E14e/Yeem2c6gtQ==
"@sentry/types@6.2.2":
version "6.2.2"
resolved "https://registry.npmjs.org/@sentry/types/-/types-6.2.2.tgz"
"@sentry/utils@6.2.1":
version "6.2.1"
resolved "https://registry.npmjs.org/@sentry/utils/-/utils-6.2.1.tgz"
dependencies:
"@sentry/types" "6.2.5"
"@sentry/types" "6.2.1"
tslib "^1.9.3"
"@sentry/utils@6.2.2":
version "6.2.2"
resolved "https://registry.npmjs.org/@sentry/utils/-/utils-6.2.2.tgz"
dependencies:
"@sentry/types" "6.2.2"
tslib "^1.9.3"
"@sindresorhus/is@^0.14.0":
@@ -1901,10 +1892,9 @@
lodash "^4.17.15"
redent "^3.0.0"
"@testing-library/react@11.2.6":
version "11.2.6"
resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.6.tgz#586a23adc63615985d85be0c903f374dab19200b"
integrity sha512-TXMCg0jT8xmuU8BkKMtp8l7Z50Ykew5WNX8UoIKTaLFwKkP2+1YDhOLA2Ga3wY4x29jyntk7EWfum0kjlYiSjQ==
"@testing-library/react@11.2.5":
version "11.2.5"
resolved "https://registry.npmjs.org/@testing-library/react/-/react-11.2.5.tgz"
dependencies:
"@babel/runtime" "^7.12.5"
"@testing-library/dom" "^7.28.1"
@@ -2075,10 +2065,9 @@
version "1.5.4"
resolved "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz"
"@types/react-dom@17.0.3":
version "17.0.3"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.3.tgz#7fdf37b8af9d6d40127137865bb3fff8871d7ee1"
integrity sha512-4NnJbCeWE+8YBzupn/YrJxZ8VnjcJq5iR1laqQ1vkpQgBiA7bwk0Rp24fxsdNinzJY2U+HHS4dJJDPdoMjdJ7w==
"@types/react-dom@17.0.2":
version "17.0.2"
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.2.tgz"
dependencies:
"@types/react" "*"
@@ -3027,7 +3016,7 @@ base64-arraybuffer@0.1.4:
version "0.1.4"
resolved "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz"
base64-js@^1.0.2, base64-js@^1.3.0, base64-js@^1.3.1:
base64-js@^1.0.2, base64-js@^1.2.3, base64-js@^1.3.0, base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
@@ -3216,10 +3205,10 @@ brorand@^1.0.1, brorand@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz"
browser-fs-access@0.16.4:
version "0.16.4"
resolved "https://registry.yarnpkg.com/browser-fs-access/-/browser-fs-access-0.16.4.tgz#5dbb85671b1199d74581db98d2975c2fc3a5c708"
integrity sha512-c1A9Y3pHJTKPYFjwL5SXX3MZ0BQcK7He7l0csclr80SEADIFOUHUM5oJBdg49XUdlLmIFiWiE3tbr/5KcD5TsQ==
browser-fs-access@0.16.2:
version "0.16.2"
resolved "https://registry.yarnpkg.com/browser-fs-access/-/browser-fs-access-0.16.2.tgz#0707e4b7ae237625b0a513f1ba79080fb33298a6"
integrity sha512-wuboS/Vmm85dYIvaY/oIVboNFr3YI1bV+PF19t6gtDUcjaXN7yyroJ/oKdkXJZp6UeQPdWP/tgKGmFbQkfVrYA==
browser-process-hrtime@^1.0.0:
version "1.0.0"
@@ -4022,14 +4011,6 @@ core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz"
cors@^2.8.5:
version "2.8.5"
resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
dependencies:
object-assign "^4"
vary "^1"
cosmiconfig@^5.0.0:
version "5.2.1"
resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz"
@@ -5330,7 +5311,7 @@ exegesis-express@^2.0.0:
dependencies:
exegesis "^2.0.0"
exegesis@^2.0.0, exegesis@^2.5.6:
exegesis@^2.0.0:
version "2.5.6"
resolved "https://registry.npmjs.org/exegesis/-/exegesis-2.5.6.tgz"
dependencies:
@@ -5654,10 +5635,9 @@ find-versions@^4.0.0:
dependencies:
semver-regex "^3.1.2"
firebase-tools@9.9.0:
version "9.9.0"
resolved "https://registry.yarnpkg.com/firebase-tools/-/firebase-tools-9.9.0.tgz#acb64d3a7a8f5dac0b1855783a067aabad8d5743"
integrity sha512-LM4HaLHg/UYJ1CB3VEzneRWk+BMJXH/9Id+v9nYeNzYFPRTxAxfWez2Etd2c+kFreLhCoMtSC4yx44zorqdkSA==
firebase-tools@9.6.1:
version "9.6.1"
resolved "https://registry.npmjs.org/firebase-tools/-/firebase-tools-9.6.1.tgz"
dependencies:
"@google-cloud/pubsub" "^2.7.0"
"@types/archiver" "^5.1.0"
@@ -5671,12 +5651,10 @@ firebase-tools@9.9.0:
cli-table "^0.3.1"
commander "^4.0.1"
configstore "^5.0.1"
cors "^2.8.5"
cross-env "^5.1.3"
cross-spawn "^7.0.1"
csv-streamify "^3.0.4"
dotenv "^6.1.0"
exegesis "^2.5.6"
exegesis-express "^2.0.0"
exit-code "^1.0.2"
express "^4.16.4"
@@ -5686,7 +5664,8 @@ firebase-tools@9.9.0:
google-auth-library "^6.1.3"
inquirer "~6.3.1"
js-yaml "^3.13.1"
jsonwebtoken "^8.5.1"
jsonschema "^1.0.2"
jsonwebtoken "^8.2.1"
leven "^3.1.0"
lodash "^4.17.19"
marked "^0.7.0"
@@ -5696,6 +5675,7 @@ firebase-tools@9.9.0:
node-fetch "^2.6.1"
open "^6.3.0"
ora "^3.4.0"
plist "^3.0.1"
portfinder "^1.0.23"
progress "^2.0.3"
proxy-agent "^4.0.0"
@@ -5713,28 +5693,26 @@ firebase-tools@9.9.0:
update-notifier "^4.1.0"
uuid "^3.0.0"
winston "^3.0.0"
winston-transport "^4.4.0"
ws "^7.2.3"
firebase@8.3.2:
version "8.3.2"
resolved "https://registry.yarnpkg.com/firebase/-/firebase-8.3.2.tgz#f647770472ede31e4411ce5ffdc2ab84796fe4dd"
integrity sha512-qGKASp6lXcV8NriHz/3wdltyLUjHOVkON6TQ1syGjW0sS5q/yTl9LK4O83hDLwG+UeRVRhLOaVa3jaLG4o3gnw==
firebase@8.2.10:
version "8.2.10"
resolved "https://registry.npmjs.org/firebase/-/firebase-8.2.10.tgz"
dependencies:
"@firebase/analytics" "0.6.7"
"@firebase/app" "0.6.18"
"@firebase/analytics" "0.6.4"
"@firebase/app" "0.6.15"
"@firebase/app-types" "0.6.1"
"@firebase/auth" "0.16.4"
"@firebase/database" "0.9.7"
"@firebase/firestore" "2.2.2"
"@firebase/functions" "0.6.5"
"@firebase/installations" "0.4.23"
"@firebase/messaging" "0.7.7"
"@firebase/performance" "0.4.9"
"@firebase/database" "0.9.4"
"@firebase/firestore" "2.1.7"
"@firebase/functions" "0.6.2"
"@firebase/installations" "0.4.20"
"@firebase/messaging" "0.7.4"
"@firebase/performance" "0.4.6"
"@firebase/polyfill" "0.3.36"
"@firebase/remote-config" "0.1.34"
"@firebase/storage" "0.4.6"
"@firebase/util" "0.4.1"
"@firebase/remote-config" "0.1.31"
"@firebase/storage" "0.4.3"
"@firebase/util" "0.3.4"
flat-arguments@^1.0.0:
version "1.0.2"
@@ -6512,10 +6490,9 @@ husky@4.3.8:
slash "^3.0.0"
which-pm-runs "^1.0.0"
i18next-browser-languagedetector@6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-6.1.0.tgz#83d3a920d5f300424aa37bd4c0a09c267844de71"
integrity sha512-NXbr/qPqkg6VyUwPrzmVOAafqIk1zdjzhYVxZWoSi338XEGmuOeroEglLdR8nJUJcf5BfOSHva80tqCPwXFTFQ==
i18next-browser-languagedetector@6.0.1:
version "6.0.1"
resolved "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-6.0.1.tgz"
dependencies:
"@babel/runtime" "^7.5.5"
@@ -7690,10 +7667,13 @@ jsonparse@^1.2.0:
version "1.3.1"
resolved "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz"
jsonwebtoken@^8.5.1:
jsonschema@^1.0.2:
version "1.4.0"
resolved "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.0.tgz"
jsonwebtoken@^8.2.1:
version "8.5.1"
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d"
integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==
resolved "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz"
dependencies:
jws "^3.2.2"
lodash.includes "^4.3.0"
@@ -8769,7 +8749,7 @@ oauth-sign@~0.9.0:
version "0.9.0"
resolved "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz"
object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz"
@@ -9314,6 +9294,14 @@ please-upgrade-node@^3.2.0:
dependencies:
semver-compare "^1.0.0"
plist@^3.0.1:
version "3.0.1"
resolved "https://registry.npmjs.org/plist/-/plist-3.0.1.tgz"
dependencies:
base64-js "^1.2.3"
xmlbuilder "^9.0.7"
xmldom "0.1.x"
png-chunk-text@1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/png-chunk-text/-/png-chunk-text-1.0.0.tgz"
@@ -10787,15 +10775,15 @@ rgba-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz"
rimraf@2, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.3:
version "2.7.1"
resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz"
rimraf@2, rimraf@2.6.3, rimraf@^2.5.4, rimraf@^2.6.3:
version "2.6.3"
resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz"
dependencies:
glob "^7.1.3"
rimraf@2.6.3:
version "2.6.3"
resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz"
rimraf@^2.2.8:
version "2.7.1"
resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz"
dependencies:
glob "^7.1.3"
@@ -11022,7 +11010,13 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0:
version "6.3.0"
resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz"
semver@^7.0.0, semver@^7.1.3, semver@^7.2.1, semver@^7.3.2:
semver@^7.0.0, semver@^7.1.3:
version "7.3.4"
resolved "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz"
dependencies:
lru-cache "^6.0.0"
semver@^7.2.1, semver@^7.3.2:
version "7.3.5"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
dependencies:
@@ -12053,11 +12047,11 @@ tsconfig-paths@^3.9.0:
minimist "^1.2.0"
strip-bom "^3.0.0"
tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
tslib@^1.11.1, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
version "1.14.1"
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0:
tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
@@ -12157,10 +12151,9 @@ typedarray@^0.0.6:
version "0.0.6"
resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz"
typescript@4.2.4:
version "4.2.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961"
integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==
typescript@4.2.3:
version "4.2.3"
resolved "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz"
unbox-primitive@^1.0.0:
version "1.0.1"
@@ -12416,7 +12409,7 @@ validate-npm-package-license@^3.0.1:
spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0"
vary@^1, vary@~1.1.2:
vary@~1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz"
@@ -12910,10 +12903,18 @@ xml-name-validator@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz"
xmlbuilder@^9.0.7:
version "9.0.7"
resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz"
xmlchars@^2.2.0:
version "2.2.0"
resolved "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz"
xmldom@0.1.x:
version "0.1.31"
resolved "https://registry.npmjs.org/xmldom/-/xmldom-0.1.31.tgz"
xmlhttprequest-ssl@~1.5.4:
version "1.5.5"
resolved "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz"