UNPKG

tldraw

Version:

A tiny little drawing editor.

449 lines (448 loc) • 14.8 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 pasteExcalidrawContent_exports = {}; __export(pasteExcalidrawContent_exports, { pasteExcalidrawContent: () => pasteExcalidrawContent }); module.exports = __toCommonJS(pasteExcalidrawContent_exports); var import_editor = require("@tldraw/editor"); async function pasteExcalidrawContent(editor, clipboard, point) { const { elements, files } = clipboard; const tldrawContent = { shapes: [], bindings: [], rootShapeIds: [], assets: [], schema: editor.store.schema.serialize() }; const groupShapeIdToChildren = /* @__PURE__ */ new Map(); const rotatedElements = /* @__PURE__ */ new Map(); const currentPageId = editor.getCurrentPageId(); const excElementIdsToTldrawShapeIds = /* @__PURE__ */ new Map(); const rootShapeIds = []; const skipIds = /* @__PURE__ */ new Set(); elements.forEach((element) => { excElementIdsToTldrawShapeIds.set(element.id, (0, import_editor.createShapeId)()); if (element.boundElements !== null) { for (const boundElement of element.boundElements) { if (boundElement.type === "text") { skipIds.add(boundElement.id); } } } }); let index = import_editor.ZERO_INDEX_KEY; for (const element of elements) { if (skipIds.has(element.id)) { continue; } const id = excElementIdsToTldrawShapeIds.get(element.id); const base = { id, typeName: "shape", parentId: currentPageId, index, x: element.x, y: element.y, rotation: 0, isLocked: element.locked, opacity: getOpacity(element.opacity), meta: {} }; if (element.angle !== 0) { rotatedElements.set(id, element.angle); } if (element.groupIds && element.groupIds.length > 0) { if (groupShapeIdToChildren.has(element.groupIds[0])) { groupShapeIdToChildren.get(element.groupIds[0])?.push(id); } else { groupShapeIdToChildren.set(element.groupIds[0], [id]); } } else { rootShapeIds.push(id); } switch (element.type) { case "rectangle": case "ellipse": case "diamond": { let text = ""; let align = "middle"; if (element.boundElements !== null) { for (const boundElement of element.boundElements) { if (boundElement.type === "text") { const labelElement = elements.find((elm) => elm.id === boundElement.id); if (labelElement) { text = labelElement.text; align = textAlignToAlignTypes[labelElement.textAlign]; } } } } const colorToUse = element.backgroundColor === "transparent" ? element.strokeColor : element.backgroundColor; tldrawContent.shapes.push({ ...base, type: "geo", props: { geo: element.type, url: element.link ?? "", w: element.width, h: element.height, size: strokeWidthsToSizes[element.strokeWidth] ?? "draw", color: colorsToColors[colorToUse] ?? "black", text, align, dash: getDash(element), fill: getFill(element) } }); break; } case "freedraw": { tldrawContent.shapes.push({ ...base, type: "draw", props: { dash: getDash(element), size: strokeWidthsToSizes[element.strokeWidth], color: colorsToColors[element.strokeColor] ?? "black", segments: [ { type: "free", points: element.points.map(([x, y, z = 0.5]) => ({ x, y, z })) } ] } }); break; } case "line": { const points = element.points.slice(); if (points.length < 2) { break; } const indices = (0, import_editor.getIndices)(element.points.length); tldrawContent.shapes.push({ ...base, type: "line", props: { dash: getDash(element), size: strokeWidthsToSizes[element.strokeWidth], color: colorsToColors[element.strokeColor] ?? "black", spline: element.roundness ? "cubic" : "line", points: { ...Object.fromEntries( element.points.map(([x, y], i) => { const index2 = indices[i]; return [index2, { id: index2, index: index2, x, y }]; }) ) } } }); break; } case "arrow": { let text = ""; if (element.boundElements !== null) { for (const boundElement of element.boundElements) { if (boundElement.type === "text") { const labelElement = elements.find((elm) => elm.id === boundElement.id); if (labelElement) { text = labelElement.text; } } } } const start = element.points[0]; const end = element.points[element.points.length - 1]; const startTargetId = excElementIdsToTldrawShapeIds.get(element.startBinding?.elementId); const endTargetId = excElementIdsToTldrawShapeIds.get(element.endBinding?.elementId); tldrawContent.shapes.push({ ...base, type: "arrow", props: { text, bend: getBend(element, start, end), dash: getDash(element), size: strokeWidthsToSizes[element.strokeWidth] ?? "m", color: colorsToColors[element.strokeColor] ?? "black", start: { x: start[0], y: start[1] }, end: { x: end[0], y: end[1] }, arrowheadEnd: arrowheadsToArrowheadTypes[element.endArrowhead] ?? "none", arrowheadStart: arrowheadsToArrowheadTypes[element.startArrowhead] ?? "none" } }); if (startTargetId) { tldrawContent.bindings.push({ id: (0, import_editor.createBindingId)(), typeName: "binding", type: "arrow", fromId: id, toId: startTargetId, props: { terminal: "start", normalizedAnchor: { x: 0.5, y: 0.5 }, isPrecise: false, isExact: false }, meta: {} }); } if (endTargetId) { tldrawContent.bindings.push({ id: (0, import_editor.createBindingId)(), typeName: "binding", type: "arrow", fromId: id, toId: endTargetId, props: { terminal: "end", normalizedAnchor: { x: 0.5, y: 0.5 }, isPrecise: false, isExact: false }, meta: {} }); } break; } case "text": { const { size, scale } = getFontSizeAndScale(element.fontSize); tldrawContent.shapes.push({ ...base, type: "text", props: { size, scale, font: fontFamilyToFontType[element.fontFamily] ?? "draw", color: colorsToColors[element.strokeColor] ?? "black", text: element.text, textAlign: textAlignToTextAlignTypes[element.textAlign] } }); break; } case "image": { const file = files[element.fileId]; if (!file) break; const assetId = import_editor.AssetRecordType.createId(); tldrawContent.assets.push({ id: assetId, typeName: "asset", type: "image", props: { w: element.width, h: element.height, fileSize: file.size, name: element.id ?? "Untitled", isAnimated: false, mimeType: file.mimeType, src: file.dataURL }, meta: {} }); tldrawContent.shapes.push({ ...base, type: "image", props: { w: element.width, h: element.height, assetId } }); } } index = (0, import_editor.getIndexAbove)(index); } const p = point ?? (editor.inputs.shiftKey ? editor.inputs.currentPagePoint : void 0); editor.markHistoryStoppingPoint("paste"); editor.putContentOntoCurrentPage(tldrawContent, { point: p, select: false, preserveIds: true }); for (const groupedShapeIds of groupShapeIdToChildren.values()) { if (groupedShapeIds.length > 1) { editor.groupShapes(groupedShapeIds); const groupShape = editor.getShape(groupedShapeIds[0]); if (groupShape?.parentId && (0, import_editor.isShapeId)(groupShape.parentId)) { rootShapeIds.push(groupShape.parentId); } } } for (const [id, angle] of rotatedElements) { editor.select(id); editor.rotateShapesBy([id], angle); } const rootShapes = (0, import_editor.compact)(rootShapeIds.map((id) => editor.getShape(id))); const bounds = import_editor.Box.Common(rootShapes.map((s) => editor.getShapePageBounds(s))); const viewPortCenter = editor.getViewportPageBounds().center; editor.updateShapes( rootShapes.map((s) => { const delta = { x: (s.x ?? 0) - (bounds.x + bounds.w / 2), y: (s.y ?? 0) - (bounds.y + bounds.h / 2) }; return { id: s.id, type: s.type, x: viewPortCenter.x + delta.x, y: viewPortCenter.y + delta.y }; }) ); editor.setSelectedShapes(rootShapeIds); } const getOpacity = (opacity) => { const t = opacity / 100; if (t < 0.2) { return 0.1; } else if (t < 0.4) { return 0.25; } else if (t < 0.6) { return 0.5; } else if (t < 0.8) { return 0.75; } return 1; }; const strokeWidthsToSizes = { 1: "s", 2: "m", 3: "l", 4: "xl" }; const fontSizesToSizes = { 16: "s", 20: "m", 28: "l", 36: "xl" }; function getFontSizeAndScale(fontSize) { const size = fontSizesToSizes[fontSize]; if (size) { return { size, scale: 1 }; } if (fontSize < 16) { return { size: "s", scale: fontSize / 16 }; } if (fontSize > 36) { return { size: "xl", scale: fontSize / 36 }; } return { size: "m", scale: 1 }; } const fontFamilyToFontType = { 1: "draw", 2: "sans", 3: "mono" }; const oc = { gray: ["#f8f9fa", "#e9ecef", "#ced4da", "#868e96", "#343a40"], red: ["#fff5f5", "#ffc9c9", "#ff8787", "#fa5252", "#e03131"], pink: ["#fff0f6", "#fcc2d7", "#f783ac", "#e64980", "#c2255c"], grape: ["#f8f0fc", "#eebefa", "#da77f2", "#be4bdb", "#9c36b5"], violet: ["#f3f0ff", "#d0bfff", "#9775fa", "#7950f2", "#6741d9"], indigo: ["#edf2ff", "#bac8ff", "#748ffc", "#4c6ef5", "#3b5bdb"], blue: ["#e7f5ff", "#a5d8ff", "#4dabf7", "#228be6", "#1971c2"], cyan: ["#e3fafc", "#99e9f2", "#3bc9db", "#15aabf", "#0c8599"], teal: ["#e6fcf5", "#96f2d7", "#38d9a9", "#12b886", "#099268"], green: ["#ebfbee", "#b2f2bb", "#69db7c", "#40c057", "#2f9e44"], lime: ["#f4fce3", "#d8f5a2", "#a9e34b", "#82c91e", "#66a80f"], yellow: ["#fff9db", "#ffec99", "#ffd43b", "#fab005", "#f08c00"], orange: ["#fff4e6", "#ffd8a8", "#ffa94d", "#fd7e14", "#e8590c"] }; function mapExcalidrawColorToTldrawColors(excalidrawColor, light, dark) { const colors = [0, 1, 2, 3, 4].map((index) => oc[excalidrawColor][index]); return Object.fromEntries(colors.map((c, i) => [c, i < 3 ? light : dark])); } const colorsToColors = { ...mapExcalidrawColorToTldrawColors("gray", "grey", "black"), ...mapExcalidrawColorToTldrawColors("red", "light-red", "red"), ...mapExcalidrawColorToTldrawColors("pink", "light-red", "red"), ...mapExcalidrawColorToTldrawColors("grape", "light-violet", "violet"), ...mapExcalidrawColorToTldrawColors("blue", "light-blue", "blue"), ...mapExcalidrawColorToTldrawColors("cyan", "light-blue", "blue"), ...mapExcalidrawColorToTldrawColors("teal", "light-green", "green"), ...mapExcalidrawColorToTldrawColors("green", "light-green", "green"), ...mapExcalidrawColorToTldrawColors("yellow", "yellow", "orange"), ...mapExcalidrawColorToTldrawColors("orange", "yellow", "orange"), "#ffffff": "white", "#000000": "black" }; const strokeStylesToStrokeTypes = { solid: "draw", dashed: "dashed", dotted: "dotted" }; const fillStylesToFillType = { "cross-hatch": "pattern", hachure: "pattern", solid: "solid" }; const textAlignToAlignTypes = { left: "start", center: "middle", right: "end" }; const textAlignToTextAlignTypes = { left: "start", center: "middle", right: "end" }; const arrowheadsToArrowheadTypes = { arrow: "arrow", dot: "dot", triangle: "triangle", bar: "pipe" }; function getBend(element, startPoint, endPoint) { let bend = 0; if (element.points.length > 2) { const start = new import_editor.Vec(startPoint[0], startPoint[1]); const end = new import_editor.Vec(endPoint[0], endPoint[1]); const handle = new import_editor.Vec(element.points[1][0], element.points[1][1]); const delta = import_editor.Vec.Sub(end, start); const v = import_editor.Vec.Per(delta); const med = import_editor.Vec.Med(end, start); const A = import_editor.Vec.Sub(med, v); const B = import_editor.Vec.Add(med, v); const point = import_editor.Vec.NearestPointOnLineSegment(A, B, handle, false); bend = import_editor.Vec.Dist(point, med); if (import_editor.Vec.Clockwise(point, end, med)) bend *= -1; } return bend; } const getDash = (element) => { let dash = strokeStylesToStrokeTypes[element.strokeStyle] ?? "draw"; if (dash === "draw" && element.roughness === 0) { dash = "solid"; } return dash; }; const getFill = (element) => { if (element.backgroundColor === "transparent") { return "none"; } return fillStylesToFillType[element.fillStyle] ?? "solid"; }; //# sourceMappingURL=pasteExcalidrawContent.js.map