tldraw
Version:
A tiny little drawing editor.
1,335 lines (1,334 loc) • 46.2 kB
JavaScript
import { jsx } from "react/jsx-runtime";
import {
Box,
DefaultColorStyle,
DefaultFillStyle,
HALF_PI,
PageRecordType,
Vec,
approximately,
compact,
createShapeId,
openWindow,
useEditor
} from "@tldraw/editor";
import * as React from "react";
import { kickoutOccludedShapes } from "../../tools/SelectTool/selectHelpers.mjs";
import { fitFrameToContent, removeFrame } from "../../utils/frames/frames.mjs";
import { EditLinkDialog } from "../components/EditLinkDialog.mjs";
import { EmbedDialog } from "../components/EmbedDialog.mjs";
import { flattenShapesToImages } from "../hooks/useFlatten.mjs";
import { useShowCollaborationUi } from "../hooks/useIsMultiplayer.mjs";
import { useDefaultHelpers } from "../overrides.mjs";
import { useUiEvents } from "./events.mjs";
const ActionsContext = React.createContext(null);
function makeActions(actions) {
return Object.fromEntries(actions.map((action) => [action.id, action]));
}
function getExportName(editor, defaultName) {
const selectedShapes = editor.getSelectedShapes();
if (selectedShapes.length === 0) {
return editor.getDocumentSettings().name || defaultName;
}
return void 0;
}
function ActionsProvider({ overrides, children }) {
const editor = useEditor();
const showCollaborationUi = useShowCollaborationUi();
const helpers = useDefaultHelpers();
const trackEvent = useUiEvents();
const defaultDocumentName = helpers.msg("document.default-name");
const actions = React.useMemo(() => {
function mustGoBackToSelectToolFirst() {
if (!editor.isIn("select")) {
editor.complete();
editor.setCurrentTool("select");
return false;
}
return false;
}
function canApplySelectionAction() {
return editor.isIn("select") && editor.getSelectedShapeIds().length > 0;
}
const actionItems = [
{
id: "edit-link",
label: "action.edit-link",
icon: "link",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("edit-link", { source });
editor.markHistoryStoppingPoint("edit-link");
helpers.addDialog({ component: EditLinkDialog });
}
},
{
id: "insert-embed",
label: "action.insert-embed",
kbd: "$i",
onSelect(source) {
trackEvent("insert-embed", { source });
helpers.addDialog({ component: EmbedDialog });
}
},
{
id: "insert-media",
label: "action.insert-media",
kbd: "$u",
onSelect(source) {
trackEvent("insert-media", { source });
helpers.insertMedia();
}
},
{
id: "undo",
label: "action.undo",
icon: "undo",
kbd: "$z",
onSelect(source) {
trackEvent("undo", { source });
editor.undo();
}
},
{
id: "redo",
label: "action.redo",
icon: "redo",
kbd: "$!z",
onSelect(source) {
trackEvent("redo", { source });
editor.redo();
}
},
{
id: "export-as-svg",
label: {
default: "action.export-as-svg",
menu: "action.export-as-svg.short",
["context-menu"]: "action.export-as-svg.short"
},
readonlyOk: true,
onSelect(source) {
let ids = editor.getSelectedShapeIds();
if (ids.length === 0) ids = Array.from(editor.getCurrentPageShapeIds().values());
if (ids.length === 0) return;
trackEvent("export-as", { format: "svg", source });
helpers.exportAs(ids, "svg", getExportName(editor, defaultDocumentName));
}
},
{
id: "export-as-png",
label: {
default: "action.export-as-png",
menu: "action.export-as-png.short",
["context-menu"]: "action.export-as-png.short"
},
readonlyOk: true,
onSelect(source) {
let ids = editor.getSelectedShapeIds();
if (ids.length === 0) ids = Array.from(editor.getCurrentPageShapeIds().values());
if (ids.length === 0) return;
trackEvent("export-as", { format: "png", source });
helpers.exportAs(ids, "png", getExportName(editor, defaultDocumentName));
}
},
{
id: "export-as-json",
label: {
default: "action.export-as-json",
menu: "action.export-as-json.short",
["context-menu"]: "action.export-as-json.short"
},
readonlyOk: true,
onSelect(source) {
let ids = editor.getSelectedShapeIds();
if (ids.length === 0) ids = Array.from(editor.getCurrentPageShapeIds().values());
if (ids.length === 0) return;
trackEvent("export-as", { format: "json", source });
helpers.exportAs(ids, "json", getExportName(editor, defaultDocumentName));
}
},
{
id: "export-all-as-svg",
label: {
default: "action.export-all-as-svg",
menu: "action.export-all-as-svg.short",
["context-menu"]: "action.export-all-as-svg.short"
},
readonlyOk: true,
onSelect(source) {
let ids = editor.getSelectedShapeIds();
if (ids.length === 0) ids = Array.from(editor.getCurrentPageShapeIds().values());
if (ids.length === 0) return;
trackEvent("export-all-as", { format: "svg", source });
helpers.exportAs(
Array.from(editor.getCurrentPageShapeIds()),
"svg",
getExportName(editor, defaultDocumentName)
);
}
},
{
id: "export-all-as-png",
label: {
default: "action.export-all-as-png",
menu: "action.export-all-as-png.short",
["context-menu"]: "action.export-all-as-png.short"
},
readonlyOk: true,
onSelect(source) {
const ids = Array.from(editor.getCurrentPageShapeIds().values());
if (ids.length === 0) return;
trackEvent("export-all-as", { format: "png", source });
helpers.exportAs(ids, "png", getExportName(editor, defaultDocumentName));
}
},
{
id: "export-all-as-json",
label: {
default: "action.export-all-as-json",
menu: "action.export-all-as-json.short",
["context-menu"]: "action.export-all-as-json.short"
},
readonlyOk: true,
onSelect(source) {
const ids = Array.from(editor.getCurrentPageShapeIds().values());
if (ids.length === 0) return;
trackEvent("export-all-as", { format: "json", source });
helpers.exportAs(ids, "json", getExportName(editor, defaultDocumentName));
}
},
{
id: "copy-as-svg",
label: {
default: "action.copy-as-svg",
menu: "action.copy-as-svg.short",
["context-menu"]: "action.copy-as-svg.short"
},
kbd: "$!c",
readonlyOk: true,
onSelect(source) {
let ids = editor.getSelectedShapeIds();
if (ids.length === 0) ids = Array.from(editor.getCurrentPageShapeIds().values());
if (ids.length === 0) return;
trackEvent("copy-as", { format: "svg", source });
helpers.copyAs(ids, "svg");
}
},
{
id: "copy-as-png",
label: {
default: "action.copy-as-png",
menu: "action.copy-as-png.short",
["context-menu"]: "action.copy-as-png.short"
},
readonlyOk: true,
onSelect(source) {
let ids = editor.getSelectedShapeIds();
if (ids.length === 0) ids = Array.from(editor.getCurrentPageShapeIds().values());
if (ids.length === 0) return;
trackEvent("copy-as", { format: "png", source });
helpers.copyAs(ids, "png");
}
},
{
id: "copy-as-json",
label: {
default: "action.copy-as-json",
menu: "action.copy-as-json.short",
["context-menu"]: "action.copy-as-json.short"
},
readonlyOk: true,
onSelect(source) {
let ids = editor.getSelectedShapeIds();
if (ids.length === 0) ids = Array.from(editor.getCurrentPageShapeIds().values());
if (ids.length === 0) return;
trackEvent("copy-as", { format: "json", source });
helpers.copyAs(ids, "json");
}
},
{
id: "toggle-auto-size",
label: "action.toggle-auto-size",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("toggle-auto-size", { source });
editor.markHistoryStoppingPoint("toggling auto size");
const shapes = editor.getSelectedShapes().filter(
(shape) => editor.isShapeOfType(shape, "text") && shape.props.autoSize === false
);
editor.updateShapes(
shapes.map((shape) => {
return {
id: shape.id,
type: shape.type,
props: {
...shape.props,
w: 8,
autoSize: true
}
};
})
);
kickoutOccludedShapes(
editor,
shapes.map((shape) => shape.id)
);
}
},
{
id: "open-embed-link",
label: "action.open-embed-link",
readonlyOk: true,
onSelect(source) {
trackEvent("open-embed-link", { source });
const ids = editor.getSelectedShapeIds();
const warnMsg = "No embed shapes selected";
if (ids.length !== 1) {
console.error(warnMsg);
return;
}
const shape = editor.getShape(ids[0]);
if (!shape || !editor.isShapeOfType(shape, "embed")) {
console.error(warnMsg);
return;
}
openWindow(shape.props.url, "_blank");
}
},
{
id: "select-zoom-tool",
readonlyOk: true,
kbd: "z",
onSelect(source) {
if (editor.root.getCurrent()?.id === "zoom") return;
trackEvent("zoom-tool", { source });
if (!(editor.inputs.shiftKey || editor.inputs.ctrlKey)) {
const currentTool = editor.root.getCurrent();
if (currentTool && currentTool.getCurrent()?.id === "idle") {
editor.setCurrentTool("zoom", { onInteractionEnd: currentTool.id, maskAs: "zoom" });
}
}
}
},
{
id: "convert-to-bookmark",
label: "action.convert-to-bookmark",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
editor.run(() => {
trackEvent("convert-to-bookmark", { source });
const shapes = editor.getSelectedShapes();
const createList = [];
const deleteList = [];
for (const shape of shapes) {
if (!shape || !editor.isShapeOfType(shape, "embed") || !shape.props.url)
continue;
const newPos = new Vec(shape.x, shape.y);
newPos.rot(-shape.rotation);
newPos.add(new Vec(shape.props.w / 2 - 300 / 2, shape.props.h / 2 - 320 / 2));
newPos.rot(shape.rotation);
const partial = {
id: createShapeId(),
type: "bookmark",
rotation: shape.rotation,
x: newPos.x,
y: newPos.y,
opacity: 1,
props: {
url: shape.props.url
}
};
createList.push(partial);
deleteList.push(shape.id);
}
editor.markHistoryStoppingPoint("convert shapes to bookmark");
editor.deleteShapes(deleteList);
editor.createShapes(createList);
});
}
},
{
id: "convert-to-embed",
label: "action.convert-to-embed",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("convert-to-embed", { source });
editor.run(() => {
const ids = editor.getSelectedShapeIds();
const shapes = compact(ids.map((id) => editor.getShape(id)));
const createList = [];
const deleteList = [];
for (const shape of shapes) {
if (!editor.isShapeOfType(shape, "bookmark")) continue;
const { url } = shape.props;
const embedInfo = helpers.getEmbedDefinition(url);
if (!embedInfo) continue;
if (!embedInfo.definition) continue;
const { width, height } = embedInfo.definition;
const newPos = new Vec(shape.x, shape.y);
newPos.rot(-shape.rotation);
newPos.add(new Vec(shape.props.w / 2 - width / 2, shape.props.h / 2 - height / 2));
newPos.rot(shape.rotation);
const shapeToCreate = {
id: createShapeId(),
type: "embed",
x: newPos.x,
y: newPos.y,
rotation: shape.rotation,
props: {
url,
w: width,
h: height
}
};
createList.push(shapeToCreate);
deleteList.push(shape.id);
}
editor.markHistoryStoppingPoint("convert shapes to embed");
editor.deleteShapes(deleteList);
editor.createShapes(createList);
});
}
},
{
id: "duplicate",
kbd: "$d",
label: "action.duplicate",
icon: "duplicate",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("duplicate-shapes", { source });
const instanceState = editor.getInstanceState();
let ids;
let offset;
if (instanceState.duplicateProps) {
ids = instanceState.duplicateProps.shapeIds;
offset = instanceState.duplicateProps.offset;
} else {
ids = editor.getSelectedShapeIds();
const commonBounds = Box.Common(compact(ids.map((id) => editor.getShapePageBounds(id))));
offset = editor.getCameraOptions().isLocked ? {
// same as the adjacent note margin
x: editor.options.adjacentShapeMargin,
y: editor.options.adjacentShapeMargin
} : {
x: commonBounds.width + editor.options.adjacentShapeMargin,
y: 0
};
}
editor.markHistoryStoppingPoint("duplicate shapes");
editor.duplicateShapes(ids, offset);
if (instanceState.duplicateProps) {
editor.updateInstanceState({
duplicateProps: {
...instanceState.duplicateProps,
shapeIds: editor.getSelectedShapeIds()
}
});
}
}
},
{
id: "ungroup",
label: "action.ungroup",
kbd: "$!g",
icon: "ungroup",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("ungroup-shapes", { source });
editor.markHistoryStoppingPoint("ungroup");
editor.ungroupShapes(editor.getSelectedShapeIds());
}
},
{
id: "group",
label: "action.group",
kbd: "$g",
icon: "group",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("group-shapes", { source });
const onlySelectedShape = editor.getOnlySelectedShape();
if (onlySelectedShape && editor.isShapeOfType(onlySelectedShape, "group")) {
editor.markHistoryStoppingPoint("ungroup");
editor.ungroupShapes(editor.getSelectedShapeIds());
} else {
editor.markHistoryStoppingPoint("group");
editor.groupShapes(editor.getSelectedShapeIds());
}
}
},
{
id: "remove-frame",
label: "action.remove-frame",
kbd: "$!f",
onSelect(source) {
if (!canApplySelectionAction()) return;
trackEvent("remove-frame", { source });
const selectedShapes = editor.getSelectedShapes();
if (selectedShapes.length > 0 && selectedShapes.every((shape) => editor.isShapeOfType(shape, "frame"))) {
editor.markHistoryStoppingPoint("remove-frame");
removeFrame(
editor,
selectedShapes.map((shape) => shape.id)
);
}
}
},
{
id: "fit-frame-to-content",
label: "action.fit-frame-to-content",
onSelect(source) {
if (!canApplySelectionAction()) return;
trackEvent("fit-frame-to-content", { source });
const onlySelectedShape = editor.getOnlySelectedShape();
if (onlySelectedShape && editor.isShapeOfType(onlySelectedShape, "frame")) {
editor.markHistoryStoppingPoint("fit-frame-to-content");
fitFrameToContent(editor, onlySelectedShape.id);
}
}
},
{
id: "align-left",
label: "action.align-left",
kbd: "?A",
icon: "align-left",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("align-shapes", { operation: "left", source });
editor.markHistoryStoppingPoint("align left");
const selectedShapeIds = editor.getSelectedShapeIds();
editor.alignShapes(selectedShapeIds, "left");
kickoutOccludedShapes(editor, selectedShapeIds);
}
},
{
id: "align-center-horizontal",
label: {
default: "action.align-center-horizontal",
["context-menu"]: "action.align-center-horizontal.short"
},
kbd: "?H",
icon: "align-center-horizontal",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("align-shapes", { operation: "center-horizontal", source });
editor.markHistoryStoppingPoint("align center horizontal");
const selectedShapeIds = editor.getSelectedShapeIds();
editor.alignShapes(selectedShapeIds, "center-horizontal");
kickoutOccludedShapes(editor, selectedShapeIds);
}
},
{
id: "align-right",
label: "action.align-right",
kbd: "?D",
icon: "align-right",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("align-shapes", { operation: "right", source });
editor.markHistoryStoppingPoint("align right");
const selectedShapeIds = editor.getSelectedShapeIds();
editor.alignShapes(selectedShapeIds, "right");
kickoutOccludedShapes(editor, selectedShapeIds);
}
},
{
id: "align-center-vertical",
label: {
default: "action.align-center-vertical",
["context-menu"]: "action.align-center-vertical.short"
},
kbd: "?V",
icon: "align-center-vertical",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("align-shapes", { operation: "center-vertical", source });
editor.markHistoryStoppingPoint("align center vertical");
const selectedShapeIds = editor.getSelectedShapeIds();
editor.alignShapes(selectedShapeIds, "center-vertical");
kickoutOccludedShapes(editor, selectedShapeIds);
}
},
{
id: "align-top",
label: "action.align-top",
icon: "align-top",
kbd: "?W",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("align-shapes", { operation: "top", source });
editor.markHistoryStoppingPoint("align top");
const selectedShapeIds = editor.getSelectedShapeIds();
editor.alignShapes(selectedShapeIds, "top");
kickoutOccludedShapes(editor, selectedShapeIds);
}
},
{
id: "align-bottom",
label: "action.align-bottom",
icon: "align-bottom",
kbd: "?S",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("align-shapes", { operation: "bottom", source });
editor.markHistoryStoppingPoint("align bottom");
const selectedShapeIds = editor.getSelectedShapeIds();
editor.alignShapes(selectedShapeIds, "bottom");
kickoutOccludedShapes(editor, selectedShapeIds);
}
},
{
id: "distribute-horizontal",
label: {
default: "action.distribute-horizontal",
["context-menu"]: "action.distribute-horizontal.short"
},
icon: "distribute-horizontal",
kbd: "?!h",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("distribute-shapes", { operation: "horizontal", source });
editor.markHistoryStoppingPoint("distribute horizontal");
const selectedShapeIds = editor.getSelectedShapeIds();
editor.distributeShapes(selectedShapeIds, "horizontal");
kickoutOccludedShapes(editor, selectedShapeIds);
}
},
{
id: "distribute-vertical",
label: {
default: "action.distribute-vertical",
["context-menu"]: "action.distribute-vertical.short"
},
icon: "distribute-vertical",
kbd: "?!V",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("distribute-shapes", { operation: "vertical", source });
editor.markHistoryStoppingPoint("distribute vertical");
const selectedShapeIds = editor.getSelectedShapeIds();
editor.distributeShapes(selectedShapeIds, "vertical");
kickoutOccludedShapes(editor, selectedShapeIds);
}
},
{
id: "stretch-horizontal",
label: {
default: "action.stretch-horizontal",
["context-menu"]: "action.stretch-horizontal.short"
},
icon: "stretch-horizontal",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("stretch-shapes", { operation: "horizontal", source });
editor.markHistoryStoppingPoint("stretch horizontal");
const selectedShapeIds = editor.getSelectedShapeIds();
editor.stretchShapes(selectedShapeIds, "horizontal");
kickoutOccludedShapes(editor, selectedShapeIds);
}
},
{
id: "stretch-vertical",
label: {
default: "action.stretch-vertical",
["context-menu"]: "action.stretch-vertical.short"
},
icon: "stretch-vertical",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("stretch-shapes", { operation: "vertical", source });
editor.markHistoryStoppingPoint("stretch vertical");
const selectedShapeIds = editor.getSelectedShapeIds();
editor.stretchShapes(selectedShapeIds, "vertical");
kickoutOccludedShapes(editor, selectedShapeIds);
}
},
{
id: "flip-horizontal",
label: {
default: "action.flip-horizontal",
["context-menu"]: "action.flip-horizontal.short"
},
kbd: "!h",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("flip-shapes", { operation: "horizontal", source });
editor.markHistoryStoppingPoint("flip horizontal");
const selectedShapeIds = editor.getSelectedShapeIds();
editor.flipShapes(selectedShapeIds, "horizontal");
kickoutOccludedShapes(editor, selectedShapeIds);
}
},
{
id: "flip-vertical",
label: { default: "action.flip-vertical", ["context-menu"]: "action.flip-vertical.short" },
kbd: "!v",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("flip-shapes", { operation: "vertical", source });
editor.markHistoryStoppingPoint("flip vertical");
const selectedShapeIds = editor.getSelectedShapeIds();
editor.flipShapes(selectedShapeIds, "vertical");
kickoutOccludedShapes(editor, selectedShapeIds);
}
},
{
id: "pack",
label: "action.pack",
icon: "pack",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("pack-shapes", { source });
editor.markHistoryStoppingPoint("pack");
const selectedShapeIds = editor.getSelectedShapeIds();
editor.packShapes(selectedShapeIds, editor.options.adjacentShapeMargin);
kickoutOccludedShapes(editor, selectedShapeIds);
}
},
{
id: "stack-vertical",
label: {
default: "action.stack-vertical",
["context-menu"]: "action.stack-vertical.short"
},
icon: "stack-vertical",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("stack-shapes", { operation: "vertical", source });
editor.markHistoryStoppingPoint("stack-vertical");
const selectedShapeIds = editor.getSelectedShapeIds();
editor.stackShapes(selectedShapeIds, "vertical", 16);
kickoutOccludedShapes(editor, selectedShapeIds);
}
},
{
id: "stack-horizontal",
label: {
default: "action.stack-horizontal",
["context-menu"]: "action.stack-horizontal.short"
},
icon: "stack-horizontal",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("stack-shapes", { operation: "horizontal", source });
editor.markHistoryStoppingPoint("stack-horizontal");
const selectedShapeIds = editor.getSelectedShapeIds();
editor.stackShapes(selectedShapeIds, "horizontal", 16);
kickoutOccludedShapes(editor, selectedShapeIds);
}
},
{
id: "bring-to-front",
label: "action.bring-to-front",
kbd: "]",
icon: "bring-to-front",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("reorder-shapes", { operation: "toFront", source });
editor.markHistoryStoppingPoint("bring to front");
editor.bringToFront(editor.getSelectedShapeIds());
}
},
{
id: "bring-forward",
label: "action.bring-forward",
icon: "bring-forward",
kbd: "?]",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("reorder-shapes", { operation: "forward", source });
editor.markHistoryStoppingPoint("bring forward");
editor.bringForward(editor.getSelectedShapeIds());
}
},
{
id: "send-backward",
label: "action.send-backward",
icon: "send-backward",
kbd: "?[",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("reorder-shapes", { operation: "backward", source });
editor.markHistoryStoppingPoint("send backward");
editor.sendBackward(editor.getSelectedShapeIds());
}
},
{
id: "send-to-back",
label: "action.send-to-back",
icon: "send-to-back",
kbd: "[",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("reorder-shapes", { operation: "toBack", source });
editor.markHistoryStoppingPoint("send to back");
editor.sendToBack(editor.getSelectedShapeIds());
}
},
{
id: "cut",
label: "action.cut",
kbd: "$x",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
editor.markHistoryStoppingPoint("cut");
helpers.cut(source);
}
},
{
id: "copy",
label: "action.copy",
kbd: "$c",
readonlyOk: true,
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
helpers.copy(source);
}
},
{
id: "paste",
label: "action.paste",
kbd: "$v",
onSelect(source) {
navigator.clipboard?.read().then((clipboardItems) => {
helpers.paste(
clipboardItems,
source,
source === "context-menu" ? editor.inputs.currentPagePoint : void 0
);
}).catch(() => {
helpers.addToast({
title: helpers.msg("action.paste-error-title"),
description: helpers.msg("action.paste-error-description"),
severity: "error"
});
});
}
},
{
id: "select-all",
label: "action.select-all",
kbd: "$a",
readonlyOk: true,
onSelect(source) {
editor.run(() => {
if (mustGoBackToSelectToolFirst()) return;
trackEvent("select-all-shapes", { source });
editor.markHistoryStoppingPoint("select all kbd");
editor.selectAll();
});
}
},
{
id: "select-none",
label: "action.select-none",
readonlyOk: true,
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("select-none-shapes", { source });
editor.markHistoryStoppingPoint("select none");
editor.selectNone();
}
},
{
id: "delete",
label: "action.delete",
kbd: "\u232B,del,backspace",
icon: "trash",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("delete-shapes", { source });
editor.markHistoryStoppingPoint("delete");
editor.deleteShapes(editor.getSelectedShapeIds());
}
},
{
id: "rotate-cw",
label: "action.rotate-cw",
icon: "rotate-cw",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("rotate-cw", { source });
editor.markHistoryStoppingPoint("rotate-cw");
const offset = editor.getSelectionRotation() % (HALF_PI / 2);
const dontUseOffset = approximately(offset, 0) || approximately(offset, HALF_PI / 2);
const selectedShapeIds = editor.getSelectedShapeIds();
editor.rotateShapesBy(selectedShapeIds, HALF_PI / 2 - (dontUseOffset ? 0 : offset));
kickoutOccludedShapes(editor, selectedShapeIds);
}
},
{
id: "rotate-ccw",
label: "action.rotate-ccw",
icon: "rotate-ccw",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("rotate-ccw", { source });
editor.markHistoryStoppingPoint("rotate-ccw");
const offset = editor.getSelectionRotation() % (HALF_PI / 2);
const offsetCloseToZero = approximately(offset, 0);
const selectedShapeIds = editor.getSelectedShapeIds();
editor.rotateShapesBy(selectedShapeIds, offsetCloseToZero ? -(HALF_PI / 2) : -offset);
kickoutOccludedShapes(editor, selectedShapeIds);
}
},
{
id: "zoom-in",
label: "action.zoom-in",
kbd: "$=,=",
readonlyOk: true,
onSelect(source) {
trackEvent("zoom-in", { source });
editor.zoomIn(void 0, {
animation: { duration: editor.options.animationMediumMs }
});
}
},
{
id: "zoom-out",
label: "action.zoom-out",
kbd: "$-,-",
readonlyOk: true,
onSelect(source) {
trackEvent("zoom-out", { source });
editor.zoomOut(void 0, {
animation: { duration: editor.options.animationMediumMs }
});
}
},
{
id: "zoom-to-100",
label: "action.zoom-to-100",
icon: "reset-zoom",
kbd: "!0",
readonlyOk: true,
onSelect(source) {
trackEvent("reset-zoom", { source });
editor.resetZoom(void 0, {
animation: { duration: editor.options.animationMediumMs }
});
}
},
{
id: "zoom-to-fit",
label: "action.zoom-to-fit",
kbd: "!1",
readonlyOk: true,
onSelect(source) {
trackEvent("zoom-to-fit", { source });
editor.zoomToFit({ animation: { duration: editor.options.animationMediumMs } });
}
},
{
id: "zoom-to-selection",
label: "action.zoom-to-selection",
kbd: "!2",
readonlyOk: true,
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("zoom-to-selection", { source });
editor.zoomToSelection({ animation: { duration: editor.options.animationMediumMs } });
}
},
{
id: "toggle-snap-mode",
label: {
default: "action.toggle-snap-mode",
menu: "action.toggle-snap-mode.menu"
},
onSelect(source) {
trackEvent("toggle-snap-mode", { source });
editor.user.updateUserPreferences({ isSnapMode: !editor.user.getIsSnapMode() });
},
checkbox: true
},
{
id: "toggle-dark-mode",
label: {
default: "action.toggle-dark-mode",
menu: "action.toggle-dark-mode.menu"
},
kbd: "$/",
readonlyOk: true,
onSelect(source) {
const value = editor.user.getIsDarkMode() ? "light" : "dark";
trackEvent("color-scheme", { source, value });
editor.user.updateUserPreferences({
colorScheme: value
});
},
checkbox: true
},
{
id: "toggle-wrap-mode",
label: {
default: "action.toggle-wrap-mode",
menu: "action.toggle-wrap-mode.menu"
},
readonlyOk: true,
onSelect(source) {
trackEvent("toggle-wrap-mode", { source });
editor.user.updateUserPreferences({
isWrapMode: !editor.user.getIsWrapMode()
});
},
checkbox: true
},
{
id: "toggle-dynamic-size-mode",
label: {
default: "action.toggle-dynamic-size-mode",
menu: "action.toggle-dynamic-size-mode.menu"
},
readonlyOk: false,
onSelect(source) {
trackEvent("toggle-dynamic-size-mode", { source });
editor.user.updateUserPreferences({
isDynamicSizeMode: !editor.user.getIsDynamicResizeMode()
});
},
checkbox: true
},
{
id: "toggle-paste-at-cursor",
label: {
default: "action.toggle-paste-at-cursor",
menu: "action.toggle-paste-at-cursor.menu"
},
readonlyOk: false,
onSelect(source) {
trackEvent("toggle-paste-at-cursor", { source });
editor.user.updateUserPreferences({
isPasteAtCursorMode: !editor.user.getIsPasteAtCursorMode()
});
},
checkbox: true
},
{
id: "toggle-reduce-motion",
label: {
default: "action.toggle-reduce-motion",
menu: "action.toggle-reduce-motion.menu"
},
readonlyOk: true,
onSelect(source) {
trackEvent("toggle-reduce-motion", { source });
editor.user.updateUserPreferences({
animationSpeed: editor.user.getAnimationSpeed() === 0 ? 1 : 0
});
},
checkbox: true
},
{
id: "toggle-edge-scrolling",
label: {
default: "action.toggle-edge-scrolling",
menu: "action.toggle-edge-scrolling.menu"
},
readonlyOk: true,
onSelect(source) {
trackEvent("toggle-edge-scrolling", { source });
editor.user.updateUserPreferences({
edgeScrollSpeed: editor.user.getEdgeScrollSpeed() === 0 ? 1 : 0
});
},
checkbox: true
},
{
id: "toggle-transparent",
label: {
default: "action.toggle-transparent",
menu: "action.toggle-transparent.menu",
["context-menu"]: "action.toggle-transparent.context-menu"
},
readonlyOk: true,
onSelect(source) {
trackEvent("toggle-transparent", { source });
editor.updateInstanceState({
exportBackground: !editor.getInstanceState().exportBackground
});
},
checkbox: true
},
{
id: "toggle-tool-lock",
label: {
default: "action.toggle-tool-lock",
menu: "action.toggle-tool-lock.menu"
},
kbd: "q",
onSelect(source) {
trackEvent("toggle-tool-lock", { source });
editor.updateInstanceState({ isToolLocked: !editor.getInstanceState().isToolLocked });
},
checkbox: true
},
{
id: "unlock-all",
label: "action.unlock-all",
onSelect(source) {
trackEvent("unlock-all", { source });
const updates = [];
for (const shape of editor.getCurrentPageShapes()) {
if (shape.isLocked) {
updates.push({ id: shape.id, type: shape.type, isLocked: false });
}
}
if (updates.length > 0) {
editor.updateShapes(updates);
}
}
},
{
id: "toggle-focus-mode",
label: {
default: "action.toggle-focus-mode",
menu: "action.toggle-focus-mode.menu"
},
readonlyOk: true,
kbd: "$.",
checkbox: true,
onSelect(source) {
editor.timers.requestAnimationFrame(() => {
editor.run(() => {
trackEvent("toggle-focus-mode", { source });
helpers.clearDialogs();
helpers.clearToasts();
editor.updateInstanceState({ isFocusMode: !editor.getInstanceState().isFocusMode });
});
});
}
},
{
id: "toggle-grid",
label: {
default: "action.toggle-grid",
menu: "action.toggle-grid.menu"
},
readonlyOk: true,
kbd: "$'",
onSelect(source) {
trackEvent("toggle-grid-mode", { source });
editor.updateInstanceState({ isGridMode: !editor.getInstanceState().isGridMode });
},
checkbox: true
},
{
id: "toggle-debug-mode",
label: {
default: "action.toggle-debug-mode",
menu: "action.toggle-debug-mode.menu"
},
readonlyOk: true,
onSelect(source) {
trackEvent("toggle-debug-mode", { source });
editor.updateInstanceState({
isDebugMode: !editor.getInstanceState().isDebugMode
});
},
checkbox: true
},
{
id: "print",
label: "action.print",
kbd: "$p",
readonlyOk: true,
onSelect(source) {
trackEvent("print", { source });
helpers.printSelectionOrPages();
}
},
{
id: "exit-pen-mode",
label: "action.exit-pen-mode",
icon: "cross-2",
readonlyOk: true,
onSelect(source) {
trackEvent("exit-pen-mode", { source });
editor.updateInstanceState({ isPenMode: false });
}
},
{
id: "stop-following",
label: "action.stop-following",
icon: "cross-2",
readonlyOk: true,
onSelect(source) {
trackEvent("stop-following", { source });
editor.stopFollowingUser();
}
},
{
id: "back-to-content",
label: "action.back-to-content",
icon: "arrow-left",
readonlyOk: true,
onSelect(source) {
trackEvent("zoom-to-content", { source });
const bounds = editor.getSelectionPageBounds() ?? editor.getCurrentPageBounds();
if (!bounds) return;
editor.zoomToBounds(bounds, {
targetZoom: Math.min(1, editor.getZoomLevel()),
animation: { duration: 220 }
});
}
},
{
id: "toggle-lock",
label: "action.toggle-lock",
kbd: "!l",
onSelect(source) {
editor.markHistoryStoppingPoint("locking");
trackEvent("toggle-lock", { source });
editor.toggleLock(editor.getSelectedShapeIds());
}
},
{
id: "move-to-new-page",
label: "context.pages.new-page",
onSelect(source) {
const newPageId = PageRecordType.createId();
const ids = editor.getSelectedShapeIds();
editor.run(() => {
editor.markHistoryStoppingPoint("move_shapes_to_page");
editor.createPage({
name: helpers.msg("page-menu.new-page-initial-name"),
id: newPageId
});
editor.moveShapesToPage(ids, newPageId);
});
trackEvent("move-to-new-page", { source });
}
},
{
id: "select-white-color",
label: "color-style.white",
kbd: "?t",
onSelect(source) {
const style = DefaultColorStyle;
editor.run(() => {
editor.markHistoryStoppingPoint("change-color");
if (editor.isIn("select")) {
editor.setStyleForSelectedShapes(style, "white");
}
editor.setStyleForNextShapes(style, "white");
});
trackEvent("set-style", { source, id: style.id, value: "white" });
}
},
{
id: "select-fill-fill",
label: "fill-style.fill",
kbd: "?f",
onSelect(source) {
const style = DefaultFillStyle;
editor.run(() => {
editor.markHistoryStoppingPoint("change-fill");
if (editor.isIn("select")) {
editor.setStyleForSelectedShapes(style, "fill");
}
editor.setStyleForNextShapes(style, "fill");
});
trackEvent("set-style", { source, id: style.id, value: "fill" });
}
},
{
id: "flatten-to-image",
label: "action.flatten-to-image",
kbd: "!f",
onSelect: async (source) => {
const ids = editor.getSelectedShapeIds();
if (ids.length === 0) return;
editor.markHistoryStoppingPoint("flattening to image");
trackEvent("flatten-to-image", { source });
const newShapeIds = await flattenShapesToImages(
editor,
ids,
editor.options.flattenImageBoundsExpand
);
if (newShapeIds?.length) {
editor.setSelectedShapes(newShapeIds);
}
}
}
];
if (showCollaborationUi) {
actionItems.push({
id: "open-cursor-chat",
label: "action.open-cursor-chat",
readonlyOk: true,
kbd: "/",
onSelect(source) {
trackEvent("open-cursor-chat", { source });
if (editor.getInstanceState().isCoarsePointer) {
return;
}
editor.timers.requestAnimationFrame(() => {
editor.updateInstanceState({ isChatting: true });
});
}
});
}
const actions2 = makeActions(actionItems);
if (overrides) {
return overrides(editor, actions2, helpers);
}
return actions2;
}, [helpers, editor, trackEvent, overrides, defaultDocumentName, showCollaborationUi]);
return /* @__PURE__ */ jsx(ActionsContext.Provider, { value: asActions(actions), children });
}
function useActions() {
const ctx = React.useContext(ActionsContext);
if (!ctx) {
throw new Error("useTools must be used within a ToolProvider");
}
return ctx;
}
function asActions(actions) {
return actions;
}
function unwrapLabel(label, menuType) {
return label ? typeof label === "string" ? label : menuType ? label[menuType] ?? label["default"] : void 0 : void 0;
}
export {
ActionsContext,
ActionsProvider,
unwrapLabel,
useActions
};
//# sourceMappingURL=actions.mjs.map