tldraw
Version:
A tiny little drawing editor.
1,383 lines (1,382 loc) • 59.7 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var actions_exports = {};
__export(actions_exports, {
ActionsContext: () => ActionsContext,
ActionsProvider: () => ActionsProvider,
supportsDownloadingOriginal: () => supportsDownloadingOriginal,
unwrapLabel: () => unwrapLabel,
useActions: () => useActions
});
module.exports = __toCommonJS(actions_exports);
var import_jsx_runtime = require("react/jsx-runtime");
var import_editor = require("@tldraw/editor");
var React = __toESM(require("react"), 1);
var import_bookmarks = require("../../shapes/bookmark/bookmarks");
var import_frames = require("../../utils/frames/frames");
var import_A11y = require("../components/A11y");
var import_EditLinkDialog = require("../components/EditLinkDialog");
var import_EmbedDialog = require("../components/EmbedDialog");
var import_DefaultKeyboardShortcutsDialog = require("../components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialog");
var import_useCollaborationStatus = require("../hooks/useCollaborationStatus");
var import_useFlatten = require("../hooks/useFlatten");
var import_useTranslation = require("../hooks/useTranslation/useTranslation");
var import_overrides = require("../overrides");
var import_a11y = require("./a11y");
var import_components = require("./components");
var import_events = require("./events");
const ActionsContext = React.createContext(null);
function supportsDownloadingOriginal(shape, editor) {
return (editor.isShapeOfType(shape, "image") || editor.isShapeOfType(shape, "video")) && !!shape.props.assetId;
}
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 = (0, import_editor.useMaybeEditor)();
const showCollaborationUi = (0, import_useCollaborationStatus.useShowCollaborationUi)();
const helpers = (0, import_overrides.useDefaultHelpers)();
const components = (0, import_components.useTldrawUiComponents)();
const trackEvent = (0, import_events.useUiEvents)();
const a11y = (0, import_a11y.useA11y)();
const msg = (0, import_useTranslation.useTranslation)();
const defaultDocumentName = helpers.msg("document.default-name");
const actions = React.useMemo(() => {
const editor = _editor;
if (!editor) return {};
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;
}
function scaleShapes(scaleFactor) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
editor.markHistoryStoppingPoint("resize shapes");
const selectedShapeIds = editor.getSelectedShapeIds();
if (selectedShapeIds.length === 0) return;
editor.run(() => {
const shapes = selectedShapeIds.map((id) => editor.getShape(id)).filter(Boolean);
shapes.forEach((shape) => {
editor.resizeShape(shape.id, new import_editor.Vec(scaleFactor, scaleFactor), {
scaleOrigin: editor.getSelectionPageBounds()?.center
});
});
});
}
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: import_EditLinkDialog.EditLinkDialog });
}
},
{
id: "insert-embed",
label: "action.insert-embed",
kbd: "cmd+i,ctrl+i",
onSelect(source) {
trackEvent("insert-embed", { source });
helpers.addDialog({ component: import_EmbedDialog.EmbedDialog });
}
},
{
id: "open-kbd-shortcuts",
label: "action.open-kbd-shortcuts",
kbd: "cmd+alt+/,ctrl+alt+/",
onSelect(source) {
trackEvent("open-kbd-shortcuts", { source });
helpers.addDialog({
component: components.KeyboardShortcutsDialog ?? import_DefaultKeyboardShortcutsDialog.DefaultKeyboardShortcutsDialog
});
}
},
{
id: "insert-media",
label: "action.insert-media",
kbd: "cmd+u,ctrl+u",
onSelect(source) {
trackEvent("insert-media", { source });
helpers.insertMedia();
}
},
{
id: "undo",
label: "action.undo",
icon: "undo",
kbd: "cmd+z,ctrl+z",
onSelect(source) {
trackEvent("undo", { source });
editor.undo();
}
},
{
id: "redo",
label: "action.redo",
icon: "redo",
kbd: "cmd+shift+z,ctrl+shift+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, { format: "svg", name: 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, { format: "png", name: 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()), {
format: "svg",
name: 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, { format: "png", name: 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: "cmd+shift+c,ctrl+shift+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: "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");
editor.run(() => {
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
}
};
})
);
(0, import_editor.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;
}
(0, import_editor.openWindow)(shape.props.url, "_blank");
}
},
{
id: "select-zoom-tool",
label: "action.select-zoom-tool",
readonlyOk: true,
kbd: "z, !z",
onSelect(source) {
if (editor.inputs.getAccelKey()) return;
const path = editor.getPath();
if (!path.endsWith(".idle")) return;
if (editor.root.getCurrent()?.id === "zoom") return;
trackEvent("zoom-tool", { source });
editor.setCurrentTool("zoom", {
onInteractionEnd: path,
maskAs: "zoom"
});
}
},
{
id: "convert-to-bookmark",
label: "action.convert-to-bookmark",
async onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("convert-to-bookmark", { source });
const shapes = editor.getSelectedShapes();
const markId = editor.markHistoryStoppingPoint("convert shapes to bookmark");
const creationPromises = [];
for (const shape of shapes) {
if (!shape || !editor.isShapeOfType(shape, "embed") || !shape.props.url) continue;
const center = editor.getShapePageBounds(shape)?.center;
if (!center) continue;
editor.deleteShapes([shape.id]);
creationPromises.push(
(0, import_bookmarks.createBookmarkFromUrl)(editor, { url: shape.props.url, center }).then((res) => {
if (!res.ok) {
throw new Error(res.error);
}
return res;
})
);
}
await Promise.all(creationPromises).catch((error) => {
editor.bailToMark(markId);
console.error(error);
});
}
},
{
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 = (0, import_editor.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 import_editor.Vec(shape.x, shape.y);
newPos.rot(-shape.rotation);
newPos.add(new import_editor.Vec(shape.props.w / 2 - width / 2, shape.props.h / 2 - height / 2));
newPos.rot(shape.rotation);
const shapeToCreate = {
id: (0, import_editor.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: "cmd+d,ctrl+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 = import_editor.Box.Common((0, import_editor.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: "cmd+shift+g,ctrl+shift+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: "cmd+g,ctrl+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: "cmd+shift+f,ctrl+shift+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");
(0, import_frames.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");
(0, import_frames.fitFrameToContent)(editor, onlySelectedShape.id);
}
}
},
{
id: "align-left",
label: "action.align-left",
kbd: "alt+A",
icon: "align-left",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("align-shapes", { operation: "left", source });
editor.markHistoryStoppingPoint("align left");
editor.run(() => {
const selectedShapeIds = editor.getSelectedShapeIds();
editor.alignShapes(selectedShapeIds, "left");
(0, import_editor.kickoutOccludedShapes)(editor, selectedShapeIds);
});
}
},
{
id: "align-center-horizontal",
label: {
default: "action.align-center-horizontal",
["context-menu"]: "action.align-center-horizontal.short"
},
kbd: "alt+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");
editor.run(() => {
const selectedShapeIds = editor.getSelectedShapeIds();
editor.alignShapes(selectedShapeIds, "center-horizontal");
(0, import_editor.kickoutOccludedShapes)(editor, selectedShapeIds);
});
}
},
{
id: "align-right",
label: "action.align-right",
kbd: "alt+D",
icon: "align-right",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("align-shapes", { operation: "right", source });
editor.markHistoryStoppingPoint("align right");
editor.run(() => {
const selectedShapeIds = editor.getSelectedShapeIds();
editor.alignShapes(selectedShapeIds, "right");
(0, import_editor.kickoutOccludedShapes)(editor, selectedShapeIds);
});
}
},
{
id: "align-center-vertical",
label: {
default: "action.align-center-vertical",
["context-menu"]: "action.align-center-vertical.short"
},
kbd: "alt+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");
editor.run(() => {
const selectedShapeIds = editor.getSelectedShapeIds();
editor.alignShapes(selectedShapeIds, "center-vertical");
(0, import_editor.kickoutOccludedShapes)(editor, selectedShapeIds);
});
}
},
{
id: "align-top",
label: "action.align-top",
icon: "align-top",
kbd: "alt+W",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("align-shapes", { operation: "top", source });
editor.markHistoryStoppingPoint("align top");
editor.run(() => {
const selectedShapeIds = editor.getSelectedShapeIds();
editor.alignShapes(selectedShapeIds, "top");
(0, import_editor.kickoutOccludedShapes)(editor, selectedShapeIds);
});
}
},
{
id: "align-bottom",
label: "action.align-bottom",
icon: "align-bottom",
kbd: "alt+S",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("align-shapes", { operation: "bottom", source });
editor.markHistoryStoppingPoint("align bottom");
editor.run(() => {
const selectedShapeIds = editor.getSelectedShapeIds();
editor.alignShapes(selectedShapeIds, "bottom");
(0, import_editor.kickoutOccludedShapes)(editor, selectedShapeIds);
});
}
},
{
id: "distribute-horizontal",
label: {
default: "action.distribute-horizontal",
["context-menu"]: "action.distribute-horizontal.short"
},
icon: "distribute-horizontal",
kbd: "alt+shift+h",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("distribute-shapes", { operation: "horizontal", source });
editor.markHistoryStoppingPoint("distribute horizontal");
editor.run(() => {
const selectedShapeIds = editor.getSelectedShapeIds();
editor.distributeShapes(selectedShapeIds, "horizontal");
(0, import_editor.kickoutOccludedShapes)(editor, selectedShapeIds);
});
}
},
{
id: "distribute-vertical",
label: {
default: "action.distribute-vertical",
["context-menu"]: "action.distribute-vertical.short"
},
icon: "distribute-vertical",
kbd: "alt+shift+V",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("distribute-shapes", { operation: "vertical", source });
editor.markHistoryStoppingPoint("distribute vertical");
editor.run(() => {
const selectedShapeIds = editor.getSelectedShapeIds();
editor.distributeShapes(selectedShapeIds, "vertical");
(0, import_editor.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");
editor.run(() => {
const selectedShapeIds = editor.getSelectedShapeIds();
editor.stretchShapes(selectedShapeIds, "horizontal");
(0, import_editor.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");
editor.run(() => {
const selectedShapeIds = editor.getSelectedShapeIds();
editor.stretchShapes(selectedShapeIds, "vertical");
(0, import_editor.kickoutOccludedShapes)(editor, selectedShapeIds);
});
}
},
{
id: "flip-horizontal",
label: {
default: "action.flip-horizontal",
["context-menu"]: "action.flip-horizontal.short"
},
kbd: "shift+h",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("flip-shapes", { operation: "horizontal", source });
editor.markHistoryStoppingPoint("flip horizontal");
editor.run(() => {
const selectedShapeIds = editor.getSelectedShapeIds();
editor.flipShapes(selectedShapeIds, "horizontal");
(0, import_editor.kickoutOccludedShapes)(editor, selectedShapeIds);
});
}
},
{
id: "flip-vertical",
label: { default: "action.flip-vertical", ["context-menu"]: "action.flip-vertical.short" },
kbd: "shift+v",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
trackEvent("flip-shapes", { operation: "vertical", source });
editor.markHistoryStoppingPoint("flip vertical");
editor.run(() => {
const selectedShapeIds = editor.getSelectedShapeIds();
editor.flipShapes(selectedShapeIds, "vertical");
(0, import_editor.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");
editor.run(() => {
const selectedShapeIds = editor.getSelectedShapeIds();
editor.packShapes(selectedShapeIds, editor.options.adjacentShapeMargin);
(0, import_editor.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");
editor.run(() => {
const selectedShapeIds = editor.getSelectedShapeIds();
editor.stackShapes(selectedShapeIds, "vertical", editor.options.adjacentShapeMargin);
(0, import_editor.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");
editor.run(() => {
const selectedShapeIds = editor.getSelectedShapeIds();
editor.stackShapes(selectedShapeIds, "horizontal", editor.options.adjacentShapeMargin);
(0, import_editor.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: "alt+]",
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: "alt+[",
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: "cmd+x,ctrl+x",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
editor.markHistoryStoppingPoint("cut");
helpers.cut(source);
}
},
{
id: "copy",
label: "action.copy",
kbd: "cmd+c,ctrl+c",
readonlyOk: true,
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
helpers.copy(source);
}
},
{
id: "paste",
label: "action.paste",
kbd: "cmd+v,ctrl+v",
onSelect(source) {
navigator.clipboard?.read().then((clipboardItems) => {
helpers.paste(
clipboardItems,
source,
source === "context-menu" ? editor.inputs.getCurrentPagePoint() : 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: "cmd+a,ctrl+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",
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",
kbd: "shift+.,shift+alt+.",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
const isFine = editor.inputs.getAltKey();
trackEvent("rotate-cw", { source, fine: isFine });
editor.markHistoryStoppingPoint("rotate-cw");
editor.run(() => {
const rotation = import_editor.HALF_PI / (isFine ? 96 : 6);
const offset = editor.getSelectionRotation() % rotation;
const dontUseOffset = (0, import_editor.approximately)(offset, 0) || (0, import_editor.approximately)(offset, rotation);
const selectedShapeIds = editor.getSelectedShapeIds();
editor.rotateShapesBy(selectedShapeIds, rotation - (dontUseOffset ? 0 : offset));
(0, import_editor.kickoutOccludedShapes)(editor, selectedShapeIds);
});
}
},
{
id: "rotate-ccw",
label: "action.rotate-ccw",
icon: "rotate-ccw",
// omg double comma
kbd: "shift+,,shift+alt+,",
onSelect(source) {
if (!canApplySelectionAction()) return;
if (mustGoBackToSelectToolFirst()) return;
const isFine = editor.inputs.getAltKey();
trackEvent("rotate-ccw", { source, fine: isFine });
editor.markHistoryStoppingPoint("rotate-ccw");
editor.run(() => {
const rotation = import_editor.HALF_PI / (isFine ? 96 : 6);
const offset = editor.getSelectionRotation() % rotation;
const offsetCloseToZero = (0, import_editor.approximately)(offset, 0);
const selectedShapeIds = editor.getSelectedShapeIds();
editor.rotateShapesBy(selectedShapeIds, offsetCloseToZero ? -rotation : -offset);
(0, import_editor.kickoutOccludedShapes)(editor, selectedShapeIds);
});
}
},
{
id: "zoom-in",
label: "action.zoom-in",
kbd: "cmd+=,ctrl+=,=",
readonlyOk: true,
onSelect(source) {
trackEvent("zoom-in", { source, towardsCursor: false });
editor.zoomIn(void 0, {
animation: { duration: editor.options.animationMediumMs }
});
}
},
{
id: "zoom-in-on-cursor",
label: "action.zoom-in",
kbd: "shift+cmd+=,shift+ctrl+=,shift+=",
readonlyOk: true,
onSelect(source) {
trackEvent("zoom-in", { source, towardsCursor: true });
editor.zoomIn(editor.inputs.getCurrentScreenPoint(), {
animation: { duration: editor.options.animationMediumMs }
});
}
},
{
id: "zoom-out",
label: "action.zoom-out",
kbd: "cmd+-,ctrl+-,-",
readonlyOk: true,
onSelect(source) {
trackEvent("zoom-out", { source, towardsCursor: false });
editor.zoomOut(void 0, {
animation: { duration: editor.options.animationMediumMs }
});
}
},
{
id: "zoom-out-on-cursor",
label: "action.zoom-out",
kbd: "shift+cmd+-,shift+ctrl+-,shift+-",
readonlyOk: true,
onSelect(source) {
trackEvent("zoom-out", { source, towardsCursor: true });
editor.zoomOut(editor.inputs.getCurrentScreenPoint(), {
animation: { duration: editor.options.animationMediumMs }
});
}
},
{
id: "zoom-to-100",
label: "action.zoom-to-100",
icon: "reset-zoom",
kbd: "shift+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: "shift+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: "shift+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: "cmd+/,ctrl+/",
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-keyboard-shortcuts",
label: {
default: "action.toggle-keyboard-shortcuts",
menu: "action.toggle-keyboard-shortcuts.menu"
},
readonlyOk: true,
onSelect(source) {
trackEvent("toggle-keyboard-shortcuts", { source });
editor.user.updateUserPreferences({
areKeyboardShortcutsEnabled: !editor.user.getAreKeyboardShortcutsEnabled()
});
},
checkbox: true
},
{
id: "enhanced-a11y-mode",
label: {
default: "action.enhanced-a11y-mode",
menu: "action.enhanced-a11y-mode.menu"
},
readonlyOk: true,
onSelect(source) {
trackEvent("enhanced-a11y-mode", { source });
editor.user.updateUserPreferences({
enhancedA11yMode: !editor.user.getEnhancedA11yMode()
});
},
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-invert-zoom",
label: {
default: "action.toggle-invert-zoom",
menu: "action.toggle-invert-zoom.menu"
},
readonlyOk: true,
onSelect(source) {
trackEvent("toggle-invert-zoom", { source });
editor.user.updateUserPreferences({
isZoomDirectionInverted: !editor.user.getIsZoomDirectionInverted()
});
},
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: "cmd+.,ctrl+.",
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: "cmd+',ctrl+'",
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: "cmd+p,ctrl+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: "shift+l",
onSelect(source) {
if (!canApplySelectionAction()) return;
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 = import_editor.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: "alt+t",
onSelect(source) {
const style = import_editor.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: "alt+f",
onSelect(source) {
const style = import_editor.DefaultFillStyle;