|  | @@ -3,7 +3,28 @@ import React from "react";
 | 
	
		
			
				|  |  |  import { RoughCanvas } from "roughjs/bin/canvas";
 | 
	
		
			
				|  |  |  import rough from "roughjs/bin/rough";
 | 
	
		
			
				|  |  |  import "../actions";
 | 
	
		
			
				|  |  | -import { actionDeleteSelected, actionFinalize } from "../actions";
 | 
	
		
			
				|  |  | +import {
 | 
	
		
			
				|  |  | +  actionAddToLibrary,
 | 
	
		
			
				|  |  | +  actionBringForward,
 | 
	
		
			
				|  |  | +  actionBringToFront,
 | 
	
		
			
				|  |  | +  actionCopy,
 | 
	
		
			
				|  |  | +  actionCopyAsPng,
 | 
	
		
			
				|  |  | +  actionCopyAsSvg,
 | 
	
		
			
				|  |  | +  actionCopyStyles,
 | 
	
		
			
				|  |  | +  actionCut,
 | 
	
		
			
				|  |  | +  actionDeleteSelected,
 | 
	
		
			
				|  |  | +  actionDuplicateSelection,
 | 
	
		
			
				|  |  | +  actionFinalize,
 | 
	
		
			
				|  |  | +  actionGroup,
 | 
	
		
			
				|  |  | +  actionPasteStyles,
 | 
	
		
			
				|  |  | +  actionSelectAll,
 | 
	
		
			
				|  |  | +  actionSendBackward,
 | 
	
		
			
				|  |  | +  actionSendToBack,
 | 
	
		
			
				|  |  | +  actionToggleGridMode,
 | 
	
		
			
				|  |  | +  actionToggleStats,
 | 
	
		
			
				|  |  | +  actionToggleZenMode,
 | 
	
		
			
				|  |  | +  actionUngroup,
 | 
	
		
			
				|  |  | +} from "../actions";
 | 
	
		
			
				|  |  |  import { createRedoAction, createUndoAction } from "../actions/actionHistory";
 | 
	
		
			
				|  |  |  import { ActionManager } from "../actions/manager";
 | 
	
		
			
				|  |  |  import { actions } from "../actions/register";
 | 
	
	
		
			
				|  | @@ -18,7 +39,6 @@ import {
 | 
	
		
			
				|  |  |  } from "../clipboard";
 | 
	
		
			
				|  |  |  import {
 | 
	
		
			
				|  |  |    APP_NAME,
 | 
	
		
			
				|  |  | -  CANVAS_ONLY_ACTIONS,
 | 
	
		
			
				|  |  |    CURSOR_TYPE,
 | 
	
		
			
				|  |  |    DEFAULT_VERTICAL_ALIGN,
 | 
	
		
			
				|  |  |    DRAGGING_THRESHOLD,
 | 
	
	
		
			
				|  | @@ -26,7 +46,6 @@ import {
 | 
	
		
			
				|  |  |    ELEMENT_TRANSLATE_AMOUNT,
 | 
	
		
			
				|  |  |    ENV,
 | 
	
		
			
				|  |  |    EVENT,
 | 
	
		
			
				|  |  | -  GRID_SIZE,
 | 
	
		
			
				|  |  |    LINE_CONFIRM_THRESHOLD,
 | 
	
		
			
				|  |  |    MIME_TYPES,
 | 
	
		
			
				|  |  |    POINTER_BUTTON,
 | 
	
	
		
			
				|  | @@ -314,6 +333,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
 | 
	
		
			
				|  |  |        this.syncActionResult,
 | 
	
		
			
				|  |  |        () => this.state,
 | 
	
		
			
				|  |  |        () => this.scene.getElementsIncludingDeleted(),
 | 
	
		
			
				|  |  | +      this,
 | 
	
		
			
				|  |  |      );
 | 
	
		
			
				|  |  |      this.actionManager.registerAll(actions);
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -927,25 +947,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  private copyToClipboardAsSvg = async () => {
 | 
	
		
			
				|  |  | -    const selectedElements = getSelectedElements(
 | 
	
		
			
				|  |  | -      this.scene.getElements(),
 | 
	
		
			
				|  |  | -      this.state,
 | 
	
		
			
				|  |  | -    );
 | 
	
		
			
				|  |  | -    try {
 | 
	
		
			
				|  |  | -      await exportCanvas(
 | 
	
		
			
				|  |  | -        "clipboard-svg",
 | 
	
		
			
				|  |  | -        selectedElements.length ? selectedElements : this.scene.getElements(),
 | 
	
		
			
				|  |  | -        this.state,
 | 
	
		
			
				|  |  | -        this.canvas!,
 | 
	
		
			
				|  |  | -        this.state,
 | 
	
		
			
				|  |  | -      );
 | 
	
		
			
				|  |  | -    } catch (error) {
 | 
	
		
			
				|  |  | -      console.error(error);
 | 
	
		
			
				|  |  | -      this.setState({ errorMessage: error.message });
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -  };
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |    private static resetTapTwice() {
 | 
	
		
			
				|  |  |      didTapTwice = false;
 | 
	
		
			
				|  |  |    }
 | 
	
	
		
			
				|  | @@ -1148,15 +1149,11 @@ class App extends React.Component<ExcalidrawProps, AppState> {
 | 
	
		
			
				|  |  |    };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    toggleZenMode = () => {
 | 
	
		
			
				|  |  | -    this.setState({
 | 
	
		
			
				|  |  | -      zenModeEnabled: !this.state.zenModeEnabled,
 | 
	
		
			
				|  |  | -    });
 | 
	
		
			
				|  |  | +    this.actionManager.executeAction(actionToggleZenMode);
 | 
	
		
			
				|  |  |    };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    toggleGridMode = () => {
 | 
	
		
			
				|  |  | -    this.setState({
 | 
	
		
			
				|  |  | -      gridSize: this.state.gridSize ? null : GRID_SIZE,
 | 
	
		
			
				|  |  | -    });
 | 
	
		
			
				|  |  | +    this.actionManager.executeAction(actionToggleGridMode);
 | 
	
		
			
				|  |  |    };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    toggleStats = () => {
 | 
	
	
		
			
				|  | @@ -3618,52 +3615,52 @@ class App extends React.Component<ExcalidrawProps, AppState> {
 | 
	
		
			
				|  |  |        this.state,
 | 
	
		
			
				|  |  |      );
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    const maybeGroupAction = actionGroup.contextItemPredicate!(
 | 
	
		
			
				|  |  | +      this.actionManager.getElementsIncludingDeleted(),
 | 
	
		
			
				|  |  | +      this.actionManager.getAppState(),
 | 
	
		
			
				|  |  | +    );
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    const maybeUngroupAction = actionUngroup.contextItemPredicate!(
 | 
	
		
			
				|  |  | +      this.actionManager.getElementsIncludingDeleted(),
 | 
	
		
			
				|  |  | +      this.actionManager.getAppState(),
 | 
	
		
			
				|  |  | +    );
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    const separator = "separator";
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      const elements = this.scene.getElements();
 | 
	
		
			
				|  |  |      const element = this.getElementAtPosition(x, y);
 | 
	
		
			
				|  |  |      if (!element) {
 | 
	
		
			
				|  |  |        ContextMenu.push({
 | 
	
		
			
				|  |  |          options: [
 | 
	
		
			
				|  |  |            navigator.clipboard && {
 | 
	
		
			
				|  |  | -            shortcutName: "paste",
 | 
	
		
			
				|  |  | -            label: t("labels.paste"),
 | 
	
		
			
				|  |  | -            action: () => this.pasteFromClipboard(null),
 | 
	
		
			
				|  |  | +            name: "paste",
 | 
	
		
			
				|  |  | +            perform: (elements, appStates) => {
 | 
	
		
			
				|  |  | +              this.pasteFromClipboard(null);
 | 
	
		
			
				|  |  | +              return {
 | 
	
		
			
				|  |  | +                commitToHistory: false,
 | 
	
		
			
				|  |  | +              };
 | 
	
		
			
				|  |  | +            },
 | 
	
		
			
				|  |  | +            contextItemLabel: "labels.paste",
 | 
	
		
			
				|  |  |            },
 | 
	
		
			
				|  |  | +          separator,
 | 
	
		
			
				|  |  |            probablySupportsClipboardBlob &&
 | 
	
		
			
				|  |  | -            elements.length > 0 && {
 | 
	
		
			
				|  |  | -              shortcutName: "copyAsPng",
 | 
	
		
			
				|  |  | -              label: t("labels.copyAsPng"),
 | 
	
		
			
				|  |  | -              action: this.copyToClipboardAsPng,
 | 
	
		
			
				|  |  | -            },
 | 
	
		
			
				|  |  | +            elements.length > 0 &&
 | 
	
		
			
				|  |  | +            actionCopyAsPng,
 | 
	
		
			
				|  |  |            probablySupportsClipboardWriteText &&
 | 
	
		
			
				|  |  | -            elements.length > 0 && {
 | 
	
		
			
				|  |  | -              shortcutName: "copyAsSvg",
 | 
	
		
			
				|  |  | -              label: t("labels.copyAsSvg"),
 | 
	
		
			
				|  |  | -              action: this.copyToClipboardAsSvg,
 | 
	
		
			
				|  |  | -            },
 | 
	
		
			
				|  |  | -          ...this.actionManager.getContextMenuItems((action) =>
 | 
	
		
			
				|  |  | -            CANVAS_ONLY_ACTIONS.includes(action.name),
 | 
	
		
			
				|  |  | -          ),
 | 
	
		
			
				|  |  | -          {
 | 
	
		
			
				|  |  | -            checked: this.state.gridSize !== null,
 | 
	
		
			
				|  |  | -            shortcutName: "gridMode",
 | 
	
		
			
				|  |  | -            label: t("labels.gridMode"),
 | 
	
		
			
				|  |  | -            action: this.toggleGridMode,
 | 
	
		
			
				|  |  | -          },
 | 
	
		
			
				|  |  | -          {
 | 
	
		
			
				|  |  | -            checked: this.state.zenModeEnabled,
 | 
	
		
			
				|  |  | -            shortcutName: "zenMode",
 | 
	
		
			
				|  |  | -            label: t("buttons.zenMode"),
 | 
	
		
			
				|  |  | -            action: this.toggleZenMode,
 | 
	
		
			
				|  |  | -          },
 | 
	
		
			
				|  |  | -          {
 | 
	
		
			
				|  |  | -            checked: this.state.showStats,
 | 
	
		
			
				|  |  | -            shortcutName: "stats",
 | 
	
		
			
				|  |  | -            label: t("stats.title"),
 | 
	
		
			
				|  |  | -            action: this.toggleStats,
 | 
	
		
			
				|  |  | -          },
 | 
	
		
			
				|  |  | +            elements.length > 0 &&
 | 
	
		
			
				|  |  | +            actionCopyAsSvg,
 | 
	
		
			
				|  |  | +          ((probablySupportsClipboardBlob && elements.length > 0) ||
 | 
	
		
			
				|  |  | +            (probablySupportsClipboardWriteText && elements.length > 0)) &&
 | 
	
		
			
				|  |  | +            separator,
 | 
	
		
			
				|  |  | +          actionSelectAll,
 | 
	
		
			
				|  |  | +          separator,
 | 
	
		
			
				|  |  | +          actionToggleGridMode,
 | 
	
		
			
				|  |  | +          actionToggleZenMode,
 | 
	
		
			
				|  |  | +          actionToggleStats,
 | 
	
		
			
				|  |  |          ],
 | 
	
		
			
				|  |  |          top: clientY,
 | 
	
		
			
				|  |  |          left: clientX,
 | 
	
		
			
				|  |  | +        actionManager: this.actionManager,
 | 
	
		
			
				|  |  |        });
 | 
	
		
			
				|  |  |        return;
 | 
	
		
			
				|  |  |      }
 | 
	
	
		
			
				|  | @@ -3674,37 +3671,41 @@ class App extends React.Component<ExcalidrawProps, AppState> {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      ContextMenu.push({
 | 
	
		
			
				|  |  |        options: [
 | 
	
		
			
				|  |  | -        {
 | 
	
		
			
				|  |  | -          shortcutName: "cut",
 | 
	
		
			
				|  |  | -          label: t("labels.cut"),
 | 
	
		
			
				|  |  | -          action: this.cutAll,
 | 
	
		
			
				|  |  | -        },
 | 
	
		
			
				|  |  | +        actionCut,
 | 
	
		
			
				|  |  | +        navigator.clipboard && actionCopy,
 | 
	
		
			
				|  |  |          navigator.clipboard && {
 | 
	
		
			
				|  |  | -          shortcutName: "copy",
 | 
	
		
			
				|  |  | -          label: t("labels.copy"),
 | 
	
		
			
				|  |  | -          action: this.copyAll,
 | 
	
		
			
				|  |  | -        },
 | 
	
		
			
				|  |  | -        navigator.clipboard && {
 | 
	
		
			
				|  |  | -          shortcutName: "paste",
 | 
	
		
			
				|  |  | -          label: t("labels.paste"),
 | 
	
		
			
				|  |  | -          action: () => this.pasteFromClipboard(null),
 | 
	
		
			
				|  |  | -        },
 | 
	
		
			
				|  |  | -        probablySupportsClipboardBlob && {
 | 
	
		
			
				|  |  | -          shortcutName: "copyAsPng",
 | 
	
		
			
				|  |  | -          label: t("labels.copyAsPng"),
 | 
	
		
			
				|  |  | -          action: this.copyToClipboardAsPng,
 | 
	
		
			
				|  |  | -        },
 | 
	
		
			
				|  |  | -        probablySupportsClipboardWriteText && {
 | 
	
		
			
				|  |  | -          shortcutName: "copyAsSvg",
 | 
	
		
			
				|  |  | -          label: t("labels.copyAsSvg"),
 | 
	
		
			
				|  |  | -          action: this.copyToClipboardAsSvg,
 | 
	
		
			
				|  |  | +          name: "paste",
 | 
	
		
			
				|  |  | +          perform: (elements, appStates) => {
 | 
	
		
			
				|  |  | +            this.pasteFromClipboard(null);
 | 
	
		
			
				|  |  | +            return {
 | 
	
		
			
				|  |  | +              commitToHistory: false,
 | 
	
		
			
				|  |  | +            };
 | 
	
		
			
				|  |  | +          },
 | 
	
		
			
				|  |  | +          contextItemLabel: "labels.paste",
 | 
	
		
			
				|  |  |          },
 | 
	
		
			
				|  |  | -        ...this.actionManager.getContextMenuItems(
 | 
	
		
			
				|  |  | -          (action) => !CANVAS_ONLY_ACTIONS.includes(action.name),
 | 
	
		
			
				|  |  | -        ),
 | 
	
		
			
				|  |  | +        separator,
 | 
	
		
			
				|  |  | +        probablySupportsClipboardBlob && actionCopyAsPng,
 | 
	
		
			
				|  |  | +        probablySupportsClipboardWriteText && actionCopyAsSvg,
 | 
	
		
			
				|  |  | +        separator,
 | 
	
		
			
				|  |  | +        actionCopyStyles,
 | 
	
		
			
				|  |  | +        actionPasteStyles,
 | 
	
		
			
				|  |  | +        separator,
 | 
	
		
			
				|  |  | +        maybeGroupAction && actionGroup,
 | 
	
		
			
				|  |  | +        maybeUngroupAction && actionUngroup,
 | 
	
		
			
				|  |  | +        (maybeGroupAction || maybeUngroupAction) && separator,
 | 
	
		
			
				|  |  | +        actionAddToLibrary,
 | 
	
		
			
				|  |  | +        separator,
 | 
	
		
			
				|  |  | +        actionSendBackward,
 | 
	
		
			
				|  |  | +        actionBringForward,
 | 
	
		
			
				|  |  | +        actionSendToBack,
 | 
	
		
			
				|  |  | +        actionBringToFront,
 | 
	
		
			
				|  |  | +        separator,
 | 
	
		
			
				|  |  | +        actionDuplicateSelection,
 | 
	
		
			
				|  |  | +        actionDeleteSelected,
 | 
	
		
			
				|  |  |        ],
 | 
	
		
			
				|  |  |        top: clientY,
 | 
	
		
			
				|  |  |        left: clientX,
 | 
	
		
			
				|  |  | +      actionManager: this.actionManager,
 | 
	
		
			
				|  |  |      });
 | 
	
		
			
				|  |  |    };
 | 
	
		
			
				|  |  |  
 |