Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13309a66c5 | ||
|
|
531829d95e | ||
|
|
d3cbceb7fa | ||
|
|
73111500d3 | ||
|
|
9e17b64e5e | ||
|
|
326da61573 | ||
|
|
994f2a3f1e | ||
|
|
5dbcf64353 | ||
|
|
eda2320dae | ||
|
|
b610c04481 | ||
|
|
d969849357 | ||
|
|
9a66fc6c05 | ||
|
|
158f169c43 | ||
|
|
ce27cb6159 | ||
|
|
2e04bcd485 | ||
|
|
7436f3926b | ||
|
|
e429b7048d | ||
|
|
e61b447413 | ||
|
|
73f0d854bf | ||
|
|
cec3cf8334 | ||
|
|
8640e75ccf | ||
|
|
ca7ce64fea | ||
|
|
e3a78fe5df | ||
|
|
554985f749 | ||
|
|
d3857fbb35 | ||
|
|
93c72cbb32 | ||
|
|
aeb4d39387 | ||
|
|
a0259360d6 | ||
|
|
243d8de7a8 | ||
|
|
81c927bab6 |
@@ -128,41 +128,14 @@ For collaboration, you will need to set up [collab server](https://github.com/ex
|
||||
|
||||
#### Commands
|
||||
|
||||
##### Install the dependencies
|
||||
|
||||
```
|
||||
yarn
|
||||
```
|
||||
|
||||
##### Run the project
|
||||
|
||||
```
|
||||
yarn start
|
||||
```
|
||||
|
||||
##### Reformat all files with Prettier
|
||||
|
||||
```
|
||||
yarn fix
|
||||
```
|
||||
|
||||
##### Run tests
|
||||
|
||||
```
|
||||
yarn test
|
||||
```
|
||||
|
||||
##### Update test snapshots
|
||||
|
||||
```
|
||||
yarn test:update
|
||||
```
|
||||
|
||||
##### Test for formatting with Prettier
|
||||
|
||||
```
|
||||
yarn test:code
|
||||
```
|
||||
| Command | Description |
|
||||
| ------------------ | --------------------------------- |
|
||||
| `yarn` | Install the dependencies |
|
||||
| `yarn start` | Run the project |
|
||||
| `yarn fix` | Reformat all files with Prettier |
|
||||
| `yarn test` | Run tests |
|
||||
| `yarn test:update` | Update test snapshots |
|
||||
| `yarn test:code` | Test for formatting with Prettier |
|
||||
|
||||
#### Docker Compose
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ColorPicker } from "../components/ColorPicker";
|
||||
import { eraser, zoomIn, zoomOut } from "../components/icons";
|
||||
import { zoomIn, zoomOut } from "../components/icons";
|
||||
import { ToolButton } from "../components/ToolButton";
|
||||
import { DarkModeToggle } from "../components/DarkModeToggle";
|
||||
import { THEME, ZOOM_STEP } from "../constants";
|
||||
@@ -15,9 +15,8 @@ import { getShortcutKey } from "../utils";
|
||||
import { register } from "./register";
|
||||
import { Tooltip } from "../components/Tooltip";
|
||||
import { newElementWith } from "../element/mutateElement";
|
||||
import { getDefaultAppState, isEraserActive } from "../appState";
|
||||
import { getDefaultAppState } from "../appState";
|
||||
import ClearCanvas from "../components/ClearCanvas";
|
||||
import clsx from "clsx";
|
||||
|
||||
export const actionChangeViewBackgroundColor = register({
|
||||
name: "changeViewBackgroundColor",
|
||||
@@ -62,6 +61,7 @@ export const actionClearCanvas = register({
|
||||
theme: appState.theme,
|
||||
elementLocked: appState.elementLocked,
|
||||
penMode: appState.penMode,
|
||||
penDetected: appState.penDetected,
|
||||
exportBackground: appState.exportBackground,
|
||||
exportEmbedScene: appState.exportEmbedScene,
|
||||
gridSize: appState.gridSize,
|
||||
@@ -289,32 +289,3 @@ export const actionToggleTheme = register({
|
||||
),
|
||||
keyTest: (event) => event.altKey && event.shiftKey && event.code === CODES.D,
|
||||
});
|
||||
|
||||
export const actionErase = register({
|
||||
name: "eraser",
|
||||
perform: (elements, appState) => {
|
||||
return {
|
||||
appState: {
|
||||
...appState,
|
||||
selectedElementIds: {},
|
||||
selectedGroupIds: {},
|
||||
elementType: isEraserActive(appState) ? "selection" : "eraser",
|
||||
},
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
keyTest: (event) => event.key === KEYS.E,
|
||||
PanelComponent: ({ elements, appState, updateData, data }) => (
|
||||
<ToolButton
|
||||
type="button"
|
||||
icon={eraser}
|
||||
className={clsx("eraser", { active: isEraserActive(appState) })}
|
||||
title={`${t("toolBar.eraser")}-${getShortcutKey("E")}`}
|
||||
aria-label={t("toolBar.eraser")}
|
||||
onClick={() => {
|
||||
updateData(null);
|
||||
}}
|
||||
size={data?.size || "medium"}
|
||||
></ToolButton>
|
||||
),
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ import { DarkModeToggle } from "../components/DarkModeToggle";
|
||||
import { loadFromJSON, saveAsJSON } from "../data";
|
||||
import { resaveAsImageWithScene } from "../data/resave";
|
||||
import { t } from "../i18n";
|
||||
import { useDeviceType } from "../components/App";
|
||||
import { useIsMobile } from "../components/App";
|
||||
import { KEYS } from "../keys";
|
||||
import { register } from "./register";
|
||||
import { CheckboxItem } from "../components/CheckboxItem";
|
||||
@@ -200,7 +200,7 @@ export const actionSaveFileToDisk = register({
|
||||
icon={saveAs}
|
||||
title={t("buttons.saveAs")}
|
||||
aria-label={t("buttons.saveAs")}
|
||||
showAriaLabel={useDeviceType().isMobile}
|
||||
showAriaLabel={useIsMobile()}
|
||||
hidden={!nativeFileSystemSupported}
|
||||
onClick={() => updateData(null)}
|
||||
data-testid="save-as-button"
|
||||
@@ -243,7 +243,7 @@ export const actionLoadScene = register({
|
||||
icon={load}
|
||||
title={t("buttons.load")}
|
||||
aria-label={t("buttons.load")}
|
||||
showAriaLabel={useDeviceType().isMobile}
|
||||
showAriaLabel={useIsMobile()}
|
||||
onClick={updateData}
|
||||
data-testid="load-button"
|
||||
/>
|
||||
|
||||
@@ -165,7 +165,7 @@ export const actionFinalize = register({
|
||||
(!appState.draggingElement && appState.multiElement === null))) ||
|
||||
((event.key === KEYS.ESCAPE || event.key === KEYS.ENTER) &&
|
||||
appState.multiElement !== null),
|
||||
PanelComponent: ({ appState, updateData, data }) => (
|
||||
PanelComponent: ({ appState, updateData }) => (
|
||||
<ToolButton
|
||||
type="button"
|
||||
icon={done}
|
||||
@@ -173,7 +173,6 @@ export const actionFinalize = register({
|
||||
aria-label={t("buttons.done")}
|
||||
onClick={updateData}
|
||||
visible={appState.multiElement != null}
|
||||
size={data?.size || "medium"}
|
||||
/>
|
||||
),
|
||||
});
|
||||
|
||||
@@ -106,8 +106,7 @@ export type ActionName =
|
||||
| "increaseFontSize"
|
||||
| "decreaseFontSize"
|
||||
| "unbindText"
|
||||
| "hyperlink"
|
||||
| "eraser";
|
||||
| "hyperlink";
|
||||
|
||||
export type PanelComponentProps = {
|
||||
elements: readonly ExcalidrawElement[];
|
||||
|
||||
+2
-6
@@ -44,6 +44,7 @@ export const getDefaultAppState = (): Omit<
|
||||
elementLocked: false,
|
||||
elementType: "selection",
|
||||
penMode: false,
|
||||
penDetected: false,
|
||||
errorMessage: null,
|
||||
exportBackground: true,
|
||||
exportScale: defaultExportScale,
|
||||
@@ -132,6 +133,7 @@ const APP_STATE_STORAGE_CONF = (<
|
||||
elementLocked: { browser: true, export: false, server: false },
|
||||
elementType: { browser: true, export: false, server: false },
|
||||
penMode: { browser: false, export: false, server: false },
|
||||
penDetected: { browser: false, export: false, server: false },
|
||||
errorMessage: { browser: false, export: false, server: false },
|
||||
exportBackground: { browser: true, export: false, server: false },
|
||||
exportEmbedScene: { browser: true, export: false, server: false },
|
||||
@@ -211,9 +213,3 @@ export const cleanAppStateForExport = (appState: Partial<AppState>) => {
|
||||
export const clearAppStateForDatabase = (appState: Partial<AppState>) => {
|
||||
return _clearAppStateForStorage(appState, "server");
|
||||
};
|
||||
|
||||
export const isEraserActive = ({
|
||||
elementType,
|
||||
}: {
|
||||
elementType: AppState["elementType"];
|
||||
}) => elementType === "eraser";
|
||||
|
||||
+44
-57
@@ -3,7 +3,7 @@ import { ActionManager } from "../actions/manager";
|
||||
import { getNonDeletedElements } from "../element";
|
||||
import { ExcalidrawElement, PointerType } from "../element/types";
|
||||
import { t } from "../i18n";
|
||||
import { useDeviceType } from "../components/App";
|
||||
import { useIsMobile } from "../components/App";
|
||||
import {
|
||||
canChangeSharpness,
|
||||
canHaveArrowheads,
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
hasText,
|
||||
} from "../scene";
|
||||
import { SHAPES } from "../shapes";
|
||||
import { AppState, DeviceType, Zoom } from "../types";
|
||||
import { AppState, Zoom } from "../types";
|
||||
import { capitalizeString, isTransparent, setCursorForShape } from "../utils";
|
||||
import Stack from "./Stack";
|
||||
import { ToolButton } from "./ToolButton";
|
||||
@@ -30,7 +30,7 @@ export const SelectedShapeActions = ({
|
||||
appState: AppState;
|
||||
elements: readonly ExcalidrawElement[];
|
||||
renderAction: ActionManager["renderAction"];
|
||||
elementType: AppState["elementType"];
|
||||
elementType: ExcalidrawElement["type"];
|
||||
}) => {
|
||||
const targetElements = getTargetElements(
|
||||
getNonDeletedElements(elements),
|
||||
@@ -46,7 +46,7 @@ export const SelectedShapeActions = ({
|
||||
isSingleElementBoundContainer = true;
|
||||
}
|
||||
const isEditing = Boolean(appState.editingElement);
|
||||
const deviceType = useDeviceType();
|
||||
const isMobile = useIsMobile();
|
||||
const isRTL = document.documentElement.getAttribute("dir") === "rtl";
|
||||
|
||||
const showFillIcons =
|
||||
@@ -168,8 +168,8 @@ export const SelectedShapeActions = ({
|
||||
<fieldset>
|
||||
<legend>{t("labels.actions")}</legend>
|
||||
<div className="buttonList">
|
||||
{!deviceType.isMobile && renderAction("duplicateSelection")}
|
||||
{!deviceType.isMobile && renderAction("deleteSelectedElements")}
|
||||
{!isMobile && renderAction("duplicateSelection")}
|
||||
{!isMobile && renderAction("deleteSelectedElements")}
|
||||
{renderAction("group")}
|
||||
{renderAction("ungroup")}
|
||||
{targetElements.length === 1 && renderAction("hyperlink")}
|
||||
@@ -185,61 +185,48 @@ export const ShapesSwitcher = ({
|
||||
elementType,
|
||||
setAppState,
|
||||
onImageAction,
|
||||
appState,
|
||||
setDeviceType,
|
||||
}: {
|
||||
canvas: HTMLCanvasElement | null;
|
||||
elementType: AppState["elementType"];
|
||||
elementType: ExcalidrawElement["type"];
|
||||
setAppState: React.Component<any, AppState>["setState"];
|
||||
onImageAction: (data: { pointerType: PointerType | null }) => void;
|
||||
appState: AppState;
|
||||
setDeviceType: (obj: Partial<DeviceType>) => void;
|
||||
}) => {
|
||||
const penDetected = useDeviceType().penDetected;
|
||||
return (
|
||||
<>
|
||||
{SHAPES.map(({ value, icon, key }, index) => {
|
||||
const label = t(`toolBar.${value}`);
|
||||
const letter = key && (typeof key === "string" ? key : key[0]);
|
||||
const shortcut = letter
|
||||
? `${capitalizeString(letter)} ${t("helpDialog.or")} ${index + 1}`
|
||||
: `${index + 1}`;
|
||||
return (
|
||||
<ToolButton
|
||||
className="Shape"
|
||||
key={value}
|
||||
type="radio"
|
||||
icon={icon}
|
||||
checked={elementType === value}
|
||||
name="editor-current-shape"
|
||||
title={`${capitalizeString(label)} — ${shortcut}`}
|
||||
keyBindingLabel={`${index + 1}`}
|
||||
aria-label={capitalizeString(label)}
|
||||
aria-keyshortcuts={shortcut}
|
||||
data-testid={value}
|
||||
onPointerDown={({ pointerType }) => {
|
||||
if (!penDetected && pointerType === "pen") {
|
||||
setAppState({ penMode: true });
|
||||
setDeviceType({ penDetected: true });
|
||||
}
|
||||
}}
|
||||
onChange={({ pointerType }) => {
|
||||
setAppState({
|
||||
elementType: value,
|
||||
multiElement: null,
|
||||
selectedElementIds: {},
|
||||
});
|
||||
setCursorForShape(canvas, { ...appState, elementType: value });
|
||||
if (value === "image") {
|
||||
onImageAction({ pointerType });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
}) => (
|
||||
<>
|
||||
{SHAPES.map(({ value, icon, key }, index) => {
|
||||
const label = t(`toolBar.${value}`);
|
||||
const letter = key && (typeof key === "string" ? key : key[0]);
|
||||
const shortcut = letter
|
||||
? `${capitalizeString(letter)} ${t("helpDialog.or")} ${index + 1}`
|
||||
: `${index + 1}`;
|
||||
return (
|
||||
<ToolButton
|
||||
className="Shape"
|
||||
key={value}
|
||||
type="radio"
|
||||
icon={icon}
|
||||
checked={elementType === value}
|
||||
name="editor-current-shape"
|
||||
title={`${capitalizeString(label)} — ${shortcut}`}
|
||||
keyBindingLabel={`${index + 1}`}
|
||||
aria-label={capitalizeString(label)}
|
||||
aria-keyshortcuts={shortcut}
|
||||
data-testid={value}
|
||||
onChange={({ pointerType }) => {
|
||||
setAppState({
|
||||
elementType: value,
|
||||
multiElement: null,
|
||||
selectedElementIds: {},
|
||||
});
|
||||
setCursorForShape(canvas, value);
|
||||
if (value === "image") {
|
||||
onImageAction({ pointerType });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
|
||||
export const ZoomActions = ({
|
||||
renderAction,
|
||||
|
||||
+47
-209
@@ -35,7 +35,7 @@ import { ActionManager } from "../actions/manager";
|
||||
import { actions } from "../actions/register";
|
||||
import { ActionResult } from "../actions/types";
|
||||
import { trackEvent } from "../analytics";
|
||||
import { getDefaultAppState, isEraserActive } from "../appState";
|
||||
import { getDefaultAppState } from "../appState";
|
||||
import {
|
||||
copyToClipboard,
|
||||
parseClipboard,
|
||||
@@ -195,7 +195,6 @@ import {
|
||||
LibraryItems,
|
||||
PointerDownState,
|
||||
SceneData,
|
||||
DeviceType,
|
||||
} from "../types";
|
||||
import {
|
||||
debounce,
|
||||
@@ -215,8 +214,6 @@ import {
|
||||
withBatchedUpdates,
|
||||
wrapEvent,
|
||||
withBatchedUpdatesThrottled,
|
||||
updateObject,
|
||||
setEraserCursor,
|
||||
} from "../utils";
|
||||
import ContextMenu, { ContextMenuOption } from "./ContextMenu";
|
||||
import LayerUI from "./LayerUI";
|
||||
@@ -255,13 +252,8 @@ import {
|
||||
isLocalLink,
|
||||
} from "../element/Hyperlink";
|
||||
|
||||
const defaultDeviceTypeContext: DeviceType = {
|
||||
isMobile: false,
|
||||
isTouchScreen: false,
|
||||
penDetected: false,
|
||||
};
|
||||
const DeviceTypeContext = React.createContext(defaultDeviceTypeContext);
|
||||
export const useDeviceType = () => useContext(DeviceTypeContext);
|
||||
const IsMobileContext = React.createContext(false);
|
||||
export const useIsMobile = () => useContext(IsMobileContext);
|
||||
const ExcalidrawContainerContext = React.createContext<{
|
||||
container: HTMLDivElement | null;
|
||||
id: string | null;
|
||||
@@ -293,11 +285,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
rc: RoughCanvas | null = null;
|
||||
unmounted: boolean = false;
|
||||
actionManager: ActionManager;
|
||||
deviceType: DeviceType = {
|
||||
isMobile: false,
|
||||
isTouchScreen: false,
|
||||
penDetected: false,
|
||||
};
|
||||
isMobile = false;
|
||||
detachIsMobileMqHandler?: () => void;
|
||||
|
||||
private excalidrawContainerRef = React.createRef<HTMLDivElement>();
|
||||
@@ -326,7 +314,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
lastPointerDown: React.PointerEvent<HTMLCanvasElement> | null = null;
|
||||
lastPointerUp: React.PointerEvent<HTMLElement> | PointerEvent | null = null;
|
||||
contextMenuOpen: boolean = false;
|
||||
lastScenePointer: { x: number; y: number } | null = null;
|
||||
|
||||
constructor(props: AppProps) {
|
||||
super(props);
|
||||
@@ -479,7 +466,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
<div
|
||||
className={clsx("excalidraw excalidraw-container", {
|
||||
"excalidraw--view-mode": viewModeEnabled,
|
||||
"excalidraw--mobile": this.deviceType.isMobile,
|
||||
"excalidraw--mobile": this.isMobile,
|
||||
})}
|
||||
ref={this.excalidrawContainerRef}
|
||||
onDrop={this.handleAppOnDrop}
|
||||
@@ -491,13 +478,12 @@ class App extends React.Component<AppProps, AppState> {
|
||||
<ExcalidrawContainerContext.Provider
|
||||
value={this.excalidrawContainerValue}
|
||||
>
|
||||
<DeviceTypeContext.Provider value={this.deviceType}>
|
||||
<IsMobileContext.Provider value={this.isMobile}>
|
||||
<LayerUI
|
||||
canvas={this.canvas}
|
||||
appState={this.state}
|
||||
files={this.files}
|
||||
setAppState={this.setAppState}
|
||||
setDeviceType={this.setDeviceType}
|
||||
actionManager={this.actionManager}
|
||||
elements={this.scene.getElements()}
|
||||
onCollabButtonClick={onCollabButtonClick}
|
||||
@@ -559,7 +545,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
/>
|
||||
)}
|
||||
<main>{this.renderCanvas()}</main>
|
||||
</DeviceTypeContext.Provider>
|
||||
</IsMobileContext.Provider>
|
||||
</ExcalidrawContainerContext.Provider>
|
||||
</div>
|
||||
);
|
||||
@@ -903,12 +889,9 @@ class App extends React.Component<AppProps, AppState> {
|
||||
// ---------------------------------------------------------------------
|
||||
const { width, height } =
|
||||
this.excalidrawContainerRef.current!.getBoundingClientRect();
|
||||
this.setDeviceType({
|
||||
isMobile:
|
||||
width < MQ_MAX_WIDTH_PORTRAIT ||
|
||||
(height < MQ_MAX_HEIGHT_LANDSCAPE &&
|
||||
width < MQ_MAX_WIDTH_LANDSCAPE),
|
||||
});
|
||||
this.isMobile =
|
||||
width < MQ_MAX_WIDTH_PORTRAIT ||
|
||||
(height < MQ_MAX_HEIGHT_LANDSCAPE && width < MQ_MAX_WIDTH_LANDSCAPE);
|
||||
// refresh offsets
|
||||
// ---------------------------------------------------------------------
|
||||
this.updateDOMRect();
|
||||
@@ -918,9 +901,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
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.setDeviceType({ isMobile: mediaQuery.matches });
|
||||
};
|
||||
const handler = () => (this.isMobile = mediaQuery.matches);
|
||||
mediaQuery.addListener(handler);
|
||||
this.detachIsMobileMqHandler = () => mediaQuery.removeListener(handler);
|
||||
}
|
||||
@@ -1063,15 +1044,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: AppProps, prevState: AppState) {
|
||||
if (
|
||||
Object.keys(this.state.selectedElementIds).length &&
|
||||
isEraserActive(this.state)
|
||||
) {
|
||||
this.setState({ elementType: "selection" });
|
||||
}
|
||||
if (prevState.theme !== this.state.theme) {
|
||||
setEraserCursor(this.canvas, this.state.theme);
|
||||
}
|
||||
// Hide hyperlink popup if shown when element type is not selection
|
||||
if (
|
||||
prevState.elementType === "selection" &&
|
||||
@@ -1222,7 +1194,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
theme: this.state.theme,
|
||||
imageCache: this.imageCache,
|
||||
isExporting: false,
|
||||
renderScrollbars: !this.deviceType.isMobile,
|
||||
renderScrollbars: !this.isMobile,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1561,10 +1533,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
this.setState(obj);
|
||||
};
|
||||
|
||||
setDeviceType = (obj: Partial<DeviceType>): void => {
|
||||
this.deviceType = updateObject(this.deviceType, obj);
|
||||
};
|
||||
|
||||
removePointer = (event: React.PointerEvent<HTMLElement> | PointerEvent) => {
|
||||
if (touchTimeout) {
|
||||
this.resetContextMenuTimer();
|
||||
@@ -1898,7 +1866,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
} else if (this.state.elementType === "selection") {
|
||||
resetCursor(this.canvas);
|
||||
} else {
|
||||
setCursorForShape(this.canvas, this.state);
|
||||
setCursorForShape(this.canvas, this.state.elementType);
|
||||
this.setState({
|
||||
selectedElementIds: {},
|
||||
selectedGroupIds: {},
|
||||
@@ -1924,7 +1892,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
|
||||
private selectShapeTool(elementType: AppState["elementType"]) {
|
||||
if (!isHoldingSpace) {
|
||||
setCursorForShape(this.canvas, this.state);
|
||||
setCursorForShape(this.canvas, elementType);
|
||||
}
|
||||
if (isToolIcon(document.activeElement)) {
|
||||
this.focusContainer();
|
||||
@@ -2068,7 +2036,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
editingElement: null,
|
||||
});
|
||||
if (this.state.elementLocked) {
|
||||
setCursorForShape(this.canvas, this.state);
|
||||
setCursorForShape(this.canvas, this.state.elementType);
|
||||
}
|
||||
|
||||
this.focusContainer();
|
||||
@@ -2412,7 +2380,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
element,
|
||||
this.state,
|
||||
[scenePointer.x, scenePointer.y],
|
||||
this.deviceType.isMobile,
|
||||
this.isMobile,
|
||||
) &&
|
||||
index <= hitElementIndex
|
||||
);
|
||||
@@ -2445,7 +2413,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
this.hitLinkElement!,
|
||||
this.state,
|
||||
[lastPointerDownCoords.x, lastPointerDownCoords.y],
|
||||
this.deviceType.isMobile,
|
||||
this.isMobile,
|
||||
);
|
||||
const lastPointerUpCoords = viewportCoordsToSceneCoords(
|
||||
this.lastPointerUp!,
|
||||
@@ -2455,7 +2423,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
this.hitLinkElement!,
|
||||
this.state,
|
||||
[lastPointerUpCoords.x, lastPointerUpCoords.y],
|
||||
this.deviceType.isMobile,
|
||||
this.isMobile,
|
||||
);
|
||||
if (lastPointerDownHittingLinkIcon && lastPointerUpHittingLinkIcon) {
|
||||
const url = this.hitLinkElement.link;
|
||||
@@ -2482,6 +2450,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
event: React.PointerEvent<HTMLCanvasElement>,
|
||||
) => {
|
||||
this.savePointer(event.clientX, event.clientY, this.state.cursorButton);
|
||||
|
||||
if (gesture.pointers.has(event.pointerId)) {
|
||||
gesture.pointers.set(event.pointerId, {
|
||||
x: event.clientX,
|
||||
@@ -2550,7 +2519,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
if (isOverScrollBar) {
|
||||
resetCursor(this.canvas);
|
||||
} else {
|
||||
setCursorForShape(this.canvas, this.state);
|
||||
setCursorForShape(this.canvas, this.state.elementType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2600,7 +2569,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
const { points, lastCommittedPoint } = multiElement;
|
||||
const lastPoint = points[points.length - 1];
|
||||
|
||||
setCursorForShape(this.canvas, this.state);
|
||||
setCursorForShape(this.canvas, this.state.elementType);
|
||||
|
||||
if (lastPoint === lastCommittedPoint) {
|
||||
// if we haven't yet created a temp point and we're beyond commit-zone
|
||||
@@ -2655,8 +2624,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
if (
|
||||
hasDeselectedButton ||
|
||||
(this.state.elementType !== "selection" &&
|
||||
this.state.elementType !== "text" &&
|
||||
this.state.elementType !== "eraser")
|
||||
this.state.elementType !== "text")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -2714,9 +2682,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
scenePointer,
|
||||
hitElement,
|
||||
);
|
||||
if (isEraserActive(this.state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
this.hitLinkElement &&
|
||||
!this.state.selectedElementIds[this.hitLinkElement.id]
|
||||
@@ -2733,7 +2699,8 @@ class App extends React.Component<AppProps, AppState> {
|
||||
!this.state.showHyperlinkPopup
|
||||
) {
|
||||
this.setState({ showHyperlinkPopup: "info" });
|
||||
} else if (this.state.elementType === "text") {
|
||||
}
|
||||
if (this.state.elementType === "text") {
|
||||
setCursor(
|
||||
this.canvas,
|
||||
isTextElement(hitElement) ? CURSOR_TYPE.TEXT : CURSOR_TYPE.CROSSHAIR,
|
||||
@@ -2774,80 +2741,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
}
|
||||
};
|
||||
|
||||
private handleEraser = (
|
||||
event: PointerEvent,
|
||||
pointerDownState: PointerDownState,
|
||||
scenePointer: { x: number; y: number },
|
||||
) => {
|
||||
const updateElementIds = (elements: ExcalidrawElement[]) => {
|
||||
elements.forEach((element) => {
|
||||
idsToUpdate.push(element.id);
|
||||
if (event.altKey) {
|
||||
if (pointerDownState.elementIdsToErase[element.id]) {
|
||||
pointerDownState.elementIdsToErase[element.id] = false;
|
||||
}
|
||||
} else {
|
||||
pointerDownState.elementIdsToErase[element.id] = true;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const idsToUpdate: Array<string> = [];
|
||||
|
||||
const distance = distance2d(
|
||||
pointerDownState.lastCoords.x,
|
||||
pointerDownState.lastCoords.y,
|
||||
scenePointer.x,
|
||||
scenePointer.y,
|
||||
);
|
||||
const threshold = 10 / this.state.zoom.value;
|
||||
const point = { ...pointerDownState.lastCoords };
|
||||
let samplingInterval = 0;
|
||||
while (samplingInterval <= distance) {
|
||||
const hitElements = this.getElementsAtPosition(point.x, point.y);
|
||||
updateElementIds(hitElements);
|
||||
|
||||
// Exit since we reached current point
|
||||
if (samplingInterval === distance) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Calculate next point in the line at a distance of sampling interval
|
||||
samplingInterval = Math.min(samplingInterval + threshold, distance);
|
||||
|
||||
const distanceRatio = samplingInterval / distance;
|
||||
const nextX =
|
||||
(1 - distanceRatio) * point.x + distanceRatio * scenePointer.x;
|
||||
const nextY =
|
||||
(1 - distanceRatio) * point.y + distanceRatio * scenePointer.y;
|
||||
point.x = nextX;
|
||||
point.y = nextY;
|
||||
}
|
||||
|
||||
const elements = this.scene.getElements().map((ele) => {
|
||||
const id =
|
||||
isBoundToContainer(ele) && idsToUpdate.includes(ele.containerId)
|
||||
? ele.containerId
|
||||
: ele.id;
|
||||
if (idsToUpdate.includes(id)) {
|
||||
if (event.altKey) {
|
||||
if (pointerDownState.elementIdsToErase[id] === false) {
|
||||
return newElementWith(ele, {
|
||||
opacity: this.state.currentItemOpacity,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return newElementWith(ele, { opacity: 20 });
|
||||
}
|
||||
}
|
||||
return ele;
|
||||
});
|
||||
|
||||
this.scene.replaceAllElements(elements);
|
||||
|
||||
pointerDownState.lastCoords.x = scenePointer.x;
|
||||
pointerDownState.lastCoords.y = scenePointer.y;
|
||||
};
|
||||
// set touch moving for mobile context menu
|
||||
private handleTouchMove = (event: React.TouchEvent<HTMLCanvasElement>) => {
|
||||
invalidateContextMenu = true;
|
||||
@@ -2868,22 +2761,18 @@ class App extends React.Component<AppProps, AppState> {
|
||||
|
||||
//fires only once, if pen is detected, penMode is enabled
|
||||
//the user can disable this by toggling the penMode button
|
||||
if (!this.deviceType.penDetected && event.pointerType === "pen") {
|
||||
this.setDeviceType({ penDetected: true });
|
||||
this.setState({ penMode: true });
|
||||
}
|
||||
|
||||
if (
|
||||
!this.deviceType.isTouchScreen &&
|
||||
["pen", "touch"].includes(event.pointerType)
|
||||
) {
|
||||
this.setDeviceType({ isTouchScreen: true });
|
||||
if (!this.state.penDetected && event.pointerType === "pen") {
|
||||
this.setState((prevState) => {
|
||||
return {
|
||||
penMode: true,
|
||||
penDetected: true,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (isPanning) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastPointerDown = event;
|
||||
this.setState({
|
||||
lastPointerDownWith: event.pointerType,
|
||||
@@ -2976,7 +2865,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
this.state.elementType,
|
||||
pointerDownState,
|
||||
);
|
||||
} else if (this.state.elementType !== "eraser") {
|
||||
} else {
|
||||
this.createGenericElementOnPointerDown(
|
||||
this.state.elementType,
|
||||
pointerDownState,
|
||||
@@ -3010,7 +2899,8 @@ class App extends React.Component<AppProps, AppState> {
|
||||
event: React.PointerEvent<HTMLCanvasElement>,
|
||||
) => {
|
||||
this.lastPointerUp = event;
|
||||
if (this.deviceType.isTouchScreen) {
|
||||
const isTouchScreen = ["pen", "touch"].includes(event.pointerType);
|
||||
if (isTouchScreen) {
|
||||
const scenePointer = viewportCoordsToSceneCoords(
|
||||
{ clientX: event.clientX, clientY: event.clientY },
|
||||
this.state,
|
||||
@@ -3028,7 +2918,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
this.hitLinkElement &&
|
||||
!this.state.selectedElementIds[this.hitLinkElement.id]
|
||||
) {
|
||||
this.redirectToLink(event, this.deviceType.isTouchScreen);
|
||||
this.redirectToLink(event, isTouchScreen);
|
||||
}
|
||||
|
||||
this.removePointer(event);
|
||||
@@ -3152,7 +3042,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
if (this.state.viewModeEnabled) {
|
||||
setCursor(this.canvas, CURSOR_TYPE.GRAB);
|
||||
} else {
|
||||
setCursorForShape(this.canvas, this.state);
|
||||
setCursorForShape(this.canvas, this.state.elementType);
|
||||
}
|
||||
}
|
||||
this.setState({
|
||||
@@ -3249,7 +3139,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
boxSelection: {
|
||||
hasOccurred: false,
|
||||
},
|
||||
elementIdsToErase: {},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3277,7 +3166,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
|
||||
const onPointerUp = withBatchedUpdates(() => {
|
||||
isDraggingScrollBar = false;
|
||||
setCursorForShape(this.canvas, this.state);
|
||||
setCursorForShape(this.canvas, this.state.elementType);
|
||||
lastPointerUp = null;
|
||||
this.setState({
|
||||
cursorButton: "up",
|
||||
@@ -3398,7 +3287,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
pointerDownState.hit.element,
|
||||
this.state,
|
||||
[pointerDownState.origin.x, pointerDownState.origin.y],
|
||||
this.deviceType.isMobile,
|
||||
this.isMobile,
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
@@ -3838,6 +3727,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const target = event.target;
|
||||
if (!(target instanceof HTMLElement)) {
|
||||
return;
|
||||
@@ -3848,12 +3738,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
}
|
||||
|
||||
const pointerCoords = viewportCoordsToSceneCoords(event, this.state);
|
||||
|
||||
if (isEraserActive(this.state)) {
|
||||
this.handleEraser(event, pointerDownState, pointerCoords);
|
||||
return;
|
||||
}
|
||||
|
||||
const [gridX, gridY] = getGridPoint(
|
||||
pointerCoords.x,
|
||||
pointerCoords.y,
|
||||
@@ -4206,6 +4090,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
isResizing,
|
||||
isRotating,
|
||||
} = this.state;
|
||||
|
||||
this.setState({
|
||||
isResizing: false,
|
||||
isRotating: false,
|
||||
@@ -4426,36 +4311,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
// Code below handles selection when element(s) weren't
|
||||
// drag or added to selection on pointer down phase.
|
||||
const hitElement = pointerDownState.hit.element;
|
||||
if (isEraserActive(this.state)) {
|
||||
const draggedDistance = distance2d(
|
||||
this.lastPointerDown!.clientX,
|
||||
this.lastPointerDown!.clientY,
|
||||
this.lastPointerUp!.clientX,
|
||||
this.lastPointerUp!.clientY,
|
||||
);
|
||||
|
||||
if (draggedDistance === 0) {
|
||||
const scenePointer = viewportCoordsToSceneCoords(
|
||||
{
|
||||
clientX: this.lastPointerUp!.clientX,
|
||||
clientY: this.lastPointerUp!.clientY,
|
||||
},
|
||||
this.state,
|
||||
);
|
||||
const hitElements = this.getElementsAtPosition(
|
||||
scenePointer.x,
|
||||
scenePointer.y,
|
||||
);
|
||||
|
||||
hitElements.forEach(
|
||||
(hitElement) =>
|
||||
(pointerDownState.elementIdsToErase[hitElement.id] = true),
|
||||
);
|
||||
}
|
||||
this.eraseElements(pointerDownState);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
hitElement &&
|
||||
!pointerDownState.drag.hasOccurred &&
|
||||
@@ -4595,23 +4450,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
});
|
||||
}
|
||||
|
||||
private eraseElements = (pointerDownState: PointerDownState) => {
|
||||
const elements = this.scene.getElements().map((ele) => {
|
||||
if (pointerDownState.elementIdsToErase[ele.id]) {
|
||||
return newElementWith(ele, { isDeleted: true });
|
||||
} else if (
|
||||
isBoundToContainer(ele) &&
|
||||
pointerDownState.elementIdsToErase[ele.containerId]
|
||||
) {
|
||||
return newElementWith(ele, { isDeleted: true });
|
||||
}
|
||||
return ele;
|
||||
});
|
||||
|
||||
this.history.resumeRecording();
|
||||
this.scene.replaceAllElements(elements);
|
||||
};
|
||||
|
||||
private initializeImage = async ({
|
||||
imageFile,
|
||||
imageElement: _imageElement,
|
||||
@@ -5428,7 +5266,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
} else {
|
||||
ContextMenu.push({
|
||||
options: [
|
||||
this.deviceType.isMobile &&
|
||||
this.isMobile &&
|
||||
navigator.clipboard && {
|
||||
name: "paste",
|
||||
perform: (elements, appStates) => {
|
||||
@@ -5439,7 +5277,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
},
|
||||
contextItemLabel: "labels.paste",
|
||||
},
|
||||
this.deviceType.isMobile && navigator.clipboard && separator,
|
||||
this.isMobile && navigator.clipboard && separator,
|
||||
probablySupportsClipboardBlob &&
|
||||
elements.length > 0 &&
|
||||
actionCopyAsPng,
|
||||
@@ -5485,9 +5323,9 @@ class App extends React.Component<AppProps, AppState> {
|
||||
} else {
|
||||
ContextMenu.push({
|
||||
options: [
|
||||
this.deviceType.isMobile && actionCut,
|
||||
this.deviceType.isMobile && navigator.clipboard && actionCopy,
|
||||
this.deviceType.isMobile &&
|
||||
this.isMobile && actionCut,
|
||||
this.isMobile && navigator.clipboard && actionCopy,
|
||||
this.isMobile &&
|
||||
navigator.clipboard && {
|
||||
name: "paste",
|
||||
perform: (elements, appStates) => {
|
||||
@@ -5498,7 +5336,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
},
|
||||
contextItemLabel: "labels.paste",
|
||||
},
|
||||
this.deviceType.isMobile && separator,
|
||||
this.isMobile && separator,
|
||||
...options,
|
||||
separator,
|
||||
actionCopyStyles,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { t } from "../i18n";
|
||||
import { useDeviceType } from "./App";
|
||||
import { useIsMobile } from "./App";
|
||||
import { trash } from "./icons";
|
||||
import { ToolButton } from "./ToolButton";
|
||||
|
||||
@@ -19,7 +19,7 @@ const ClearCanvas = ({ onConfirm }: { onConfirm: () => void }) => {
|
||||
icon={trash}
|
||||
title={t("buttons.clearReset")}
|
||||
aria-label={t("buttons.clearReset")}
|
||||
showAriaLabel={useDeviceType().isMobile}
|
||||
showAriaLabel={useIsMobile()}
|
||||
onClick={toggleDialog}
|
||||
data-testid="clear-canvas-button"
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import clsx from "clsx";
|
||||
import { ToolButton } from "./ToolButton";
|
||||
import { t } from "../i18n";
|
||||
import { useDeviceType } from "../components/App";
|
||||
import { useIsMobile } from "../components/App";
|
||||
import { users } from "./icons";
|
||||
|
||||
import "./CollabButton.scss";
|
||||
@@ -26,7 +26,7 @@ const CollabButton = ({
|
||||
type="button"
|
||||
title={t("labels.liveCollaboration")}
|
||||
aria-label={t("labels.liveCollaboration")}
|
||||
showAriaLabel={useDeviceType().isMobile}
|
||||
showAriaLabel={useIsMobile()}
|
||||
>
|
||||
{collaboratorCount > 0 && (
|
||||
<div className="CollabButton-collaborators">{collaboratorCount}</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import clsx from "clsx";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useCallbackRefState } from "../hooks/useCallbackRefState";
|
||||
import { t } from "../i18n";
|
||||
import { useExcalidrawContainer, useDeviceType } from "../components/App";
|
||||
import { useExcalidrawContainer, useIsMobile } from "../components/App";
|
||||
import { KEYS } from "../keys";
|
||||
import "./Dialog.scss";
|
||||
import { back, close } from "./icons";
|
||||
@@ -94,7 +94,7 @@ export const Dialog = (props: DialogProps) => {
|
||||
onClick={onClose}
|
||||
aria-label={t("buttons.close")}
|
||||
>
|
||||
{useDeviceType().isMobile ? back : close}
|
||||
{useIsMobile() ? back : close}
|
||||
</button>
|
||||
</h2>
|
||||
<div className="Dialog__content">{props.children}</div>
|
||||
|
||||
@@ -139,7 +139,7 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
|
||||
<Section title={t("helpDialog.shortcuts")}>
|
||||
<Columns>
|
||||
<Column>
|
||||
<ShortcutIsland caption={t("helpDialog.tools")}>
|
||||
<ShortcutIsland caption={t("helpDialog.shapes")}>
|
||||
<Shortcut
|
||||
label={t("toolBar.selection")}
|
||||
shortcuts={["V", "1"]}
|
||||
@@ -149,7 +149,7 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
|
||||
shortcuts={["R", "2"]}
|
||||
/>
|
||||
<Shortcut label={t("toolBar.diamond")} shortcuts={["D", "3"]} />
|
||||
<Shortcut label={t("toolBar.ellipse")} shortcuts={["O", "4"]} />
|
||||
<Shortcut label={t("toolBar.ellipse")} shortcuts={["E", "4"]} />
|
||||
<Shortcut label={t("toolBar.arrow")} shortcuts={["A", "5"]} />
|
||||
<Shortcut label={t("toolBar.line")} shortcuts={["P", "6"]} />
|
||||
<Shortcut
|
||||
@@ -159,10 +159,6 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
|
||||
<Shortcut label={t("toolBar.text")} shortcuts={["T", "8"]} />
|
||||
<Shortcut label={t("toolBar.image")} shortcuts={["9"]} />
|
||||
<Shortcut label={t("toolBar.library")} shortcuts={["0"]} />
|
||||
<Shortcut
|
||||
label={t("toolBar.eraser")}
|
||||
shortcuts={[getShortcutKey("E")]}
|
||||
/>
|
||||
<Shortcut
|
||||
label={t("helpDialog.editSelectedShape")}
|
||||
shortcuts={[
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
isTextElement,
|
||||
} from "../element/typeChecks";
|
||||
import { getShortcutKey } from "../utils";
|
||||
import { isEraserActive } from "../appState";
|
||||
|
||||
interface HintViewerProps {
|
||||
appState: AppState;
|
||||
@@ -23,9 +22,6 @@ const getHints = ({ appState, elements, isMobile }: HintViewerProps) => {
|
||||
const { elementType, isResizing, isRotating, lastPointerDownWith } = appState;
|
||||
const multiMode = appState.multiElement !== null;
|
||||
|
||||
if (isEraserActive(appState)) {
|
||||
return t("hints.eraserRevert");
|
||||
}
|
||||
if (elementType === "arrow" || elementType === "line") {
|
||||
if (!multiMode) {
|
||||
return t("hints.linearElement");
|
||||
|
||||
@@ -6,7 +6,7 @@ import { canvasToBlob } from "../data/blob";
|
||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||
import { CanvasError } from "../errors";
|
||||
import { t } from "../i18n";
|
||||
import { useDeviceType } from "./App";
|
||||
import { useIsMobile } from "./App";
|
||||
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
||||
import { exportToCanvas } from "../scene/export";
|
||||
import { AppState, BinaryFiles } from "../types";
|
||||
@@ -250,7 +250,7 @@ export const ImageExportDialog = ({
|
||||
icon={exportImage}
|
||||
type="button"
|
||||
aria-label={t("buttons.exportImage")}
|
||||
showAriaLabel={useDeviceType().isMobile}
|
||||
showAriaLabel={useIsMobile()}
|
||||
title={t("buttons.exportImage")}
|
||||
/>
|
||||
{modalIsShown && (
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useState } from "react";
|
||||
import { ActionsManagerInterface } from "../actions/types";
|
||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||
import { t } from "../i18n";
|
||||
import { useDeviceType } from "./App";
|
||||
import { useIsMobile } from "./App";
|
||||
import { AppState, ExportOpts, BinaryFiles } from "../types";
|
||||
import { Dialog } from "./Dialog";
|
||||
import { exportFile, exportToFileIcon, link } from "./icons";
|
||||
@@ -114,7 +114,7 @@ export const JSONExportDialog = ({
|
||||
icon={exportFile}
|
||||
type="button"
|
||||
aria-label={t("buttons.export")}
|
||||
showAriaLabel={useDeviceType().isMobile}
|
||||
showAriaLabel={useIsMobile()}
|
||||
title={t("buttons.export")}
|
||||
/>
|
||||
{modalIsShown && (
|
||||
|
||||
+16
-50
@@ -6,15 +6,10 @@ import { exportCanvas } from "../data";
|
||||
import { isTextElement, showSelectedShapeActions } from "../element";
|
||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||
import { Language, t } from "../i18n";
|
||||
import { useIsMobile } from "../components/App";
|
||||
import { calculateScrollCenter, getSelectedElements } from "../scene";
|
||||
import { ExportType } from "../scene/types";
|
||||
import {
|
||||
AppProps,
|
||||
AppState,
|
||||
ExcalidrawProps,
|
||||
BinaryFiles,
|
||||
DeviceType,
|
||||
} from "../types";
|
||||
import { AppProps, AppState, ExcalidrawProps, BinaryFiles } from "../types";
|
||||
import { muteFSAbortError } from "../utils";
|
||||
import { SelectedShapeActions, ShapesSwitcher, ZoomActions } from "./Actions";
|
||||
import { BackgroundPickerAndDarkModeToggle } from "./BackgroundPickerAndDarkModeToggle";
|
||||
@@ -42,7 +37,6 @@ import { LibraryMenu } from "./LibraryMenu";
|
||||
import "./LayerUI.scss";
|
||||
import "./Toolbar.scss";
|
||||
import { PenModeButton } from "./PenModeButton";
|
||||
import { useDeviceType } from "../components/App";
|
||||
|
||||
interface LayerUIProps {
|
||||
actionManager: ActionManager;
|
||||
@@ -50,7 +44,6 @@ interface LayerUIProps {
|
||||
files: BinaryFiles;
|
||||
canvas: HTMLCanvasElement | null;
|
||||
setAppState: React.Component<any, AppState>["setState"];
|
||||
setDeviceType: (obj: Partial<DeviceType>) => void;
|
||||
elements: readonly NonDeletedExcalidrawElement[];
|
||||
onCollabButtonClick?: () => void;
|
||||
onLockToggle: () => void;
|
||||
@@ -81,7 +74,6 @@ const LayerUI = ({
|
||||
appState,
|
||||
files,
|
||||
setAppState,
|
||||
setDeviceType,
|
||||
canvas,
|
||||
elements,
|
||||
onCollabButtonClick,
|
||||
@@ -103,7 +95,7 @@ const LayerUI = ({
|
||||
id,
|
||||
onImageAction,
|
||||
}: LayerUIProps) => {
|
||||
const deviceType = useDeviceType();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const renderJSONExportDialog = () => {
|
||||
if (!UIOptions.canvasActions.export) {
|
||||
@@ -329,7 +321,7 @@ const LayerUI = ({
|
||||
checked={appState.penMode}
|
||||
onChange={onPenModeToggle}
|
||||
title={t("toolBar.penMode")}
|
||||
penDetected={deviceType.penDetected}
|
||||
penDetected={appState.penDetected}
|
||||
/>
|
||||
<LockButton
|
||||
zenModeEnabled={zenModeEnabled}
|
||||
@@ -346,12 +338,11 @@ const LayerUI = ({
|
||||
<HintViewer
|
||||
appState={appState}
|
||||
elements={elements}
|
||||
isMobile={deviceType.isMobile}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
{heading}
|
||||
<Stack.Row gap={1}>
|
||||
<ShapesSwitcher
|
||||
appState={appState}
|
||||
canvas={canvas}
|
||||
elementType={appState.elementType}
|
||||
setAppState={setAppState}
|
||||
@@ -360,7 +351,6 @@ const LayerUI = ({
|
||||
insertOnCanvasDirectly: pointerType !== "mouse",
|
||||
});
|
||||
}}
|
||||
setDeviceType={setDeviceType}
|
||||
/>
|
||||
</Stack.Row>
|
||||
</Island>
|
||||
@@ -398,7 +388,7 @@ const LayerUI = ({
|
||||
</Tooltip>
|
||||
))}
|
||||
</UserList>
|
||||
{renderTopRightUI?.(deviceType.isMobile, appState)}
|
||||
{renderTopRightUI?.(isMobile, appState)}
|
||||
</div>
|
||||
</div>
|
||||
</FixedSideContainer>
|
||||
@@ -428,39 +418,16 @@ const LayerUI = ({
|
||||
/>
|
||||
</Island>
|
||||
{!viewModeEnabled && (
|
||||
<>
|
||||
<div
|
||||
className={clsx("undo-redo-buttons zen-mode-transition", {
|
||||
"layer-ui__wrapper__footer-left--transition-bottom":
|
||||
zenModeEnabled,
|
||||
})}
|
||||
>
|
||||
{actionManager.renderAction("undo", { size: "small" })}
|
||||
{actionManager.renderAction("redo", { size: "small" })}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={clsx("eraser-buttons zen-mode-transition", {
|
||||
"layer-ui__wrapper__footer-left--transition-left":
|
||||
zenModeEnabled,
|
||||
})}
|
||||
>
|
||||
{actionManager.renderAction("eraser", { size: "small" })}
|
||||
</div>
|
||||
</>
|
||||
<div
|
||||
className={clsx("undo-redo-buttons zen-mode-transition", {
|
||||
"layer-ui__wrapper__footer-left--transition-bottom":
|
||||
zenModeEnabled,
|
||||
})}
|
||||
>
|
||||
{actionManager.renderAction("undo", { size: "small" })}
|
||||
{actionManager.renderAction("redo", { size: "small" })}
|
||||
</div>
|
||||
)}
|
||||
{!viewModeEnabled &&
|
||||
appState.multiElement &&
|
||||
deviceType.isTouchScreen && (
|
||||
<div
|
||||
className={clsx("finalize-button zen-mode-transition", {
|
||||
"layer-ui__wrapper__footer-left--transition-left":
|
||||
zenModeEnabled,
|
||||
})}
|
||||
>
|
||||
{actionManager.renderAction("finalize", { size: "small" })}
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
</Stack.Col>
|
||||
</div>
|
||||
@@ -528,7 +495,7 @@ const LayerUI = ({
|
||||
</>
|
||||
);
|
||||
|
||||
return deviceType.isMobile ? (
|
||||
return isMobile ? (
|
||||
<>
|
||||
{dialogs}
|
||||
<MobileMenu
|
||||
@@ -539,7 +506,6 @@ const LayerUI = ({
|
||||
renderJSONExportDialog={renderJSONExportDialog}
|
||||
renderImageExportDialog={renderImageExportDialog}
|
||||
setAppState={setAppState}
|
||||
setDeviceType={setDeviceType}
|
||||
onCollabButtonClick={onCollabButtonClick}
|
||||
onLockToggle={onLockToggle}
|
||||
onPenModeToggle={onPenModeToggle}
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
LibraryItems,
|
||||
} from "../types";
|
||||
import { muteFSAbortError } from "../utils";
|
||||
import { useDeviceType } from "./App";
|
||||
import { useIsMobile } from "./App";
|
||||
import ConfirmDialog from "./ConfirmDialog";
|
||||
import { exportToFileIcon, load, publishIcon, trash } from "./icons";
|
||||
import { LibraryUnit } from "./LibraryUnit";
|
||||
@@ -85,7 +85,7 @@ const LibraryMenuItems = ({
|
||||
|
||||
const [showRemoveLibAlert, setShowRemoveLibAlert] = useState(false);
|
||||
|
||||
const isMobile = useDeviceType().isMobile;
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const renderLibraryActions = () => {
|
||||
const itemsSelected = !!selectedItems.length;
|
||||
|
||||
@@ -2,7 +2,7 @@ import clsx from "clsx";
|
||||
import oc from "open-color";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { MIME_TYPES } from "../constants";
|
||||
import { useDeviceType } from "../components/App";
|
||||
import { useIsMobile } from "../components/App";
|
||||
import { exportToSvg } from "../scene/export";
|
||||
import { BinaryFiles, LibraryItem } from "../types";
|
||||
import "./LibraryUnit.scss";
|
||||
@@ -66,7 +66,7 @@ export const LibraryUnit = ({
|
||||
}, [elements, files]);
|
||||
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const isMobile = useDeviceType().isMobile;
|
||||
const isMobile = useIsMobile();
|
||||
const adder = isPending && (
|
||||
<div className="library-unit__adder">{PLUS_ICON}</div>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { AppState, DeviceType } from "../types";
|
||||
import { AppState } from "../types";
|
||||
import { ActionManager } from "../actions/manager";
|
||||
import { t } from "../i18n";
|
||||
import Stack from "./Stack";
|
||||
@@ -8,7 +8,7 @@ import { NonDeletedExcalidrawElement } from "../element/types";
|
||||
import { FixedSideContainer } from "./FixedSideContainer";
|
||||
import { Island } from "./Island";
|
||||
import { HintViewer } from "./HintViewer";
|
||||
import { calculateScrollCenter, getSelectedElements } from "../scene";
|
||||
import { calculateScrollCenter } from "../scene";
|
||||
import { SelectedShapeActions, ShapesSwitcher } from "./Actions";
|
||||
import { Section } from "./Section";
|
||||
import CollabButton from "./CollabButton";
|
||||
@@ -18,7 +18,6 @@ import { UserList } from "./UserList";
|
||||
import { BackgroundPickerAndDarkModeToggle } from "./BackgroundPickerAndDarkModeToggle";
|
||||
import { LibraryButton } from "./LibraryButton";
|
||||
import { PenModeButton } from "./PenModeButton";
|
||||
import { useDeviceType } from "./App";
|
||||
|
||||
type MobileMenuProps = {
|
||||
appState: AppState;
|
||||
@@ -26,7 +25,6 @@ type MobileMenuProps = {
|
||||
renderJSONExportDialog: () => React.ReactNode;
|
||||
renderImageExportDialog: () => React.ReactNode;
|
||||
setAppState: React.Component<any, AppState>["setState"];
|
||||
setDeviceType: (obj: Partial<DeviceType>) => void;
|
||||
elements: readonly NonDeletedExcalidrawElement[];
|
||||
libraryMenu: JSX.Element | null;
|
||||
onCollabButtonClick?: () => void;
|
||||
@@ -52,7 +50,6 @@ export const MobileMenu = ({
|
||||
renderJSONExportDialog,
|
||||
renderImageExportDialog,
|
||||
setAppState,
|
||||
setDeviceType,
|
||||
onCollabButtonClick,
|
||||
onLockToggle,
|
||||
onPenModeToggle,
|
||||
@@ -64,7 +61,6 @@ export const MobileMenu = ({
|
||||
onImageAction,
|
||||
renderTopRightUI,
|
||||
}: MobileMenuProps) => {
|
||||
const deviceType = useDeviceType();
|
||||
const renderToolbar = () => {
|
||||
return (
|
||||
<FixedSideContainer side="top" className="App-top-bar">
|
||||
@@ -76,7 +72,6 @@ export const MobileMenu = ({
|
||||
{heading}
|
||||
<Stack.Row gap={1}>
|
||||
<ShapesSwitcher
|
||||
appState={appState}
|
||||
canvas={canvas}
|
||||
elementType={appState.elementType}
|
||||
setAppState={setAppState}
|
||||
@@ -85,7 +80,6 @@ export const MobileMenu = ({
|
||||
insertOnCanvasDirectly: pointerType !== "mouse",
|
||||
});
|
||||
}}
|
||||
setDeviceType={setDeviceType}
|
||||
/>
|
||||
</Stack.Row>
|
||||
</Island>
|
||||
@@ -106,7 +100,7 @@ export const MobileMenu = ({
|
||||
onChange={onPenModeToggle}
|
||||
title={t("toolBar.penMode")}
|
||||
isMobile
|
||||
penDetected={deviceType.penDetected}
|
||||
penDetected={appState.penDetected}
|
||||
/>
|
||||
</Stack.Row>
|
||||
{libraryMenu}
|
||||
@@ -119,12 +113,6 @@ export const MobileMenu = ({
|
||||
};
|
||||
|
||||
const renderAppToolbar = () => {
|
||||
// Render eraser conditionally in mobile
|
||||
const showEraser =
|
||||
!appState.viewModeEnabled &&
|
||||
!appState.editingElement &&
|
||||
getSelectedElements(elements, appState).length === 0;
|
||||
|
||||
if (viewModeEnabled) {
|
||||
return (
|
||||
<div className="App-toolbar-content">
|
||||
@@ -132,16 +120,12 @@ export const MobileMenu = ({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="App-toolbar-content">
|
||||
{actionManager.renderAction("toggleCanvasMenu")}
|
||||
{actionManager.renderAction("toggleEditMenu")}
|
||||
|
||||
{actionManager.renderAction("undo")}
|
||||
{actionManager.renderAction("redo")}
|
||||
{showEraser && actionManager.renderAction("eraser")}
|
||||
|
||||
{actionManager.renderAction(
|
||||
appState.multiElement ? "finalize" : "duplicateSelection",
|
||||
)}
|
||||
|
||||
@@ -4,7 +4,7 @@ import React, { useState, useLayoutEffect, useRef } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import clsx from "clsx";
|
||||
import { KEYS } from "../keys";
|
||||
import { useExcalidrawContainer, useDeviceType } from "./App";
|
||||
import { useExcalidrawContainer, useIsMobile } from "./App";
|
||||
import { AppState } from "../types";
|
||||
import { THEME } from "../constants";
|
||||
|
||||
@@ -59,17 +59,17 @@ export const Modal = (props: {
|
||||
const useBodyRoot = (theme: AppState["theme"]) => {
|
||||
const [div, setDiv] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
const deviceType = useDeviceType();
|
||||
const isMobileRef = useRef(deviceType.isMobile);
|
||||
isMobileRef.current = deviceType.isMobile;
|
||||
const isMobile = useIsMobile();
|
||||
const isMobileRef = useRef(isMobile);
|
||||
isMobileRef.current = isMobile;
|
||||
|
||||
const { container: excalidrawContainer } = useExcalidrawContainer();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (div) {
|
||||
div.classList.toggle("excalidraw--mobile", deviceType.isMobile);
|
||||
div.classList.toggle("excalidraw--mobile", isMobile);
|
||||
}
|
||||
}, [div, deviceType.isMobile]);
|
||||
}, [div, isMobile]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const isDarkTheme =
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from "react";
|
||||
import { getCommonBounds } from "../element/bounds";
|
||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||
import { t } from "../i18n";
|
||||
import { useDeviceType } from "../components/App";
|
||||
import { useIsMobile } from "../components/App";
|
||||
import { getTargetElements } from "../scene";
|
||||
import { AppState, ExcalidrawProps } from "../types";
|
||||
import { close } from "./icons";
|
||||
@@ -16,13 +16,13 @@ export const Stats = (props: {
|
||||
onClose: () => void;
|
||||
renderCustomStats: ExcalidrawProps["renderCustomStats"];
|
||||
}) => {
|
||||
const deviceType = useDeviceType();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const boundingBox = getCommonBounds(props.elements);
|
||||
const selectedElements = getTargetElements(props.elements, props.appState);
|
||||
const selectedBoundingBox = getCommonBounds(selectedElements);
|
||||
|
||||
if (deviceType.isMobile && props.appState.openMenu) {
|
||||
if (isMobile && props.appState.openMenu) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,6 @@ type ToolButtonProps =
|
||||
type: "radio";
|
||||
checked: boolean;
|
||||
onChange?(data: { pointerType: PointerType | null }): void;
|
||||
onPointerDown?(data: { pointerType: PointerType }): void;
|
||||
});
|
||||
|
||||
export const ToolButton = React.forwardRef((props: ToolButtonProps, ref) => {
|
||||
@@ -150,7 +149,6 @@ export const ToolButton = React.forwardRef((props: ToolButtonProps, ref) => {
|
||||
title={props.title}
|
||||
onPointerDown={(event) => {
|
||||
lastPointerTypeRef.current = event.pointerType || null;
|
||||
props.onPointerDown?.({ pointerType: event.pointerType || null });
|
||||
}}
|
||||
onPointerUp={() => {
|
||||
requestAnimationFrame(() => {
|
||||
|
||||
@@ -934,7 +934,3 @@ export const editIcon = createIcon(
|
||||
></path>,
|
||||
{ width: 640, height: 512 },
|
||||
);
|
||||
|
||||
export const eraser = createIcon(
|
||||
<path d="M480 416C497.7 416 512 430.3 512 448C512 465.7 497.7 480 480 480H150.6C133.7 480 117.4 473.3 105.4 461.3L25.37 381.3C.3786 356.3 .3786 315.7 25.37 290.7L258.7 57.37C283.7 32.38 324.3 32.38 349.3 57.37L486.6 194.7C511.6 219.7 511.6 260.3 486.6 285.3L355.9 416H480zM265.4 416L332.7 348.7L195.3 211.3L70.63 336L150.6 416L265.4 416z" />,
|
||||
);
|
||||
|
||||
@@ -63,6 +63,8 @@ export const ENV = {
|
||||
|
||||
export const CLASSES = {
|
||||
SHAPE_ACTIONS_MENU: "App-menu__left",
|
||||
SHAPE_ACTIONS_MOBILE_MENU: "App-mobile-menu",
|
||||
MOBILE_TOOLBAR: "App-toolbar-content",
|
||||
};
|
||||
|
||||
// 1-based in case we ever do `if(element.fontFamily)`
|
||||
|
||||
+1
-21
@@ -290,16 +290,6 @@
|
||||
width: 100%;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
.eraser {
|
||||
&.ToolIcon:hover {
|
||||
--icon-fill-color: #fff;
|
||||
--keybinding-color: #fff;
|
||||
}
|
||||
&.active {
|
||||
background-color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.App-toolbar-content {
|
||||
@@ -477,17 +467,7 @@
|
||||
font-family: var(--ui-font);
|
||||
}
|
||||
|
||||
.finalize-button {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
gap: 0.4em;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
margin-inline-start: 0.6em;
|
||||
}
|
||||
|
||||
.undo-redo-buttons,
|
||||
.eraser-buttons {
|
||||
.undo-redo-buttons {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
gap: 0.4em;
|
||||
|
||||
+2
-3
@@ -31,8 +31,8 @@ type RestoredAppState = Omit<
|
||||
>;
|
||||
|
||||
export const AllowedExcalidrawElementTypes: Record<
|
||||
AppState["elementType"],
|
||||
boolean
|
||||
ExcalidrawElement["type"],
|
||||
true
|
||||
> = {
|
||||
selection: true,
|
||||
text: true,
|
||||
@@ -43,7 +43,6 @@ export const AllowedExcalidrawElementTypes: Record<
|
||||
image: true,
|
||||
arrow: true,
|
||||
freedraw: true,
|
||||
eraser: false,
|
||||
};
|
||||
|
||||
export type RestoredDataState = {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { SHAPES } from "../shapes";
|
||||
import { updateBoundElements } from "./binding";
|
||||
import { getCommonBounds } from "./bounds";
|
||||
import { mutateElement } from "./mutateElement";
|
||||
@@ -92,7 +93,7 @@ export const getDragOffsetXY = (
|
||||
|
||||
export const dragNewElement = (
|
||||
draggingElement: NonDeletedExcalidrawElement,
|
||||
elementType: AppState["elementType"],
|
||||
elementType: typeof SHAPES[number]["value"],
|
||||
originX: number,
|
||||
originY: number,
|
||||
x: number,
|
||||
|
||||
@@ -10,6 +10,5 @@ export const showSelectedShapeActions = (
|
||||
!appState.viewModeEnabled &&
|
||||
(appState.editingElement ||
|
||||
getSelectedElements(elements, appState).length ||
|
||||
(appState.elementType !== "selection" &&
|
||||
appState.elementType !== "eraser")),
|
||||
appState.elementType !== "selection"),
|
||||
);
|
||||
|
||||
@@ -542,9 +542,29 @@ export const textWysiwyg = ({
|
||||
target.closest(".color-picker-input") &&
|
||||
isWritableElement(target);
|
||||
|
||||
const isShapeActionsPanel =
|
||||
(target instanceof HTMLElement || target instanceof SVGElement) &&
|
||||
(target.closest(`.${CLASSES.SHAPE_ACTIONS_MENU}`) ||
|
||||
target.closest(`.${CLASSES.SHAPE_ACTIONS_MOBILE_MENU}`) ||
|
||||
target.closest(`.${CLASSES.MOBILE_TOOLBAR}`));
|
||||
|
||||
setTimeout(() => {
|
||||
editable.onblur = handleSubmit;
|
||||
if (target && isTargetColorPicker) {
|
||||
editable.onblur = () => {
|
||||
app.setState({
|
||||
toastMessage:
|
||||
target instanceof HTMLElement
|
||||
? target.tagName ?? "no tagName"
|
||||
: "not an HTMLElement",
|
||||
});
|
||||
if (isShapeActionsPanel) {
|
||||
return;
|
||||
}
|
||||
app.setState({
|
||||
toastMessage: "debug: onblur",
|
||||
});
|
||||
handleSubmit();
|
||||
};
|
||||
if (target && (isTargetColorPicker || isShapeActionsPanel)) {
|
||||
target.onblur = () => {
|
||||
editable.focus();
|
||||
};
|
||||
@@ -562,13 +582,22 @@ export const textWysiwyg = ({
|
||||
event.target instanceof HTMLInputElement &&
|
||||
event.target.closest(".color-picker-input") &&
|
||||
isWritableElement(event.target);
|
||||
const isShapeActionsPanel =
|
||||
(event.target instanceof HTMLElement ||
|
||||
event.target instanceof SVGElement) &&
|
||||
(event.target.closest(`.${CLASSES.SHAPE_ACTIONS_MENU}`) ||
|
||||
event.target.closest(`.${CLASSES.SHAPE_ACTIONS_MOBILE_MENU}`) ||
|
||||
event.target.closest(`.${CLASSES.MOBILE_TOOLBAR}`));
|
||||
if (
|
||||
((event.target instanceof HTMLElement ||
|
||||
event.target instanceof SVGElement) &&
|
||||
event.target.closest(`.${CLASSES.SHAPE_ACTIONS_MENU}`) &&
|
||||
isShapeActionsPanel &&
|
||||
!isWritableElement(event.target)) ||
|
||||
isTargetColorPicker
|
||||
) {
|
||||
app.setState({
|
||||
toastMessage: "debug: onPointerDown",
|
||||
});
|
||||
editable.onblur = null;
|
||||
window.addEventListener("pointerup", bindBlurEvent);
|
||||
// handle edge-case where pointerup doesn't fire e.g. due to user
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { AppState } from "../types";
|
||||
import {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawTextElement,
|
||||
@@ -61,7 +60,7 @@ export const isLinearElement = (
|
||||
};
|
||||
|
||||
export const isLinearElementType = (
|
||||
elementType: AppState["elementType"],
|
||||
elementType: ExcalidrawElement["type"],
|
||||
): boolean => {
|
||||
return (
|
||||
elementType === "arrow" || elementType === "line" // || elementType === "freedraw"
|
||||
@@ -75,7 +74,7 @@ export const isBindingElement = (
|
||||
};
|
||||
|
||||
export const isBindingElementType = (
|
||||
elementType: AppState["elementType"],
|
||||
elementType: ExcalidrawElement["type"],
|
||||
): boolean => {
|
||||
return elementType === "arrow";
|
||||
};
|
||||
|
||||
+3
-5
@@ -196,8 +196,7 @@
|
||||
"library": "Library",
|
||||
"lock": "Keep selected tool active after drawing",
|
||||
"penMode": "Prevent pinch-zoom and accept freedraw input only from pen",
|
||||
"link": "Add/ Update link for a selected shape",
|
||||
"eraser": "Eraser"
|
||||
"link": "Add/ Update link for a selected shape"
|
||||
},
|
||||
"headings": {
|
||||
"canvasActions": "Canvas actions",
|
||||
@@ -222,8 +221,7 @@
|
||||
"placeImage": "Click to place the image, or click and drag to set its size manually",
|
||||
"publishLibrary": "Publish your own library",
|
||||
"bindTextToElement": "Press enter to add text",
|
||||
"deepBoxSelect": "Hold CtrlOrCmd to deep select, and to prevent dragging",
|
||||
"eraserRevert": "Hold Alt to revert the elements marked for deletion"
|
||||
"deepBoxSelect": "Hold CtrlOrCmd to deep select, and to prevent dragging"
|
||||
},
|
||||
"canvasError": {
|
||||
"cannotShowPreview": "Cannot show preview",
|
||||
@@ -283,7 +281,7 @@
|
||||
"howto": "Follow our guides",
|
||||
"or": "or",
|
||||
"preventBinding": "Prevent arrow binding",
|
||||
"tools": "Tools",
|
||||
"shapes": "Shapes",
|
||||
"shortcuts": "Keyboard shortcuts",
|
||||
"textFinish": "Finish editing (text editor)",
|
||||
"textNewLine": "Add new line (text editor)",
|
||||
|
||||
@@ -56,12 +56,12 @@
|
||||
"babel-loader": "8.2.3",
|
||||
"babel-plugin-transform-class-properties": "6.24.1",
|
||||
"cross-env": "7.0.3",
|
||||
"css-loader": "6.7.1",
|
||||
"css-loader": "6.6.0",
|
||||
"mini-css-extract-plugin": "2.4.6",
|
||||
"postcss-loader": "6.2.1",
|
||||
"sass-loader": "12.4.0",
|
||||
"terser-webpack-plugin": "5.3.1",
|
||||
"ts-loader": "9.2.8",
|
||||
"ts-loader": "9.2.6",
|
||||
"typescript": "4.5.4",
|
||||
"webpack": "5.65.0",
|
||||
"webpack-bundle-analyzer": "4.5.0",
|
||||
|
||||
@@ -2069,13 +2069,13 @@ cross-spawn@^7.0.1, cross-spawn@^7.0.3:
|
||||
shebang-command "^2.0.0"
|
||||
which "^2.0.1"
|
||||
|
||||
css-loader@6.7.1:
|
||||
version "6.7.1"
|
||||
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.1.tgz#e98106f154f6e1baf3fc3bc455cb9981c1d5fd2e"
|
||||
integrity sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==
|
||||
css-loader@6.6.0:
|
||||
version "6.6.0"
|
||||
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.6.0.tgz#c792ad5510bd1712618b49381bd0310574fafbd3"
|
||||
integrity sha512-FK7H2lisOixPT406s5gZM1S3l8GrfhEBT3ZiL2UX1Ng1XWs0y2GPllz/OTyvbaHe12VgQrIXIzuEGVlbUhodqg==
|
||||
dependencies:
|
||||
icss-utils "^5.1.0"
|
||||
postcss "^8.4.7"
|
||||
postcss "^8.4.5"
|
||||
postcss-modules-extract-imports "^3.0.0"
|
||||
postcss-modules-local-by-default "^4.0.0"
|
||||
postcss-modules-scope "^3.0.0"
|
||||
@@ -3036,7 +3036,15 @@ methods@~1.1.2:
|
||||
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
|
||||
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
|
||||
|
||||
micromatch@^4.0.0, micromatch@^4.0.2, micromatch@^4.0.4:
|
||||
micromatch@^4.0.0:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259"
|
||||
integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==
|
||||
dependencies:
|
||||
braces "^3.0.1"
|
||||
picomatch "^2.0.5"
|
||||
|
||||
micromatch@^4.0.2, micromatch@^4.0.4:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9"
|
||||
integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==
|
||||
@@ -3142,10 +3150,10 @@ multicast-dns@^6.0.1:
|
||||
dns-packet "^1.3.1"
|
||||
thunky "^1.0.2"
|
||||
|
||||
nanoid@^3.3.1:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35"
|
||||
integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==
|
||||
nanoid@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c"
|
||||
integrity sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==
|
||||
|
||||
negotiator@0.6.2:
|
||||
version "0.6.2"
|
||||
@@ -3348,6 +3356,11 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3:
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
|
||||
integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==
|
||||
|
||||
picomatch@^2.0.5:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
|
||||
integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
|
||||
|
||||
pkg-dir@^4.1.0, pkg-dir@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
|
||||
@@ -3416,12 +3429,12 @@ postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
|
||||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
|
||||
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
|
||||
|
||||
postcss@^8.4.7:
|
||||
version "8.4.8"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.8.tgz#dad963a76e82c081a0657d3a2f3602ce10c2e032"
|
||||
integrity sha512-2tXEqGxrjvAO6U+CJzDL2Fk2kPHTv1jQsYkSoMeOis2SsYaXRO2COxTdQp99cYvif9JTXaAk9lYGc3VhJt7JPQ==
|
||||
postcss@^8.4.5:
|
||||
version "8.4.6"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.6.tgz#c5ff3c3c457a23864f32cb45ac9b741498a09ae1"
|
||||
integrity sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==
|
||||
dependencies:
|
||||
nanoid "^3.3.1"
|
||||
nanoid "^3.2.0"
|
||||
picocolors "^1.0.0"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
@@ -3989,10 +4002,10 @@ totalist@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df"
|
||||
integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==
|
||||
|
||||
ts-loader@9.2.8:
|
||||
version "9.2.8"
|
||||
resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.2.8.tgz#e89aa32fa829c5cad0a1d023d6b3adecd51d5a48"
|
||||
integrity sha512-gxSak7IHUuRtwKf3FIPSW1VpZcqF9+MBrHOvBp9cjHh+525SjtCIJKVGjRKIAfxBwDGDGCFF00rTfzB1quxdSw==
|
||||
ts-loader@9.2.6:
|
||||
version "9.2.6"
|
||||
resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.2.6.tgz#9937c4dd0a1e3dbbb5e433f8102a6601c6615d74"
|
||||
integrity sha512-QMTC4UFzHmu9wU2VHZEmWWE9cUajjfcdcws+Gh7FhiO+Dy0RnR1bNz0YCHqhI0yRowCE9arVnNxYHqELOy9Hjw==
|
||||
dependencies:
|
||||
chalk "^4.1.0"
|
||||
enhanced-resolve "^5.0.0"
|
||||
|
||||
@@ -44,10 +44,10 @@
|
||||
"babel-loader": "8.2.3",
|
||||
"babel-plugin-transform-class-properties": "6.24.1",
|
||||
"cross-env": "7.0.3",
|
||||
"css-loader": "6.7.1",
|
||||
"css-loader": "6.5.1",
|
||||
"file-loader": "6.2.0",
|
||||
"sass-loader": "12.4.0",
|
||||
"ts-loader": "9.2.8",
|
||||
"ts-loader": "9.2.6",
|
||||
"webpack": "5.66.0",
|
||||
"webpack-bundle-analyzer": "4.5.0",
|
||||
"webpack-cli": "4.9.2"
|
||||
|
||||
@@ -1565,6 +1565,11 @@ color-name@~1.1.4:
|
||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||
|
||||
colorette@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94"
|
||||
integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==
|
||||
|
||||
colorette@^2.0.14:
|
||||
version "2.0.16"
|
||||
resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da"
|
||||
@@ -1629,18 +1634,18 @@ cross-spawn@^7.0.1, cross-spawn@^7.0.3:
|
||||
shebang-command "^2.0.0"
|
||||
which "^2.0.1"
|
||||
|
||||
css-loader@6.7.1:
|
||||
version "6.7.1"
|
||||
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.1.tgz#e98106f154f6e1baf3fc3bc455cb9981c1d5fd2e"
|
||||
integrity sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==
|
||||
css-loader@6.5.1:
|
||||
version "6.5.1"
|
||||
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.5.1.tgz#0c43d4fbe0d97f699c91e9818cb585759091d1b1"
|
||||
integrity sha512-gEy2w9AnJNnD9Kuo4XAP9VflW/ujKoS9c/syO+uWMlm5igc7LysKzPXaDoR2vroROkSwsTS2tGr1yGGEbZOYZQ==
|
||||
dependencies:
|
||||
icss-utils "^5.1.0"
|
||||
postcss "^8.4.7"
|
||||
postcss "^8.2.15"
|
||||
postcss-modules-extract-imports "^3.0.0"
|
||||
postcss-modules-local-by-default "^4.0.0"
|
||||
postcss-modules-scope "^3.0.0"
|
||||
postcss-modules-values "^4.0.0"
|
||||
postcss-value-parser "^4.2.0"
|
||||
postcss-value-parser "^4.1.0"
|
||||
semver "^7.3.5"
|
||||
|
||||
cssesc@^3.0.0:
|
||||
@@ -2137,10 +2142,10 @@ ms@2.1.2:
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||
|
||||
nanoid@^3.3.1:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35"
|
||||
integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==
|
||||
nanoid@^3.1.23:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c"
|
||||
integrity sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==
|
||||
|
||||
neo-async@^2.6.2:
|
||||
version "2.6.2"
|
||||
@@ -2287,19 +2292,19 @@ postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4:
|
||||
uniq "^1.0.1"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
|
||||
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
|
||||
postcss-value-parser@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb"
|
||||
integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==
|
||||
|
||||
postcss@^8.4.7:
|
||||
version "8.4.8"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.8.tgz#dad963a76e82c081a0657d3a2f3602ce10c2e032"
|
||||
integrity sha512-2tXEqGxrjvAO6U+CJzDL2Fk2kPHTv1jQsYkSoMeOis2SsYaXRO2COxTdQp99cYvif9JTXaAk9lYGc3VhJt7JPQ==
|
||||
postcss@^8.2.15:
|
||||
version "8.3.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.0.tgz#b1a713f6172ca427e3f05ef1303de8b65683325f"
|
||||
integrity sha512-+ogXpdAjWGa+fdYY5BQ96V/6tAo+TdSSIMP5huJBIygdWwKtVoB5JWZ7yUd4xZ8r+8Kvvx4nyg/PQ071H4UtcQ==
|
||||
dependencies:
|
||||
nanoid "^3.3.1"
|
||||
picocolors "^1.0.0"
|
||||
source-map-js "^1.0.2"
|
||||
colorette "^1.2.2"
|
||||
nanoid "^3.1.23"
|
||||
source-map-js "^0.6.2"
|
||||
|
||||
punycode@^2.1.0:
|
||||
version "2.1.1"
|
||||
@@ -2486,10 +2491,10 @@ sirv@^1.0.7:
|
||||
mime "^2.3.1"
|
||||
totalist "^1.0.0"
|
||||
|
||||
source-map-js@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
|
||||
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
|
||||
source-map-js@^0.6.2:
|
||||
version "0.6.2"
|
||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e"
|
||||
integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==
|
||||
|
||||
source-map-support@~0.5.19:
|
||||
version "0.5.19"
|
||||
@@ -2600,10 +2605,10 @@ totalist@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df"
|
||||
integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==
|
||||
|
||||
ts-loader@9.2.8:
|
||||
version "9.2.8"
|
||||
resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.2.8.tgz#e89aa32fa829c5cad0a1d023d6b3adecd51d5a48"
|
||||
integrity sha512-gxSak7IHUuRtwKf3FIPSW1VpZcqF9+MBrHOvBp9cjHh+525SjtCIJKVGjRKIAfxBwDGDGCFF00rTfzB1quxdSw==
|
||||
ts-loader@9.2.6:
|
||||
version "9.2.6"
|
||||
resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.2.6.tgz#9937c4dd0a1e3dbbb5e433f8102a6601c6615d74"
|
||||
integrity sha512-QMTC4UFzHmu9wU2VHZEmWWE9cUajjfcdcws+Gh7FhiO+Dy0RnR1bNz0YCHqhI0yRowCE9arVnNxYHqELOy9Hjw==
|
||||
dependencies:
|
||||
chalk "^4.1.0"
|
||||
enhanced-resolve "^5.0.0"
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ export const SHAPES = [
|
||||
</svg>
|
||||
),
|
||||
value: "ellipse",
|
||||
key: KEYS.O,
|
||||
key: KEYS.E,
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
|
||||
@@ -49,6 +49,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -219,6 +220,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -545,6 +547,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -871,6 +874,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -1041,6 +1045,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -1246,6 +1251,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -1508,6 +1514,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -1852,6 +1859,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -2626,6 +2634,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -2952,6 +2961,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -3278,6 +3288,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -3682,6 +3693,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -3950,6 +3962,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -4297,6 +4310,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -4398,6 +4412,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -4477,6 +4492,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
|
||||
@@ -49,6 +49,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -541,6 +542,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -1039,6 +1041,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -1872,6 +1875,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -2085,6 +2089,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -2574,6 +2579,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -2840,6 +2846,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -3010,6 +3017,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -3482,6 +3490,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -3730,6 +3739,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -3943,6 +3953,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -4199,6 +4210,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -4464,6 +4476,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -4871,6 +4884,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -5183,6 +5197,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -5471,6 +5486,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -5689,6 +5705,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -5859,6 +5876,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -6343,6 +6361,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -6679,6 +6698,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -8856,6 +8876,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -9240,6 +9261,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -9506,6 +9528,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -9734,6 +9757,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -10029,6 +10053,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -10199,6 +10224,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -10369,6 +10395,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -10539,6 +10566,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -10739,6 +10767,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -10939,6 +10968,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -11157,6 +11187,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -11357,6 +11388,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -11478,6 +11510,177 @@ exports[`regression tests key d selects diamond tool: [end of test] number of el
|
||||
|
||||
exports[`regression tests key d selects diamond tool: [end of test] number of renders 1`] = `8`;
|
||||
|
||||
exports[`regression tests key e selects ellipse tool: [end of test] appState 1`] = `
|
||||
Object {
|
||||
"collaborators": Map {},
|
||||
"currentChartType": "bar",
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemEndArrowhead": "arrow",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemLinearStrokeSharpness": "round",
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStartArrowhead": null,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
"currentItemStrokeSharpness": "sharp",
|
||||
"currentItemStrokeStyle": "solid",
|
||||
"currentItemStrokeWidth": 1,
|
||||
"currentItemTextAlign": "left",
|
||||
"cursorButton": "up",
|
||||
"draggingElement": null,
|
||||
"editingElement": null,
|
||||
"editingGroupId": null,
|
||||
"editingLinearElement": null,
|
||||
"elementLocked": false,
|
||||
"elementType": "selection",
|
||||
"errorMessage": null,
|
||||
"exportBackground": true,
|
||||
"exportEmbedScene": false,
|
||||
"exportScale": 1,
|
||||
"exportWithDarkMode": false,
|
||||
"fileHandle": null,
|
||||
"gridSize": null,
|
||||
"height": 768,
|
||||
"isBindingEnabled": true,
|
||||
"isLibraryOpen": false,
|
||||
"isLoading": false,
|
||||
"isResizing": false,
|
||||
"isRotating": false,
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
"openPopup": null,
|
||||
"pasteDialog": Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
"resizingElement": null,
|
||||
"scrollX": 0,
|
||||
"scrollY": 0,
|
||||
"scrolledOutside": false,
|
||||
"selectedElementIds": Object {
|
||||
"id0": true,
|
||||
},
|
||||
"selectedGroupIds": Object {},
|
||||
"selectionElement": null,
|
||||
"shouldCacheIgnoreZoom": false,
|
||||
"showHelpDialog": false,
|
||||
"showHyperlinkPopup": false,
|
||||
"showStats": false,
|
||||
"startBoundElement": null,
|
||||
"suggestedBindings": Array [],
|
||||
"theme": "light",
|
||||
"toastMessage": null,
|
||||
"viewBackgroundColor": "#ffffff",
|
||||
"viewModeEnabled": false,
|
||||
"width": 1024,
|
||||
"zenModeEnabled": false,
|
||||
"zoom": Object {
|
||||
"value": 1,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`regression tests key e selects ellipse tool: [end of test] element 0 1`] = `
|
||||
Object {
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"fillStyle": "hachure",
|
||||
"groupIds": Array [],
|
||||
"height": 10,
|
||||
"id": "id0",
|
||||
"isDeleted": false,
|
||||
"link": null,
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"seed": 337897,
|
||||
"strokeColor": "#000000",
|
||||
"strokeSharpness": "sharp",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "ellipse",
|
||||
"updated": 1,
|
||||
"version": 2,
|
||||
"versionNonce": 1278240551,
|
||||
"width": 10,
|
||||
"x": 10,
|
||||
"y": 10,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`regression tests key e selects ellipse tool: [end of test] history 1`] = `
|
||||
Object {
|
||||
"recording": false,
|
||||
"redoStack": Array [],
|
||||
"stateHistory": Array [
|
||||
Object {
|
||||
"appState": Object {
|
||||
"editingGroupId": null,
|
||||
"editingLinearElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"selectedElementIds": Object {},
|
||||
"selectedGroupIds": Object {},
|
||||
"viewBackgroundColor": "#ffffff",
|
||||
},
|
||||
"elements": Array [],
|
||||
},
|
||||
Object {
|
||||
"appState": Object {
|
||||
"editingGroupId": null,
|
||||
"editingLinearElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"selectedElementIds": Object {
|
||||
"id0": true,
|
||||
},
|
||||
"selectedGroupIds": Object {},
|
||||
"viewBackgroundColor": "#ffffff",
|
||||
},
|
||||
"elements": Array [
|
||||
Object {
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"fillStyle": "hachure",
|
||||
"groupIds": Array [],
|
||||
"height": 10,
|
||||
"id": "id0",
|
||||
"isDeleted": false,
|
||||
"link": null,
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"seed": 337897,
|
||||
"strokeColor": "#000000",
|
||||
"strokeSharpness": "sharp",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "ellipse",
|
||||
"updated": 1,
|
||||
"version": 2,
|
||||
"versionNonce": 1278240551,
|
||||
"width": 10,
|
||||
"x": 10,
|
||||
"y": 10,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`regression tests key e selects ellipse tool: [end of test] number of elements 1`] = `1`;
|
||||
|
||||
exports[`regression tests key e selects ellipse tool: [end of test] number of renders 1`] = `8`;
|
||||
|
||||
exports[`regression tests key l selects line tool: [end of test] appState 1`] = `
|
||||
Object {
|
||||
"collaborators": Map {},
|
||||
@@ -11527,6 +11730,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -11678,176 +11882,6 @@ exports[`regression tests key l selects line tool: [end of test] number of eleme
|
||||
|
||||
exports[`regression tests key l selects line tool: [end of test] number of renders 1`] = `8`;
|
||||
|
||||
exports[`regression tests key o selects ellipse tool: [end of test] appState 1`] = `
|
||||
Object {
|
||||
"collaborators": Map {},
|
||||
"currentChartType": "bar",
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemEndArrowhead": "arrow",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemLinearStrokeSharpness": "round",
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStartArrowhead": null,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
"currentItemStrokeSharpness": "sharp",
|
||||
"currentItemStrokeStyle": "solid",
|
||||
"currentItemStrokeWidth": 1,
|
||||
"currentItemTextAlign": "left",
|
||||
"cursorButton": "up",
|
||||
"draggingElement": null,
|
||||
"editingElement": null,
|
||||
"editingGroupId": null,
|
||||
"editingLinearElement": null,
|
||||
"elementLocked": false,
|
||||
"elementType": "selection",
|
||||
"errorMessage": null,
|
||||
"exportBackground": true,
|
||||
"exportEmbedScene": false,
|
||||
"exportScale": 1,
|
||||
"exportWithDarkMode": false,
|
||||
"fileHandle": null,
|
||||
"gridSize": null,
|
||||
"height": 768,
|
||||
"isBindingEnabled": true,
|
||||
"isLibraryOpen": false,
|
||||
"isLoading": false,
|
||||
"isResizing": false,
|
||||
"isRotating": false,
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
"openPopup": null,
|
||||
"pasteDialog": Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
"resizingElement": null,
|
||||
"scrollX": 0,
|
||||
"scrollY": 0,
|
||||
"scrolledOutside": false,
|
||||
"selectedElementIds": Object {
|
||||
"id0": true,
|
||||
},
|
||||
"selectedGroupIds": Object {},
|
||||
"selectionElement": null,
|
||||
"shouldCacheIgnoreZoom": false,
|
||||
"showHelpDialog": false,
|
||||
"showHyperlinkPopup": false,
|
||||
"showStats": false,
|
||||
"startBoundElement": null,
|
||||
"suggestedBindings": Array [],
|
||||
"theme": "light",
|
||||
"toastMessage": null,
|
||||
"viewBackgroundColor": "#ffffff",
|
||||
"viewModeEnabled": false,
|
||||
"width": 1024,
|
||||
"zenModeEnabled": false,
|
||||
"zoom": Object {
|
||||
"value": 1,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`regression tests key o selects ellipse tool: [end of test] element 0 1`] = `
|
||||
Object {
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"fillStyle": "hachure",
|
||||
"groupIds": Array [],
|
||||
"height": 10,
|
||||
"id": "id0",
|
||||
"isDeleted": false,
|
||||
"link": null,
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"seed": 337897,
|
||||
"strokeColor": "#000000",
|
||||
"strokeSharpness": "sharp",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "ellipse",
|
||||
"updated": 1,
|
||||
"version": 2,
|
||||
"versionNonce": 1278240551,
|
||||
"width": 10,
|
||||
"x": 10,
|
||||
"y": 10,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`regression tests key o selects ellipse tool: [end of test] history 1`] = `
|
||||
Object {
|
||||
"recording": false,
|
||||
"redoStack": Array [],
|
||||
"stateHistory": Array [
|
||||
Object {
|
||||
"appState": Object {
|
||||
"editingGroupId": null,
|
||||
"editingLinearElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"selectedElementIds": Object {},
|
||||
"selectedGroupIds": Object {},
|
||||
"viewBackgroundColor": "#ffffff",
|
||||
},
|
||||
"elements": Array [],
|
||||
},
|
||||
Object {
|
||||
"appState": Object {
|
||||
"editingGroupId": null,
|
||||
"editingLinearElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"selectedElementIds": Object {
|
||||
"id0": true,
|
||||
},
|
||||
"selectedGroupIds": Object {},
|
||||
"viewBackgroundColor": "#ffffff",
|
||||
},
|
||||
"elements": Array [
|
||||
Object {
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"fillStyle": "hachure",
|
||||
"groupIds": Array [],
|
||||
"height": 10,
|
||||
"id": "id0",
|
||||
"isDeleted": false,
|
||||
"link": null,
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"seed": 337897,
|
||||
"strokeColor": "#000000",
|
||||
"strokeSharpness": "sharp",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "ellipse",
|
||||
"updated": 1,
|
||||
"version": 2,
|
||||
"versionNonce": 1278240551,
|
||||
"width": 10,
|
||||
"x": 10,
|
||||
"y": 10,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`regression tests key o selects ellipse tool: [end of test] number of elements 1`] = `1`;
|
||||
|
||||
exports[`regression tests key o selects ellipse tool: [end of test] number of renders 1`] = `8`;
|
||||
|
||||
exports[`regression tests key r selects rectangle tool: [end of test] appState 1`] = `
|
||||
Object {
|
||||
"collaborators": Map {},
|
||||
@@ -11897,6 +11931,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -12067,6 +12102,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -12285,6 +12321,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -13053,6 +13090,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -13319,6 +13357,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -13422,6 +13461,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -13523,6 +13563,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -13696,6 +13737,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -14035,6 +14077,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -14244,6 +14287,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -15127,6 +15171,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -15228,6 +15273,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -16040,6 +16086,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -16471,6 +16518,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {
|
||||
@@ -16759,6 +16807,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -16862,6 +16911,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -17389,6 +17439,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -17490,6 +17541,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
@@ -17540,4 +17592,4 @@ Object {
|
||||
|
||||
exports[`regression tests zoom hotkeys: [end of test] number of elements 1`] = `0`;
|
||||
|
||||
exports[`regression tests zoom hotkeys: [end of test] number of renders 1`] = `6`;
|
||||
exports[`regression tests zoom hotkeys: [end of test] number of renders 1`] = `6`;
|
||||
@@ -47,6 +47,7 @@ Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"penDetected": false,
|
||||
"penMode": false,
|
||||
"pendingImageElement": null,
|
||||
"previousSelectedElementIds": Object {},
|
||||
|
||||
@@ -135,7 +135,7 @@ describe("regression tests", () => {
|
||||
for (const [keys, shape, shouldSelect] of [
|
||||
[`2${KEYS.R}`, "rectangle", true],
|
||||
[`3${KEYS.D}`, "diamond", true],
|
||||
[`4${KEYS.O}`, "ellipse", true],
|
||||
[`4${KEYS.E}`, "ellipse", true],
|
||||
[`5${KEYS.A}`, "arrow", true],
|
||||
[`6${KEYS.L}`, "line", true],
|
||||
[`7${KEYS.X}`, "freedraw", false],
|
||||
|
||||
+2
-8
@@ -77,9 +77,10 @@ export type AppState = {
|
||||
// (e.g. text element when typing into the input)
|
||||
editingElement: NonDeletedExcalidrawElement | null;
|
||||
editingLinearElement: LinearElementEditor | null;
|
||||
elementType: typeof SHAPES[number]["value"] | "eraser";
|
||||
elementType: typeof SHAPES[number]["value"];
|
||||
elementLocked: boolean;
|
||||
penMode: boolean;
|
||||
penDetected: boolean;
|
||||
exportBackground: boolean;
|
||||
exportEmbedScene: boolean;
|
||||
exportWithDarkMode: boolean;
|
||||
@@ -383,7 +384,6 @@ export type PointerDownState = Readonly<{
|
||||
boxSelection: {
|
||||
hasOccurred: boolean;
|
||||
};
|
||||
elementIdsToErase: { [key: ExcalidrawElement["id"]]: boolean };
|
||||
}>;
|
||||
|
||||
export type ExcalidrawImperativeAPI = {
|
||||
@@ -407,9 +407,3 @@ export type ExcalidrawImperativeAPI = {
|
||||
ready: true;
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type DeviceType = {
|
||||
isMobile: boolean;
|
||||
isTouchScreen: boolean;
|
||||
penDetected: boolean;
|
||||
};
|
||||
|
||||
+4
-81
@@ -1,17 +1,13 @@
|
||||
import oc from "open-color";
|
||||
|
||||
import colors from "./colors";
|
||||
import {
|
||||
CURSOR_TYPE,
|
||||
DEFAULT_VERSION,
|
||||
EVENT,
|
||||
FONT_FAMILY,
|
||||
MIME_TYPES,
|
||||
THEME,
|
||||
WINDOWS_EMOJI_FALLBACK_FONT,
|
||||
} from "./constants";
|
||||
import { FontFamilyValues, FontString } from "./element/types";
|
||||
import { AppState, DataURL, Zoom } from "./types";
|
||||
import { Zoom } from "./types";
|
||||
import { unstable_batchedUpdates } from "react-dom";
|
||||
import { isDarwin } from "./keys";
|
||||
|
||||
@@ -219,62 +215,18 @@ export const setCursor = (canvas: HTMLCanvasElement | null, cursor: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
let eraserCanvasCache: any;
|
||||
let previewDataURL: string;
|
||||
export const setEraserCursor = (
|
||||
canvas: HTMLCanvasElement | null,
|
||||
theme: AppState["theme"],
|
||||
) => {
|
||||
const cursorImageSizePx = 20;
|
||||
|
||||
const drawCanvas = () => {
|
||||
const isDarkTheme = theme === THEME.DARK;
|
||||
eraserCanvasCache = document.createElement("canvas");
|
||||
eraserCanvasCache.theme = theme;
|
||||
eraserCanvasCache.height = cursorImageSizePx;
|
||||
eraserCanvasCache.width = cursorImageSizePx;
|
||||
const context = eraserCanvasCache.getContext("2d")!;
|
||||
context.lineWidth = 1;
|
||||
context.beginPath();
|
||||
context.arc(
|
||||
eraserCanvasCache.width / 2,
|
||||
eraserCanvasCache.height / 2,
|
||||
5,
|
||||
0,
|
||||
2 * Math.PI,
|
||||
);
|
||||
context.fillStyle = isDarkTheme ? oc.black : oc.white;
|
||||
context.fill();
|
||||
context.strokeStyle = isDarkTheme ? oc.white : oc.black;
|
||||
context.stroke();
|
||||
previewDataURL = eraserCanvasCache.toDataURL(MIME_TYPES.svg) as DataURL;
|
||||
};
|
||||
if (!eraserCanvasCache || eraserCanvasCache.theme !== theme) {
|
||||
drawCanvas();
|
||||
}
|
||||
|
||||
setCursor(
|
||||
canvas,
|
||||
`url(${previewDataURL}) ${cursorImageSizePx / 2} ${
|
||||
cursorImageSizePx / 2
|
||||
}, auto`,
|
||||
);
|
||||
};
|
||||
|
||||
export const setCursorForShape = (
|
||||
canvas: HTMLCanvasElement | null,
|
||||
appState: AppState,
|
||||
shape: string,
|
||||
) => {
|
||||
if (!canvas) {
|
||||
return;
|
||||
}
|
||||
if (appState.elementType === "selection") {
|
||||
if (shape === "selection") {
|
||||
resetCursor(canvas);
|
||||
} else if (appState.elementType === "eraser") {
|
||||
setEraserCursor(canvas, appState.theme);
|
||||
// do nothing if image tool is selected which suggests there's
|
||||
// a image-preview set as the cursor
|
||||
} else if (appState.elementType !== "image") {
|
||||
} else if (shape !== "image") {
|
||||
canvas.style.cursor = CURSOR_TYPE.CROSSHAIR;
|
||||
}
|
||||
};
|
||||
@@ -583,32 +535,3 @@ export const wrapEvent = <T extends Event>(name: EVENT, nativeEvent: T) => {
|
||||
cancelable: true,
|
||||
});
|
||||
};
|
||||
|
||||
export const updateObject = <T extends Record<string, any>>(
|
||||
obj: T,
|
||||
updates: Partial<T>,
|
||||
): T => {
|
||||
let didChange = false;
|
||||
for (const key in updates) {
|
||||
const value = (updates as any)[key];
|
||||
if (typeof value !== "undefined") {
|
||||
if (
|
||||
(obj as any)[key] === value &&
|
||||
// if object, always update because its attrs could have changed
|
||||
(typeof value !== "object" || value === null)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
didChange = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!didChange) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
return {
|
||||
...obj,
|
||||
...updates,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user