mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-10-24 16:34:24 +02:00
refactor device to editor interface and derive styles panel
This commit is contained in:
@@ -9,7 +9,7 @@ You will need to import the `Footer` component from the package and wrap your co
|
|||||||
```jsx live
|
```jsx live
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<div style={{ height: "500px"}}>
|
<div style={{ height: "500px" }}>
|
||||||
<Excalidraw>
|
<Excalidraw>
|
||||||
<Footer>
|
<Footer>
|
||||||
<button
|
<button
|
||||||
@@ -27,19 +27,19 @@ function App() {
|
|||||||
|
|
||||||
This will only work for `Desktop` devices.
|
This will only work for `Desktop` devices.
|
||||||
|
|
||||||
For `mobile` you will need to render it inside the [MainMenu](#mainmenu). You can use the [`useDevice`](#useDevice) hook to check the type of device, this will be available only inside the `children` of `Excalidraw` component.
|
For `mobile` you will need to render it inside the [MainMenu](#mainmenu). You can use the [`useEditorInterface`](#useEditorInterface) hook to check the type of device, this will be available only inside the `children` of `Excalidraw` component.
|
||||||
|
|
||||||
Open the `Menu` in the below playground and you will see the `custom footer` rendered.
|
Open the `Menu` in the below playground and you will see the `custom footer` rendered.
|
||||||
|
|
||||||
```jsx live noInline
|
```jsx live noInline
|
||||||
const MobileFooter = ({}) => {
|
const MobileFooter = ({}) => {
|
||||||
const device = useDevice();
|
const editorInterface = useEditorInterface();
|
||||||
if (device.editor.isMobile) {
|
if (editorInterface.formFactor === "phone") {
|
||||||
return (
|
return (
|
||||||
<Footer>
|
<Footer>
|
||||||
<button
|
<button
|
||||||
className="custom-footer"
|
className="custom-footer"
|
||||||
style= {{ marginLeft: '20px', height: '2rem'}}
|
style={{ marginLeft: "20px", height: "2rem" }}
|
||||||
onClick={() => alert("This is custom footer in mobile menu")}
|
onClick={() => alert("This is custom footer in mobile menu")}
|
||||||
>
|
>
|
||||||
custom footer
|
custom footer
|
||||||
|
|||||||
@@ -292,7 +292,7 @@ viewportCoordsToSceneCoords({ clientX: number, clientY: number },<br/> 
|
|||||||
appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a><br/>): {x: number, y: number}
|
appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a><br/>): {x: number, y: number}
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
### useDevice
|
### useEditorInterface
|
||||||
|
|
||||||
This hook can be used to check the type of device which is being used. It can only be used inside the `children` of `Excalidraw` component.
|
This hook can be used to check the type of device which is being used. It can only be used inside the `children` of `Excalidraw` component.
|
||||||
|
|
||||||
@@ -300,8 +300,8 @@ Open the `main menu` in the below example to view the footer.
|
|||||||
|
|
||||||
```jsx live noInline
|
```jsx live noInline
|
||||||
const MobileFooter = ({}) => {
|
const MobileFooter = ({}) => {
|
||||||
const device = useDevice();
|
const editorInterface = useEditorInterface();
|
||||||
if (device.editor.isMobile) {
|
if (editorInterface.formFactor === "phone") {
|
||||||
return (
|
return (
|
||||||
<Footer>
|
<Footer>
|
||||||
<button
|
<button
|
||||||
@@ -336,12 +336,20 @@ render(<App />);
|
|||||||
The `device` has the following `attributes`, some grouped into `viewport` and `editor` objects, per context.
|
The `device` has the following `attributes`, some grouped into `viewport` and `editor` objects, per context.
|
||||||
|
|
||||||
| Name | Type | Description |
|
| Name | Type | Description |
|
||||||
| --- | --- | --- |
|
| ---- | ---- | ----------- |
|
||||||
| `viewport.isMobile` | `boolean` | Set to `true` when viewport is in `mobile` breakpoint |
|
|
||||||
| `viewport.isLandscape` | `boolean` | Set to `true` when the viewport is in `landscape` mode |
|
The `EditorInterface` object has the following properties:
|
||||||
| `editor.canFitSidebar` | `boolean` | Set to `true` if there's enough space to fit the `sidebar` |
|
|
||||||
| `editor.isMobile` | `boolean` | Set to `true` when editor container is in `mobile` breakpoint |
|
| Name | Type | Description |
|
||||||
| `isTouchScreen` | `boolean` | Set to `true` for `touch` when touch event detected |
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| `formFactor` | `'phone' | 'tablet' | 'desktop'` | Indicates the device type based on screen size |
|
||||||
|
| `desktopUIMode` | `'compact' | 'full'` | UI mode for desktop form factor |
|
||||||
|
| `userAgent.raw` | `string` | Raw user agent string |
|
||||||
|
| `userAgent.isMobileDevice` | `boolean` | True if device is mobile |
|
||||||
|
| `userAgent.platform` | `'ios' | 'android' | 'other' | 'unknown'` | Device platform |
|
||||||
|
| `isTouchScreen` | `boolean` | True if touch events are detected |
|
||||||
|
| `canFitSidebar` | `boolean` | True if sidebar can fit in the viewport |
|
||||||
|
| `isLandscape` | `boolean` | True if viewport is in landscape mode |
|
||||||
|
|
||||||
### i18n
|
### i18n
|
||||||
|
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ const MobileFooter = ({
|
|||||||
excalidrawAPI: ExcalidrawImperativeAPI;
|
excalidrawAPI: ExcalidrawImperativeAPI;
|
||||||
excalidrawLib: typeof TExcalidraw;
|
excalidrawLib: typeof TExcalidraw;
|
||||||
}) => {
|
}) => {
|
||||||
const { useDevice, Footer } = excalidrawLib;
|
const { useEditorInterface, Footer } = excalidrawLib;
|
||||||
|
|
||||||
const device = useDevice();
|
const editorInterface = useEditorInterface();
|
||||||
if (device.editor.isMobile) {
|
if (editorInterface.formFactor === "phone") {
|
||||||
return (
|
return (
|
||||||
<Footer>
|
<Footer>
|
||||||
<CustomFooter
|
<CustomFooter
|
||||||
|
|||||||
@@ -18,26 +18,25 @@ describe("Test MobileMenu", () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await render(<ExcalidrawApp />);
|
await render(<ExcalidrawApp />);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
h.app.refreshViewportBreakpoints();
|
h.app.refreshEditorInterface();
|
||||||
// @ts-ignore
|
|
||||||
h.app.refreshEditorBreakpoints();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
restoreOriginalGetBoundingClientRect();
|
restoreOriginalGetBoundingClientRect();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should set device correctly", () => {
|
it("should set editor interface correctly", () => {
|
||||||
expect(h.app.device).toMatchInlineSnapshot(`
|
expect(h.app.editorInterface).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
"editor": {
|
"canFitSidebar": false,
|
||||||
"canFitSidebar": false,
|
"desktopUIMode": "full",
|
||||||
"isMobile": true,
|
"formFactor": "phone",
|
||||||
},
|
"isLandscape": true,
|
||||||
"isTouchScreen": false,
|
"isTouchScreen": false,
|
||||||
"viewport": {
|
"userAgent": {
|
||||||
"isLandscape": true,
|
"isMobileDevice": false,
|
||||||
"isMobile": true,
|
"platform": "other",
|
||||||
|
"raw": "Mozilla/5.0 (darwin) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/22.1.0",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|||||||
@@ -9,13 +9,17 @@ import { SIDE_RESIZING_THRESHOLD } from "@excalidraw/common";
|
|||||||
|
|
||||||
import type { GlobalPoint, LineSegment, LocalPoint } from "@excalidraw/math";
|
import type { GlobalPoint, LineSegment, LocalPoint } from "@excalidraw/math";
|
||||||
|
|
||||||
import type { AppState, Device, Zoom } from "@excalidraw/excalidraw/types";
|
import type {
|
||||||
|
AppState,
|
||||||
|
EditorInterface,
|
||||||
|
Zoom,
|
||||||
|
} from "@excalidraw/excalidraw/types";
|
||||||
|
|
||||||
import { getElementAbsoluteCoords } from "./bounds";
|
import { getElementAbsoluteCoords } from "./bounds";
|
||||||
import {
|
import {
|
||||||
getTransformHandlesFromCoords,
|
getTransformHandlesFromCoords,
|
||||||
getTransformHandles,
|
getTransformHandles,
|
||||||
getOmitSidesForDevice,
|
getOmitSidesForEditorInterface,
|
||||||
canResizeFromSides,
|
canResizeFromSides,
|
||||||
} from "./transformHandles";
|
} from "./transformHandles";
|
||||||
import { isImageElement, isLinearElement } from "./typeChecks";
|
import { isImageElement, isLinearElement } from "./typeChecks";
|
||||||
@@ -51,7 +55,7 @@ export const resizeTest = <Point extends GlobalPoint | LocalPoint>(
|
|||||||
y: number,
|
y: number,
|
||||||
zoom: Zoom,
|
zoom: Zoom,
|
||||||
pointerType: PointerType,
|
pointerType: PointerType,
|
||||||
device: Device,
|
editorInterface: EditorInterface,
|
||||||
): MaybeTransformHandleType => {
|
): MaybeTransformHandleType => {
|
||||||
if (!appState.selectedElementIds[element.id]) {
|
if (!appState.selectedElementIds[element.id]) {
|
||||||
return false;
|
return false;
|
||||||
@@ -63,7 +67,7 @@ export const resizeTest = <Point extends GlobalPoint | LocalPoint>(
|
|||||||
zoom,
|
zoom,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
pointerType,
|
pointerType,
|
||||||
getOmitSidesForDevice(device),
|
getOmitSidesForEditorInterface(editorInterface),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -86,7 +90,7 @@ export const resizeTest = <Point extends GlobalPoint | LocalPoint>(
|
|||||||
return filter[0] as TransformHandleType;
|
return filter[0] as TransformHandleType;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canResizeFromSides(device)) {
|
if (canResizeFromSides(editorInterface)) {
|
||||||
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
|
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
|
||||||
element,
|
element,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
@@ -132,7 +136,7 @@ export const getElementWithTransformHandleType = (
|
|||||||
zoom: Zoom,
|
zoom: Zoom,
|
||||||
pointerType: PointerType,
|
pointerType: PointerType,
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
device: Device,
|
editorInterface: EditorInterface,
|
||||||
) => {
|
) => {
|
||||||
return elements.reduce((result, element) => {
|
return elements.reduce((result, element) => {
|
||||||
if (result) {
|
if (result) {
|
||||||
@@ -146,7 +150,7 @@ export const getElementWithTransformHandleType = (
|
|||||||
scenePointerY,
|
scenePointerY,
|
||||||
zoom,
|
zoom,
|
||||||
pointerType,
|
pointerType,
|
||||||
device,
|
editorInterface,
|
||||||
);
|
);
|
||||||
return transformHandleType ? { element, transformHandleType } : null;
|
return transformHandleType ? { element, transformHandleType } : null;
|
||||||
}, null as { element: NonDeletedExcalidrawElement; transformHandleType: MaybeTransformHandleType } | null);
|
}, null as { element: NonDeletedExcalidrawElement; transformHandleType: MaybeTransformHandleType } | null);
|
||||||
@@ -160,14 +164,14 @@ export const getTransformHandleTypeFromCoords = <
|
|||||||
scenePointerY: number,
|
scenePointerY: number,
|
||||||
zoom: Zoom,
|
zoom: Zoom,
|
||||||
pointerType: PointerType,
|
pointerType: PointerType,
|
||||||
device: Device,
|
editorInterface: EditorInterface,
|
||||||
): MaybeTransformHandleType => {
|
): MaybeTransformHandleType => {
|
||||||
const transformHandles = getTransformHandlesFromCoords(
|
const transformHandles = getTransformHandlesFromCoords(
|
||||||
[x1, y1, x2, y2, (x1 + x2) / 2, (y1 + y2) / 2],
|
[x1, y1, x2, y2, (x1 + x2) / 2, (y1 + y2) / 2],
|
||||||
0 as Radians,
|
0 as Radians,
|
||||||
zoom,
|
zoom,
|
||||||
pointerType,
|
pointerType,
|
||||||
getOmitSidesForDevice(device),
|
getOmitSidesForEditorInterface(editorInterface),
|
||||||
);
|
);
|
||||||
|
|
||||||
const found = Object.keys(transformHandles).find((key) => {
|
const found = Object.keys(transformHandles).find((key) => {
|
||||||
@@ -183,7 +187,7 @@ export const getTransformHandleTypeFromCoords = <
|
|||||||
return found as MaybeTransformHandleType;
|
return found as MaybeTransformHandleType;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canResizeFromSides(device)) {
|
if (canResizeFromSides(editorInterface)) {
|
||||||
const cx = (x1 + x2) / 2;
|
const cx = (x1 + x2) / 2;
|
||||||
const cy = (y1 + y2) / 2;
|
const cy = (y1 + y2) / 2;
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
import {
|
import { DEFAULT_TRANSFORM_HANDLE_SPACING } from "@excalidraw/common";
|
||||||
DEFAULT_TRANSFORM_HANDLE_SPACING,
|
|
||||||
isAndroid,
|
|
||||||
isIOS,
|
|
||||||
} from "@excalidraw/common";
|
|
||||||
|
|
||||||
import { pointFrom, pointRotateRads } from "@excalidraw/math";
|
import { pointFrom, pointRotateRads } from "@excalidraw/math";
|
||||||
|
|
||||||
import type { Radians } from "@excalidraw/math";
|
import type { Radians } from "@excalidraw/math";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
Device,
|
EditorInterface,
|
||||||
InteractiveCanvasAppState,
|
InteractiveCanvasAppState,
|
||||||
Zoom,
|
Zoom,
|
||||||
} from "@excalidraw/excalidraw/types";
|
} from "@excalidraw/excalidraw/types";
|
||||||
@@ -111,20 +107,21 @@ const generateTransformHandle = (
|
|||||||
return [xx - width / 2, yy - height / 2, width, height];
|
return [xx - width / 2, yy - height / 2, width, height];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const canResizeFromSides = (device: Device) => {
|
export const canResizeFromSides = (editorInterface: EditorInterface) => {
|
||||||
if (device.viewport.isMobile) {
|
if (
|
||||||
return false;
|
editorInterface.formFactor === "phone" &&
|
||||||
}
|
editorInterface.userAgent.isMobileDevice
|
||||||
|
) {
|
||||||
if (device.isTouchScreen && (isAndroid || isIOS)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getOmitSidesForDevice = (device: Device) => {
|
export const getOmitSidesForEditorInterface = (
|
||||||
if (canResizeFromSides(device)) {
|
editorInterface: EditorInterface,
|
||||||
|
) => {
|
||||||
|
if (canResizeFromSides(editorInterface)) {
|
||||||
return DEFAULT_OMIT_SIDES;
|
return DEFAULT_OMIT_SIDES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,6 @@ export const actionChangeViewBackgroundColor = register({
|
|||||||
elements={elements}
|
elements={elements}
|
||||||
appState={appState}
|
appState={appState}
|
||||||
updateData={updateData}
|
updateData={updateData}
|
||||||
compactMode={appState.stylesPanelMode === "compact"}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ import { getSelectedElements, isSomeElementSelected } from "../scene";
|
|||||||
import { TrashIcon } from "../components/icons";
|
import { TrashIcon } from "../components/icons";
|
||||||
import { ToolButton } from "../components/ToolButton";
|
import { ToolButton } from "../components/ToolButton";
|
||||||
|
|
||||||
|
import { useStylesPanelMode } from "..";
|
||||||
|
|
||||||
import { register } from "./register";
|
import { register } from "./register";
|
||||||
|
|
||||||
import type { AppClassProperties, AppState } from "../types";
|
import type { AppClassProperties, AppState } from "../types";
|
||||||
@@ -320,22 +322,25 @@ export const actionDeleteSelected = register({
|
|||||||
keyTest: (event, appState, elements) =>
|
keyTest: (event, appState, elements) =>
|
||||||
(event.key === KEYS.BACKSPACE || event.key === KEYS.DELETE) &&
|
(event.key === KEYS.BACKSPACE || event.key === KEYS.DELETE) &&
|
||||||
!event[KEYS.CTRL_OR_CMD],
|
!event[KEYS.CTRL_OR_CMD],
|
||||||
PanelComponent: ({ elements, appState, updateData }) => (
|
PanelComponent: ({ elements, appState, updateData, app }) => {
|
||||||
<ToolButton
|
const isMobile = useStylesPanelMode() === "mobile";
|
||||||
type="button"
|
|
||||||
icon={TrashIcon}
|
return (
|
||||||
title={t("labels.delete")}
|
<ToolButton
|
||||||
aria-label={t("labels.delete")}
|
type="button"
|
||||||
onClick={() => updateData(null)}
|
icon={TrashIcon}
|
||||||
disabled={
|
title={t("labels.delete")}
|
||||||
!isSomeElementSelected(getNonDeletedElements(elements), appState)
|
aria-label={t("labels.delete")}
|
||||||
}
|
onClick={() => updateData(null)}
|
||||||
style={{
|
disabled={
|
||||||
...(appState.stylesPanelMode === "mobile" &&
|
!isSomeElementSelected(getNonDeletedElements(elements), appState)
|
||||||
appState.openPopup !== "compactOtherProperties"
|
}
|
||||||
? MOBILE_ACTION_BUTTON_BG
|
style={{
|
||||||
: {}),
|
...(isMobile && appState.openPopup !== "compactOtherProperties"
|
||||||
}}
|
? MOBILE_ACTION_BUTTON_BG
|
||||||
/>
|
: {}),
|
||||||
),
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ import { DuplicateIcon } from "../components/icons";
|
|||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { isSomeElementSelected } from "../scene";
|
import { isSomeElementSelected } from "../scene";
|
||||||
|
|
||||||
|
import { useStylesPanelMode } from "..";
|
||||||
|
|
||||||
import { register } from "./register";
|
import { register } from "./register";
|
||||||
|
|
||||||
export const actionDuplicateSelection = register({
|
export const actionDuplicateSelection = register({
|
||||||
@@ -107,24 +109,27 @@ export const actionDuplicateSelection = register({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.D,
|
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.D,
|
||||||
PanelComponent: ({ elements, appState, updateData }) => (
|
PanelComponent: ({ elements, appState, updateData, app }) => {
|
||||||
<ToolButton
|
const isMobile = useStylesPanelMode() === "mobile";
|
||||||
type="button"
|
|
||||||
icon={DuplicateIcon}
|
return (
|
||||||
title={`${t("labels.duplicateSelection")} — ${getShortcutKey(
|
<ToolButton
|
||||||
"CtrlOrCmd+D",
|
type="button"
|
||||||
)}`}
|
icon={DuplicateIcon}
|
||||||
aria-label={t("labels.duplicateSelection")}
|
title={`${t("labels.duplicateSelection")} — ${getShortcutKey(
|
||||||
onClick={() => updateData(null)}
|
"CtrlOrCmd+D",
|
||||||
disabled={
|
)}`}
|
||||||
!isSomeElementSelected(getNonDeletedElements(elements), appState)
|
aria-label={t("labels.duplicateSelection")}
|
||||||
}
|
onClick={() => updateData(null)}
|
||||||
style={{
|
disabled={
|
||||||
...(appState.stylesPanelMode === "mobile" &&
|
!isSomeElementSelected(getNonDeletedElements(elements), appState)
|
||||||
appState.openPopup !== "compactOtherProperties"
|
}
|
||||||
? MOBILE_ACTION_BUTTON_BG
|
style={{
|
||||||
: {}),
|
...(isMobile && appState.openPopup !== "compactOtherProperties"
|
||||||
}}
|
? MOBILE_ACTION_BUTTON_BG
|
||||||
/>
|
: {}),
|
||||||
),
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { CaptureUpdateAction } from "@excalidraw/element";
|
|||||||
|
|
||||||
import type { Theme } from "@excalidraw/element/types";
|
import type { Theme } from "@excalidraw/element/types";
|
||||||
|
|
||||||
import { useDevice } from "../components/App";
|
import { useEditorInterface } from "../components/App";
|
||||||
import { CheckboxItem } from "../components/CheckboxItem";
|
import { CheckboxItem } from "../components/CheckboxItem";
|
||||||
import { DarkModeToggle } from "../components/DarkModeToggle";
|
import { DarkModeToggle } from "../components/DarkModeToggle";
|
||||||
import { ProjectName } from "../components/ProjectName";
|
import { ProjectName } from "../components/ProjectName";
|
||||||
@@ -242,7 +242,7 @@ export const actionSaveFileToDisk = register({
|
|||||||
icon={saveAs}
|
icon={saveAs}
|
||||||
title={t("buttons.saveAs")}
|
title={t("buttons.saveAs")}
|
||||||
aria-label={t("buttons.saveAs")}
|
aria-label={t("buttons.saveAs")}
|
||||||
showAriaLabel={useDevice().editor.isMobile}
|
showAriaLabel={useEditorInterface().formFactor === "phone"}
|
||||||
hidden={!nativeFileSystemSupported}
|
hidden={!nativeFileSystemSupported}
|
||||||
onClick={() => updateData(null)}
|
onClick={() => updateData(null)}
|
||||||
data-testid="save-as-button"
|
data-testid="save-as-button"
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import { HistoryChangedEvent } from "../history";
|
|||||||
import { useEmitter } from "../hooks/useEmitter";
|
import { useEmitter } from "../hooks/useEmitter";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
|
|
||||||
|
import { useStylesPanelMode } from "..";
|
||||||
|
|
||||||
import type { History } from "../history";
|
import type { History } from "../history";
|
||||||
import type { AppClassProperties, AppState } from "../types";
|
import type { AppClassProperties, AppState } from "../types";
|
||||||
import type { Action, ActionResult } from "./types";
|
import type { Action, ActionResult } from "./types";
|
||||||
@@ -73,7 +75,7 @@ export const createUndoAction: ActionCreator = (history) => ({
|
|||||||
),
|
),
|
||||||
keyTest: (event) =>
|
keyTest: (event) =>
|
||||||
event[KEYS.CTRL_OR_CMD] && matchKey(event, KEYS.Z) && !event.shiftKey,
|
event[KEYS.CTRL_OR_CMD] && matchKey(event, KEYS.Z) && !event.shiftKey,
|
||||||
PanelComponent: ({ appState, updateData, data }) => {
|
PanelComponent: ({ appState, updateData, data, app }) => {
|
||||||
const { isUndoStackEmpty } = useEmitter<HistoryChangedEvent>(
|
const { isUndoStackEmpty } = useEmitter<HistoryChangedEvent>(
|
||||||
history.onHistoryChangedEmitter,
|
history.onHistoryChangedEmitter,
|
||||||
new HistoryChangedEvent(
|
new HistoryChangedEvent(
|
||||||
@@ -81,6 +83,7 @@ export const createUndoAction: ActionCreator = (history) => ({
|
|||||||
history.isRedoStackEmpty,
|
history.isRedoStackEmpty,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
const isMobile = useStylesPanelMode() === "mobile";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolButton
|
<ToolButton
|
||||||
@@ -92,9 +95,7 @@ export const createUndoAction: ActionCreator = (history) => ({
|
|||||||
disabled={isUndoStackEmpty}
|
disabled={isUndoStackEmpty}
|
||||||
data-testid="button-undo"
|
data-testid="button-undo"
|
||||||
style={{
|
style={{
|
||||||
...(appState.stylesPanelMode === "mobile"
|
...(isMobile ? MOBILE_ACTION_BUTTON_BG : {}),
|
||||||
? MOBILE_ACTION_BUTTON_BG
|
|
||||||
: {}),
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -114,7 +115,7 @@ export const createRedoAction: ActionCreator = (history) => ({
|
|||||||
keyTest: (event) =>
|
keyTest: (event) =>
|
||||||
(event[KEYS.CTRL_OR_CMD] && event.shiftKey && matchKey(event, KEYS.Z)) ||
|
(event[KEYS.CTRL_OR_CMD] && event.shiftKey && matchKey(event, KEYS.Z)) ||
|
||||||
(isWindows && event.ctrlKey && !event.shiftKey && matchKey(event, KEYS.Y)),
|
(isWindows && event.ctrlKey && !event.shiftKey && matchKey(event, KEYS.Y)),
|
||||||
PanelComponent: ({ appState, updateData, data }) => {
|
PanelComponent: ({ appState, updateData, data, app }) => {
|
||||||
const { isRedoStackEmpty } = useEmitter(
|
const { isRedoStackEmpty } = useEmitter(
|
||||||
history.onHistoryChangedEmitter,
|
history.onHistoryChangedEmitter,
|
||||||
new HistoryChangedEvent(
|
new HistoryChangedEvent(
|
||||||
@@ -122,6 +123,7 @@ export const createRedoAction: ActionCreator = (history) => ({
|
|||||||
history.isRedoStackEmpty,
|
history.isRedoStackEmpty,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
const isMobile = useStylesPanelMode() === "mobile";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolButton
|
<ToolButton
|
||||||
@@ -133,9 +135,7 @@ export const createRedoAction: ActionCreator = (history) => ({
|
|||||||
disabled={isRedoStackEmpty}
|
disabled={isRedoStackEmpty}
|
||||||
data-testid="button-redo"
|
data-testid="button-redo"
|
||||||
style={{
|
style={{
|
||||||
...(appState.stylesPanelMode === "mobile"
|
...(isMobile ? MOBILE_ACTION_BUTTON_BG : {}),
|
||||||
? MOBILE_ACTION_BUTTON_BG
|
|
||||||
: {}),
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -81,9 +81,6 @@ import { RadioSelection } from "../components/RadioSelection";
|
|||||||
import { ColorPicker } from "../components/ColorPicker/ColorPicker";
|
import { ColorPicker } from "../components/ColorPicker/ColorPicker";
|
||||||
import { FontPicker } from "../components/FontPicker/FontPicker";
|
import { FontPicker } from "../components/FontPicker/FontPicker";
|
||||||
import { IconPicker } from "../components/IconPicker";
|
import { IconPicker } from "../components/IconPicker";
|
||||||
// TODO barnabasmolnar/editor-redesign
|
|
||||||
// TextAlignTopIcon, TextAlignBottomIcon,TextAlignMiddleIcon,
|
|
||||||
// ArrowHead icons
|
|
||||||
import { Range } from "../components/Range";
|
import { Range } from "../components/Range";
|
||||||
import {
|
import {
|
||||||
ArrowheadArrowIcon,
|
ArrowheadArrowIcon,
|
||||||
@@ -142,12 +139,23 @@ import {
|
|||||||
restoreCaretPosition,
|
restoreCaretPosition,
|
||||||
} from "../hooks/useTextEditorFocus";
|
} from "../hooks/useTextEditorFocus";
|
||||||
|
|
||||||
|
import { deriveStylesPanelMode } from "../editorInterface";
|
||||||
|
|
||||||
import { register } from "./register";
|
import { register } from "./register";
|
||||||
|
|
||||||
import type { AppClassProperties, AppState, Primitive } from "../types";
|
import type { AppClassProperties, AppState, Primitive } from "../types";
|
||||||
|
|
||||||
const FONT_SIZE_RELATIVE_INCREASE_STEP = 0.1;
|
const FONT_SIZE_RELATIVE_INCREASE_STEP = 0.1;
|
||||||
|
|
||||||
|
const getStylesPanelInfo = (app: AppClassProperties) => {
|
||||||
|
const stylesPanelMode = deriveStylesPanelMode(app.editorInterface);
|
||||||
|
return {
|
||||||
|
stylesPanelMode,
|
||||||
|
isCompact: stylesPanelMode !== "full",
|
||||||
|
isMobile: stylesPanelMode === "mobile",
|
||||||
|
} as const;
|
||||||
|
};
|
||||||
|
|
||||||
export const changeProperty = (
|
export const changeProperty = (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
@@ -326,35 +334,35 @@ export const actionChangeStrokeColor = register({
|
|||||||
: CaptureUpdateAction.EVENTUALLY,
|
: CaptureUpdateAction.EVENTUALLY,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, app, data }) => (
|
PanelComponent: ({ elements, appState, updateData, app, data }) => {
|
||||||
<>
|
const { stylesPanelMode } = getStylesPanelInfo(app);
|
||||||
{appState.stylesPanelMode === "full" && (
|
|
||||||
<h3 aria-hidden="true">{t("labels.stroke")}</h3>
|
return (
|
||||||
)}
|
<>
|
||||||
<ColorPicker
|
{stylesPanelMode === "full" && (
|
||||||
topPicks={DEFAULT_ELEMENT_STROKE_PICKS}
|
<h3 aria-hidden="true">{t("labels.stroke")}</h3>
|
||||||
palette={DEFAULT_ELEMENT_STROKE_COLOR_PALETTE}
|
|
||||||
type="elementStroke"
|
|
||||||
label={t("labels.stroke")}
|
|
||||||
color={getFormValue(
|
|
||||||
elements,
|
|
||||||
app,
|
|
||||||
(element) => element.strokeColor,
|
|
||||||
true,
|
|
||||||
(hasSelection) =>
|
|
||||||
!hasSelection ? appState.currentItemStrokeColor : null,
|
|
||||||
)}
|
)}
|
||||||
onChange={(color) => updateData({ currentItemStrokeColor: color })}
|
<ColorPicker
|
||||||
elements={elements}
|
topPicks={DEFAULT_ELEMENT_STROKE_PICKS}
|
||||||
appState={appState}
|
palette={DEFAULT_ELEMENT_STROKE_COLOR_PALETTE}
|
||||||
updateData={updateData}
|
type="elementStroke"
|
||||||
compactMode={
|
label={t("labels.stroke")}
|
||||||
appState.stylesPanelMode === "compact" ||
|
color={getFormValue(
|
||||||
appState.stylesPanelMode === "mobile"
|
elements,
|
||||||
}
|
app,
|
||||||
/>
|
(element) => element.strokeColor,
|
||||||
</>
|
true,
|
||||||
),
|
(hasSelection) =>
|
||||||
|
!hasSelection ? appState.currentItemStrokeColor : null,
|
||||||
|
)}
|
||||||
|
onChange={(color) => updateData({ currentItemStrokeColor: color })}
|
||||||
|
elements={elements}
|
||||||
|
appState={appState}
|
||||||
|
updateData={updateData}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const actionChangeBackgroundColor = register({
|
export const actionChangeBackgroundColor = register({
|
||||||
@@ -409,35 +417,37 @@ export const actionChangeBackgroundColor = register({
|
|||||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, app, data }) => (
|
PanelComponent: ({ elements, appState, updateData, app, data }) => {
|
||||||
<>
|
const { stylesPanelMode } = getStylesPanelInfo(app);
|
||||||
{appState.stylesPanelMode === "full" && (
|
|
||||||
<h3 aria-hidden="true">{t("labels.background")}</h3>
|
return (
|
||||||
)}
|
<>
|
||||||
<ColorPicker
|
{stylesPanelMode === "full" && (
|
||||||
topPicks={DEFAULT_ELEMENT_BACKGROUND_PICKS}
|
<h3 aria-hidden="true">{t("labels.background")}</h3>
|
||||||
palette={DEFAULT_ELEMENT_BACKGROUND_COLOR_PALETTE}
|
|
||||||
type="elementBackground"
|
|
||||||
label={t("labels.background")}
|
|
||||||
color={getFormValue(
|
|
||||||
elements,
|
|
||||||
app,
|
|
||||||
(element) => element.backgroundColor,
|
|
||||||
true,
|
|
||||||
(hasSelection) =>
|
|
||||||
!hasSelection ? appState.currentItemBackgroundColor : null,
|
|
||||||
)}
|
)}
|
||||||
onChange={(color) => updateData({ currentItemBackgroundColor: color })}
|
<ColorPicker
|
||||||
elements={elements}
|
topPicks={DEFAULT_ELEMENT_BACKGROUND_PICKS}
|
||||||
appState={appState}
|
palette={DEFAULT_ELEMENT_BACKGROUND_COLOR_PALETTE}
|
||||||
updateData={updateData}
|
type="elementBackground"
|
||||||
compactMode={
|
label={t("labels.background")}
|
||||||
appState.stylesPanelMode === "compact" ||
|
color={getFormValue(
|
||||||
appState.stylesPanelMode === "mobile"
|
elements,
|
||||||
}
|
app,
|
||||||
/>
|
(element) => element.backgroundColor,
|
||||||
</>
|
true,
|
||||||
),
|
(hasSelection) =>
|
||||||
|
!hasSelection ? appState.currentItemBackgroundColor : null,
|
||||||
|
)}
|
||||||
|
onChange={(color) =>
|
||||||
|
updateData({ currentItemBackgroundColor: color })
|
||||||
|
}
|
||||||
|
elements={elements}
|
||||||
|
appState={appState}
|
||||||
|
updateData={updateData}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const actionChangeFillStyle = register({
|
export const actionChangeFillStyle = register({
|
||||||
@@ -448,7 +458,9 @@ export const actionChangeFillStyle = register({
|
|||||||
trackEvent(
|
trackEvent(
|
||||||
"element",
|
"element",
|
||||||
"changeFillStyle",
|
"changeFillStyle",
|
||||||
`${value} (${app.device.editor.isMobile ? "mobile" : "desktop"})`,
|
`${value} (${
|
||||||
|
app.editorInterface.formFactor === "phone" ? "mobile" : "desktop"
|
||||||
|
})`,
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
elements: changeProperty(elements, appState, (el) =>
|
elements: changeProperty(elements, appState, (el) =>
|
||||||
@@ -714,78 +726,81 @@ export const actionChangeFontSize = register({
|
|||||||
perform: (elements, appState, value, app) => {
|
perform: (elements, appState, value, app) => {
|
||||||
return changeFontSize(elements, appState, app, () => value, value);
|
return changeFontSize(elements, appState, app, () => value, value);
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, app, data }) => (
|
PanelComponent: ({ elements, appState, updateData, app, data }) => {
|
||||||
<fieldset>
|
const { isCompact } = getStylesPanelInfo(app);
|
||||||
<legend>{t("labels.fontSize")}</legend>
|
|
||||||
<div className="buttonList">
|
return (
|
||||||
<RadioSelection
|
<fieldset>
|
||||||
group="font-size"
|
<legend>{t("labels.fontSize")}</legend>
|
||||||
options={[
|
<div className="buttonList">
|
||||||
{
|
<RadioSelection
|
||||||
value: 16,
|
group="font-size"
|
||||||
text: t("labels.small"),
|
options={[
|
||||||
icon: FontSizeSmallIcon,
|
{
|
||||||
testId: "fontSize-small",
|
value: 16,
|
||||||
},
|
text: t("labels.small"),
|
||||||
{
|
icon: FontSizeSmallIcon,
|
||||||
value: 20,
|
testId: "fontSize-small",
|
||||||
text: t("labels.medium"),
|
},
|
||||||
icon: FontSizeMediumIcon,
|
{
|
||||||
testId: "fontSize-medium",
|
value: 20,
|
||||||
},
|
text: t("labels.medium"),
|
||||||
{
|
icon: FontSizeMediumIcon,
|
||||||
value: 28,
|
testId: "fontSize-medium",
|
||||||
text: t("labels.large"),
|
},
|
||||||
icon: FontSizeLargeIcon,
|
{
|
||||||
testId: "fontSize-large",
|
value: 28,
|
||||||
},
|
text: t("labels.large"),
|
||||||
{
|
icon: FontSizeLargeIcon,
|
||||||
value: 36,
|
testId: "fontSize-large",
|
||||||
text: t("labels.veryLarge"),
|
},
|
||||||
icon: FontSizeExtraLargeIcon,
|
{
|
||||||
testId: "fontSize-veryLarge",
|
value: 36,
|
||||||
},
|
text: t("labels.veryLarge"),
|
||||||
]}
|
icon: FontSizeExtraLargeIcon,
|
||||||
value={getFormValue(
|
testId: "fontSize-veryLarge",
|
||||||
elements,
|
},
|
||||||
app,
|
]}
|
||||||
(element) => {
|
value={getFormValue(
|
||||||
if (isTextElement(element)) {
|
elements,
|
||||||
return element.fontSize;
|
app,
|
||||||
}
|
(element) => {
|
||||||
const boundTextElement = getBoundTextElement(
|
if (isTextElement(element)) {
|
||||||
element,
|
return element.fontSize;
|
||||||
app.scene.getNonDeletedElementsMap(),
|
}
|
||||||
|
const boundTextElement = getBoundTextElement(
|
||||||
|
element,
|
||||||
|
app.scene.getNonDeletedElementsMap(),
|
||||||
|
);
|
||||||
|
if (boundTextElement) {
|
||||||
|
return boundTextElement.fontSize;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
(element) =>
|
||||||
|
isTextElement(element) ||
|
||||||
|
getBoundTextElement(
|
||||||
|
element,
|
||||||
|
app.scene.getNonDeletedElementsMap(),
|
||||||
|
) !== null,
|
||||||
|
(hasSelection) =>
|
||||||
|
hasSelection
|
||||||
|
? null
|
||||||
|
: appState.currentItemFontSize || DEFAULT_FONT_SIZE,
|
||||||
|
)}
|
||||||
|
onChange={(value) => {
|
||||||
|
withCaretPositionPreservation(
|
||||||
|
() => updateData(value),
|
||||||
|
isCompact,
|
||||||
|
!!appState.editingTextElement,
|
||||||
|
data?.onPreventClose,
|
||||||
);
|
);
|
||||||
if (boundTextElement) {
|
}}
|
||||||
return boundTextElement.fontSize;
|
/>
|
||||||
}
|
</div>
|
||||||
return null;
|
</fieldset>
|
||||||
},
|
);
|
||||||
(element) =>
|
},
|
||||||
isTextElement(element) ||
|
|
||||||
getBoundTextElement(
|
|
||||||
element,
|
|
||||||
app.scene.getNonDeletedElementsMap(),
|
|
||||||
) !== null,
|
|
||||||
(hasSelection) =>
|
|
||||||
hasSelection
|
|
||||||
? null
|
|
||||||
: appState.currentItemFontSize || DEFAULT_FONT_SIZE,
|
|
||||||
)}
|
|
||||||
onChange={(value) => {
|
|
||||||
withCaretPositionPreservation(
|
|
||||||
() => updateData(value),
|
|
||||||
appState.stylesPanelMode === "compact" ||
|
|
||||||
appState.stylesPanelMode === "mobile",
|
|
||||||
!!appState.editingTextElement,
|
|
||||||
data?.onPreventClose,
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const actionDecreaseFontSize = register({
|
export const actionDecreaseFontSize = register({
|
||||||
@@ -1047,6 +1062,7 @@ export const actionChangeFontFamily = register({
|
|||||||
// relying on state batching as multiple `FontPicker` handlers could be called in rapid succession and we want to combine them
|
// relying on state batching as multiple `FontPicker` handlers could be called in rapid succession and we want to combine them
|
||||||
const [batchedData, setBatchedData] = useState<ChangeFontFamilyData>({});
|
const [batchedData, setBatchedData] = useState<ChangeFontFamilyData>({});
|
||||||
const isUnmounted = useRef(true);
|
const isUnmounted = useRef(true);
|
||||||
|
const { stylesPanelMode, isCompact } = getStylesPanelInfo(app);
|
||||||
|
|
||||||
const selectedFontFamily = useMemo(() => {
|
const selectedFontFamily = useMemo(() => {
|
||||||
const getFontFamily = (
|
const getFontFamily = (
|
||||||
@@ -1119,14 +1135,14 @@ export const actionChangeFontFamily = register({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{appState.stylesPanelMode === "full" && (
|
{stylesPanelMode === "full" && (
|
||||||
<legend>{t("labels.fontFamily")}</legend>
|
<legend>{t("labels.fontFamily")}</legend>
|
||||||
)}
|
)}
|
||||||
<FontPicker
|
<FontPicker
|
||||||
isOpened={appState.openPopup === "fontFamily"}
|
isOpened={appState.openPopup === "fontFamily"}
|
||||||
selectedFontFamily={selectedFontFamily}
|
selectedFontFamily={selectedFontFamily}
|
||||||
hoveredFontFamily={appState.currentHoveredFontFamily}
|
hoveredFontFamily={appState.currentHoveredFontFamily}
|
||||||
compactMode={appState.stylesPanelMode !== "full"}
|
compactMode={stylesPanelMode !== "full"}
|
||||||
onSelect={(fontFamily) => {
|
onSelect={(fontFamily) => {
|
||||||
withCaretPositionPreservation(
|
withCaretPositionPreservation(
|
||||||
() => {
|
() => {
|
||||||
@@ -1138,8 +1154,7 @@ export const actionChangeFontFamily = register({
|
|||||||
// defensive clear so immediate close won't abuse the cached elements
|
// defensive clear so immediate close won't abuse the cached elements
|
||||||
cachedElementsRef.current.clear();
|
cachedElementsRef.current.clear();
|
||||||
},
|
},
|
||||||
appState.stylesPanelMode === "compact" ||
|
isCompact,
|
||||||
appState.stylesPanelMode === "mobile",
|
|
||||||
!!appState.editingTextElement,
|
!!appState.editingTextElement,
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
@@ -1214,11 +1229,7 @@ export const actionChangeFontFamily = register({
|
|||||||
cachedElementsRef.current.clear();
|
cachedElementsRef.current.clear();
|
||||||
|
|
||||||
// Refocus text editor when font picker closes if we were editing text
|
// Refocus text editor when font picker closes if we were editing text
|
||||||
if (
|
if (isCompact && appState.editingTextElement) {
|
||||||
(appState.stylesPanelMode === "compact" ||
|
|
||||||
appState.stylesPanelMode === "mobile") &&
|
|
||||||
appState.editingTextElement
|
|
||||||
) {
|
|
||||||
restoreCaretPosition(null); // Just refocus without saved position
|
restoreCaretPosition(null); // Just refocus without saved position
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1265,6 +1276,7 @@ export const actionChangeTextAlign = register({
|
|||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, app, data }) => {
|
PanelComponent: ({ elements, appState, updateData, app, data }) => {
|
||||||
const elementsMap = app.scene.getNonDeletedElementsMap();
|
const elementsMap = app.scene.getNonDeletedElementsMap();
|
||||||
|
const { isCompact } = getStylesPanelInfo(app);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<fieldset>
|
<fieldset>
|
||||||
@@ -1317,8 +1329,7 @@ export const actionChangeTextAlign = register({
|
|||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
withCaretPositionPreservation(
|
withCaretPositionPreservation(
|
||||||
() => updateData(value),
|
() => updateData(value),
|
||||||
appState.stylesPanelMode === "compact" ||
|
isCompact,
|
||||||
appState.stylesPanelMode === "mobile",
|
|
||||||
!!appState.editingTextElement,
|
!!appState.editingTextElement,
|
||||||
data?.onPreventClose,
|
data?.onPreventClose,
|
||||||
);
|
);
|
||||||
@@ -1365,6 +1376,7 @@ export const actionChangeVerticalAlign = register({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, app, data }) => {
|
PanelComponent: ({ elements, appState, updateData, app, data }) => {
|
||||||
|
const { isCompact } = getStylesPanelInfo(app);
|
||||||
return (
|
return (
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div className="buttonList">
|
<div className="buttonList">
|
||||||
@@ -1417,8 +1429,7 @@ export const actionChangeVerticalAlign = register({
|
|||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
withCaretPositionPreservation(
|
withCaretPositionPreservation(
|
||||||
() => updateData(value),
|
() => updateData(value),
|
||||||
appState.stylesPanelMode === "compact" ||
|
isCompact,
|
||||||
appState.stylesPanelMode === "mobile",
|
|
||||||
!!appState.editingTextElement,
|
!!appState.editingTextElement,
|
||||||
data?.onPreventClose,
|
data?.onPreventClose,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -37,7 +37,9 @@ const trackAction = (
|
|||||||
trackEvent(
|
trackEvent(
|
||||||
action.trackEvent.category,
|
action.trackEvent.category,
|
||||||
action.trackEvent.action || action.name,
|
action.trackEvent.action || action.name,
|
||||||
`${source} (${app.device.editor.isMobile ? "mobile" : "desktop"})`,
|
`${source} (${
|
||||||
|
app.editorInterface.formFactor === "phone" ? "mobile" : "desktop"
|
||||||
|
})`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,7 +127,6 @@ export const getDefaultAppState = (): Omit<
|
|||||||
searchMatches: null,
|
searchMatches: null,
|
||||||
lockedMultiSelections: {},
|
lockedMultiSelections: {},
|
||||||
activeLockedId: null,
|
activeLockedId: null,
|
||||||
stylesPanelMode: "full",
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -253,7 +252,6 @@ const APP_STATE_STORAGE_CONF = (<
|
|||||||
searchMatches: { browser: false, export: false, server: false },
|
searchMatches: { browser: false, export: false, server: false },
|
||||||
lockedMultiSelections: { browser: true, export: true, server: true },
|
lockedMultiSelections: { browser: true, export: true, server: true },
|
||||||
activeLockedId: { browser: false, export: false, server: false },
|
activeLockedId: { browser: false, export: false, server: false },
|
||||||
stylesPanelMode: { browser: false, export: false, server: false },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const _clearAppStateForStorage = <
|
const _clearAppStateForStorage = <
|
||||||
|
|||||||
@@ -53,7 +53,11 @@ import { getToolbarTools } from "./shapes";
|
|||||||
|
|
||||||
import "./Actions.scss";
|
import "./Actions.scss";
|
||||||
|
|
||||||
import { useDevice, useExcalidrawContainer } from "./App";
|
import {
|
||||||
|
useEditorInterface,
|
||||||
|
useStylesPanelMode,
|
||||||
|
useExcalidrawContainer,
|
||||||
|
} from "./App";
|
||||||
import Stack from "./Stack";
|
import Stack from "./Stack";
|
||||||
import { ToolButton } from "./ToolButton";
|
import { ToolButton } from "./ToolButton";
|
||||||
import { ToolPopover } from "./ToolPopover";
|
import { ToolPopover } from "./ToolPopover";
|
||||||
@@ -151,7 +155,7 @@ export const SelectedShapeActions = ({
|
|||||||
const isEditingTextOrNewElement = Boolean(
|
const isEditingTextOrNewElement = Boolean(
|
||||||
appState.editingTextElement || appState.newElement,
|
appState.editingTextElement || appState.newElement,
|
||||||
);
|
);
|
||||||
const device = useDevice();
|
const editorInterface = useEditorInterface();
|
||||||
const isRTL = document.documentElement.getAttribute("dir") === "rtl";
|
const isRTL = document.documentElement.getAttribute("dir") === "rtl";
|
||||||
|
|
||||||
const showFillIcons =
|
const showFillIcons =
|
||||||
@@ -292,8 +296,10 @@ export const SelectedShapeActions = ({
|
|||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{t("labels.actions")}</legend>
|
<legend>{t("labels.actions")}</legend>
|
||||||
<div className="buttonList">
|
<div className="buttonList">
|
||||||
{!device.editor.isMobile && renderAction("duplicateSelection")}
|
{editorInterface.formFactor !== "phone" &&
|
||||||
{!device.editor.isMobile && renderAction("deleteSelectedElements")}
|
renderAction("duplicateSelection")}
|
||||||
|
{editorInterface.formFactor !== "phone" &&
|
||||||
|
renderAction("deleteSelectedElements")}
|
||||||
{renderAction("group")}
|
{renderAction("group")}
|
||||||
{renderAction("ungroup")}
|
{renderAction("ungroup")}
|
||||||
{showLinkIcon && renderAction("hyperlink")}
|
{showLinkIcon && renderAction("hyperlink")}
|
||||||
@@ -1041,6 +1047,9 @@ export const ShapesSwitcher = ({
|
|||||||
UIOptions: AppProps["UIOptions"];
|
UIOptions: AppProps["UIOptions"];
|
||||||
}) => {
|
}) => {
|
||||||
const [isExtraToolsMenuOpen, setIsExtraToolsMenuOpen] = useState(false);
|
const [isExtraToolsMenuOpen, setIsExtraToolsMenuOpen] = useState(false);
|
||||||
|
const stylesPanelMode = useStylesPanelMode();
|
||||||
|
const isFullStylesPanel = stylesPanelMode === "full";
|
||||||
|
const isCompactStylesPanel = stylesPanelMode === "compact";
|
||||||
|
|
||||||
const SELECTION_TOOLS = [
|
const SELECTION_TOOLS = [
|
||||||
{
|
{
|
||||||
@@ -1058,7 +1067,7 @@ export const ShapesSwitcher = ({
|
|||||||
const frameToolSelected = activeTool.type === "frame";
|
const frameToolSelected = activeTool.type === "frame";
|
||||||
const laserToolSelected = activeTool.type === "laser";
|
const laserToolSelected = activeTool.type === "laser";
|
||||||
const lassoToolSelected =
|
const lassoToolSelected =
|
||||||
app.state.stylesPanelMode === "full" &&
|
isFullStylesPanel &&
|
||||||
activeTool.type === "lasso" &&
|
activeTool.type === "lasso" &&
|
||||||
app.state.preferredSelectionTool.type !== "lasso";
|
app.state.preferredSelectionTool.type !== "lasso";
|
||||||
|
|
||||||
@@ -1091,7 +1100,7 @@ export const ShapesSwitcher = ({
|
|||||||
// use a ToolPopover for selection/lasso toggle as well
|
// use a ToolPopover for selection/lasso toggle as well
|
||||||
if (
|
if (
|
||||||
(value === "selection" || value === "lasso") &&
|
(value === "selection" || value === "lasso") &&
|
||||||
app.state.stylesPanelMode === "compact"
|
isCompactStylesPanel
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<ToolPopover
|
<ToolPopover
|
||||||
@@ -1222,7 +1231,7 @@ export const ShapesSwitcher = ({
|
|||||||
>
|
>
|
||||||
{t("toolBar.laser")}
|
{t("toolBar.laser")}
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
{app.state.stylesPanelMode === "full" && (
|
{isFullStylesPanel && (
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
onSelect={() => app.setActiveTool({ type: "lasso" })}
|
onSelect={() => app.setActiveTool({ type: "lasso" })}
|
||||||
icon={LassoIcon}
|
icon={LassoIcon}
|
||||||
|
|||||||
@@ -406,6 +406,13 @@ import { LassoTrail } from "../lasso";
|
|||||||
|
|
||||||
import { EraserTrail } from "../eraser";
|
import { EraserTrail } from "../eraser";
|
||||||
|
|
||||||
|
import {
|
||||||
|
DESKTOP_UI_MODE_STORAGE_KEY,
|
||||||
|
createUserAgentDescriptor,
|
||||||
|
deriveFormFactor,
|
||||||
|
deriveStylesPanelMode,
|
||||||
|
} from "../editorInterface";
|
||||||
|
|
||||||
import ConvertElementTypePopup, {
|
import ConvertElementTypePopup, {
|
||||||
getConversionTypeFromElements,
|
getConversionTypeFromElements,
|
||||||
convertElementTypePopupAtom,
|
convertElementTypePopupAtom,
|
||||||
@@ -459,7 +466,8 @@ import type {
|
|||||||
LibraryItems,
|
LibraryItems,
|
||||||
PointerDownState,
|
PointerDownState,
|
||||||
SceneData,
|
SceneData,
|
||||||
Device,
|
EditorInterface,
|
||||||
|
StylesPanelMode,
|
||||||
FrameNameBoundsCache,
|
FrameNameBoundsCache,
|
||||||
SidebarName,
|
SidebarName,
|
||||||
SidebarTabName,
|
SidebarTabName,
|
||||||
@@ -480,19 +488,20 @@ import type { Action, ActionResult } from "../actions/types";
|
|||||||
const AppContext = React.createContext<AppClassProperties>(null!);
|
const AppContext = React.createContext<AppClassProperties>(null!);
|
||||||
const AppPropsContext = React.createContext<AppProps>(null!);
|
const AppPropsContext = React.createContext<AppProps>(null!);
|
||||||
|
|
||||||
const deviceContextInitialValue = {
|
const editorInterfaceContextInitialValue: EditorInterface = {
|
||||||
viewport: {
|
formFactor: "desktop",
|
||||||
isMobile: false,
|
desktopUIMode: "full",
|
||||||
isLandscape: false,
|
userAgent: createUserAgentDescriptor(
|
||||||
},
|
typeof navigator !== "undefined" ? navigator.userAgent : "",
|
||||||
editor: {
|
),
|
||||||
isMobile: false,
|
|
||||||
canFitSidebar: false,
|
|
||||||
},
|
|
||||||
isTouchScreen: false,
|
isTouchScreen: false,
|
||||||
|
canFitSidebar: false,
|
||||||
|
isLandscape: true,
|
||||||
};
|
};
|
||||||
const DeviceContext = React.createContext<Device>(deviceContextInitialValue);
|
const EditorInterfaceContext = React.createContext<EditorInterface>(
|
||||||
DeviceContext.displayName = "DeviceContext";
|
editorInterfaceContextInitialValue,
|
||||||
|
);
|
||||||
|
EditorInterfaceContext.displayName = "EditorInterfaceContext";
|
||||||
|
|
||||||
export const ExcalidrawContainerContext = React.createContext<{
|
export const ExcalidrawContainerContext = React.createContext<{
|
||||||
container: HTMLDivElement | null;
|
container: HTMLDivElement | null;
|
||||||
@@ -528,7 +537,10 @@ ExcalidrawActionManagerContext.displayName = "ExcalidrawActionManagerContext";
|
|||||||
|
|
||||||
export const useApp = () => useContext(AppContext);
|
export const useApp = () => useContext(AppContext);
|
||||||
export const useAppProps = () => useContext(AppPropsContext);
|
export const useAppProps = () => useContext(AppPropsContext);
|
||||||
export const useDevice = () => useContext<Device>(DeviceContext);
|
export const useEditorInterface = () =>
|
||||||
|
useContext<EditorInterface>(EditorInterfaceContext);
|
||||||
|
export const useStylesPanelMode = () =>
|
||||||
|
deriveStylesPanelMode(useEditorInterface());
|
||||||
export const useExcalidrawContainer = () =>
|
export const useExcalidrawContainer = () =>
|
||||||
useContext(ExcalidrawContainerContext);
|
useContext(ExcalidrawContainerContext);
|
||||||
export const useExcalidrawElements = () =>
|
export const useExcalidrawElements = () =>
|
||||||
@@ -576,7 +588,52 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
rc: RoughCanvas;
|
rc: RoughCanvas;
|
||||||
unmounted: boolean = false;
|
unmounted: boolean = false;
|
||||||
actionManager: ActionManager;
|
actionManager: ActionManager;
|
||||||
device: Device = deviceContextInitialValue;
|
editorInterface: EditorInterface = editorInterfaceContextInitialValue;
|
||||||
|
private stylesPanelMode: StylesPanelMode = deriveStylesPanelMode(
|
||||||
|
editorInterfaceContextInitialValue,
|
||||||
|
);
|
||||||
|
private loadDesktopUIModePreference = () => {
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stored = window.localStorage.getItem(DESKTOP_UI_MODE_STORAGE_KEY);
|
||||||
|
if (stored === "compact" || stored === "full") {
|
||||||
|
return stored as EditorInterface["desktopUIMode"];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// ignore storage access issues (e.g., Safari private mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
private persistDesktopUIMode = (mode: EditorInterface["desktopUIMode"]) => {
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
window.localStorage.setItem(DESKTOP_UI_MODE_STORAGE_KEY, mode);
|
||||||
|
} catch (error) {
|
||||||
|
// ignore storage access issues (e.g., Safari private mode)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public setDesktopUIMode = (mode: EditorInterface["desktopUIMode"]) => {
|
||||||
|
if (mode !== "compact" && mode !== "full") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mode === this.editorInterface.desktopUIMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.editorInterface = updateObject(this.editorInterface, {
|
||||||
|
desktopUIMode: mode,
|
||||||
|
});
|
||||||
|
this.persistDesktopUIMode(mode);
|
||||||
|
this.reconcileStylesPanelMode(this.editorInterface);
|
||||||
|
};
|
||||||
|
|
||||||
private excalidrawContainerRef = React.createRef<HTMLDivElement>();
|
private excalidrawContainerRef = React.createRef<HTMLDivElement>();
|
||||||
|
|
||||||
@@ -692,6 +749,16 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
height: window.innerHeight,
|
height: window.innerHeight,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const storedDesktopUIMode = this.loadDesktopUIModePreference();
|
||||||
|
const userAgentDescriptor = createUserAgentDescriptor(
|
||||||
|
typeof navigator !== "undefined" ? navigator.userAgent : "",
|
||||||
|
);
|
||||||
|
this.editorInterface = updateObject(this.editorInterface, {
|
||||||
|
desktopUIMode: storedDesktopUIMode ?? this.editorInterface.desktopUIMode,
|
||||||
|
userAgent: userAgentDescriptor,
|
||||||
|
});
|
||||||
|
this.stylesPanelMode = deriveStylesPanelMode(this.editorInterface);
|
||||||
|
|
||||||
this.id = nanoid();
|
this.id = nanoid();
|
||||||
this.library = new Library(this);
|
this.library = new Library(this);
|
||||||
this.actionManager = new ActionManager(
|
this.actionManager = new ActionManager(
|
||||||
@@ -1566,7 +1633,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
"excalidraw--view-mode":
|
"excalidraw--view-mode":
|
||||||
this.state.viewModeEnabled ||
|
this.state.viewModeEnabled ||
|
||||||
this.state.openDialog?.name === "elementLinkSelector",
|
this.state.openDialog?.name === "elementLinkSelector",
|
||||||
"excalidraw--mobile": this.device.editor.isMobile,
|
"excalidraw--mobile": this.editorInterface.formFactor === "phone",
|
||||||
})}
|
})}
|
||||||
style={{
|
style={{
|
||||||
["--ui-pointerEvents" as any]: shouldBlockPointerEvents
|
["--ui-pointerEvents" as any]: shouldBlockPointerEvents
|
||||||
@@ -1588,7 +1655,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
<ExcalidrawContainerContext.Provider
|
<ExcalidrawContainerContext.Provider
|
||||||
value={this.excalidrawContainerValue}
|
value={this.excalidrawContainerValue}
|
||||||
>
|
>
|
||||||
<DeviceContext.Provider value={this.device}>
|
<EditorInterfaceContext.Provider value={this.editorInterface}>
|
||||||
<ExcalidrawSetAppStateContext.Provider value={this.setAppState}>
|
<ExcalidrawSetAppStateContext.Provider value={this.setAppState}>
|
||||||
<ExcalidrawAppStateContext.Provider value={this.state}>
|
<ExcalidrawAppStateContext.Provider value={this.state}>
|
||||||
<ExcalidrawElementsContext.Provider
|
<ExcalidrawElementsContext.Provider
|
||||||
@@ -1816,7 +1883,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
renderScrollbars={
|
renderScrollbars={
|
||||||
this.props.renderScrollbars === true
|
this.props.renderScrollbars === true
|
||||||
}
|
}
|
||||||
device={this.device}
|
editorInterface={this.editorInterface}
|
||||||
renderInteractiveSceneCallback={
|
renderInteractiveSceneCallback={
|
||||||
this.renderInteractiveSceneCallback
|
this.renderInteractiveSceneCallback
|
||||||
}
|
}
|
||||||
@@ -1852,7 +1919,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
</ExcalidrawElementsContext.Provider>
|
</ExcalidrawElementsContext.Provider>
|
||||||
</ExcalidrawAppStateContext.Provider>
|
</ExcalidrawAppStateContext.Provider>
|
||||||
</ExcalidrawSetAppStateContext.Provider>
|
</ExcalidrawSetAppStateContext.Provider>
|
||||||
</DeviceContext.Provider>
|
</EditorInterfaceContext.Provider>
|
||||||
</ExcalidrawContainerContext.Provider>
|
</ExcalidrawContainerContext.Provider>
|
||||||
</AppPropsContext.Provider>
|
</AppPropsContext.Provider>
|
||||||
</AppContext.Provider>
|
</AppContext.Provider>
|
||||||
@@ -2369,7 +2436,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
|
|
||||||
if (!scene.appState.preferredSelectionTool.initialized) {
|
if (!scene.appState.preferredSelectionTool.initialized) {
|
||||||
scene.appState.preferredSelectionTool = {
|
scene.appState.preferredSelectionTool = {
|
||||||
type: this.device.editor.isMobile ? "lasso" : "selection",
|
type:
|
||||||
|
this.editorInterface.formFactor === "phone" ? "lasso" : "selection",
|
||||||
initialized: true,
|
initialized: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -2443,30 +2511,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
return minSide >= MQ_MIN_TABLET && maxSide <= MQ_MAX_TABLET;
|
return minSide >= MQ_MIN_TABLET && maxSide <= MQ_MAX_TABLET;
|
||||||
};
|
};
|
||||||
|
|
||||||
private refreshViewportBreakpoints = () => {
|
private refreshEditorInterface = () => {
|
||||||
const container = this.excalidrawContainerRef.current;
|
|
||||||
if (!container) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { width: editorWidth, height: editorHeight } =
|
|
||||||
container.getBoundingClientRect();
|
|
||||||
|
|
||||||
const prevViewportState = this.device.viewport;
|
|
||||||
|
|
||||||
const nextViewportState = updateObject(prevViewportState, {
|
|
||||||
isLandscape: editorWidth > editorHeight,
|
|
||||||
isMobile: this.isMobileBreakpoint(editorWidth, editorHeight),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (prevViewportState !== nextViewportState) {
|
|
||||||
this.device = { ...this.device, viewport: nextViewportState };
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
private refreshEditorBreakpoints = () => {
|
|
||||||
const container = this.excalidrawContainerRef.current;
|
const container = this.excalidrawContainerRef.current;
|
||||||
if (!container) {
|
if (!container) {
|
||||||
return;
|
return;
|
||||||
@@ -2480,42 +2525,43 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
? this.props.UIOptions.dockedSidebarBreakpoint
|
? this.props.UIOptions.dockedSidebarBreakpoint
|
||||||
: MQ_RIGHT_SIDEBAR_MIN_WIDTH;
|
: MQ_RIGHT_SIDEBAR_MIN_WIDTH;
|
||||||
|
|
||||||
const prevEditorState = this.device.editor;
|
const nextEditorInterface = updateObject(this.editorInterface, {
|
||||||
|
formFactor: deriveFormFactor(editorWidth, editorHeight, {
|
||||||
const nextEditorState = updateObject(prevEditorState, {
|
isMobile: (width, height) => this.isMobileBreakpoint(width, height),
|
||||||
isMobile: this.isMobileBreakpoint(editorWidth, editorHeight),
|
isTablet: (width, height) => this.isTabletBreakpoint(width, height),
|
||||||
|
}),
|
||||||
canFitSidebar: editorWidth > sidebarBreakpoint,
|
canFitSidebar: editorWidth > sidebarBreakpoint,
|
||||||
|
isLandscape: editorWidth > editorHeight,
|
||||||
});
|
});
|
||||||
|
|
||||||
const stylesPanelMode =
|
const didChange = nextEditorInterface !== this.editorInterface;
|
||||||
// NOTE: we could also remove the isMobileOrTablet check here and
|
|
||||||
// always switch to compact mode when the editor is narrow (e.g. < MQ_MIN_WIDTH_DESKTOP)
|
|
||||||
// but not too narrow (> MQ_MAX_WIDTH_MOBILE)
|
|
||||||
this.isTabletBreakpoint(editorWidth, editorHeight) && isMobileOrTablet()
|
|
||||||
? "compact"
|
|
||||||
: this.isMobileBreakpoint(editorWidth, editorHeight)
|
|
||||||
? "mobile"
|
|
||||||
: "full";
|
|
||||||
|
|
||||||
// also check if we need to update the app state
|
if (didChange) {
|
||||||
this.setState((prevState) => ({
|
this.editorInterface = nextEditorInterface;
|
||||||
stylesPanelMode,
|
}
|
||||||
// reset to box selection mode if the UI changes to full
|
|
||||||
// where you'd not be able to change the mode yourself currently
|
this.reconcileStylesPanelMode(nextEditorInterface);
|
||||||
preferredSelectionTool:
|
|
||||||
stylesPanelMode === "full"
|
return didChange;
|
||||||
? {
|
};
|
||||||
type: "selection",
|
|
||||||
initialized: true,
|
private reconcileStylesPanelMode = (nextEditorInterface: EditorInterface) => {
|
||||||
}
|
const nextStylesPanelMode = deriveStylesPanelMode(nextEditorInterface);
|
||||||
: prevState.preferredSelectionTool,
|
if (nextStylesPanelMode === this.stylesPanelMode) {
|
||||||
}));
|
return;
|
||||||
|
}
|
||||||
if (prevEditorState !== nextEditorState) {
|
|
||||||
this.device = { ...this.device, editor: nextEditorState };
|
const prevStylesPanelMode = this.stylesPanelMode;
|
||||||
return true;
|
this.stylesPanelMode = nextStylesPanelMode;
|
||||||
|
|
||||||
|
if (prevStylesPanelMode !== "full" && nextStylesPanelMode === "full") {
|
||||||
|
this.setState((prevState) => ({
|
||||||
|
preferredSelectionTool: {
|
||||||
|
type: "selection",
|
||||||
|
initialized: true,
|
||||||
|
},
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private clearImageShapeCache(filesMap?: BinaryFiles) {
|
private clearImageShapeCache(filesMap?: BinaryFiles) {
|
||||||
@@ -2593,13 +2639,12 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
// in mobile breakpoint (0 width/height), making everything fail
|
// in mobile breakpoint (0 width/height), making everything fail
|
||||||
!isTestEnv()
|
!isTestEnv()
|
||||||
) {
|
) {
|
||||||
this.refreshViewportBreakpoints();
|
this.refreshEditorInterface();
|
||||||
this.refreshEditorBreakpoints();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (supportsResizeObserver && this.excalidrawContainerRef.current) {
|
if (supportsResizeObserver && this.excalidrawContainerRef.current) {
|
||||||
this.resizeObserver = new ResizeObserver(() => {
|
this.resizeObserver = new ResizeObserver(() => {
|
||||||
this.refreshEditorBreakpoints();
|
this.refreshEditorInterface();
|
||||||
this.updateDOMRect();
|
this.updateDOMRect();
|
||||||
});
|
});
|
||||||
this.resizeObserver?.observe(this.excalidrawContainerRef.current);
|
this.resizeObserver?.observe(this.excalidrawContainerRef.current);
|
||||||
@@ -2653,11 +2698,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.scene
|
this.scene
|
||||||
.getElementsIncludingDeleted()
|
.getElementsIncludingDeleted()
|
||||||
.forEach((element) => ShapeCache.delete(element));
|
.forEach((element) => ShapeCache.delete(element));
|
||||||
this.refreshViewportBreakpoints();
|
this.refreshEditorInterface();
|
||||||
this.updateDOMRect();
|
this.updateDOMRect();
|
||||||
if (!supportsResizeObserver) {
|
|
||||||
this.refreshEditorBreakpoints();
|
|
||||||
}
|
|
||||||
this.setState({});
|
this.setState({});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2820,7 +2862,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
prevProps.UIOptions.dockedSidebarBreakpoint !==
|
prevProps.UIOptions.dockedSidebarBreakpoint !==
|
||||||
this.props.UIOptions.dockedSidebarBreakpoint
|
this.props.UIOptions.dockedSidebarBreakpoint
|
||||||
) {
|
) {
|
||||||
this.refreshEditorBreakpoints();
|
this.refreshEditorInterface();
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasFollowedPersonLeft =
|
const hasFollowedPersonLeft =
|
||||||
@@ -3428,7 +3470,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
// from library, not when pasting from clipboard. Alas.
|
// from library, not when pasting from clipboard. Alas.
|
||||||
openSidebar:
|
openSidebar:
|
||||||
this.state.openSidebar &&
|
this.state.openSidebar &&
|
||||||
this.device.editor.canFitSidebar &&
|
this.editorInterface.canFitSidebar &&
|
||||||
editorJotaiStore.get(isSidebarDockedAtom)
|
editorJotaiStore.get(isSidebarDockedAtom)
|
||||||
? this.state.openSidebar
|
? this.state.openSidebar
|
||||||
: null,
|
: null,
|
||||||
@@ -3626,7 +3668,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
!isPlainPaste &&
|
!isPlainPaste &&
|
||||||
textElements.length > 1 &&
|
textElements.length > 1 &&
|
||||||
PLAIN_PASTE_TOAST_SHOWN === false &&
|
PLAIN_PASTE_TOAST_SHOWN === false &&
|
||||||
!this.device.editor.isMobile
|
this.editorInterface.formFactor !== "phone"
|
||||||
) {
|
) {
|
||||||
this.setToast({
|
this.setToast({
|
||||||
message: t("toast.pasteAsSingleElement", {
|
message: t("toast.pasteAsSingleElement", {
|
||||||
@@ -3658,7 +3700,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
trackEvent(
|
trackEvent(
|
||||||
"toolbar",
|
"toolbar",
|
||||||
"toggleLock",
|
"toggleLock",
|
||||||
`${source} (${this.device.editor.isMobile ? "mobile" : "desktop"})`,
|
`${source} (${
|
||||||
|
this.editorInterface.formFactor === "phone" ? "mobile" : "desktop"
|
||||||
|
})`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.setState((prevState) => {
|
this.setState((prevState) => {
|
||||||
@@ -4010,12 +4054,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (appState) {
|
if (appState) {
|
||||||
this.setState({
|
this.setState(appState as Pick<AppState, K> | null);
|
||||||
...appState,
|
|
||||||
// keep existing stylesPanelMode as it needs to be preserved
|
|
||||||
// or set at startup
|
|
||||||
stylesPanelMode: this.state.stylesPanelMode,
|
|
||||||
} as Pick<AppState, K> | null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (elements) {
|
if (elements) {
|
||||||
@@ -4593,7 +4632,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
"toolbar",
|
"toolbar",
|
||||||
shape,
|
shape,
|
||||||
`keyboard (${
|
`keyboard (${
|
||||||
this.device.editor.isMobile ? "mobile" : "desktop"
|
this.editorInterface.formFactor === "phone"
|
||||||
|
? "mobile"
|
||||||
|
: "desktop"
|
||||||
})`,
|
})`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -5099,7 +5140,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
// caret (i.e. deselect). There's not much use for always selecting
|
// caret (i.e. deselect). There's not much use for always selecting
|
||||||
// the text on edit anyway (and users can select-all from contextmenu
|
// the text on edit anyway (and users can select-all from contextmenu
|
||||||
// if needed)
|
// if needed)
|
||||||
autoSelect: !this.device.isTouchScreen,
|
autoSelect: !this.editorInterface.isTouchScreen,
|
||||||
});
|
});
|
||||||
// deselect all other elements when inserting text
|
// deselect all other elements when inserting text
|
||||||
this.deselectElements();
|
this.deselectElements();
|
||||||
@@ -5732,7 +5773,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.scene.getNonDeletedElementsMap(),
|
this.scene.getNonDeletedElementsMap(),
|
||||||
this.state,
|
this.state,
|
||||||
pointFrom(scenePointer.x, scenePointer.y),
|
pointFrom(scenePointer.x, scenePointer.y),
|
||||||
this.device.editor.isMobile,
|
this.editorInterface.formFactor === "phone",
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return element;
|
return element;
|
||||||
@@ -5767,7 +5808,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
elementsMap,
|
elementsMap,
|
||||||
this.state,
|
this.state,
|
||||||
pointFrom(lastPointerDownCoords.x, lastPointerDownCoords.y),
|
pointFrom(lastPointerDownCoords.x, lastPointerDownCoords.y),
|
||||||
this.device.editor.isMobile,
|
this.editorInterface.formFactor === "phone",
|
||||||
);
|
);
|
||||||
const lastPointerUpCoords = viewportCoordsToSceneCoords(
|
const lastPointerUpCoords = viewportCoordsToSceneCoords(
|
||||||
this.lastPointerUpEvent!,
|
this.lastPointerUpEvent!,
|
||||||
@@ -5778,7 +5819,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
elementsMap,
|
elementsMap,
|
||||||
this.state,
|
this.state,
|
||||||
pointFrom(lastPointerUpCoords.x, lastPointerUpCoords.y),
|
pointFrom(lastPointerUpCoords.x, lastPointerUpCoords.y),
|
||||||
this.device.editor.isMobile,
|
this.editorInterface.formFactor === "phone",
|
||||||
);
|
);
|
||||||
if (lastPointerDownHittingLinkIcon && lastPointerUpHittingLinkIcon) {
|
if (lastPointerDownHittingLinkIcon && lastPointerUpHittingLinkIcon) {
|
||||||
hideHyperlinkToolip();
|
hideHyperlinkToolip();
|
||||||
@@ -6176,7 +6217,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.state.zoom,
|
this.state.zoom,
|
||||||
event.pointerType,
|
event.pointerType,
|
||||||
this.scene.getNonDeletedElementsMap(),
|
this.scene.getNonDeletedElementsMap(),
|
||||||
this.device,
|
this.editorInterface,
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
elementWithTransformHandleType &&
|
elementWithTransformHandleType &&
|
||||||
@@ -6200,7 +6241,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
scenePointerY,
|
scenePointerY,
|
||||||
this.state.zoom,
|
this.state.zoom,
|
||||||
event.pointerType,
|
event.pointerType,
|
||||||
this.device,
|
this.editorInterface,
|
||||||
);
|
);
|
||||||
if (transformHandleType) {
|
if (transformHandleType) {
|
||||||
setCursor(
|
setCursor(
|
||||||
@@ -6586,10 +6627,12 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!this.device.isTouchScreen &&
|
!this.editorInterface.isTouchScreen &&
|
||||||
["pen", "touch"].includes(event.pointerType)
|
["pen", "touch"].includes(event.pointerType)
|
||||||
) {
|
) {
|
||||||
this.device = updateObject(this.device, { isTouchScreen: true });
|
this.editorInterface = updateObject(this.editorInterface, {
|
||||||
|
isTouchScreen: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPanning) {
|
if (isPanning) {
|
||||||
@@ -6912,7 +6955,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
const clicklength =
|
const clicklength =
|
||||||
event.timeStamp - (this.lastPointerDownEvent?.timeStamp ?? 0);
|
event.timeStamp - (this.lastPointerDownEvent?.timeStamp ?? 0);
|
||||||
|
|
||||||
if (this.device.editor.isMobile && clicklength < 300) {
|
if (this.editorInterface.formFactor === "phone" && clicklength < 300) {
|
||||||
const hitElement = this.getElementAtPosition(
|
const hitElement = this.getElementAtPosition(
|
||||||
scenePointer.x,
|
scenePointer.x,
|
||||||
scenePointer.y,
|
scenePointer.y,
|
||||||
@@ -6931,7 +6974,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.device.isTouchScreen) {
|
if (this.editorInterface.isTouchScreen) {
|
||||||
const hitElement = this.getElementAtPosition(
|
const hitElement = this.getElementAtPosition(
|
||||||
scenePointer.x,
|
scenePointer.x,
|
||||||
scenePointer.y,
|
scenePointer.y,
|
||||||
@@ -6961,7 +7004,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
) {
|
) {
|
||||||
this.handleEmbeddableCenterClick(this.hitLinkElement);
|
this.handleEmbeddableCenterClick(this.hitLinkElement);
|
||||||
} else {
|
} else {
|
||||||
this.redirectToLink(event, this.device.isTouchScreen);
|
this.redirectToLink(event, this.editorInterface.isTouchScreen);
|
||||||
}
|
}
|
||||||
} else if (this.state.viewModeEnabled) {
|
} else if (this.state.viewModeEnabled) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -7308,7 +7351,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.state.zoom,
|
this.state.zoom,
|
||||||
event.pointerType,
|
event.pointerType,
|
||||||
this.scene.getNonDeletedElementsMap(),
|
this.scene.getNonDeletedElementsMap(),
|
||||||
this.device,
|
this.editorInterface,
|
||||||
);
|
);
|
||||||
if (elementWithTransformHandleType != null) {
|
if (elementWithTransformHandleType != null) {
|
||||||
if (
|
if (
|
||||||
@@ -7337,7 +7380,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
pointerDownState.origin.y,
|
pointerDownState.origin.y,
|
||||||
this.state.zoom,
|
this.state.zoom,
|
||||||
event.pointerType,
|
event.pointerType,
|
||||||
this.device,
|
this.editorInterface,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (pointerDownState.resize.handleType) {
|
if (pointerDownState.resize.handleType) {
|
||||||
@@ -9387,7 +9430,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
newElement &&
|
newElement &&
|
||||||
!multiElement
|
!multiElement
|
||||||
) {
|
) {
|
||||||
if (this.device.isTouchScreen) {
|
if (this.editorInterface.isTouchScreen) {
|
||||||
const FIXED_DELTA_X = Math.min(
|
const FIXED_DELTA_X = Math.min(
|
||||||
(this.state.width * 0.7) / this.state.zoom.value,
|
(this.state.width * 0.7) / this.state.zoom.value,
|
||||||
100,
|
100,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { KEYS, getShortcutKey } from "@excalidraw/common";
|
|||||||
|
|
||||||
import { useAtom } from "../../editor-jotai";
|
import { useAtom } from "../../editor-jotai";
|
||||||
import { t } from "../../i18n";
|
import { t } from "../../i18n";
|
||||||
import { useDevice } from "../App";
|
import { useEditorInterface } from "../App";
|
||||||
import { activeEyeDropperAtom } from "../EyeDropper";
|
import { activeEyeDropperAtom } from "../EyeDropper";
|
||||||
import { eyeDropperIcon } from "../icons";
|
import { eyeDropperIcon } from "../icons";
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ export const ColorInput = ({
|
|||||||
colorPickerType,
|
colorPickerType,
|
||||||
placeholder,
|
placeholder,
|
||||||
}: ColorInputProps) => {
|
}: ColorInputProps) => {
|
||||||
const device = useDevice();
|
const editorInterface = useEditorInterface();
|
||||||
const [innerValue, setInnerValue] = useState(color);
|
const [innerValue, setInnerValue] = useState(color);
|
||||||
const [activeSection, setActiveColorPickerSection] = useAtom(
|
const [activeSection, setActiveColorPickerSection] = useAtom(
|
||||||
activeColorPickerSectionAtom,
|
activeColorPickerSectionAtom,
|
||||||
@@ -98,7 +98,7 @@ export const ColorInput = ({
|
|||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
/>
|
/>
|
||||||
{/* TODO reenable on mobile with a better UX */}
|
{/* TODO reenable on mobile with a better UX */}
|
||||||
{!device.editor.isMobile && (
|
{editorInterface.formFactor !== "phone" && (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import type { ExcalidrawElement } from "@excalidraw/element/types";
|
|||||||
|
|
||||||
import { useAtom } from "../../editor-jotai";
|
import { useAtom } from "../../editor-jotai";
|
||||||
import { t } from "../../i18n";
|
import { t } from "../../i18n";
|
||||||
import { useExcalidrawContainer } from "../App";
|
import { useExcalidrawContainer, useStylesPanelMode } from "../App";
|
||||||
import { ButtonSeparator } from "../ButtonSeparator";
|
import { ButtonSeparator } from "../ButtonSeparator";
|
||||||
import { activeEyeDropperAtom } from "../EyeDropper";
|
import { activeEyeDropperAtom } from "../EyeDropper";
|
||||||
import { PropertiesPopover } from "../PropertiesPopover";
|
import { PropertiesPopover } from "../PropertiesPopover";
|
||||||
@@ -73,7 +73,6 @@ interface ColorPickerProps {
|
|||||||
palette?: ColorPaletteCustom | null;
|
palette?: ColorPaletteCustom | null;
|
||||||
topPicks?: ColorTuple;
|
topPicks?: ColorTuple;
|
||||||
updateData: (formData?: any) => void;
|
updateData: (formData?: any) => void;
|
||||||
compactMode?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ColorPickerPopupContent = ({
|
const ColorPickerPopupContent = ({
|
||||||
@@ -100,6 +99,9 @@ const ColorPickerPopupContent = ({
|
|||||||
getOpenPopup: () => AppState["openPopup"];
|
getOpenPopup: () => AppState["openPopup"];
|
||||||
}) => {
|
}) => {
|
||||||
const { container } = useExcalidrawContainer();
|
const { container } = useExcalidrawContainer();
|
||||||
|
const stylesPanelMode = useStylesPanelMode();
|
||||||
|
const isCompactMode = stylesPanelMode !== "full";
|
||||||
|
const isMobileMode = stylesPanelMode === "mobile";
|
||||||
const [, setActiveColorPickerSection] = useAtom(activeColorPickerSectionAtom);
|
const [, setActiveColorPickerSection] = useAtom(activeColorPickerSectionAtom);
|
||||||
|
|
||||||
const [eyeDropperState, setEyeDropperState] = useAtom(activeEyeDropperAtom);
|
const [eyeDropperState, setEyeDropperState] = useAtom(activeEyeDropperAtom);
|
||||||
@@ -216,11 +218,8 @@ const ColorPickerPopupContent = ({
|
|||||||
type={type}
|
type={type}
|
||||||
elements={elements}
|
elements={elements}
|
||||||
updateData={updateData}
|
updateData={updateData}
|
||||||
showTitle={
|
showTitle={isCompactMode}
|
||||||
appState.stylesPanelMode === "compact" ||
|
showHotKey={!isMobileMode}
|
||||||
appState.stylesPanelMode === "mobile"
|
|
||||||
}
|
|
||||||
showHotKey={appState.stylesPanelMode !== "mobile"}
|
|
||||||
>
|
>
|
||||||
{colorInputJSX}
|
{colorInputJSX}
|
||||||
</Picker>
|
</Picker>
|
||||||
@@ -235,7 +234,6 @@ const ColorPickerTrigger = ({
|
|||||||
label,
|
label,
|
||||||
color,
|
color,
|
||||||
type,
|
type,
|
||||||
stylesPanelMode,
|
|
||||||
mode = "background",
|
mode = "background",
|
||||||
onToggle,
|
onToggle,
|
||||||
editingTextElement,
|
editingTextElement,
|
||||||
@@ -243,11 +241,13 @@ const ColorPickerTrigger = ({
|
|||||||
color: string | null;
|
color: string | null;
|
||||||
label: string;
|
label: string;
|
||||||
type: ColorPickerType;
|
type: ColorPickerType;
|
||||||
stylesPanelMode?: AppState["stylesPanelMode"];
|
|
||||||
mode?: "background" | "stroke";
|
mode?: "background" | "stroke";
|
||||||
onToggle: () => void;
|
onToggle: () => void;
|
||||||
editingTextElement?: boolean;
|
editingTextElement?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
|
const stylesPanelMode = useStylesPanelMode();
|
||||||
|
const isCompactMode = stylesPanelMode !== "full";
|
||||||
|
const isMobileMode = stylesPanelMode === "mobile";
|
||||||
const handleClick = (e: React.MouseEvent) => {
|
const handleClick = (e: React.MouseEvent) => {
|
||||||
// use pointerdown so we run before outside-close logic
|
// use pointerdown so we run before outside-close logic
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -268,9 +268,8 @@ const ColorPickerTrigger = ({
|
|||||||
"is-transparent": !color || color === "transparent",
|
"is-transparent": !color || color === "transparent",
|
||||||
"has-outline":
|
"has-outline":
|
||||||
!color || !isColorDark(color, COLOR_OUTLINE_CONTRAST_THRESHOLD),
|
!color || !isColorDark(color, COLOR_OUTLINE_CONTRAST_THRESHOLD),
|
||||||
"compact-sizing":
|
"compact-sizing": isCompactMode,
|
||||||
stylesPanelMode === "compact" || stylesPanelMode === "mobile",
|
"mobile-border": isMobileMode,
|
||||||
"mobile-border": stylesPanelMode === "mobile",
|
|
||||||
})}
|
})}
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
style={color ? { "--swatch-color": color } : undefined}
|
style={color ? { "--swatch-color": color } : undefined}
|
||||||
@@ -283,22 +282,20 @@ const ColorPickerTrigger = ({
|
|||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
<div className="color-picker__button-outline">{!color && slashIcon}</div>
|
<div className="color-picker__button-outline">{!color && slashIcon}</div>
|
||||||
{(stylesPanelMode === "compact" || stylesPanelMode === "mobile") &&
|
{isCompactMode && color && mode === "stroke" && (
|
||||||
color &&
|
<div className="color-picker__button-background">
|
||||||
mode === "stroke" && (
|
<span
|
||||||
<div className="color-picker__button-background">
|
style={{
|
||||||
<span
|
color:
|
||||||
style={{
|
color && isColorDark(color, COLOR_OUTLINE_CONTRAST_THRESHOLD)
|
||||||
color:
|
? "#fff"
|
||||||
color && isColorDark(color, COLOR_OUTLINE_CONTRAST_THRESHOLD)
|
: "#111",
|
||||||
? "#fff"
|
}}
|
||||||
: "#111",
|
>
|
||||||
}}
|
{strokeIcon}
|
||||||
>
|
</span>
|
||||||
{strokeIcon}
|
</div>
|
||||||
</span>
|
)}
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Popover.Trigger>
|
</Popover.Trigger>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -318,9 +315,8 @@ export const ColorPicker = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
openRef.current = appState.openPopup;
|
openRef.current = appState.openPopup;
|
||||||
}, [appState.openPopup]);
|
}, [appState.openPopup]);
|
||||||
const compactMode =
|
const stylesPanelMode = useStylesPanelMode();
|
||||||
appState.stylesPanelMode === "compact" ||
|
const isCompactMode = stylesPanelMode !== "full";
|
||||||
appState.stylesPanelMode === "mobile";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -328,10 +324,10 @@ export const ColorPicker = ({
|
|||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
className={clsx("color-picker-container", {
|
className={clsx("color-picker-container", {
|
||||||
"color-picker-container--no-top-picks": compactMode,
|
"color-picker-container--no-top-picks": isCompactMode,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{!compactMode && (
|
{!isCompactMode && (
|
||||||
<TopPicks
|
<TopPicks
|
||||||
activeColor={color}
|
activeColor={color}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
@@ -339,7 +335,7 @@ export const ColorPicker = ({
|
|||||||
topPicks={topPicks}
|
topPicks={topPicks}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!compactMode && <ButtonSeparator />}
|
{!isCompactMode && <ButtonSeparator />}
|
||||||
<Popover.Root
|
<Popover.Root
|
||||||
open={appState.openPopup === type}
|
open={appState.openPopup === type}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
@@ -353,7 +349,6 @@ export const ColorPicker = ({
|
|||||||
color={color}
|
color={color}
|
||||||
label={label}
|
label={label}
|
||||||
type={type}
|
type={type}
|
||||||
stylesPanelMode={appState.stylesPanelMode}
|
|
||||||
mode={type === "elementStroke" ? "stroke" : "background"}
|
mode={type === "elementStroke" ? "stroke" : "background"}
|
||||||
editingTextElement={!!appState.editingTextElement}
|
editingTextElement={!!appState.editingTextElement}
|
||||||
onToggle={() => {
|
onToggle={() => {
|
||||||
|
|||||||
@@ -899,7 +899,7 @@ function CommandPaletteInner({
|
|||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!app.device.viewport.isMobile && (
|
{app.editorInterface.formFactor !== "phone" && (
|
||||||
<div className="shortcuts-wrapper">
|
<div className="shortcuts-wrapper">
|
||||||
<CommandShortcutHint shortcut="↑↓">
|
<CommandShortcutHint shortcut="↑↓">
|
||||||
{t("commandPalette.shortcuts.select")}
|
{t("commandPalette.shortcuts.select")}
|
||||||
@@ -933,7 +933,7 @@ function CommandPaletteInner({
|
|||||||
onClick={(event) => executeCommand(lastUsed, event)}
|
onClick={(event) => executeCommand(lastUsed, event)}
|
||||||
disabled={!isCommandAvailable(lastUsed)}
|
disabled={!isCommandAvailable(lastUsed)}
|
||||||
onMouseMove={() => setCurrentCommand(lastUsed)}
|
onMouseMove={() => setCurrentCommand(lastUsed)}
|
||||||
showShortcut={!app.device.viewport.isMobile}
|
showShortcut={app.editorInterface.formFactor !== "phone"}
|
||||||
appState={uiAppState}
|
appState={uiAppState}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -951,7 +951,7 @@ function CommandPaletteInner({
|
|||||||
isSelected={command.label === currentCommand?.label}
|
isSelected={command.label === currentCommand?.label}
|
||||||
onClick={(event) => executeCommand(command, event)}
|
onClick={(event) => executeCommand(command, event)}
|
||||||
onMouseMove={() => setCurrentCommand(command)}
|
onMouseMove={() => setCurrentCommand(command)}
|
||||||
showShortcut={!app.device.viewport.isMobile}
|
showShortcut={app.editorInterface.formFactor !== "phone"}
|
||||||
appState={uiAppState}
|
appState={uiAppState}
|
||||||
size={category === "Library" ? "large" : "small"}
|
size={category === "Library" ? "large" : "small"}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { t } from "../i18n";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
useExcalidrawContainer,
|
useExcalidrawContainer,
|
||||||
useDevice,
|
useEditorInterface,
|
||||||
useExcalidrawSetAppState,
|
useExcalidrawSetAppState,
|
||||||
} from "./App";
|
} from "./App";
|
||||||
import { Island } from "./Island";
|
import { Island } from "./Island";
|
||||||
@@ -51,7 +51,7 @@ export const Dialog = (props: DialogProps) => {
|
|||||||
const [islandNode, setIslandNode] = useCallbackRefState<HTMLDivElement>();
|
const [islandNode, setIslandNode] = useCallbackRefState<HTMLDivElement>();
|
||||||
const [lastActiveElement] = useState(document.activeElement);
|
const [lastActiveElement] = useState(document.activeElement);
|
||||||
const { id } = useExcalidrawContainer();
|
const { id } = useExcalidrawContainer();
|
||||||
const isFullscreen = useDevice().viewport.isMobile;
|
const isFullscreen = useEditorInterface().formFactor === "phone";
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!islandNode) {
|
if (!islandNode) {
|
||||||
|
|||||||
@@ -20,7 +20,12 @@ import type { ValueOf } from "@excalidraw/common/utility-types";
|
|||||||
|
|
||||||
import { Fonts } from "../../fonts";
|
import { Fonts } from "../../fonts";
|
||||||
import { t } from "../../i18n";
|
import { t } from "../../i18n";
|
||||||
import { useApp, useAppProps, useExcalidrawContainer } from "../App";
|
import {
|
||||||
|
useApp,
|
||||||
|
useAppProps,
|
||||||
|
useExcalidrawContainer,
|
||||||
|
useStylesPanelMode,
|
||||||
|
} from "../App";
|
||||||
import { PropertiesPopover } from "../PropertiesPopover";
|
import { PropertiesPopover } from "../PropertiesPopover";
|
||||||
import { QuickSearch } from "../QuickSearch";
|
import { QuickSearch } from "../QuickSearch";
|
||||||
import { ScrollableList } from "../ScrollableList";
|
import { ScrollableList } from "../ScrollableList";
|
||||||
@@ -93,6 +98,7 @@ export const FontPickerList = React.memo(
|
|||||||
const app = useApp();
|
const app = useApp();
|
||||||
const { fonts } = app;
|
const { fonts } = app;
|
||||||
const { showDeprecatedFonts } = useAppProps();
|
const { showDeprecatedFonts } = useAppProps();
|
||||||
|
const stylesPanelMode = useStylesPanelMode();
|
||||||
|
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
@@ -338,7 +344,7 @@ export const FontPickerList = React.memo(
|
|||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
preventAutoFocusOnTouch={!!app.state.editingTextElement}
|
preventAutoFocusOnTouch={!!app.state.editingTextElement}
|
||||||
>
|
>
|
||||||
{app.state.stylesPanelMode === "full" && (
|
{stylesPanelMode === "full" && (
|
||||||
<QuickSearch
|
<QuickSearch
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
placeholder={t("quickSearch.placeholder")}
|
placeholder={t("quickSearch.placeholder")}
|
||||||
|
|||||||
@@ -19,19 +19,19 @@ import { isGridModeEnabled } from "../snapping";
|
|||||||
|
|
||||||
import "./HintViewer.scss";
|
import "./HintViewer.scss";
|
||||||
|
|
||||||
import type { AppClassProperties, Device, UIAppState } from "../types";
|
import type { AppClassProperties, EditorInterface, UIAppState } from "../types";
|
||||||
|
|
||||||
interface HintViewerProps {
|
interface HintViewerProps {
|
||||||
appState: UIAppState;
|
appState: UIAppState;
|
||||||
isMobile: boolean;
|
isMobile: boolean;
|
||||||
device: Device;
|
editorInterface: EditorInterface;
|
||||||
app: AppClassProperties;
|
app: AppClassProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getHints = ({
|
const getHints = ({
|
||||||
appState,
|
appState,
|
||||||
isMobile,
|
isMobile,
|
||||||
device,
|
editorInterface,
|
||||||
app,
|
app,
|
||||||
}: HintViewerProps): null | string | string[] => {
|
}: HintViewerProps): null | string | string[] => {
|
||||||
const { activeTool, isResizing, isRotating, lastPointerDownWith } = appState;
|
const { activeTool, isResizing, isRotating, lastPointerDownWith } = appState;
|
||||||
@@ -45,7 +45,7 @@ const getHints = ({
|
|||||||
return t("hints.dismissSearch");
|
return t("hints.dismissSearch");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appState.openSidebar && !device.editor.canFitSidebar) {
|
if (appState.openSidebar && !editorInterface.canFitSidebar) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,13 +168,13 @@ const getHints = ({
|
|||||||
export const HintViewer = ({
|
export const HintViewer = ({
|
||||||
appState,
|
appState,
|
||||||
isMobile,
|
isMobile,
|
||||||
device,
|
editorInterface,
|
||||||
app,
|
app,
|
||||||
}: HintViewerProps) => {
|
}: HintViewerProps) => {
|
||||||
const hints = getHints({
|
const hints = getHints({
|
||||||
appState,
|
appState,
|
||||||
isMobile,
|
isMobile,
|
||||||
device,
|
editorInterface,
|
||||||
app,
|
app,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { atom, useAtom } from "../editor-jotai";
|
|||||||
import { getLanguage, t } from "../i18n";
|
import { getLanguage, t } from "../i18n";
|
||||||
|
|
||||||
import Collapsible from "./Stats/Collapsible";
|
import Collapsible from "./Stats/Collapsible";
|
||||||
import { useDevice } from "./App";
|
import { useEditorInterface } from "./App";
|
||||||
|
|
||||||
import "./IconPicker.scss";
|
import "./IconPicker.scss";
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ function Picker<T>({
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
numberOfOptionsToAlwaysShow?: number;
|
numberOfOptionsToAlwaysShow?: number;
|
||||||
}) {
|
}) {
|
||||||
const device = useDevice();
|
const editorInterface = useEditorInterface();
|
||||||
|
|
||||||
const handleKeyDown = (event: React.KeyboardEvent) => {
|
const handleKeyDown = (event: React.KeyboardEvent) => {
|
||||||
const pressedOption = options.find(
|
const pressedOption = options.find(
|
||||||
@@ -152,7 +152,7 @@ function Picker<T>({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isMobile = device.editor.isMobile;
|
const isMobile = editorInterface.formFactor === "phone";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover.Content
|
<Popover.Content
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ import Footer from "./footer/Footer";
|
|||||||
import { isSidebarDockedAtom } from "./Sidebar/Sidebar";
|
import { isSidebarDockedAtom } from "./Sidebar/Sidebar";
|
||||||
import MainMenu from "./main-menu/MainMenu";
|
import MainMenu from "./main-menu/MainMenu";
|
||||||
import { ActiveConfirmDialog } from "./ActiveConfirmDialog";
|
import { ActiveConfirmDialog } from "./ActiveConfirmDialog";
|
||||||
import { useDevice } from "./App";
|
import { useEditorInterface, useStylesPanelMode } from "./App";
|
||||||
import { OverwriteConfirmDialog } from "./OverwriteConfirm/OverwriteConfirm";
|
import { OverwriteConfirmDialog } from "./OverwriteConfirm/OverwriteConfirm";
|
||||||
import { LibraryIcon } from "./icons";
|
import { LibraryIcon } from "./icons";
|
||||||
import { DefaultSidebar } from "./DefaultSidebar";
|
import { DefaultSidebar } from "./DefaultSidebar";
|
||||||
@@ -161,27 +161,28 @@ const LayerUI = ({
|
|||||||
isCollaborating,
|
isCollaborating,
|
||||||
generateLinkForSelection,
|
generateLinkForSelection,
|
||||||
}: LayerUIProps) => {
|
}: LayerUIProps) => {
|
||||||
const device = useDevice();
|
const editorInterface = useEditorInterface();
|
||||||
|
const stylesPanelMode = useStylesPanelMode();
|
||||||
|
const isCompactStylesPanel = stylesPanelMode === "compact";
|
||||||
const tunnels = useInitializeTunnels();
|
const tunnels = useInitializeTunnels();
|
||||||
|
|
||||||
const spacing =
|
const spacing = isCompactStylesPanel
|
||||||
appState.stylesPanelMode === "compact"
|
? {
|
||||||
? {
|
menuTopGap: 4,
|
||||||
menuTopGap: 4,
|
toolbarColGap: 4,
|
||||||
toolbarColGap: 4,
|
toolbarRowGap: 1,
|
||||||
toolbarRowGap: 1,
|
toolbarInnerRowGap: 0.5,
|
||||||
toolbarInnerRowGap: 0.5,
|
islandPadding: 1,
|
||||||
islandPadding: 1,
|
collabMarginLeft: 8,
|
||||||
collabMarginLeft: 8,
|
}
|
||||||
}
|
: {
|
||||||
: {
|
menuTopGap: 6,
|
||||||
menuTopGap: 6,
|
toolbarColGap: 4,
|
||||||
toolbarColGap: 4,
|
toolbarRowGap: 1,
|
||||||
toolbarRowGap: 1,
|
toolbarInnerRowGap: 1,
|
||||||
toolbarInnerRowGap: 1,
|
islandPadding: 1,
|
||||||
islandPadding: 1,
|
collabMarginLeft: 8,
|
||||||
collabMarginLeft: 8,
|
};
|
||||||
};
|
|
||||||
|
|
||||||
const TunnelsJotaiProvider = tunnels.tunnelsJotai.Provider;
|
const TunnelsJotaiProvider = tunnels.tunnelsJotai.Provider;
|
||||||
|
|
||||||
@@ -236,7 +237,7 @@ const LayerUI = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const renderSelectedShapeActions = () => {
|
const renderSelectedShapeActions = () => {
|
||||||
const isCompactMode = appState.stylesPanelMode === "compact";
|
const isCompactMode = isCompactStylesPanel;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Section
|
<Section
|
||||||
@@ -308,7 +309,7 @@ const LayerUI = ({
|
|||||||
<div
|
<div
|
||||||
className={clsx("selected-shape-actions-container", {
|
className={clsx("selected-shape-actions-container", {
|
||||||
"selected-shape-actions-container--compact":
|
"selected-shape-actions-container--compact":
|
||||||
appState.stylesPanelMode === "compact",
|
isCompactStylesPanel,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{shouldRenderSelectedShapeActions && renderSelectedShapeActions()}
|
{shouldRenderSelectedShapeActions && renderSelectedShapeActions()}
|
||||||
@@ -333,14 +334,13 @@ const LayerUI = ({
|
|||||||
padding={spacing.islandPadding}
|
padding={spacing.islandPadding}
|
||||||
className={clsx("App-toolbar", {
|
className={clsx("App-toolbar", {
|
||||||
"zen-mode": appState.zenModeEnabled,
|
"zen-mode": appState.zenModeEnabled,
|
||||||
"App-toolbar--compact":
|
"App-toolbar--compact": isCompactStylesPanel,
|
||||||
appState.stylesPanelMode === "compact",
|
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<HintViewer
|
<HintViewer
|
||||||
appState={appState}
|
appState={appState}
|
||||||
isMobile={device.editor.isMobile}
|
isMobile={editorInterface.formFactor === "phone"}
|
||||||
device={device}
|
editorInterface={editorInterface}
|
||||||
app={app}
|
app={app}
|
||||||
/>
|
/>
|
||||||
{heading}
|
{heading}
|
||||||
@@ -406,8 +406,7 @@ const LayerUI = ({
|
|||||||
"layer-ui__wrapper__top-right zen-mode-transition",
|
"layer-ui__wrapper__top-right zen-mode-transition",
|
||||||
{
|
{
|
||||||
"transition-right": appState.zenModeEnabled,
|
"transition-right": appState.zenModeEnabled,
|
||||||
"layer-ui__wrapper__top-right--compact":
|
"layer-ui__wrapper__top-right--compact": isCompactStylesPanel,
|
||||||
appState.stylesPanelMode === "compact",
|
|
||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -417,7 +416,10 @@ const LayerUI = ({
|
|||||||
userToFollow={appState.userToFollow?.socketId || null}
|
userToFollow={appState.userToFollow?.socketId || null}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{renderTopRightUI?.(device.editor.isMobile, appState)}
|
{renderTopRightUI?.(
|
||||||
|
editorInterface.formFactor === "phone",
|
||||||
|
appState,
|
||||||
|
)}
|
||||||
{!appState.viewModeEnabled &&
|
{!appState.viewModeEnabled &&
|
||||||
appState.openDialog?.name !== "elementLinkSelector" &&
|
appState.openDialog?.name !== "elementLinkSelector" &&
|
||||||
// hide button when sidebar docked
|
// hide button when sidebar docked
|
||||||
@@ -448,7 +450,9 @@ const LayerUI = ({
|
|||||||
trackEvent(
|
trackEvent(
|
||||||
"sidebar",
|
"sidebar",
|
||||||
`toggleDock (${docked ? "dock" : "undock"})`,
|
`toggleDock (${docked ? "dock" : "undock"})`,
|
||||||
`(${device.editor.isMobile ? "mobile" : "desktop"})`,
|
`(${
|
||||||
|
editorInterface.formFactor === "phone" ? "mobile" : "desktop"
|
||||||
|
})`,
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -476,13 +480,15 @@ const LayerUI = ({
|
|||||||
trackEvent(
|
trackEvent(
|
||||||
"sidebar",
|
"sidebar",
|
||||||
`${DEFAULT_SIDEBAR.name} (open)`,
|
`${DEFAULT_SIDEBAR.name} (open)`,
|
||||||
`button (${device.editor.isMobile ? "mobile" : "desktop"})`,
|
`button (${
|
||||||
|
editorInterface.formFactor === "phone" ? "mobile" : "desktop"
|
||||||
|
})`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
tab={DEFAULT_SIDEBAR.defaultTab}
|
tab={DEFAULT_SIDEBAR.defaultTab}
|
||||||
>
|
>
|
||||||
{appState.stylesPanelMode === "full" &&
|
{stylesPanelMode === "full" &&
|
||||||
appState.width >= MQ_MIN_WIDTH_DESKTOP &&
|
appState.width >= MQ_MIN_WIDTH_DESKTOP &&
|
||||||
t("toolBar.library")}
|
t("toolBar.library")}
|
||||||
</DefaultSidebar.Trigger>
|
</DefaultSidebar.Trigger>
|
||||||
@@ -496,7 +502,7 @@ const LayerUI = ({
|
|||||||
{appState.errorMessage}
|
{appState.errorMessage}
|
||||||
</ErrorDialog>
|
</ErrorDialog>
|
||||||
)}
|
)}
|
||||||
{eyeDropperState && !device.editor.isMobile && (
|
{eyeDropperState && editorInterface.formFactor !== "phone" && (
|
||||||
<EyeDropper
|
<EyeDropper
|
||||||
colorPickerType={eyeDropperState.colorPickerType}
|
colorPickerType={eyeDropperState.colorPickerType}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
@@ -575,7 +581,7 @@ const LayerUI = ({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{device.editor.isMobile && (
|
{editorInterface.formFactor === "phone" && (
|
||||||
<MobileMenu
|
<MobileMenu
|
||||||
app={app}
|
app={app}
|
||||||
appState={appState}
|
appState={appState}
|
||||||
@@ -593,14 +599,14 @@ const LayerUI = ({
|
|||||||
UIOptions={UIOptions}
|
UIOptions={UIOptions}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!device.editor.isMobile && (
|
{editorInterface.formFactor !== "phone" && (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className="layer-ui__wrapper"
|
className="layer-ui__wrapper"
|
||||||
style={
|
style={
|
||||||
appState.openSidebar &&
|
appState.openSidebar &&
|
||||||
isSidebarDocked &&
|
isSidebarDocked &&
|
||||||
device.editor.canFitSidebar
|
editorInterface.canFitSidebar
|
||||||
? { width: `calc(100% - var(--right-sidebar-width))` }
|
? { width: `calc(100% - var(--right-sidebar-width))` }
|
||||||
: {}
|
: {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import "./LibraryMenuItems.scss";
|
|||||||
|
|
||||||
import { TextField } from "./TextField";
|
import { TextField } from "./TextField";
|
||||||
|
|
||||||
import { useDevice } from "./App";
|
import { useEditorInterface } from "./App";
|
||||||
|
|
||||||
import { Button } from "./Button";
|
import { Button } from "./Button";
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ export default function LibraryMenuItems({
|
|||||||
selectedItems: LibraryItem["id"][];
|
selectedItems: LibraryItem["id"][];
|
||||||
onSelectItems: (id: LibraryItem["id"][]) => void;
|
onSelectItems: (id: LibraryItem["id"][]) => void;
|
||||||
}) {
|
}) {
|
||||||
const device = useDevice();
|
const editorInterface = useEditorInterface();
|
||||||
const libraryContainerRef = useRef<HTMLDivElement>(null);
|
const libraryContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const scrollPosition = useScrollPosition<HTMLDivElement>(libraryContainerRef);
|
const scrollPosition = useScrollPosition<HTMLDivElement>(libraryContainerRef);
|
||||||
|
|
||||||
@@ -392,7 +392,7 @@ export default function LibraryMenuItems({
|
|||||||
ref={searchInputRef}
|
ref={searchInputRef}
|
||||||
type="search"
|
type="search"
|
||||||
className={clsx("library-menu-items-container__search", {
|
className={clsx("library-menu-items-container__search", {
|
||||||
hideCancelButton: !device.editor.isMobile,
|
hideCancelButton: editorInterface.formFactor !== "phone",
|
||||||
})}
|
})}
|
||||||
placeholder={t("library.search.inputPlaceholder")}
|
placeholder={t("library.search.inputPlaceholder")}
|
||||||
value={searchInputValue}
|
value={searchInputValue}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { memo, useRef, useState } from "react";
|
|||||||
|
|
||||||
import { useLibraryItemSvg } from "../hooks/useLibraryItemSvg";
|
import { useLibraryItemSvg } from "../hooks/useLibraryItemSvg";
|
||||||
|
|
||||||
import { useDevice } from "./App";
|
import { useEditorInterface } from "./App";
|
||||||
import { CheckboxItem } from "./CheckboxItem";
|
import { CheckboxItem } from "./CheckboxItem";
|
||||||
import { PlusIcon } from "./icons";
|
import { PlusIcon } from "./icons";
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ export const LibraryUnit = memo(
|
|||||||
const svg = useLibraryItemSvg(id, elements, svgCache, ref);
|
const svg = useLibraryItemSvg(id, elements, svgCache, ref);
|
||||||
|
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
const isMobile = useDevice().editor.isMobile;
|
const isMobile = useEditorInterface().formFactor === "phone";
|
||||||
const adder = isPending && (
|
const adder = isPending && (
|
||||||
<div className="library-unit__adder">{PlusIcon}</div>
|
<div className="library-unit__adder">{PlusIcon}</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import React, { type ReactNode } from "react";
|
|||||||
|
|
||||||
import { isInteractive } from "@excalidraw/common";
|
import { isInteractive } from "@excalidraw/common";
|
||||||
|
|
||||||
import { useDevice } from "./App";
|
import { useEditorInterface } from "./App";
|
||||||
import { Island } from "./Island";
|
import { Island } from "./Island";
|
||||||
|
|
||||||
interface PropertiesPopoverProps {
|
interface PropertiesPopoverProps {
|
||||||
@@ -39,7 +39,7 @@ export const PropertiesPopover = React.forwardRef<
|
|||||||
},
|
},
|
||||||
ref,
|
ref,
|
||||||
) => {
|
) => {
|
||||||
const device = useDevice();
|
const editorInterface = useEditorInterface();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover.Portal container={container}>
|
<Popover.Portal container={container}>
|
||||||
@@ -48,12 +48,14 @@ export const PropertiesPopover = React.forwardRef<
|
|||||||
className={clsx("focus-visible-none", className)}
|
className={clsx("focus-visible-none", className)}
|
||||||
data-prevent-outside-click
|
data-prevent-outside-click
|
||||||
side={
|
side={
|
||||||
device.editor.isMobile && !device.viewport.isLandscape
|
editorInterface.formFactor === "phone" &&
|
||||||
|
!editorInterface.isLandscape
|
||||||
? "bottom"
|
? "bottom"
|
||||||
: "right"
|
: "right"
|
||||||
}
|
}
|
||||||
align={
|
align={
|
||||||
device.editor.isMobile && !device.viewport.isLandscape
|
editorInterface.formFactor === "phone" &&
|
||||||
|
!editorInterface.isLandscape
|
||||||
? "center"
|
? "center"
|
||||||
: "start"
|
: "start"
|
||||||
}
|
}
|
||||||
@@ -68,7 +70,7 @@ export const PropertiesPopover = React.forwardRef<
|
|||||||
onPointerDownOutside={onPointerDownOutside}
|
onPointerDownOutside={onPointerDownOutside}
|
||||||
onOpenAutoFocus={(e) => {
|
onOpenAutoFocus={(e) => {
|
||||||
// prevent auto-focus on touch devices to avoid keyboard popup
|
// prevent auto-focus on touch devices to avoid keyboard popup
|
||||||
if (preventAutoFocusOnTouch && device.isTouchScreen) {
|
if (preventAutoFocusOnTouch && editorInterface.isTouchScreen) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
import { useUIAppState } from "../../context/ui-appState";
|
import { useUIAppState } from "../../context/ui-appState";
|
||||||
import { atom, useSetAtom } from "../../editor-jotai";
|
import { atom, useSetAtom } from "../../editor-jotai";
|
||||||
import { useOutsideClick } from "../../hooks/useOutsideClick";
|
import { useOutsideClick } from "../../hooks/useOutsideClick";
|
||||||
import { useDevice, useExcalidrawSetAppState } from "../App";
|
import { useEditorInterface, useExcalidrawSetAppState } from "../App";
|
||||||
import { Island } from "../Island";
|
import { Island } from "../Island";
|
||||||
|
|
||||||
import { SidebarHeader } from "./SidebarHeader";
|
import { SidebarHeader } from "./SidebarHeader";
|
||||||
@@ -96,7 +96,7 @@ export const SidebarInner = forwardRef(
|
|||||||
return islandRef.current!;
|
return islandRef.current!;
|
||||||
});
|
});
|
||||||
|
|
||||||
const device = useDevice();
|
const editorInterface = useEditorInterface();
|
||||||
|
|
||||||
const closeLibrary = useCallback(() => {
|
const closeLibrary = useCallback(() => {
|
||||||
const isDialogOpen = !!document.querySelector(".Dialog");
|
const isDialogOpen = !!document.querySelector(".Dialog");
|
||||||
@@ -117,11 +117,11 @@ export const SidebarInner = forwardRef(
|
|||||||
if ((event.target as Element).closest(".sidebar-trigger")) {
|
if ((event.target as Element).closest(".sidebar-trigger")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!docked || !device.editor.canFitSidebar) {
|
if (!docked || !editorInterface.canFitSidebar) {
|
||||||
closeLibrary();
|
closeLibrary();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[closeLibrary, docked, device.editor.canFitSidebar],
|
[closeLibrary, docked, editorInterface.canFitSidebar],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ export const SidebarInner = forwardRef(
|
|||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
if (
|
if (
|
||||||
event.key === KEYS.ESCAPE &&
|
event.key === KEYS.ESCAPE &&
|
||||||
(!docked || !device.editor.canFitSidebar)
|
(!docked || !editorInterface.canFitSidebar)
|
||||||
) {
|
) {
|
||||||
closeLibrary();
|
closeLibrary();
|
||||||
}
|
}
|
||||||
@@ -138,7 +138,7 @@ export const SidebarInner = forwardRef(
|
|||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener(EVENT.KEYDOWN, handleKeyDown);
|
document.removeEventListener(EVENT.KEYDOWN, handleKeyDown);
|
||||||
};
|
};
|
||||||
}, [closeLibrary, docked, device.editor.canFitSidebar]);
|
}, [closeLibrary, docked, editorInterface.canFitSidebar]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Island
|
<Island
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import clsx from "clsx";
|
|||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
|
|
||||||
import { t } from "../../i18n";
|
import { t } from "../../i18n";
|
||||||
import { useDevice } from "../App";
|
import { useEditorInterface } from "../App";
|
||||||
import { Button } from "../Button";
|
import { Button } from "../Button";
|
||||||
import { Tooltip } from "../Tooltip";
|
import { Tooltip } from "../Tooltip";
|
||||||
import { CloseIcon, PinIcon } from "../icons";
|
import { CloseIcon, PinIcon } from "../icons";
|
||||||
@@ -16,11 +16,11 @@ export const SidebarHeader = ({
|
|||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const device = useDevice();
|
const editorInterface = useEditorInterface();
|
||||||
const props = useContext(SidebarPropsContext);
|
const props = useContext(SidebarPropsContext);
|
||||||
|
|
||||||
const renderDockButton = !!(
|
const renderDockButton = !!(
|
||||||
device.editor.canFitSidebar && props.shouldRenderDockButton
|
editorInterface.canFitSidebar && props.shouldRenderDockButton
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -20,7 +20,11 @@ import type {
|
|||||||
RenderableElementsMap,
|
RenderableElementsMap,
|
||||||
RenderInteractiveSceneCallback,
|
RenderInteractiveSceneCallback,
|
||||||
} from "../../scene/types";
|
} from "../../scene/types";
|
||||||
import type { AppState, Device, InteractiveCanvasAppState } from "../../types";
|
import type {
|
||||||
|
AppState,
|
||||||
|
EditorInterface,
|
||||||
|
InteractiveCanvasAppState,
|
||||||
|
} from "../../types";
|
||||||
import type { DOMAttributes } from "react";
|
import type { DOMAttributes } from "react";
|
||||||
|
|
||||||
type InteractiveCanvasProps = {
|
type InteractiveCanvasProps = {
|
||||||
@@ -35,7 +39,7 @@ type InteractiveCanvasProps = {
|
|||||||
scale: number;
|
scale: number;
|
||||||
appState: InteractiveCanvasAppState;
|
appState: InteractiveCanvasAppState;
|
||||||
renderScrollbars: boolean;
|
renderScrollbars: boolean;
|
||||||
device: Device;
|
editorInterface: EditorInterface;
|
||||||
renderInteractiveSceneCallback: (
|
renderInteractiveSceneCallback: (
|
||||||
data: RenderInteractiveSceneCallback,
|
data: RenderInteractiveSceneCallback,
|
||||||
) => void;
|
) => void;
|
||||||
@@ -146,7 +150,7 @@ const InteractiveCanvas = (props: InteractiveCanvasProps) => {
|
|||||||
selectionColor,
|
selectionColor,
|
||||||
renderScrollbars: props.renderScrollbars,
|
renderScrollbars: props.renderScrollbars,
|
||||||
},
|
},
|
||||||
device: props.device,
|
editorInterface: props.editorInterface,
|
||||||
callback: props.renderInteractiveSceneCallback,
|
callback: props.renderInteractiveSceneCallback,
|
||||||
},
|
},
|
||||||
isRenderThrottlingEnabled(),
|
isRenderThrottlingEnabled(),
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { EVENT, KEYS } from "@excalidraw/common";
|
|||||||
|
|
||||||
import { useOutsideClick } from "../../hooks/useOutsideClick";
|
import { useOutsideClick } from "../../hooks/useOutsideClick";
|
||||||
import { useStable } from "../../hooks/useStable";
|
import { useStable } from "../../hooks/useStable";
|
||||||
import { useDevice } from "../App";
|
import { useEditorInterface } from "../App";
|
||||||
import { Island } from "../Island";
|
import { Island } from "../Island";
|
||||||
import Stack from "../Stack";
|
import Stack from "../Stack";
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ const MenuContent = ({
|
|||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
placement?: "top" | "bottom";
|
placement?: "top" | "bottom";
|
||||||
}) => {
|
}) => {
|
||||||
const device = useDevice();
|
const editorInterface = useEditorInterface();
|
||||||
const menuRef = useRef<HTMLDivElement>(null);
|
const menuRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const callbacksRef = useStable({ onClickOutside });
|
const callbacksRef = useStable({ onClickOutside });
|
||||||
@@ -59,7 +59,7 @@ const MenuContent = ({
|
|||||||
}, [callbacksRef]);
|
}, [callbacksRef]);
|
||||||
|
|
||||||
const classNames = clsx(`dropdown-menu ${className}`, {
|
const classNames = clsx(`dropdown-menu ${className}`, {
|
||||||
"dropdown-menu--mobile": device.editor.isMobile,
|
"dropdown-menu--mobile": editorInterface.formFactor === "phone",
|
||||||
"dropdown-menu--placement-top": placement === "top",
|
"dropdown-menu--placement-top": placement === "top",
|
||||||
}).trim();
|
}).trim();
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ const MenuContent = ({
|
|||||||
>
|
>
|
||||||
{/* the zIndex ensures this menu has higher stacking order,
|
{/* the zIndex ensures this menu has higher stacking order,
|
||||||
see https://github.com/excalidraw/excalidraw/pull/1445 */}
|
see https://github.com/excalidraw/excalidraw/pull/1445 */}
|
||||||
{device.editor.isMobile ? (
|
{editorInterface.formFactor === "phone" ? (
|
||||||
<Stack.Col className="dropdown-menu-container">{children}</Stack.Col>
|
<Stack.Col className="dropdown-menu-container">{children}</Stack.Col>
|
||||||
) : (
|
) : (
|
||||||
<Island
|
<Island
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useDevice } from "../App";
|
import { useEditorInterface } from "../App";
|
||||||
|
|
||||||
import { Ellipsify } from "../Ellipsify";
|
import { Ellipsify } from "../Ellipsify";
|
||||||
|
|
||||||
@@ -15,14 +15,14 @@ const MenuItemContent = ({
|
|||||||
textStyle?: React.CSSProperties;
|
textStyle?: React.CSSProperties;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) => {
|
}) => {
|
||||||
const device = useDevice();
|
const editorInterface = useEditorInterface();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{icon && <div className="dropdown-menu-item__icon">{icon}</div>}
|
{icon && <div className="dropdown-menu-item__icon">{icon}</div>}
|
||||||
<div style={textStyle} className="dropdown-menu-item__text">
|
<div style={textStyle} className="dropdown-menu-item__text">
|
||||||
<Ellipsify>{children}</Ellipsify>
|
<Ellipsify>{children}</Ellipsify>
|
||||||
</div>
|
</div>
|
||||||
{shortcut && !device.editor.isMobile && (
|
{shortcut && editorInterface.formFactor !== "phone" && (
|
||||||
<div className="dropdown-menu-item__shortcut">{shortcut}</div>
|
<div className="dropdown-menu-item__shortcut">{shortcut}</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useDevice } from "../App";
|
import { useEditorInterface } from "../App";
|
||||||
import { RadioGroup } from "../RadioGroup";
|
import { RadioGroup } from "../RadioGroup";
|
||||||
|
|
||||||
type Props<T> = {
|
type Props<T> = {
|
||||||
@@ -22,7 +22,7 @@ const DropdownMenuItemContentRadio = <T,>({
|
|||||||
children,
|
children,
|
||||||
name,
|
name,
|
||||||
}: Props<T>) => {
|
}: Props<T>) => {
|
||||||
const device = useDevice();
|
const editorInterface = useEditorInterface();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -37,7 +37,7 @@ const DropdownMenuItemContentRadio = <T,>({
|
|||||||
choices={choices}
|
choices={choices}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{shortcut && !device.editor.isMobile && (
|
{shortcut && editorInterface.formFactor !== "phone" && (
|
||||||
<div className="dropdown-menu-item__shortcut dropdown-menu-item__shortcut--orphaned">
|
<div className="dropdown-menu-item__shortcut dropdown-menu-item__shortcut--orphaned">
|
||||||
{shortcut}
|
{shortcut}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
import { useDevice } from "../App";
|
import { useEditorInterface } from "../App";
|
||||||
|
|
||||||
const MenuTrigger = ({
|
const MenuTrigger = ({
|
||||||
className = "",
|
className = "",
|
||||||
@@ -14,12 +14,12 @@ const MenuTrigger = ({
|
|||||||
onToggle: () => void;
|
onToggle: () => void;
|
||||||
title?: string;
|
title?: string;
|
||||||
} & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "onSelect">) => {
|
} & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "onSelect">) => {
|
||||||
const device = useDevice();
|
const editorInterface = useEditorInterface();
|
||||||
const classNames = clsx(
|
const classNames = clsx(
|
||||||
`dropdown-menu-button ${className}`,
|
`dropdown-menu-button ${className}`,
|
||||||
"zen-mode-transition",
|
"zen-mode-transition",
|
||||||
{
|
{
|
||||||
"dropdown-menu-button--mobile": device.editor.isMobile,
|
"dropdown-menu-button--mobile": editorInterface.formFactor === "phone",
|
||||||
},
|
},
|
||||||
).trim();
|
).trim();
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ import { getTooltipDiv, updateTooltipPosition } from "../../components/Tooltip";
|
|||||||
|
|
||||||
import { t } from "../../i18n";
|
import { t } from "../../i18n";
|
||||||
|
|
||||||
import { useAppProps, useDevice, useExcalidrawAppState } from "../App";
|
import { useAppProps, useEditorInterface, useExcalidrawAppState } from "../App";
|
||||||
import { ToolButton } from "../ToolButton";
|
import { ToolButton } from "../ToolButton";
|
||||||
import { FreedrawIcon, TrashIcon, elementLinkIcon } from "../icons";
|
import { FreedrawIcon, TrashIcon, elementLinkIcon } from "../icons";
|
||||||
import { getSelectedElements } from "../../scene";
|
import { getSelectedElements } from "../../scene";
|
||||||
@@ -88,7 +88,7 @@ export const Hyperlink = ({
|
|||||||
const elementsMap = scene.getNonDeletedElementsMap();
|
const elementsMap = scene.getNonDeletedElementsMap();
|
||||||
const appState = useExcalidrawAppState();
|
const appState = useExcalidrawAppState();
|
||||||
const appProps = useAppProps();
|
const appProps = useAppProps();
|
||||||
const device = useDevice();
|
const editorInterface = useEditorInterface();
|
||||||
|
|
||||||
const linkVal = element.link || "";
|
const linkVal = element.link || "";
|
||||||
|
|
||||||
@@ -189,11 +189,11 @@ export const Hyperlink = ({
|
|||||||
if (
|
if (
|
||||||
isEditing &&
|
isEditing &&
|
||||||
inputRef?.current &&
|
inputRef?.current &&
|
||||||
!(device.viewport.isMobile || device.isTouchScreen)
|
!(editorInterface.formFactor === "phone" || editorInterface.isTouchScreen)
|
||||||
) {
|
) {
|
||||||
inputRef.current.select();
|
inputRef.current.select();
|
||||||
}
|
}
|
||||||
}, [isEditing, device.viewport.isMobile, device.isTouchScreen]);
|
}, [isEditing, editorInterface.formFactor, editorInterface.isTouchScreen]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let timeoutId: number | null = null;
|
let timeoutId: number | null = null;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { composeEventHandlers } from "@excalidraw/common";
|
|||||||
import { useTunnels } from "../../context/tunnels";
|
import { useTunnels } from "../../context/tunnels";
|
||||||
import { useUIAppState } from "../../context/ui-appState";
|
import { useUIAppState } from "../../context/ui-appState";
|
||||||
import { t } from "../../i18n";
|
import { t } from "../../i18n";
|
||||||
import { useDevice, useExcalidrawSetAppState } from "../App";
|
import { useEditorInterface, useExcalidrawSetAppState } from "../App";
|
||||||
import { UserList } from "../UserList";
|
import { UserList } from "../UserList";
|
||||||
import DropdownMenu from "../dropdownMenu/DropdownMenu";
|
import DropdownMenu from "../dropdownMenu/DropdownMenu";
|
||||||
import { withInternalFallback } from "../hoc/withInternalFallback";
|
import { withInternalFallback } from "../hoc/withInternalFallback";
|
||||||
@@ -27,12 +27,13 @@ const MainMenu = Object.assign(
|
|||||||
onSelect?: (event: Event) => void;
|
onSelect?: (event: Event) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { MainMenuTunnel } = useTunnels();
|
const { MainMenuTunnel } = useTunnels();
|
||||||
const device = useDevice();
|
const editorInterface = useEditorInterface();
|
||||||
const appState = useUIAppState();
|
const appState = useUIAppState();
|
||||||
const setAppState = useExcalidrawSetAppState();
|
const setAppState = useExcalidrawSetAppState();
|
||||||
const onClickOutside = device.editor.isMobile
|
const onClickOutside =
|
||||||
? undefined
|
editorInterface.formFactor === "phone"
|
||||||
: () => setAppState({ openMenu: null });
|
? undefined
|
||||||
|
: () => setAppState({ openMenu: null });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainMenuTunnel.In>
|
<MainMenuTunnel.In>
|
||||||
@@ -54,19 +55,24 @@ const MainMenu = Object.assign(
|
|||||||
setAppState({ openMenu: null });
|
setAppState({ openMenu: null });
|
||||||
})}
|
})}
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
className={device.editor.isMobile ? "main-menu-dropdown" : ""}
|
className={
|
||||||
|
editorInterface.formFactor === "phone"
|
||||||
|
? "main-menu-dropdown"
|
||||||
|
: ""
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
{device.editor.isMobile && appState.collaborators.size > 0 && (
|
{editorInterface.formFactor === "phone" &&
|
||||||
<fieldset className="UserList-Wrapper">
|
appState.collaborators.size > 0 && (
|
||||||
<legend>{t("labels.collaborators")}</legend>
|
<fieldset className="UserList-Wrapper">
|
||||||
<UserList
|
<legend>{t("labels.collaborators")}</legend>
|
||||||
mobile={true}
|
<UserList
|
||||||
collaborators={appState.collaborators}
|
mobile={true}
|
||||||
userToFollow={appState.userToFollow?.socketId || null}
|
collaborators={appState.collaborators}
|
||||||
/>
|
userToFollow={appState.userToFollow?.socketId || null}
|
||||||
</fieldset>
|
/>
|
||||||
)}
|
</fieldset>
|
||||||
|
)}
|
||||||
</DropdownMenu.Content>
|
</DropdownMenu.Content>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</MainMenuTunnel.In>
|
</MainMenuTunnel.In>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { getShortcutFromShortcutName } from "../../actions/shortcuts";
|
|||||||
import { useTunnels } from "../../context/tunnels";
|
import { useTunnels } from "../../context/tunnels";
|
||||||
import { useUIAppState } from "../../context/ui-appState";
|
import { useUIAppState } from "../../context/ui-appState";
|
||||||
import { t, useI18n } from "../../i18n";
|
import { t, useI18n } from "../../i18n";
|
||||||
import { useDevice, useExcalidrawActionManager } from "../App";
|
import { useEditorInterface, useExcalidrawActionManager } from "../App";
|
||||||
import { ExcalidrawLogo } from "../ExcalidrawLogo";
|
import { ExcalidrawLogo } from "../ExcalidrawLogo";
|
||||||
import { HelpIcon, LoadIcon, usersIcon } from "../icons";
|
import { HelpIcon, LoadIcon, usersIcon } from "../icons";
|
||||||
|
|
||||||
@@ -18,12 +18,12 @@ const WelcomeScreenMenuItemContent = ({
|
|||||||
shortcut?: string | null;
|
shortcut?: string | null;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) => {
|
}) => {
|
||||||
const device = useDevice();
|
const editorInterface = useEditorInterface();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="welcome-screen-menu-item__icon">{icon}</div>
|
<div className="welcome-screen-menu-item__icon">{icon}</div>
|
||||||
<div className="welcome-screen-menu-item__text">{children}</div>
|
<div className="welcome-screen-menu-item__text">{children}</div>
|
||||||
{shortcut && !device.editor.isMobile && (
|
{shortcut && editorInterface.formFactor !== "phone" && (
|
||||||
<div className="welcome-screen-menu-item__shortcut">{shortcut}</div>
|
<div className="welcome-screen-menu-item__shortcut">{shortcut}</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
59
packages/excalidraw/editorInterface.ts
Normal file
59
packages/excalidraw/editorInterface.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { isAndroid, isIOS, isMobileOrTablet } from "@excalidraw/common";
|
||||||
|
|
||||||
|
import type { EditorInterface, StylesPanelMode } from "./types";
|
||||||
|
|
||||||
|
export const DESKTOP_UI_MODE_STORAGE_KEY = "excalidraw.desktopUIMode";
|
||||||
|
|
||||||
|
export const deriveFormFactor = (
|
||||||
|
editorWidth: number,
|
||||||
|
editorHeight: number,
|
||||||
|
breakpoints: {
|
||||||
|
isMobile: (width: number, height: number) => boolean;
|
||||||
|
isTablet: (width: number, height: number) => boolean;
|
||||||
|
},
|
||||||
|
): EditorInterface["formFactor"] => {
|
||||||
|
if (breakpoints.isMobile(editorWidth, editorHeight)) {
|
||||||
|
return "phone";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (breakpoints.isTablet(editorWidth, editorHeight)) {
|
||||||
|
return "tablet";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "desktop";
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deriveStylesPanelMode = (
|
||||||
|
editorInterface: EditorInterface,
|
||||||
|
): StylesPanelMode => {
|
||||||
|
if (editorInterface.formFactor === "phone") {
|
||||||
|
return "mobile";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editorInterface.formFactor === "tablet") {
|
||||||
|
return "compact";
|
||||||
|
}
|
||||||
|
|
||||||
|
return editorInterface.desktopUIMode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createUserAgentDescriptor = (
|
||||||
|
userAgentString: string,
|
||||||
|
): EditorInterface["userAgent"] => {
|
||||||
|
const normalizedUA = userAgentString ?? "";
|
||||||
|
let platform: EditorInterface["userAgent"]["platform"] = "unknown";
|
||||||
|
|
||||||
|
if (isIOS) {
|
||||||
|
platform = "ios";
|
||||||
|
} else if (isAndroid) {
|
||||||
|
platform = "android";
|
||||||
|
} else if (normalizedUA) {
|
||||||
|
platform = "other";
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
raw: normalizedUA,
|
||||||
|
isMobileDevice: isMobileOrTablet(),
|
||||||
|
platform,
|
||||||
|
} as const;
|
||||||
|
};
|
||||||
@@ -2,7 +2,7 @@ import { useState, useLayoutEffect } from "react";
|
|||||||
|
|
||||||
import { THEME } from "@excalidraw/common";
|
import { THEME } from "@excalidraw/common";
|
||||||
|
|
||||||
import { useDevice, useExcalidrawContainer } from "../components/App";
|
import { useEditorInterface, useExcalidrawContainer } from "../components/App";
|
||||||
import { useUIAppState } from "../context/ui-appState";
|
import { useUIAppState } from "../context/ui-appState";
|
||||||
|
|
||||||
export const useCreatePortalContainer = (opts?: {
|
export const useCreatePortalContainer = (opts?: {
|
||||||
@@ -11,7 +11,7 @@ export const useCreatePortalContainer = (opts?: {
|
|||||||
}) => {
|
}) => {
|
||||||
const [div, setDiv] = useState<HTMLDivElement | null>(null);
|
const [div, setDiv] = useState<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const device = useDevice();
|
const editorInterface = useEditorInterface();
|
||||||
const { theme } = useUIAppState();
|
const { theme } = useUIAppState();
|
||||||
|
|
||||||
const { container: excalidrawContainer } = useExcalidrawContainer();
|
const { container: excalidrawContainer } = useExcalidrawContainer();
|
||||||
@@ -20,10 +20,13 @@ export const useCreatePortalContainer = (opts?: {
|
|||||||
if (div) {
|
if (div) {
|
||||||
div.className = "";
|
div.className = "";
|
||||||
div.classList.add("excalidraw", ...(opts?.className?.split(/\s+/) || []));
|
div.classList.add("excalidraw", ...(opts?.className?.split(/\s+/) || []));
|
||||||
div.classList.toggle("excalidraw--mobile", device.editor.isMobile);
|
div.classList.toggle(
|
||||||
|
"excalidraw--mobile",
|
||||||
|
editorInterface.formFactor === "phone",
|
||||||
|
);
|
||||||
div.classList.toggle("theme--dark", theme === THEME.DARK);
|
div.classList.toggle("theme--dark", theme === THEME.DARK);
|
||||||
}
|
}
|
||||||
}, [div, theme, device.editor.isMobile, opts?.className]);
|
}, [div, theme, editorInterface.formFactor, opts?.className]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const container = opts?.parentSelector
|
const container = opts?.parentSelector
|
||||||
|
|||||||
@@ -285,7 +285,7 @@ export { Button } from "./components/Button";
|
|||||||
export { Footer };
|
export { Footer };
|
||||||
export { MainMenu };
|
export { MainMenu };
|
||||||
export { Ellipsify } from "./components/Ellipsify";
|
export { Ellipsify } from "./components/Ellipsify";
|
||||||
export { useDevice } from "./components/App";
|
export { useEditorInterface, useStylesPanelMode } from "./components/App";
|
||||||
export { WelcomeScreen };
|
export { WelcomeScreen };
|
||||||
export { LiveCollaborationTrigger };
|
export { LiveCollaborationTrigger };
|
||||||
export { Stats } from "./components/Stats";
|
export { Stats } from "./components/Stats";
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
import { FIXED_BINDING_DISTANCE, maxBindingGap } from "@excalidraw/element";
|
import { FIXED_BINDING_DISTANCE, maxBindingGap } from "@excalidraw/element";
|
||||||
import { LinearElementEditor } from "@excalidraw/element";
|
import { LinearElementEditor } from "@excalidraw/element";
|
||||||
import {
|
import {
|
||||||
getOmitSidesForDevice,
|
getOmitSidesForEditorInterface,
|
||||||
getTransformHandles,
|
getTransformHandles,
|
||||||
getTransformHandlesFromCoords,
|
getTransformHandlesFromCoords,
|
||||||
shouldShowBoundingBox,
|
shouldShowBoundingBox,
|
||||||
@@ -734,7 +734,7 @@ const _renderInteractiveScene = ({
|
|||||||
scale,
|
scale,
|
||||||
appState,
|
appState,
|
||||||
renderConfig,
|
renderConfig,
|
||||||
device,
|
editorInterface,
|
||||||
}: InteractiveSceneRenderConfig) => {
|
}: InteractiveSceneRenderConfig) => {
|
||||||
if (canvas === null) {
|
if (canvas === null) {
|
||||||
return { atLeastOneVisibleElement: false, elementsMap };
|
return { atLeastOneVisibleElement: false, elementsMap };
|
||||||
@@ -1024,7 +1024,7 @@ const _renderInteractiveScene = ({
|
|||||||
appState.zoom,
|
appState.zoom,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
"mouse", // when we render we don't know which pointer type so use mouse,
|
"mouse", // when we render we don't know which pointer type so use mouse,
|
||||||
getOmitSidesForDevice(device),
|
getOmitSidesForEditorInterface(editorInterface),
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
!appState.viewModeEnabled &&
|
!appState.viewModeEnabled &&
|
||||||
@@ -1088,8 +1088,11 @@ const _renderInteractiveScene = ({
|
|||||||
appState.zoom,
|
appState.zoom,
|
||||||
"mouse",
|
"mouse",
|
||||||
isFrameSelected
|
isFrameSelected
|
||||||
? { ...getOmitSidesForDevice(device), rotation: true }
|
? {
|
||||||
: getOmitSidesForDevice(device),
|
...getOmitSidesForEditorInterface(editorInterface),
|
||||||
|
rotation: true,
|
||||||
|
}
|
||||||
|
: getOmitSidesForEditorInterface(editorInterface),
|
||||||
);
|
);
|
||||||
if (selectedElements.some((element) => !element.locked)) {
|
if (selectedElements.some((element) => !element.locked)) {
|
||||||
renderTransformHandles(
|
renderTransformHandles(
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import type {
|
|||||||
InteractiveCanvasAppState,
|
InteractiveCanvasAppState,
|
||||||
StaticCanvasAppState,
|
StaticCanvasAppState,
|
||||||
SocketId,
|
SocketId,
|
||||||
Device,
|
EditorInterface,
|
||||||
PendingExcalidrawElements,
|
PendingExcalidrawElements,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import type { RoughCanvas } from "roughjs/bin/canvas";
|
import type { RoughCanvas } from "roughjs/bin/canvas";
|
||||||
@@ -97,7 +97,7 @@ export type InteractiveSceneRenderConfig = {
|
|||||||
scale: number;
|
scale: number;
|
||||||
appState: InteractiveCanvasAppState;
|
appState: InteractiveCanvasAppState;
|
||||||
renderConfig: InteractiveCanvasRenderConfig;
|
renderConfig: InteractiveCanvasRenderConfig;
|
||||||
device: Device;
|
editorInterface: EditorInterface;
|
||||||
callback: (data: RenderInteractiveSceneCallback) => void;
|
callback: (data: RenderInteractiveSceneCallback) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -985,7 +985,6 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -1181,7 +1180,6 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": {
|
"toast": {
|
||||||
@@ -1398,7 +1396,6 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -1732,7 +1729,6 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -2066,7 +2062,6 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": {
|
"toast": {
|
||||||
@@ -2281,7 +2276,6 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -2527,7 +2521,6 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -2833,7 +2826,6 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -3203,7 +3195,6 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": {
|
"toast": {
|
||||||
@@ -3699,7 +3690,6 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -4025,7 +4015,6 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -4354,7 +4343,6 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -5642,7 +5630,6 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -6864,7 +6851,6 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -7798,7 +7784,6 @@ exports[`contextMenu element > shows context menu for canvas > [end of test] app
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -8800,7 +8785,6 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -9797,7 +9781,6 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
|
|||||||
@@ -104,7 +104,6 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -723,7 +722,6 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -1211,7 +1209,6 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -1578,7 +1575,6 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -1948,7 +1944,6 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -2213,7 +2208,6 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -2659,7 +2653,6 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -2965,7 +2958,6 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -3287,7 +3279,6 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -3584,7 +3575,6 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -3873,7 +3863,6 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -4111,7 +4100,6 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -4371,7 +4359,6 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -4645,7 +4632,6 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -4877,7 +4863,6 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -5109,7 +5094,6 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -5359,7 +5343,6 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -5618,7 +5601,6 @@ exports[`history > multiplayer undo/redo > conflicts in frames and their childre
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -5878,7 +5860,6 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -6210,7 +6191,6 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -6643,7 +6623,6 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -7026,7 +7005,6 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -7330,7 +7308,6 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -7649,7 +7626,6 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -7882,7 +7858,6 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -8237,7 +8212,6 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -8598,7 +8572,6 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -9001,7 +8974,6 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -9293,7 +9265,6 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -9560,7 +9531,6 @@ exports[`history > multiplayer undo/redo > should not override remote changes on
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -9828,7 +9798,6 @@ exports[`history > multiplayer undo/redo > should not override remote changes on
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -10064,7 +10033,6 @@ exports[`history > multiplayer undo/redo > should override remotely added groups
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -10363,7 +10331,6 @@ exports[`history > multiplayer undo/redo > should override remotely added points
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -10713,7 +10680,6 @@ exports[`history > multiplayer undo/redo > should redistribute deltas when eleme
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -10955,7 +10921,6 @@ exports[`history > multiplayer undo/redo > should redraw arrows on undo > [end o
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -11405,7 +11370,6 @@ exports[`history > multiplayer undo/redo > should update history entries after r
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -11666,7 +11630,6 @@ exports[`history > singleplayer undo/redo > remounting undo/redo buttons should
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -11906,7 +11869,6 @@ exports[`history > singleplayer undo/redo > should clear the redo stack on eleme
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -12144,7 +12106,6 @@ exports[`history > singleplayer undo/redo > should create entry when selecting f
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -12555,7 +12516,6 @@ exports[`history > singleplayer undo/redo > should create new history entry on e
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -12765,7 +12725,6 @@ exports[`history > singleplayer undo/redo > should create new history entry on e
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -12979,7 +12938,6 @@ exports[`history > singleplayer undo/redo > should create new history entry on i
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -13280,7 +13238,6 @@ exports[`history > singleplayer undo/redo > should create new history entry on i
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -13581,7 +13538,6 @@ exports[`history > singleplayer undo/redo > should create new history entry on s
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -13828,7 +13784,6 @@ exports[`history > singleplayer undo/redo > should disable undo/redo buttons whe
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -14068,7 +14023,6 @@ exports[`history > singleplayer undo/redo > should end up with no history entry
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -14308,7 +14262,6 @@ exports[`history > singleplayer undo/redo > should iterate through the history w
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -14558,7 +14511,6 @@ exports[`history > singleplayer undo/redo > should not clear the redo stack on s
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -14893,7 +14845,6 @@ exports[`history > singleplayer undo/redo > should not collapse when applying co
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -15068,7 +15019,6 @@ exports[`history > singleplayer undo/redo > should not end up with history entry
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -15353,7 +15303,6 @@ exports[`history > singleplayer undo/redo > should not end up with history entry
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -15619,7 +15568,6 @@ exports[`history > singleplayer undo/redo > should not modify anything on unrela
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -15776,7 +15724,6 @@ exports[`history > singleplayer undo/redo > should not override appstate changes
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -16060,7 +16007,6 @@ exports[`history > singleplayer undo/redo > should support appstate name or view
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -16226,7 +16172,6 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -16934,7 +16879,6 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -17572,7 +17516,6 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -18208,7 +18151,6 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -18933,7 +18875,6 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -19687,7 +19628,6 @@ exports[`history > singleplayer undo/redo > should support changes in elements'
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -20172,7 +20112,6 @@ exports[`history > singleplayer undo/redo > should support duplication of groups
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -20681,7 +20620,6 @@ exports[`history > singleplayer undo/redo > should support element creation, del
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -21145,7 +21083,6 @@ exports[`history > singleplayer undo/redo > should support linear element creati
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
|
|||||||
@@ -112,7 +112,6 @@ exports[`given element A and group of elements B and given both are selected whe
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -543,7 +542,6 @@ exports[`given element A and group of elements B and given both are selected whe
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -953,7 +951,6 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -1522,7 +1519,6 @@ exports[`regression tests > Drags selected element when hitting only bounding bo
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -1737,7 +1733,6 @@ exports[`regression tests > adjusts z order when grouping > [end of test] appSta
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -2121,7 +2116,6 @@ exports[`regression tests > alt-drag duplicates an element > [end of test] appSt
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -2367,7 +2361,6 @@ exports[`regression tests > arrow keys > [end of test] appState 1`] = `
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -2552,7 +2545,6 @@ exports[`regression tests > can drag element that covers another element, while
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -2878,7 +2870,6 @@ exports[`regression tests > change the properties of a shape > [end of test] app
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -3138,7 +3129,6 @@ exports[`regression tests > click on an element and drag it > [dragged] appState
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -3382,7 +3372,6 @@ exports[`regression tests > click on an element and drag it > [end of test] appS
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -3621,7 +3610,6 @@ exports[`regression tests > click to select a shape > [end of test] appState 1`]
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -3883,7 +3871,6 @@ exports[`regression tests > click-drag to select a group > [end of test] appStat
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -4199,7 +4186,6 @@ exports[`regression tests > deleting last but one element in editing group shoul
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -4665,7 +4651,6 @@ exports[`regression tests > deselects group of selected elements on pointer down
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -4923,7 +4908,6 @@ exports[`regression tests > deselects group of selected elements on pointer up w
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -5229,7 +5213,6 @@ exports[`regression tests > deselects selected element on pointer down when poin
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -5412,7 +5395,6 @@ exports[`regression tests > deselects selected element, on pointer up, when clic
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -5615,7 +5597,6 @@ exports[`regression tests > double click to edit a group > [end of test] appStat
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -6015,7 +5996,6 @@ exports[`regression tests > drags selected elements from point inside common bou
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -6309,7 +6289,6 @@ exports[`regression tests > draw every type of shape > [end of test] appState 1`
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -7173,7 +7152,6 @@ exports[`regression tests > given a group of selected elements with an element t
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -7510,7 +7488,6 @@ exports[`regression tests > given a selected element A and a not selected elemen
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -7791,7 +7768,6 @@ exports[`regression tests > given selected element A with lower z-index than uns
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -8029,7 +8005,6 @@ exports[`regression tests > given selected element A with lower z-index than uns
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -8270,7 +8245,6 @@ exports[`regression tests > key 2 selects rectangle tool > [end of test] appStat
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -8453,7 +8427,6 @@ exports[`regression tests > key 3 selects diamond tool > [end of test] appState
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -8636,7 +8609,6 @@ exports[`regression tests > key 4 selects ellipse tool > [end of test] appState
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -8846,7 +8818,6 @@ exports[`regression tests > key 5 selects arrow tool > [end of test] appState 1`
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -9079,7 +9050,6 @@ exports[`regression tests > key 6 selects line tool > [end of test] appState 1`]
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -9281,7 +9251,6 @@ exports[`regression tests > key 7 selects freedraw tool > [end of test] appState
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -9509,7 +9478,6 @@ exports[`regression tests > key a selects arrow tool > [end of test] appState 1`
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -9715,7 +9683,6 @@ exports[`regression tests > key d selects diamond tool > [end of test] appState
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -9925,7 +9892,6 @@ exports[`regression tests > key l selects line tool > [end of test] appState 1`]
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -10129,7 +10095,6 @@ exports[`regression tests > key o selects ellipse tool > [end of test] appState
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -10310,7 +10275,6 @@ exports[`regression tests > key p selects freedraw tool > [end of test] appState
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -10511,7 +10475,6 @@ exports[`regression tests > key r selects rectangle tool > [end of test] appStat
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -10702,7 +10665,6 @@ exports[`regression tests > make a group and duplicate it > [end of test] appSta
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -11230,7 +11192,6 @@ exports[`regression tests > noop interaction after undo shouldn't create history
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -11509,7 +11470,6 @@ exports[`regression tests > pinch-to-zoom works > [end of test] appState 1`] = `
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -11637,7 +11597,6 @@ exports[`regression tests > shift click on selected element should deselect it o
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -11844,7 +11803,6 @@ exports[`regression tests > shift-click to multiselect, then drag > [end of test
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -12168,7 +12126,6 @@ exports[`regression tests > should group elements and ungroup them > [end of tes
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -12604,7 +12561,6 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -13238,7 +13194,6 @@ exports[`regression tests > spacebar + drag scrolls the canvas > [end of test] a
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -13368,7 +13323,6 @@ exports[`regression tests > supports nested groups > [end of test] appState 1`]
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -14031,7 +13985,6 @@ exports[`regression tests > switches from group of selected elements to another
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -14372,7 +14325,6 @@ exports[`regression tests > switches selected element on pointer down > [end of
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -14607,7 +14559,6 @@ exports[`regression tests > two-finger scroll works > [end of test] appState 1`]
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -14735,7 +14686,6 @@ exports[`regression tests > undo/redo drawing an element > [end of test] appStat
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -15128,7 +15078,6 @@ exports[`regression tests > updates fontSize & fontFamily appState > [end of tes
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
@@ -15257,7 +15206,6 @@ exports[`regression tests > zoom hotkeys > [end of test] appState 1`] = `
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
|
|||||||
@@ -192,9 +192,7 @@ export const withExcalidrawDimensions = async (
|
|||||||
mockBoundingClientRect(dimensions);
|
mockBoundingClientRect(dimensions);
|
||||||
act(() => {
|
act(() => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
h.app.refreshViewportBreakpoints();
|
h.app.refreshEditorInterface();
|
||||||
// @ts-ignore
|
|
||||||
h.app.refreshEditorBreakpoints();
|
|
||||||
window.h.app.refresh();
|
window.h.app.refresh();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -203,9 +201,7 @@ export const withExcalidrawDimensions = async (
|
|||||||
restoreOriginalGetBoundingClientRect();
|
restoreOriginalGetBoundingClientRect();
|
||||||
act(() => {
|
act(() => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
h.app.refreshViewportBreakpoints();
|
h.app.refreshEditorInterface();
|
||||||
// @ts-ignore
|
|
||||||
h.app.refreshEditorBreakpoints();
|
|
||||||
window.h.app.refresh();
|
window.h.app.refresh();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -449,9 +449,6 @@ export interface AppState {
|
|||||||
// as elements are unlocked, we remove the groupId from the elements
|
// as elements are unlocked, we remove the groupId from the elements
|
||||||
// and also remove groupId from this map
|
// and also remove groupId from this map
|
||||||
lockedMultiSelections: { [groupId: string]: true };
|
lockedMultiSelections: { [groupId: string]: true };
|
||||||
|
|
||||||
/** properties sidebar mode - determines whether to show compact or complete sidebar */
|
|
||||||
stylesPanelMode: "compact" | "full" | "mobile";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SearchMatch = {
|
export type SearchMatch = {
|
||||||
@@ -715,7 +712,7 @@ export type AppClassProperties = {
|
|||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
files: BinaryFiles;
|
files: BinaryFiles;
|
||||||
device: App["device"];
|
editorInterface: App["editorInterface"];
|
||||||
scene: App["scene"];
|
scene: App["scene"];
|
||||||
syncActionResult: App["syncActionResult"];
|
syncActionResult: App["syncActionResult"];
|
||||||
fonts: App["fonts"];
|
fonts: App["fonts"];
|
||||||
@@ -732,6 +729,7 @@ export type AppClassProperties = {
|
|||||||
setActiveTool: App["setActiveTool"];
|
setActiveTool: App["setActiveTool"];
|
||||||
setOpenDialog: App["setOpenDialog"];
|
setOpenDialog: App["setOpenDialog"];
|
||||||
insertEmbeddableElement: App["insertEmbeddableElement"];
|
insertEmbeddableElement: App["insertEmbeddableElement"];
|
||||||
|
setDesktopUIMode: App["setDesktopUIMode"];
|
||||||
onMagicframeToolSelect: App["onMagicframeToolSelect"];
|
onMagicframeToolSelect: App["onMagicframeToolSelect"];
|
||||||
getName: App["getName"];
|
getName: App["getName"];
|
||||||
dismissLinearEditor: App["dismissLinearEditor"];
|
dismissLinearEditor: App["dismissLinearEditor"];
|
||||||
@@ -885,16 +883,19 @@ export interface ExcalidrawImperativeAPI {
|
|||||||
) => UnsubscribeCallback;
|
) => UnsubscribeCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Device = Readonly<{
|
export type StylesPanelMode = "compact" | "full" | "mobile";
|
||||||
viewport: {
|
|
||||||
isMobile: boolean;
|
export type EditorInterface = Readonly<{
|
||||||
isLandscape: boolean;
|
formFactor: "phone" | "tablet" | "desktop";
|
||||||
};
|
desktopUIMode: "compact" | "full";
|
||||||
editor: {
|
userAgent: Readonly<{
|
||||||
isMobile: boolean;
|
raw: string;
|
||||||
canFitSidebar: boolean;
|
isMobileDevice: boolean;
|
||||||
};
|
platform: "ios" | "android" | "other" | "unknown";
|
||||||
|
}>;
|
||||||
isTouchScreen: boolean;
|
isTouchScreen: boolean;
|
||||||
|
canFitSidebar: boolean;
|
||||||
|
isLandscape: boolean;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type FrameNameBounds = {
|
export type FrameNameBounds = {
|
||||||
|
|||||||
@@ -254,9 +254,7 @@ describe("textWysiwyg", () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
h.app.refreshViewportBreakpoints();
|
h.app.refreshEditorInterface();
|
||||||
// @ts-ignore
|
|
||||||
h.app.refreshEditorBreakpoints();
|
|
||||||
|
|
||||||
API.setElements([]);
|
API.setElements([]);
|
||||||
});
|
});
|
||||||
@@ -363,9 +361,7 @@ describe("textWysiwyg", () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
h.app.refreshViewportBreakpoints();
|
h.app.refreshEditorInterface();
|
||||||
// @ts-ignore
|
|
||||||
h.app.refreshEditorBreakpoints();
|
|
||||||
|
|
||||||
textElement = UI.createElement("text");
|
textElement = UI.createElement("text");
|
||||||
|
|
||||||
|
|||||||
@@ -104,7 +104,6 @@ exports[`exportToSvg > with default arguments 1`] = `
|
|||||||
"open": false,
|
"open": false,
|
||||||
"panels": 3,
|
"panels": 3,
|
||||||
},
|
},
|
||||||
"stylesPanelMode": "full",
|
|
||||||
"suggestedBindings": [],
|
"suggestedBindings": [],
|
||||||
"theme": "light",
|
"theme": "light",
|
||||||
"toast": null,
|
"toast": null,
|
||||||
|
|||||||
Reference in New Issue
Block a user