feat: add undo/redo buttons & tweak footer (#3832)
Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
This commit is contained in:
parent
685abac81a
commit
99623334d1
@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { getDefaultAppState } from "../appState";
|
import { getDefaultAppState } from "../appState";
|
||||||
import { ColorPicker } from "../components/ColorPicker";
|
import { ColorPicker } from "../components/ColorPicker";
|
||||||
import { resetZoom, trash, zoomIn, zoomOut } from "../components/icons";
|
import { trash, zoomIn, zoomOut } from "../components/icons";
|
||||||
import { ToolButton } from "../components/ToolButton";
|
import { ToolButton } from "../components/ToolButton";
|
||||||
import { DarkModeToggle } from "../components/DarkModeToggle";
|
import { DarkModeToggle } from "../components/DarkModeToggle";
|
||||||
import { ZOOM_STEP } from "../constants";
|
import { ZOOM_STEP } from "../constants";
|
||||||
@ -17,6 +17,7 @@ import { getNewZoom } from "../scene/zoom";
|
|||||||
import { AppState, NormalizedZoomValue } from "../types";
|
import { AppState, NormalizedZoomValue } from "../types";
|
||||||
import { getShortcutKey } from "../utils";
|
import { getShortcutKey } from "../utils";
|
||||||
import { register } from "./register";
|
import { register } from "./register";
|
||||||
|
import { Tooltip } from "../components/Tooltip";
|
||||||
|
|
||||||
export const actionChangeViewBackgroundColor = register({
|
export const actionChangeViewBackgroundColor = register({
|
||||||
name: "changeViewBackgroundColor",
|
name: "changeViewBackgroundColor",
|
||||||
@ -108,6 +109,7 @@ export const actionZoomIn = register({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
updateData(null);
|
updateData(null);
|
||||||
}}
|
}}
|
||||||
|
size="small"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
keyTest: (event) =>
|
keyTest: (event) =>
|
||||||
@ -142,6 +144,7 @@ export const actionZoomOut = register({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
updateData(null);
|
updateData(null);
|
||||||
}}
|
}}
|
||||||
|
size="small"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
keyTest: (event) =>
|
keyTest: (event) =>
|
||||||
@ -168,16 +171,21 @@ export const actionResetZoom = register({
|
|||||||
commitToHistory: false,
|
commitToHistory: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ updateData }) => (
|
PanelComponent: ({ updateData, appState }) => (
|
||||||
<ToolButton
|
<Tooltip label={t("buttons.resetZoom")}>
|
||||||
type="button"
|
<ToolButton
|
||||||
icon={resetZoom}
|
type="button"
|
||||||
title={t("buttons.resetZoom")}
|
className="reset-zoom-button"
|
||||||
aria-label={t("buttons.resetZoom")}
|
title={t("buttons.resetZoom")}
|
||||||
onClick={() => {
|
aria-label={t("buttons.resetZoom")}
|
||||||
updateData(null);
|
onClick={() => {
|
||||||
}}
|
updateData(null);
|
||||||
/>
|
}}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{(appState.zoom.value * 100).toFixed(0)}%
|
||||||
|
</ToolButton>
|
||||||
|
</Tooltip>
|
||||||
),
|
),
|
||||||
keyTest: (event) =>
|
keyTest: (event) =>
|
||||||
(event.code === CODES.ZERO || event.code === CODES.NUM_ZERO) &&
|
(event.code === CODES.ZERO || event.code === CODES.NUM_ZERO) &&
|
||||||
|
@ -70,7 +70,7 @@ export const actionChangeExportScale = register({
|
|||||||
return (
|
return (
|
||||||
<ToolButton
|
<ToolButton
|
||||||
key={s}
|
key={s}
|
||||||
size="s"
|
size="small"
|
||||||
type="radio"
|
type="radio"
|
||||||
icon={`${s}x`}
|
icon={`${s}x`}
|
||||||
name="export-canvas-scale"
|
name="export-canvas-scale"
|
||||||
@ -120,7 +120,7 @@ export const actionChangeExportEmbedScene = register({
|
|||||||
>
|
>
|
||||||
{t("labels.exportEmbedScene")}
|
{t("labels.exportEmbedScene")}
|
||||||
<Tooltip label={t("labels.exportEmbedScene_details")} long={true}>
|
<Tooltip label={t("labels.exportEmbedScene_details")} long={true}>
|
||||||
<div className="Tooltip-icon">{questionCircle}</div>
|
<div className="excalidraw-tooltip-icon">{questionCircle}</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</CheckboxItem>
|
</CheckboxItem>
|
||||||
),
|
),
|
||||||
|
@ -69,12 +69,13 @@ export const createUndoAction: ActionCreator = (history) => ({
|
|||||||
event[KEYS.CTRL_OR_CMD] &&
|
event[KEYS.CTRL_OR_CMD] &&
|
||||||
event.key.toLowerCase() === KEYS.Z &&
|
event.key.toLowerCase() === KEYS.Z &&
|
||||||
!event.shiftKey,
|
!event.shiftKey,
|
||||||
PanelComponent: ({ updateData }) => (
|
PanelComponent: ({ updateData, data }) => (
|
||||||
<ToolButton
|
<ToolButton
|
||||||
type="button"
|
type="button"
|
||||||
icon={undo}
|
icon={undo}
|
||||||
aria-label={t("buttons.undo")}
|
aria-label={t("buttons.undo")}
|
||||||
onClick={updateData}
|
onClick={updateData}
|
||||||
|
size={data?.size || "medium"}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
commitToHistory: () => false,
|
commitToHistory: () => false,
|
||||||
@ -89,12 +90,13 @@ export const createRedoAction: ActionCreator = (history) => ({
|
|||||||
event.shiftKey &&
|
event.shiftKey &&
|
||||||
event.key.toLowerCase() === KEYS.Z) ||
|
event.key.toLowerCase() === KEYS.Z) ||
|
||||||
(isWindows && event.ctrlKey && !event.shiftKey && event.key === KEYS.Y),
|
(isWindows && event.ctrlKey && !event.shiftKey && event.key === KEYS.Y),
|
||||||
PanelComponent: ({ updateData }) => (
|
PanelComponent: ({ updateData, data }) => (
|
||||||
<ToolButton
|
<ToolButton
|
||||||
type="button"
|
type="button"
|
||||||
icon={redo}
|
icon={redo}
|
||||||
aria-label={t("buttons.redo")}
|
aria-label={t("buttons.redo")}
|
||||||
onClick={updateData}
|
onClick={updateData}
|
||||||
|
size={data?.size || "medium"}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
commitToHistory: () => false,
|
commitToHistory: () => false,
|
||||||
|
@ -30,8 +30,8 @@ export const actionGoToCollaborator = register({
|
|||||||
commitToHistory: false,
|
commitToHistory: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ appState, updateData, id }) => {
|
PanelComponent: ({ appState, updateData, data }) => {
|
||||||
const clientId = id;
|
const clientId: string | undefined = data?.id;
|
||||||
if (!clientId) {
|
if (!clientId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import {
|
|||||||
UpdaterFn,
|
UpdaterFn,
|
||||||
ActionName,
|
ActionName,
|
||||||
ActionResult,
|
ActionResult,
|
||||||
|
PanelComponentProps,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { ExcalidrawElement } from "../element/types";
|
import { ExcalidrawElement } from "../element/types";
|
||||||
import { AppProps, AppState } from "../types";
|
import { AppProps, AppState } from "../types";
|
||||||
@ -107,11 +108,10 @@ export class ActionManager implements ActionsManagerInterface {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Id is an attribute that we can use to pass in data like keys.
|
/**
|
||||||
// This is needed for dynamically generated action components
|
* @param data additional data sent to the PanelComponent
|
||||||
// like the user list. We can use this key to extract more
|
*/
|
||||||
// data from app state. This is an alternative to generic prop hell!
|
renderAction = (name: ActionName, data?: PanelComponentProps["data"]) => {
|
||||||
renderAction = (name: ActionName, id?: string) => {
|
|
||||||
const canvasActions = this.app.props.UIOptions.canvasActions;
|
const canvasActions = this.app.props.UIOptions.canvasActions;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -139,8 +139,8 @@ export class ActionManager implements ActionsManagerInterface {
|
|||||||
elements={this.getElementsIncludingDeleted()}
|
elements={this.getElementsIncludingDeleted()}
|
||||||
appState={this.getAppState()}
|
appState={this.getAppState()}
|
||||||
updateData={updateData}
|
updateData={updateData}
|
||||||
id={id}
|
|
||||||
appProps={this.app.props}
|
appProps={this.app.props}
|
||||||
|
data={data}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import React from "react";
|
|||||||
import { ExcalidrawElement } from "../element/types";
|
import { ExcalidrawElement } from "../element/types";
|
||||||
import { AppState, ExcalidrawProps } from "../types";
|
import { AppState, ExcalidrawProps } from "../types";
|
||||||
import Library from "../data/library";
|
import Library from "../data/library";
|
||||||
|
import { ToolButtonSize } from "../components/ToolButton";
|
||||||
|
|
||||||
/** if false, the action should be prevented */
|
/** if false, the action should be prevented */
|
||||||
export type ActionResult =
|
export type ActionResult =
|
||||||
@ -102,15 +103,17 @@ export type ActionName =
|
|||||||
| "exportWithDarkMode"
|
| "exportWithDarkMode"
|
||||||
| "toggleTheme";
|
| "toggleTheme";
|
||||||
|
|
||||||
|
export type PanelComponentProps = {
|
||||||
|
elements: readonly ExcalidrawElement[];
|
||||||
|
appState: AppState;
|
||||||
|
updateData: (formData?: any) => void;
|
||||||
|
appProps: ExcalidrawProps;
|
||||||
|
data?: Partial<{ id: string; size: ToolButtonSize }>;
|
||||||
|
};
|
||||||
|
|
||||||
export interface Action {
|
export interface Action {
|
||||||
name: ActionName;
|
name: ActionName;
|
||||||
PanelComponent?: React.FC<{
|
PanelComponent?: React.FC<PanelComponentProps>;
|
||||||
elements: readonly ExcalidrawElement[];
|
|
||||||
appState: AppState;
|
|
||||||
updateData: (formData?: any) => void;
|
|
||||||
appProps: ExcalidrawProps;
|
|
||||||
id?: string;
|
|
||||||
}>;
|
|
||||||
perform: ActionFn;
|
perform: ActionFn;
|
||||||
keyPriority?: number;
|
keyPriority?: number;
|
||||||
keyTest?: (
|
keyTest?: (
|
||||||
|
@ -207,9 +207,6 @@ export const ZoomActions = ({
|
|||||||
{renderAction("zoomIn")}
|
{renderAction("zoomIn")}
|
||||||
{renderAction("zoomOut")}
|
{renderAction("zoomOut")}
|
||||||
{renderAction("resetZoom")}
|
{renderAction("resetZoom")}
|
||||||
<div style={{ marginInlineStart: 4 }}>
|
|
||||||
{(zoom.value * 100).toFixed(0)}%
|
|
||||||
</div>
|
|
||||||
</Stack.Row>
|
</Stack.Row>
|
||||||
</Stack.Col>
|
</Stack.Col>
|
||||||
);
|
);
|
||||||
|
@ -81,7 +81,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Tooltip-icon {
|
.excalidraw-tooltip-icon {
|
||||||
width: 1em;
|
width: 1em;
|
||||||
height: 1em;
|
height: 1em;
|
||||||
}
|
}
|
||||||
|
@ -73,10 +73,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:root[dir="ltr"] &.layer-ui__wrapper__footer-left--transition-left {
|
:root[dir="ltr"] &.layer-ui__wrapper__footer-left--transition-left {
|
||||||
transform: translate(-92px, 0);
|
transform: translate(-76px, 0);
|
||||||
}
|
}
|
||||||
:root[dir="rtl"] &.layer-ui__wrapper__footer-left--transition-left {
|
:root[dir="rtl"] &.layer-ui__wrapper__footer-left--transition-left {
|
||||||
transform: translate(92px, 0);
|
transform: translate(76px, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.layer-ui__wrapper__footer-left--transition-bottom {
|
&.layer-ui__wrapper__footer-left--transition-bottom {
|
||||||
@ -120,5 +120,15 @@
|
|||||||
.disable-zen-mode--visible {
|
.disable-zen-mode--visible {
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.layer-ui__wrapper__footer-left {
|
||||||
|
margin-bottom: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-ui__wrapper__footer-right {
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
margin-inline-end: 1em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -632,7 +632,9 @@ const LayerUI = ({
|
|||||||
label={client.username || "Unknown user"}
|
label={client.username || "Unknown user"}
|
||||||
key={clientId}
|
key={clientId}
|
||||||
>
|
>
|
||||||
{actionManager.renderAction("goToCollaborator", clientId)}
|
{actionManager.renderAction("goToCollaborator", {
|
||||||
|
id: clientId,
|
||||||
|
})}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
))}
|
))}
|
||||||
</UserList>
|
</UserList>
|
||||||
@ -665,6 +667,16 @@ const LayerUI = ({
|
|||||||
zoom={appState.zoom}
|
zoom={appState.zoom}
|
||||||
/>
|
/>
|
||||||
</Island>
|
</Island>
|
||||||
|
{!viewModeEnabled && (
|
||||||
|
<div
|
||||||
|
className={clsx("undo-redo-buttons zen-mode-transition", {
|
||||||
|
"layer-ui__wrapper__footer-left--transition-bottom": zenModeEnabled,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{actionManager.renderAction("undo", { size: "small" })}
|
||||||
|
{actionManager.renderAction("redo", { size: "small" })}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Section>
|
</Section>
|
||||||
</Stack.Col>
|
</Stack.Col>
|
||||||
</div>
|
</div>
|
||||||
|
@ -21,7 +21,7 @@ export const LibraryButton: React.FC<{
|
|||||||
<label
|
<label
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"ToolIcon ToolIcon_type_floating ToolIcon__library zen-mode-visibility",
|
"ToolIcon ToolIcon_type_floating ToolIcon__library zen-mode-visibility",
|
||||||
`ToolIcon_size_m`,
|
`ToolIcon_size_medium`,
|
||||||
{
|
{
|
||||||
"zen-mode-visibility--hidden": appState.zenModeEnabled,
|
"zen-mode-visibility--hidden": appState.zenModeEnabled,
|
||||||
},
|
},
|
||||||
|
@ -2,8 +2,7 @@ import "./ToolIcon.scss";
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
import { ToolButtonSize } from "./ToolButton";
|
||||||
type LockIconSize = "s" | "m";
|
|
||||||
|
|
||||||
type LockIconProps = {
|
type LockIconProps = {
|
||||||
title?: string;
|
title?: string;
|
||||||
@ -13,7 +12,7 @@ type LockIconProps = {
|
|||||||
zenModeEnabled?: boolean;
|
zenModeEnabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_SIZE: LockIconSize = "m";
|
const DEFAULT_SIZE: ToolButtonSize = "medium";
|
||||||
|
|
||||||
const ICONS = {
|
const ICONS = {
|
||||||
CHECKED: (
|
CHECKED: (
|
||||||
|
@ -168,10 +168,9 @@ export const MobileMenu = ({
|
|||||||
)
|
)
|
||||||
.map(([clientId, client]) => (
|
.map(([clientId, client]) => (
|
||||||
<React.Fragment key={clientId}>
|
<React.Fragment key={clientId}>
|
||||||
{actionManager.renderAction(
|
{actionManager.renderAction("goToCollaborator", {
|
||||||
"goToCollaborator",
|
id: clientId,
|
||||||
clientId,
|
})}
|
||||||
)}
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
</UserList>
|
</UserList>
|
||||||
|
@ -4,7 +4,7 @@ import React from "react";
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { useExcalidrawContainer } from "./App";
|
import { useExcalidrawContainer } from "./App";
|
||||||
|
|
||||||
type ToolIconSize = "s" | "m";
|
export type ToolButtonSize = "small" | "medium";
|
||||||
|
|
||||||
type ToolButtonBaseProps = {
|
type ToolButtonBaseProps = {
|
||||||
icon?: React.ReactNode;
|
icon?: React.ReactNode;
|
||||||
@ -15,7 +15,7 @@ type ToolButtonBaseProps = {
|
|||||||
title?: string;
|
title?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
size?: ToolIconSize;
|
size?: ToolButtonSize;
|
||||||
keyBindingLabel?: string;
|
keyBindingLabel?: string;
|
||||||
showAriaLabel?: boolean;
|
showAriaLabel?: boolean;
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
@ -41,13 +41,11 @@ type ToolButtonProps =
|
|||||||
onChange?(): void;
|
onChange?(): void;
|
||||||
});
|
});
|
||||||
|
|
||||||
const DEFAULT_SIZE: ToolIconSize = "m";
|
|
||||||
|
|
||||||
export const ToolButton = React.forwardRef((props: ToolButtonProps, ref) => {
|
export const ToolButton = React.forwardRef((props: ToolButtonProps, ref) => {
|
||||||
const { id: excalId } = useExcalidrawContainer();
|
const { id: excalId } = useExcalidrawContainer();
|
||||||
const innerRef = React.useRef(null);
|
const innerRef = React.useRef(null);
|
||||||
React.useImperativeHandle(ref, () => innerRef.current);
|
React.useImperativeHandle(ref, () => innerRef.current);
|
||||||
const sizeCn = `ToolIcon_size_${props.size || DEFAULT_SIZE}`;
|
const sizeCn = `ToolIcon_size_${props.size}`;
|
||||||
|
|
||||||
if (props.type === "button" || props.type === "icon") {
|
if (props.type === "button" || props.type === "icon") {
|
||||||
return (
|
return (
|
||||||
@ -118,4 +116,5 @@ export const ToolButton = React.forwardRef((props: ToolButtonProps, ref) => {
|
|||||||
ToolButton.defaultProps = {
|
ToolButton.defaultProps = {
|
||||||
visible: true,
|
visible: true,
|
||||||
className: "",
|
className: "",
|
||||||
|
size: "medium",
|
||||||
};
|
};
|
||||||
|
@ -60,9 +60,9 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ToolIcon_size_s .ToolIcon__icon {
|
.ToolIcon_size_small .ToolIcon__icon {
|
||||||
width: 1.4rem;
|
width: 2rem;
|
||||||
height: 1.4rem;
|
height: 2rem;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
@import "../css/variables.module";
|
@import "../css/variables.module";
|
||||||
|
|
||||||
|
// container in body where the actual tooltip is appended to
|
||||||
.excalidraw-tooltip {
|
.excalidraw-tooltip {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
@ -24,16 +26,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.excalidraw {
|
// wraps the element we want to apply the tooltip to
|
||||||
.Tooltip-icon {
|
.excalidraw-tooltip-wrapper {
|
||||||
width: 0.9em;
|
display: flex;
|
||||||
height: 0.9em;
|
height: 100%;
|
||||||
margin-left: 5px;
|
}
|
||||||
margin-top: 1px;
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
@include isMobile {
|
.excalidraw-tooltip-icon {
|
||||||
display: none;
|
width: 0.9em;
|
||||||
}
|
height: 0.9em;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-top: 1px;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
@include isMobile {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,6 +74,7 @@ export const Tooltip = ({ children, label, long = false }: TooltipProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
className="excalidraw-tooltip-wrapper"
|
||||||
onPointerEnter={(event) =>
|
onPointerEnter={(event) =>
|
||||||
updateTooltip(
|
updateTooltip(
|
||||||
event.currentTarget as HTMLDivElement,
|
event.currentTarget as HTMLDivElement,
|
||||||
|
@ -414,22 +414,6 @@
|
|||||||
&:active {
|
&:active {
|
||||||
background-color: var(--button-gray-2);
|
background-color: var(--button-gray-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.dropdown-select--floating {
|
|
||||||
margin: 0.5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-select__language.dropdown-select--floating {
|
|
||||||
bottom: 10px;
|
|
||||||
|
|
||||||
:root[dir="ltr"] & {
|
|
||||||
right: 44px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root[dir="rtl"] & {
|
|
||||||
left: 44px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.zIndexButton {
|
.zIndexButton {
|
||||||
@ -456,26 +440,32 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.help-icon {
|
.help-icon {
|
||||||
|
display: flex;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
fill: $oc-gray-6;
|
fill: $oc-gray-6;
|
||||||
width: 1.5rem;
|
width: 1.5rem;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
margin-top: 5px;
|
|
||||||
background: none;
|
background: none;
|
||||||
color: var(--icon-fill-color);
|
color: var(--icon-fill-color);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
:root[dir="ltr"] & {
|
.reset-zoom-button {
|
||||||
margin-right: 14px;
|
padding: 0.2em;
|
||||||
}
|
background: transparent;
|
||||||
|
color: var(--text-primary-color);
|
||||||
|
font-family: var(--ui-font);
|
||||||
|
}
|
||||||
|
|
||||||
:root[dir="rtl"] & {
|
.undo-redo-buttons {
|
||||||
margin-left: 14px;
|
display: flex;
|
||||||
}
|
gap: 0.4em;
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
margin-inline-start: 0.6em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include isMobile {
|
@include isMobile {
|
||||||
|
@ -1,23 +1,18 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import clsx from "clsx";
|
|
||||||
import * as i18n from "../../i18n";
|
import * as i18n from "../../i18n";
|
||||||
|
|
||||||
export const LanguageList = ({
|
export const LanguageList = ({
|
||||||
onChange,
|
onChange,
|
||||||
languages = i18n.languages,
|
languages = i18n.languages,
|
||||||
currentLangCode = i18n.getLanguage().code,
|
currentLangCode = i18n.getLanguage().code,
|
||||||
floating,
|
|
||||||
}: {
|
}: {
|
||||||
languages?: { code: string; label: string }[];
|
languages?: { code: string; label: string }[];
|
||||||
onChange: (langCode: i18n.Language["code"]) => void;
|
onChange: (langCode: i18n.Language["code"]) => void;
|
||||||
currentLangCode?: i18n.Language["code"];
|
currentLangCode?: i18n.Language["code"];
|
||||||
floating?: boolean;
|
|
||||||
}) => (
|
}) => (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<select
|
<select
|
||||||
className={clsx("dropdown-select dropdown-select__language", {
|
className="dropdown-select dropdown-select__language"
|
||||||
"dropdown-select--floating": floating,
|
|
||||||
})}
|
|
||||||
onChange={({ target }) => onChange(target.value)}
|
onChange={({ target }) => onChange(target.value)}
|
||||||
value={currentLangCode}
|
value={currentLangCode}
|
||||||
aria-label={i18n.t("buttons.selectLanguage")}
|
aria-label={i18n.t("buttons.selectLanguage")}
|
||||||
|
@ -2,12 +2,18 @@
|
|||||||
.layer-ui__wrapper__footer-center {
|
.layer-ui__wrapper__footer-center {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
margin-inline-start: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.encrypted-icon {
|
.encrypted-icon {
|
||||||
border-radius: var(--space-factor);
|
border-radius: var(--space-factor);
|
||||||
color: var(--icon-green-fill-color);
|
color: var(--icon-green-fill-color);
|
||||||
margin-top: 13px;
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
margin-inline-start: auto;
|
||||||
|
margin-inline-end: 0.6em;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
width: 1.2rem;
|
width: 1.2rem;
|
||||||
|
@ -348,11 +348,8 @@ const ExcalidrawWrapper = () => {
|
|||||||
|
|
||||||
const renderLanguageList = () => (
|
const renderLanguageList = () => (
|
||||||
<LanguageList
|
<LanguageList
|
||||||
onChange={(langCode) => {
|
onChange={(langCode) => setLangCode(langCode)}
|
||||||
setLangCode(langCode);
|
|
||||||
}}
|
|
||||||
languages={languages}
|
languages={languages}
|
||||||
floating={!isMobile}
|
|
||||||
currentLangCode={langCode}
|
currentLangCode={langCode}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -17,7 +17,7 @@ beforeEach(async () => {
|
|||||||
mouse.reset();
|
mouse.reset();
|
||||||
|
|
||||||
await setLanguage(defaultLang);
|
await setLanguage(defaultLang);
|
||||||
render(<App />);
|
await render(<App />);
|
||||||
});
|
});
|
||||||
|
|
||||||
const createAndSelectOneRectangle = (angle: number = 0) => {
|
const createAndSelectOneRectangle = (angle: number = 0) => {
|
||||||
|
@ -25,7 +25,7 @@ exports[`<Excalidraw/> Test UIOptions prop Test canvasActions should not hide an
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
aria-label="Reset the canvas"
|
aria-label="Reset the canvas"
|
||||||
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
|
class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon"
|
||||||
data-testid="clear-canvas-button"
|
data-testid="clear-canvas-button"
|
||||||
title="Reset the canvas"
|
title="Reset the canvas"
|
||||||
type="button"
|
type="button"
|
||||||
@ -53,7 +53,7 @@ exports[`<Excalidraw/> Test UIOptions prop Test canvasActions should not hide an
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
aria-label="Load"
|
aria-label="Load"
|
||||||
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
|
class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon"
|
||||||
data-testid="load-button"
|
data-testid="load-button"
|
||||||
title="Load"
|
title="Load"
|
||||||
type="button"
|
type="button"
|
||||||
@ -78,7 +78,7 @@ exports[`<Excalidraw/> Test UIOptions prop Test canvasActions should not hide an
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
aria-label="Export"
|
aria-label="Export"
|
||||||
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
|
class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon"
|
||||||
data-testid="json-export-button"
|
data-testid="json-export-button"
|
||||||
title="Export"
|
title="Export"
|
||||||
type="button"
|
type="button"
|
||||||
@ -103,7 +103,7 @@ exports[`<Excalidraw/> Test UIOptions prop Test canvasActions should not hide an
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
aria-label="Save as image"
|
aria-label="Save as image"
|
||||||
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
|
class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon"
|
||||||
data-testid="image-export-button"
|
data-testid="image-export-button"
|
||||||
title="Save as image"
|
title="Save as image"
|
||||||
type="button"
|
type="button"
|
||||||
@ -170,7 +170,7 @@ exports[`<Excalidraw/> Test UIOptions prop Test canvasActions should not hide an
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
aria-label="Dark mode"
|
aria-label="Dark mode"
|
||||||
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon ToolIcon--plain"
|
class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon ToolIcon--plain"
|
||||||
data-testid="toggle-dark-mode"
|
data-testid="toggle-dark-mode"
|
||||||
title="Dark mode"
|
title="Dark mode"
|
||||||
type="button"
|
type="button"
|
||||||
@ -224,7 +224,7 @@ exports[`<Excalidraw/> Test UIOptions prop should not hide any UI element when t
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
aria-label="Reset the canvas"
|
aria-label="Reset the canvas"
|
||||||
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
|
class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon"
|
||||||
data-testid="clear-canvas-button"
|
data-testid="clear-canvas-button"
|
||||||
title="Reset the canvas"
|
title="Reset the canvas"
|
||||||
type="button"
|
type="button"
|
||||||
@ -252,7 +252,7 @@ exports[`<Excalidraw/> Test UIOptions prop should not hide any UI element when t
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
aria-label="Load"
|
aria-label="Load"
|
||||||
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
|
class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon"
|
||||||
data-testid="load-button"
|
data-testid="load-button"
|
||||||
title="Load"
|
title="Load"
|
||||||
type="button"
|
type="button"
|
||||||
@ -277,7 +277,7 @@ exports[`<Excalidraw/> Test UIOptions prop should not hide any UI element when t
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
aria-label="Export"
|
aria-label="Export"
|
||||||
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
|
class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon"
|
||||||
data-testid="json-export-button"
|
data-testid="json-export-button"
|
||||||
title="Export"
|
title="Export"
|
||||||
type="button"
|
type="button"
|
||||||
@ -302,7 +302,7 @@ exports[`<Excalidraw/> Test UIOptions prop should not hide any UI element when t
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
aria-label="Save as image"
|
aria-label="Save as image"
|
||||||
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
|
class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon"
|
||||||
data-testid="image-export-button"
|
data-testid="image-export-button"
|
||||||
title="Save as image"
|
title="Save as image"
|
||||||
type="button"
|
type="button"
|
||||||
@ -369,7 +369,7 @@ exports[`<Excalidraw/> Test UIOptions prop should not hide any UI element when t
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
aria-label="Dark mode"
|
aria-label="Dark mode"
|
||||||
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon ToolIcon--plain"
|
class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon ToolIcon--plain"
|
||||||
data-testid="toggle-dark-mode"
|
data-testid="toggle-dark-mode"
|
||||||
title="Dark mode"
|
title="Dark mode"
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -45,14 +45,17 @@ describe("resize rectangle ellipses and diamond elements", () => {
|
|||||||
${"se"} | ${[-30, -10]} | ${[70, 90]} | ${[elemData.x, elemData.y]}
|
${"se"} | ${[-30, -10]} | ${[70, 90]} | ${[elemData.x, elemData.y]}
|
||||||
${"nw"} | ${[-300, -200]} | ${[400, 300]} | ${[-300, -200]}
|
${"nw"} | ${[-300, -200]} | ${[400, 300]} | ${[-300, -200]}
|
||||||
${"sw"} | ${[40, -20]} | ${[60, 80]} | ${[40, 0]}
|
${"sw"} | ${[40, -20]} | ${[60, 80]} | ${[40, 0]}
|
||||||
`("resizes with handle $handle", ({ handle, move, dimensions, topLeft }) => {
|
`(
|
||||||
render(<App />);
|
"resizes with handle $handle",
|
||||||
const rectangle = UI.createElement("rectangle", elemData);
|
async ({ handle, move, dimensions, topLeft }) => {
|
||||||
resize(rectangle, handle, move);
|
await render(<App />);
|
||||||
const element = h.elements[0];
|
const rectangle = UI.createElement("rectangle", elemData);
|
||||||
expect([element.width, element.height]).toEqual(dimensions);
|
resize(rectangle, handle, move);
|
||||||
expect([element.x, element.y]).toEqual(topLeft);
|
const element = h.elements[0];
|
||||||
});
|
expect([element.width, element.height]).toEqual(dimensions);
|
||||||
|
expect([element.x, element.y]).toEqual(topLeft);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
it.each`
|
it.each`
|
||||||
handle | move | dimensions | topLeft
|
handle | move | dimensions | topLeft
|
||||||
@ -61,8 +64,8 @@ describe("resize rectangle ellipses and diamond elements", () => {
|
|||||||
${"sw"} | ${[40, -20]} | ${[80, 80]} | ${[20, 0]}
|
${"sw"} | ${[40, -20]} | ${[80, 80]} | ${[20, 0]}
|
||||||
`(
|
`(
|
||||||
"resizes with fixed side ratios on handle $handle",
|
"resizes with fixed side ratios on handle $handle",
|
||||||
({ handle, move, dimensions, topLeft }) => {
|
async ({ handle, move, dimensions, topLeft }) => {
|
||||||
render(<App />);
|
await render(<App />);
|
||||||
const rectangle = UI.createElement("rectangle", elemData);
|
const rectangle = UI.createElement("rectangle", elemData);
|
||||||
resize(rectangle, handle, move, { shift: true });
|
resize(rectangle, handle, move, { shift: true });
|
||||||
const element = h.elements[0];
|
const element = h.elements[0];
|
||||||
@ -79,8 +82,8 @@ describe("resize rectangle ellipses and diamond elements", () => {
|
|||||||
${"n"} | ${[_, 150]} | ${[50, 50]} | ${[25, 100]}
|
${"n"} | ${[_, 150]} | ${[50, 50]} | ${[25, 100]}
|
||||||
`(
|
`(
|
||||||
"Flips while resizing and keeping side ratios on handle $handle",
|
"Flips while resizing and keeping side ratios on handle $handle",
|
||||||
({ handle, move, dimensions, topLeft }) => {
|
async ({ handle, move, dimensions, topLeft }) => {
|
||||||
render(<App />);
|
await render(<App />);
|
||||||
const rectangle = UI.createElement("rectangle", elemData);
|
const rectangle = UI.createElement("rectangle", elemData);
|
||||||
resize(rectangle, handle, move, { shift: true });
|
resize(rectangle, handle, move, { shift: true });
|
||||||
const element = h.elements[0];
|
const element = h.elements[0];
|
||||||
@ -95,8 +98,8 @@ describe("resize rectangle ellipses and diamond elements", () => {
|
|||||||
${"s"} | ${[_, -20]} | ${[100, 60]} | ${[0, 20]}
|
${"s"} | ${[_, -20]} | ${[100, 60]} | ${[0, 20]}
|
||||||
`(
|
`(
|
||||||
"Resizes from center on handle $handle",
|
"Resizes from center on handle $handle",
|
||||||
({ handle, move, dimensions, topLeft }) => {
|
async ({ handle, move, dimensions, topLeft }) => {
|
||||||
render(<App />);
|
await render(<App />);
|
||||||
const rectangle = UI.createElement("rectangle", elemData);
|
const rectangle = UI.createElement("rectangle", elemData);
|
||||||
resize(rectangle, handle, move, { alt: true });
|
resize(rectangle, handle, move, { alt: true });
|
||||||
const element = h.elements[0];
|
const element = h.elements[0];
|
||||||
@ -111,8 +114,8 @@ describe("resize rectangle ellipses and diamond elements", () => {
|
|||||||
${"e"} | ${[-130, _]} | ${[160, 160]} | ${[-30, -30]}
|
${"e"} | ${[-130, _]} | ${[160, 160]} | ${[-30, -30]}
|
||||||
`(
|
`(
|
||||||
"Resizes from center, flips and keeps side rations on handle $handle",
|
"Resizes from center, flips and keeps side rations on handle $handle",
|
||||||
({ handle, move, dimensions, topLeft }) => {
|
async ({ handle, move, dimensions, topLeft }) => {
|
||||||
render(<App />);
|
await render(<App />);
|
||||||
const rectangle = UI.createElement("rectangle", elemData);
|
const rectangle = UI.createElement("rectangle", elemData);
|
||||||
resize(rectangle, handle, move, { alt: true, shift: true });
|
resize(rectangle, handle, move, { alt: true, shift: true });
|
||||||
const element = h.elements[0];
|
const element = h.elements[0];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user