Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Add multiElement-edit finalize action to Desktop (currently only visible in Mobile view) #4764

Merged
merged 15 commits into from Mar 16, 2022
Merged
6 changes: 3 additions & 3 deletions src/actions/actionExport.tsx
Expand Up @@ -8,7 +8,7 @@ import { DarkModeToggle } from "../components/DarkModeToggle";
import { loadFromJSON, saveAsJSON } from "../data";
import { resaveAsImageWithScene } from "../data/resave";
import { t } from "../i18n";
import { useIsMobile } from "../components/App";
import { useDeviceType } from "../components/App";
import { KEYS } from "../keys";
import { register } from "./register";
import { CheckboxItem } from "../components/CheckboxItem";
Expand Down Expand Up @@ -200,7 +200,7 @@ export const actionSaveFileToDisk = register({
icon={saveAs}
title={t("buttons.saveAs")}
aria-label={t("buttons.saveAs")}
showAriaLabel={useIsMobile()}
showAriaLabel={useDeviceType().isMobile}
hidden={!nativeFileSystemSupported}
onClick={() => updateData(null)}
data-testid="save-as-button"
Expand Down Expand Up @@ -243,7 +243,7 @@ export const actionLoadScene = register({
icon={load}
title={t("buttons.load")}
aria-label={t("buttons.load")}
showAriaLabel={useIsMobile()}
showAriaLabel={useDeviceType().isMobile}
onClick={updateData}
data-testid="load-button"
/>
Expand Down
3 changes: 2 additions & 1 deletion src/actions/actionFinalize.tsx
Expand Up @@ -165,14 +165,15 @@ export const actionFinalize = register({
(!appState.draggingElement && appState.multiElement === null))) ||
((event.key === KEYS.ESCAPE || event.key === KEYS.ENTER) &&
appState.multiElement !== null),
PanelComponent: ({ appState, updateData }) => (
PanelComponent: ({ appState, updateData, data }) => (
<ToolButton
type="button"
icon={done}
title={t("buttons.done")}
aria-label={t("buttons.done")}
onClick={updateData}
visible={appState.multiElement != null}
size={data?.size || "medium"}
/>
),
});
8 changes: 4 additions & 4 deletions src/components/Actions.tsx
Expand Up @@ -3,7 +3,7 @@ import { ActionManager } from "../actions/manager";
import { getNonDeletedElements } from "../element";
import { ExcalidrawElement, PointerType } from "../element/types";
import { t } from "../i18n";
import { useIsMobile } from "../components/App";
import { useDeviceType } from "../components/App";
import {
canChangeSharpness,
canHaveArrowheads,
Expand Down Expand Up @@ -46,7 +46,7 @@ export const SelectedShapeActions = ({
isSingleElementBoundContainer = true;
}
const isEditing = Boolean(appState.editingElement);
const isMobile = useIsMobile();
const deviceType = useDeviceType();
const isRTL = document.documentElement.getAttribute("dir") === "rtl";

const showFillIcons =
Expand Down Expand Up @@ -168,8 +168,8 @@ export const SelectedShapeActions = ({
<fieldset>
<legend>{t("labels.actions")}</legend>
<div className="buttonList">
{!isMobile && renderAction("duplicateSelection")}
{!isMobile && renderAction("deleteSelectedElements")}
{!deviceType.isMobile && renderAction("duplicateSelection")}
{!deviceType.isMobile && renderAction("deleteSelectedElements")}
{renderAction("group")}
{renderAction("ungroup")}
{targetElements.length === 1 && renderAction("hyperlink")}
Expand Down
71 changes: 46 additions & 25 deletions src/components/App.tsx
Expand Up @@ -195,6 +195,7 @@ import {
LibraryItems,
PointerDownState,
SceneData,
DeviceType,
} from "../types";
import {
debounce,
Expand All @@ -214,6 +215,7 @@ import {
withBatchedUpdates,
wrapEvent,
withBatchedUpdatesThrottled,
updateObject,
setEraserCursor,
} from "../utils";
import ContextMenu, { ContextMenuOption } from "./ContextMenu";
Expand Down Expand Up @@ -253,8 +255,12 @@ import {
isLocalLink,
} from "../element/Hyperlink";

const IsMobileContext = React.createContext(false);
export const useIsMobile = () => useContext(IsMobileContext);
const defaultDeviceTypeContext: DeviceType = {
isMobile: false,
isTouchScreen: false,
};
const DeviceTypeContext = React.createContext(defaultDeviceTypeContext);
export const useDeviceType = () => useContext(DeviceTypeContext);
const ExcalidrawContainerContext = React.createContext<{
container: HTMLDivElement | null;
id: string | null;
Expand Down Expand Up @@ -286,7 +292,10 @@ class App extends React.Component<AppProps, AppState> {
rc: RoughCanvas | null = null;
unmounted: boolean = false;
actionManager: ActionManager;
isMobile = false;
deviceType: DeviceType = {
isMobile: false,
isTouchScreen: false,
};
detachIsMobileMqHandler?: () => void;

private excalidrawContainerRef = React.createRef<HTMLDivElement>();
Expand Down Expand Up @@ -468,7 +477,7 @@ class App extends React.Component<AppProps, AppState> {
<div
className={clsx("excalidraw excalidraw-container", {
"excalidraw--view-mode": viewModeEnabled,
"excalidraw--mobile": this.isMobile,
"excalidraw--mobile": this.deviceType.isMobile,
})}
ref={this.excalidrawContainerRef}
onDrop={this.handleAppOnDrop}
Expand All @@ -480,7 +489,7 @@ class App extends React.Component<AppProps, AppState> {
<ExcalidrawContainerContext.Provider
value={this.excalidrawContainerValue}
>
<IsMobileContext.Provider value={this.isMobile}>
<DeviceTypeContext.Provider value={this.deviceType}>
<LayerUI
canvas={this.canvas}
appState={this.state}
Expand Down Expand Up @@ -547,7 +556,7 @@ class App extends React.Component<AppProps, AppState> {
/>
)}
<main>{this.renderCanvas()}</main>
</IsMobileContext.Provider>
</DeviceTypeContext.Provider>
</ExcalidrawContainerContext.Provider>
</div>
);
Expand Down Expand Up @@ -891,9 +900,12 @@ class App extends React.Component<AppProps, AppState> {
// ---------------------------------------------------------------------
const { width, height } =
this.excalidrawContainerRef.current!.getBoundingClientRect();
this.isMobile =
width < MQ_MAX_WIDTH_PORTRAIT ||
(height < MQ_MAX_HEIGHT_LANDSCAPE && width < MQ_MAX_WIDTH_LANDSCAPE);
this.deviceType = updateObject(this.deviceType, {
isMobile:
width < MQ_MAX_WIDTH_PORTRAIT ||
(height < MQ_MAX_HEIGHT_LANDSCAPE &&
width < MQ_MAX_WIDTH_LANDSCAPE),
});
// refresh offsets
// ---------------------------------------------------------------------
this.updateDOMRect();
Expand All @@ -903,7 +915,11 @@ 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.isMobile = mediaQuery.matches);
const handler = () => {
this.deviceType = updateObject(this.deviceType, {
isMobile: mediaQuery.matches,
});
};
mediaQuery.addListener(handler);
this.detachIsMobileMqHandler = () => mediaQuery.removeListener(handler);
}
Expand Down Expand Up @@ -1205,7 +1221,7 @@ class App extends React.Component<AppProps, AppState> {
theme: this.state.theme,
imageCache: this.imageCache,
isExporting: false,
renderScrollbars: !this.isMobile,
renderScrollbars: !this.deviceType.isMobile,
},
);

Expand Down Expand Up @@ -2391,7 +2407,7 @@ class App extends React.Component<AppProps, AppState> {
element,
this.state,
[scenePointer.x, scenePointer.y],
this.isMobile,
this.deviceType.isMobile,
) &&
index <= hitElementIndex
);
Expand Down Expand Up @@ -2424,7 +2440,7 @@ class App extends React.Component<AppProps, AppState> {
this.hitLinkElement!,
this.state,
[lastPointerDownCoords.x, lastPointerDownCoords.y],
this.isMobile,
this.deviceType.isMobile,
);
const lastPointerUpCoords = viewportCoordsToSceneCoords(
this.lastPointerUp!,
Expand All @@ -2434,7 +2450,7 @@ class App extends React.Component<AppProps, AppState> {
this.hitLinkElement!,
this.state,
[lastPointerUpCoords.x, lastPointerUpCoords.y],
this.isMobile,
this.deviceType.isMobile,
);
if (lastPointerDownHittingLinkIcon && lastPointerUpHittingLinkIcon) {
const url = this.hitLinkElement.link;
Expand Down Expand Up @@ -2856,6 +2872,13 @@ class App extends React.Component<AppProps, AppState> {
});
}

if (
!this.deviceType.isTouchScreen &&
["pen", "touch"].includes(event.pointerType)
) {
this.deviceType = updateObject(this.deviceType, { isTouchScreen: true });
}

if (isPanning) {
return;
}
Expand Down Expand Up @@ -2986,9 +3009,7 @@ class App extends React.Component<AppProps, AppState> {
event: React.PointerEvent<HTMLCanvasElement>,
) => {
this.lastPointerUp = event;
const isTouchScreen = ["pen", "touch"].includes(event.pointerType);

if (isTouchScreen) {
if (this.deviceType.isTouchScreen) {
const scenePointer = viewportCoordsToSceneCoords(
{ clientX: event.clientX, clientY: event.clientY },
this.state,
Expand All @@ -3006,7 +3027,7 @@ class App extends React.Component<AppProps, AppState> {
this.hitLinkElement &&
!this.state.selectedElementIds[this.hitLinkElement.id]
) {
this.redirectToLink(event, isTouchScreen);
this.redirectToLink(event, this.deviceType.isTouchScreen);
}

this.removePointer(event);
Expand Down Expand Up @@ -3376,7 +3397,7 @@ class App extends React.Component<AppProps, AppState> {
pointerDownState.hit.element,
this.state,
[pointerDownState.origin.x, pointerDownState.origin.y],
this.isMobile,
this.deviceType.isMobile,
)
) {
return false;
Expand Down Expand Up @@ -5407,7 +5428,7 @@ class App extends React.Component<AppProps, AppState> {
} else {
ContextMenu.push({
options: [
this.isMobile &&
this.deviceType.isMobile &&
navigator.clipboard && {
name: "paste",
perform: (elements, appStates) => {
Expand All @@ -5418,7 +5439,7 @@ class App extends React.Component<AppProps, AppState> {
},
contextItemLabel: "labels.paste",
},
this.isMobile && navigator.clipboard && separator,
this.deviceType.isMobile && navigator.clipboard && separator,
probablySupportsClipboardBlob &&
elements.length > 0 &&
actionCopyAsPng,
Expand Down Expand Up @@ -5464,9 +5485,9 @@ class App extends React.Component<AppProps, AppState> {
} else {
ContextMenu.push({
options: [
this.isMobile && actionCut,
this.isMobile && navigator.clipboard && actionCopy,
this.isMobile &&
this.deviceType.isMobile && actionCut,
this.deviceType.isMobile && navigator.clipboard && actionCopy,
this.deviceType.isMobile &&
navigator.clipboard && {
name: "paste",
perform: (elements, appStates) => {
Expand All @@ -5477,7 +5498,7 @@ class App extends React.Component<AppProps, AppState> {
},
contextItemLabel: "labels.paste",
},
this.isMobile && separator,
this.deviceType.isMobile && separator,
...options,
separator,
actionCopyStyles,
Expand Down
4 changes: 2 additions & 2 deletions src/components/ClearCanvas.tsx
@@ -1,6 +1,6 @@
import { useState } from "react";
import { t } from "../i18n";
import { useIsMobile } from "./App";
import { useDeviceType } from "./App";
import { trash } from "./icons";
import { ToolButton } from "./ToolButton";

Expand All @@ -19,7 +19,7 @@ const ClearCanvas = ({ onConfirm }: { onConfirm: () => void }) => {
icon={trash}
title={t("buttons.clearReset")}
aria-label={t("buttons.clearReset")}
showAriaLabel={useIsMobile()}
showAriaLabel={useDeviceType().isMobile}
onClick={toggleDialog}
data-testid="clear-canvas-button"
/>
Expand Down
4 changes: 2 additions & 2 deletions src/components/CollabButton.tsx
@@ -1,7 +1,7 @@
import clsx from "clsx";
import { ToolButton } from "./ToolButton";
import { t } from "../i18n";
import { useIsMobile } from "../components/App";
import { useDeviceType } from "../components/App";
import { users } from "./icons";

import "./CollabButton.scss";
Expand All @@ -26,7 +26,7 @@ const CollabButton = ({
type="button"
title={t("labels.liveCollaboration")}
aria-label={t("labels.liveCollaboration")}
showAriaLabel={useIsMobile()}
showAriaLabel={useDeviceType().isMobile}
>
{collaboratorCount > 0 && (
<div className="CollabButton-collaborators">{collaboratorCount}</div>
Expand Down
4 changes: 2 additions & 2 deletions src/components/Dialog.tsx
Expand Up @@ -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, useIsMobile } from "../components/App";
import { useExcalidrawContainer, useDeviceType } from "../components/App";
import { KEYS } from "../keys";
import "./Dialog.scss";
import { back, close } from "./icons";
Expand Down Expand Up @@ -94,7 +94,7 @@ export const Dialog = (props: DialogProps) => {
onClick={onClose}
aria-label={t("buttons.close")}
>
{useIsMobile() ? back : close}
{useDeviceType().isMobile ? back : close}
</button>
</h2>
<div className="Dialog__content">{props.children}</div>
Expand Down
4 changes: 2 additions & 2 deletions src/components/ImageExportDialog.tsx
Expand Up @@ -6,7 +6,7 @@ import { canvasToBlob } from "../data/blob";
import { NonDeletedExcalidrawElement } from "../element/types";
import { CanvasError } from "../errors";
import { t } from "../i18n";
import { useIsMobile } from "./App";
import { useDeviceType } from "./App";
import { getSelectedElements, isSomeElementSelected } from "../scene";
import { exportToCanvas } from "../scene/export";
import { AppState, BinaryFiles } from "../types";
Expand Down Expand Up @@ -250,7 +250,7 @@ export const ImageExportDialog = ({
icon={exportImage}
type="button"
aria-label={t("buttons.exportImage")}
showAriaLabel={useIsMobile()}
showAriaLabel={useDeviceType().isMobile}
title={t("buttons.exportImage")}
/>
{modalIsShown && (
Expand Down
4 changes: 2 additions & 2 deletions src/components/JSONExportDialog.tsx
Expand Up @@ -2,7 +2,7 @@ import React, { useState } from "react";
import { ActionsManagerInterface } from "../actions/types";
import { NonDeletedExcalidrawElement } from "../element/types";
import { t } from "../i18n";
import { useIsMobile } from "./App";
import { useDeviceType } from "./App";
import { AppState, ExportOpts, BinaryFiles } from "../types";
import { Dialog } from "./Dialog";
import { exportFile, exportToFileIcon, link } from "./icons";
Expand Down Expand Up @@ -114,7 +114,7 @@ export const JSONExportDialog = ({
icon={exportFile}
type="button"
aria-label={t("buttons.export")}
showAriaLabel={useIsMobile()}
showAriaLabel={useDeviceType().isMobile}
title={t("buttons.export")}
/>
{modalIsShown && (
Expand Down