tldraw
Version:
A tiny little drawing editor.
449 lines (448 loc) • 14.8 kB
JavaScript
"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