Compare commits

..
Author SHA1 Message Date
zsviczianandGitHub 13309a66c5 Update textWysiwyg.tsx 2022-03-14 07:15:21 +01:00
zsviczianandGitHub 531829d95e Update textWysiwyg.tsx 2022-03-14 07:11:12 +01:00
zsviczianandGitHub d3cbceb7fa Update textWysiwyg.tsx 2022-03-13 23:45:03 +01:00
zsviczianandGitHub 73111500d3 Update textWysiwyg.tsx 2022-03-13 23:43:03 +01:00
zsviczianandGitHub 9e17b64e5e Update textWysiwyg.tsx 2022-03-13 23:38:54 +01:00
zsviczianandGitHub 326da61573 Update textWysiwyg.tsx 2022-03-13 23:36:05 +01:00
zsviczianandGitHub 994f2a3f1e Update textWysiwyg.tsx 2022-03-13 23:30:43 +01:00
zsviczianandGitHub 5dbcf64353 Update textWysiwyg.tsx 2022-03-13 23:25:37 +01:00
zsviczianandGitHub eda2320dae Update textWysiwyg.tsx 2022-03-13 23:17:19 +01:00
zsviczianandGitHub b610c04481 Update textWysiwyg.tsx 2022-03-13 23:04:22 +01:00
zsviczianandGitHub d969849357 Update textWysiwyg.tsx 2022-03-13 23:01:04 +01:00
zsviczianandGitHub 9a66fc6c05 Update textWysiwyg.tsx 2022-03-13 22:49:14 +01:00
zsviczianandGitHub 158f169c43 Update textWysiwyg.tsx 2022-03-13 22:28:37 +01:00
zsviczianandGitHub ce27cb6159 Update textWysiwyg.tsx 2022-03-13 22:23:08 +01:00
zsviczianandGitHub 2e04bcd485 Update textWysiwyg.tsx 2022-03-13 21:59:07 +01:00
zsviczianandGitHub 7436f3926b debug iOS 2022-03-13 21:55:21 +01:00
zsviczianandGitHub e429b7048d Update textWysiwyg.tsx 2022-03-11 13:44:25 +01:00
zsviczianandGitHub e61b447413 Update textWysiwyg.tsx 2022-03-11 13:39:19 +01:00
zsviczianandGitHub 73f0d854bf Update MobileMenu.tsx 2022-03-11 13:34:42 +01:00
zsviczianandGitHub cec3cf8334 Update textWysiwyg.tsx 2022-03-11 13:33:15 +01:00
zsviczianandGitHub 8640e75ccf Update constants.ts 2022-03-11 13:28:21 +01:00
zsviczianandGitHub ca7ce64fea Update MobileMenu.tsx 2022-03-11 12:02:07 +01:00
zsviczianandGitHub e3a78fe5df Update MobileMenu.tsx 2022-03-11 11:49:18 +01:00
zsviczianandGitHub 554985f749 Update MobileMenu.tsx 2022-03-11 11:46:53 +01:00
zsviczianandGitHub d3857fbb35 Update MobileMenu.tsx 2022-03-11 11:41:49 +01:00
zsviczianandGitHub 93c72cbb32 Update MobileMenu.tsx 2022-03-11 11:21:55 +01:00
zsviczianandGitHub aeb4d39387 Update MobileMenu.tsx 2022-03-11 11:18:20 +01:00
zsviczianandGitHub a0259360d6 Update MobileMenu.tsx 2022-03-11 11:15:24 +01:00
zsviczianandGitHub 243d8de7a8 Update MobileMenu.tsx 2022-03-11 11:12:50 +01:00
zsviczianandGitHub 81c927bab6 Update MobileMenu.tsx 2022-03-11 11:07:28 +01:00
42 changed files with 516 additions and 806 deletions
+8 -35
View File
@@ -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
+3 -32
View File
@@ -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>
),
});
+3 -3
View File
@@ -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"
/>
+1 -2
View File
@@ -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"}
/>
),
});
+1 -2
View File
@@ -106,8 +106,7 @@ export type ActionName =
| "increaseFontSize"
| "decreaseFontSize"
| "unbindText"
| "hyperlink"
| "eraser";
| "hyperlink";
export type PanelComponentProps = {
elements: readonly ExcalidrawElement[];
+2 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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,
+2 -2
View File
@@ -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"
/>
+2 -2
View File
@@ -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 -2
View File
@@ -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>
+2 -6
View File
@@ -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={[
-4
View File
@@ -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");
+2 -2
View File
@@ -6,7 +6,7 @@ import { canvasToBlob } from "../data/blob";
import { NonDeletedExcalidrawElement } from "../element/types";
import { CanvasError } from "../errors";
import { t } from "../i18n";
import { 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 -2
View File
@@ -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
View File
@@ -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}
+2 -2
View File
@@ -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 -2
View File
@@ -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>
);
+3 -19
View File
@@ -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",
)}
+6 -6
View File
@@ -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 =
+3 -3
View File
@@ -2,7 +2,7 @@ import React from "react";
import { getCommonBounds } from "../element/bounds";
import { NonDeletedExcalidrawElement } from "../element/types";
import { t } from "../i18n";
import { 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;
}
-2
View File
@@ -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(() => {
-4
View File
@@ -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" />,
);
+2
View File
@@ -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
View File
@@ -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
View File
@@ -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 = {
+2 -1
View File
@@ -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,
+1 -2
View File
@@ -10,6 +10,5 @@ export const showSelectedShapeActions = (
!appState.viewModeEnabled &&
(appState.editingElement ||
getSelectedElements(elements, appState).length ||
(appState.elementType !== "selection" &&
appState.elementType !== "eraser")),
appState.elementType !== "selection"),
);
+32 -3
View File
@@ -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
+2 -3
View File
@@ -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
View File
@@ -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)",
+2 -2
View File
@@ -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",
+32 -19
View File
@@ -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"
+2 -2
View File
@@ -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"
+34 -29
View File
@@ -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
View File
@@ -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 {},
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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,
};
};