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