UNPKG

tldraw

Version:

A tiny little drawing editor.

285 lines (284 loc) • 11.9 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; 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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var DefaultImageToolbarContent_exports = {}; __export(DefaultImageToolbarContent_exports, { DefaultImageToolbarContent: () => DefaultImageToolbarContent }); module.exports = __toCommonJS(DefaultImageToolbarContent_exports); var import_jsx_runtime = require("react/jsx-runtime"); var import_editor = require("@tldraw/editor"); var import_react = require("react"); var import_crop = require("../../../shapes/shared/crop"); var import_actions = require("../../context/actions"); var import_events = require("../../context/events"); var import_useTranslation = require("../../hooks/useTranslation/useTranslation"); var import_TldrawUiButtonIcon = require("../primitives/Button/TldrawUiButtonIcon"); var import_TldrawUiButtonLabel = require("../primitives/Button/TldrawUiButtonLabel"); var import_TldrawUiDropdownMenu = require("../primitives/TldrawUiDropdownMenu"); var import_TldrawUiSlider = require("../primitives/TldrawUiSlider"); var import_TldrawUiToolbar = require("../primitives/TldrawUiToolbar"); const DefaultImageToolbarContent = (0, import_editor.track)(function DefaultImageToolbarContent2({ imageShapeId, isManipulating, onEditAltTextStart, onManipulatingStart, onManipulatingEnd }) { const editor = (0, import_editor.useEditor)(); const trackEvent = (0, import_events.useUiEvents)(); const msg = (0, import_useTranslation.useTranslation)(); const source = "image-toolbar"; const sliderRef = (0, import_react.useRef)(null); const isReadonly = editor.getIsReadonly(); const crop = (0, import_editor.useValue)("crop", () => editor.getShape(imageShapeId).props.crop, [ editor, imageShapeId ]); const zoom = crop ? Math.min(1 - (crop.bottomRight.x - crop.topLeft.x), 1 - (crop.bottomRight.y - crop.topLeft.y)) : 0; const [maxZoom, setMaxZoom] = (0, import_react.useState)( crop ? Math.max(zoom, 1 - 1 / import_crop.MAX_ZOOM) : import_crop.MAX_ZOOM ); const actions = (0, import_actions.useActions)(); (0, import_react.useEffect)(() => { setMaxZoom(crop ? Math.max(zoom, 1 - 1 / import_crop.MAX_ZOOM) : import_crop.MAX_ZOOM); }, [crop, zoom, maxZoom]); const onHistoryMark = (0, import_react.useCallback)((id) => editor.markHistoryStoppingPoint(id), [editor]); const easeZoom = (0, import_react.useCallback)((value, maxValue) => { const maxRatioConversion = import_crop.MAX_ZOOM / (import_crop.MAX_ZOOM - 1); return Math.pow(value / maxValue, maxRatioConversion) * maxValue; }, []); const displayValue = crop && maxZoom ? (0, import_editor.modulate)( easeZoom(zoom, maxZoom), [0, maxZoom], [0, 100], true /* clamp */ ) : 0; const handleZoomChange = (0, import_react.useCallback)( (value) => { editor.setCurrentTool("select.crop.idle"); const sliderPercent = value / 100; const maxDimension = 1 - 1 / import_crop.MAX_ZOOM; const clampedMaxZoom = Math.min(maxDimension, maxZoom ?? maxDimension); const maxRatioConversion = import_crop.MAX_ZOOM / (import_crop.MAX_ZOOM - 1); const zOut = Math.pow(sliderPercent, 1 / maxRatioConversion) * clampedMaxZoom; const zoom2 = zOut >= 1 ? 1 : zOut / (2 * (1 - zOut)); const imageShape = editor.getShape(imageShapeId); if (!imageShape) return; const change = (0, import_crop.getCroppedImageDataWhenZooming)(zoom2, imageShape, maxZoom); editor.updateShape({ id: imageShape.id, type: imageShape.type, x: change.x, y: change.y, props: { w: change.w, h: change.h, crop: change.crop } }); trackEvent("set-style", { source: "image-toolbar", id: "zoom", value }); }, [editor, trackEvent, imageShapeId, maxZoom] ); const handleImageReplace = (0, import_react.useCallback)( () => actions["image-replace"].onSelect("image-toolbar"), [actions] ); const handleImageDownload = (0, import_react.useCallback)( () => actions["download-original"].onSelect("image-toolbar"), [actions] ); const handleAspectRatioChange = (aspectRatio) => { const imageShape = editor.getShape(imageShapeId); if (!imageShape) return; editor.run(() => { editor.setCurrentTool("select.crop.idle"); const change = (0, import_crop.getCroppedImageDataForAspectRatio)(aspectRatio, imageShape); editor.markHistoryStoppingPoint("aspect ratio"); editor.updateShape({ id: imageShapeId, type: "image", x: change.x, y: change.y, props: { crop: change.crop, w: change.w, h: change.h } }); (0, import_editor.kickoutOccludedShapes)(editor, [imageShapeId]); }); }; const altText = (0, import_editor.useValue)( "altText", () => editor.getShape(imageShapeId).props.altText, [editor, imageShapeId] ); const shapeAspectRatio = (0, import_editor.useValue)( "shapeAspectRatio", () => { const imageShape = editor.getShape(imageShapeId); return imageShape.props.w / imageShape.props.h; }, [editor, imageShapeId] ); const isOriginalCrop = !crop || (0, import_editor.isEqual)(crop, (0, import_crop.getDefaultCrop)()); (0, import_react.useEffect)(() => { if (isManipulating) { editor.timers.setTimeout(() => sliderRef.current?.focus(), 0); } }, [editor, isManipulating]); (0, import_react.useEffect)(() => { function handleKeyDown(e) { if (isManipulating) { if (e.key === "Escape") { editor.cancel(); onManipulatingEnd(); } else if (e.key === "Enter") { editor.complete(); onManipulatingEnd(); } } } const elm = sliderRef.current; if (elm) { elm.addEventListener("keydown", handleKeyDown); } return () => { if (elm) { elm.removeEventListener("keydown", handleKeyDown); } }; }, [editor, isManipulating, onManipulatingEnd]); if (isManipulating) { return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)( import_TldrawUiSlider.TldrawUiSlider, { ref: sliderRef, value: displayValue, label: "tool.image-zoom", onValueChange: handleZoomChange, onHistoryMark, min: 0, steps: 100, "data-testid": "tool.image-zoom", title: msg("tool.image-zoom") } ), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_TldrawUiDropdownMenu.TldrawUiDropdownMenuRoot, { id: "image-toolbar-aspect-ratio", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_TldrawUiDropdownMenu.TldrawUiDropdownMenuTrigger, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)( import_TldrawUiToolbar.TldrawUiToolbarButton, { title: msg("tool.aspect-ratio"), type: "icon", "data-testid": "tool.image-aspect-ratio", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_TldrawUiButtonIcon.TldrawUiButtonIcon, { icon: "corners" }) } ) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_TldrawUiDropdownMenu.TldrawUiDropdownMenuContent, { side: "top", align: "center", children: import_crop.ASPECT_RATIO_OPTIONS.map((aspectRatio) => { let checked = false; if (isOriginalCrop) { if (aspectRatio === "original") { checked = true; } } else { if (aspectRatio === "circle") { checked = !!crop.isCircle; } else if (aspectRatio === "square") { checked = !crop?.isCircle && (0, import_editor.approximately)(shapeAspectRatio, import_crop.ASPECT_RATIO_TO_VALUE[aspectRatio], 0.1); } else if (aspectRatio === "original") { checked = false; } else { checked = !isOriginalCrop && (0, import_editor.approximately)(shapeAspectRatio, import_crop.ASPECT_RATIO_TO_VALUE[aspectRatio], 0.01); } } return /* @__PURE__ */ (0, import_jsx_runtime.jsx)( import_TldrawUiDropdownMenu.TldrawUiDropdownMenuCheckboxItem, { onSelect: () => handleAspectRatioChange(aspectRatio), checked, title: msg(`tool.aspect-ratio.${aspectRatio}`), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_TldrawUiButtonLabel.TldrawUiButtonLabel, { children: msg(`tool.aspect-ratio.${aspectRatio}`) }) }, aspectRatio ); }) }) ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)( import_TldrawUiToolbar.TldrawUiToolbarButton, { type: "icon", onClick: onManipulatingEnd, "data-testid": "tool.image-crop-confirm", style: { borderLeft: "1px solid var(--tl-color-divider)", marginLeft: "2px" }, title: msg("tool.image-crop-confirm"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_TldrawUiButtonIcon.TldrawUiButtonIcon, { small: true, icon: "check" }) } ) ] }); } return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [ !isReadonly && /* @__PURE__ */ (0, import_jsx_runtime.jsx)( import_TldrawUiToolbar.TldrawUiToolbarButton, { type: "icon", "data-testid": "tool.image-replace", onClick: handleImageReplace, title: msg("tool.replace-media"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_TldrawUiButtonIcon.TldrawUiButtonIcon, { small: true, icon: "tool-media" }) } ), !isReadonly && /* @__PURE__ */ (0, import_jsx_runtime.jsx)( import_TldrawUiToolbar.TldrawUiToolbarButton, { type: "icon", title: msg("tool.image-crop"), onClick: onManipulatingStart, "data-testid": "tool.image-crop", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_TldrawUiButtonIcon.TldrawUiButtonIcon, { small: true, icon: "crop" }) } ), /* @__PURE__ */ (0, import_jsx_runtime.jsx)( import_TldrawUiToolbar.TldrawUiToolbarButton, { type: "icon", title: msg("action.download-original"), onClick: handleImageDownload, "data-testid": "tool.image-download", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_TldrawUiButtonIcon.TldrawUiButtonIcon, { small: true, icon: "download" }) } ), (altText || !isReadonly) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)( import_TldrawUiToolbar.TldrawUiToolbarButton, { type: "icon", title: msg("tool.media-alt-text"), "data-testid": "tool.image-alt-text", onClick: () => { trackEvent("alt-text-start", { source }); onEditAltTextStart(); }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_TldrawUiButtonIcon.TldrawUiButtonIcon, { small: true, icon: "alt" }) } ) ] }); }); //# sourceMappingURL=DefaultImageToolbarContent.js.map