tldraw
Version:
A tiny little drawing editor.
307 lines (306 loc) • 9.94 kB
JavaScript
;
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 LineShapeUtil_exports = {};
__export(LineShapeUtil_exports, {
LineShapeUtil: () => LineShapeUtil
});
module.exports = __toCommonJS(LineShapeUtil_exports);
var import_jsx_runtime = require("react/jsx-runtime");
var import_editor = require("@tldraw/editor");
var import_shared = require("../arrow/shared");
var import_PathBuilder = require("../shared/PathBuilder");
var import_useDefaultColorTheme = require("../shared/useDefaultColorTheme");
const handlesCache = new import_editor.WeakCache();
class LineShapeUtil extends import_editor.ShapeUtil {
static type = "line";
static props = import_editor.lineShapeProps;
static migrations = import_editor.lineShapeMigrations;
hideResizeHandles() {
return true;
}
hideRotateHandle() {
return true;
}
hideSelectionBoundsFg() {
return true;
}
hideSelectionBoundsBg() {
return true;
}
hideInMinimap() {
return true;
}
getDefaultProps() {
const [start, end] = (0, import_editor.getIndices)(2);
return {
dash: "draw",
size: "m",
color: "black",
spline: "line",
points: {
[start]: { id: start, index: start, x: 0, y: 0 },
[end]: { id: end, index: end, x: 0.1, y: 0.1 }
},
scale: 1
};
}
getGeometry(shape) {
const geometry = getPathForLineShape(shape).toGeometry();
(0, import_editor.assert)(geometry instanceof import_PathBuilder.PathBuilderGeometry2d);
return geometry;
}
getHandles(shape) {
return handlesCache.get(shape.props, () => {
const spline = this.getGeometry(shape);
const points = linePointsToArray(shape);
const results = points.map((point) => ({
...point,
id: point.index,
type: "vertex",
canSnap: true
}));
for (let i = 0; i < points.length - 1; i++) {
const index = (0, import_editor.getIndexBetween)(points[i].index, points[i + 1].index);
const segment = spline.getSegments()[i];
const point = segment.interpolateAlongEdge(0.5);
results.push({
id: index,
type: "create",
index,
x: point.x,
y: point.y,
canSnap: true
});
}
return results.sort(import_editor.sortByIndex);
});
}
// Events
onResize(shape, info) {
const { scaleX, scaleY } = info;
return {
props: {
points: (0, import_editor.mapObjectMapValues)(shape.props.points, (_, { id, index, x, y }) => ({
id,
index,
x: x * scaleX,
y: y * scaleY
}))
}
};
}
onBeforeCreate(next) {
const {
props: { points }
} = next;
const pointKeys = Object.keys(points);
if (pointKeys.length < 2) {
return;
}
const firstPoint = points[pointKeys[0]];
const allSame = pointKeys.every((key) => {
const point = points[key];
return point.x === firstPoint.x && point.y === firstPoint.y;
});
if (allSame) {
const lastKey = pointKeys[pointKeys.length - 1];
points[lastKey] = {
...points[lastKey],
x: points[lastKey].x + 0.1,
y: points[lastKey].y + 0.1
};
return next;
}
return;
}
onHandleDrag(shape, { handle }) {
const newPoint = (0, import_editor.maybeSnapToGrid)(new import_editor.Vec(handle.x, handle.y), this.editor);
return {
...shape,
props: {
...shape.props,
points: {
...shape.props.points,
[handle.id]: { id: handle.id, index: handle.index, x: newPoint.x, y: newPoint.y }
}
}
};
}
onHandleDragStart(shape, { handle }) {
if (handle.type === "create") {
return {
...shape,
props: {
...shape.props,
points: {
...shape.props.points,
[handle.index]: { id: handle.index, index: handle.index, x: handle.x, y: handle.y }
}
}
};
}
return;
}
component(shape) {
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_editor.SVGContainer, { style: { minWidth: 50, minHeight: 50 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LineShapeSvg, { shape }) });
}
indicator(shape) {
const strokeWidth = import_shared.STROKE_SIZES[shape.props.size] * shape.props.scale;
const path = getPathForLineShape(shape);
const { dash } = shape.props;
return path.toSvg({
style: dash === "draw" ? "draw" : "solid",
strokeWidth: 1,
passes: 1,
randomSeed: shape.id,
offset: 0,
roundness: strokeWidth * 2,
props: { strokeWidth: void 0 }
});
}
useLegacyIndicator() {
return false;
}
getIndicatorPath(shape) {
const strokeWidth = import_shared.STROKE_SIZES[shape.props.size] * shape.props.scale;
const path = getPathForLineShape(shape);
const { dash } = shape.props;
return path.toPath2D({
style: dash === "draw" ? "draw" : "solid",
strokeWidth: 1,
passes: 1,
randomSeed: shape.id,
offset: 0,
roundness: strokeWidth * 2
});
}
toSvg(shape) {
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LineShapeSvg, { shouldScale: true, shape });
}
getHandleSnapGeometry(shape) {
const points = linePointsToArray(shape);
return {
points,
getSelfSnapPoints: (handle) => {
const index = this.getHandles(shape).filter((h) => h.type === "vertex").findIndex((h) => h.id === handle.id);
return points.filter((_, i) => Math.abs(i - index) > 1).map(import_editor.Vec.From);
},
getSelfSnapOutline: (handle) => {
const index = this.getHandles(shape).filter((h) => h.type === "vertex").findIndex((h) => h.id === handle.id);
const segments = this.getGeometry(shape).getSegments().filter((_, i) => i !== index - 1 && i !== index);
if (!segments.length) return null;
return new import_editor.Group2d({ children: segments });
}
};
}
getInterpolatedProps(startShape, endShape, t) {
const startPoints = linePointsToArray(startShape);
const endPoints = linePointsToArray(endShape);
const pointsToUseStart = [];
const pointsToUseEnd = [];
let index = import_editor.ZERO_INDEX_KEY;
if (startPoints.length > endPoints.length) {
for (let i = 0; i < startPoints.length; i++) {
pointsToUseStart[i] = { ...startPoints[i] };
if (endPoints[i] === void 0) {
pointsToUseEnd[i] = { ...endPoints[endPoints.length - 1], id: index };
} else {
pointsToUseEnd[i] = { ...endPoints[i], id: index };
}
index = (0, import_editor.getIndexAbove)(index);
}
} else if (endPoints.length > startPoints.length) {
for (let i = 0; i < endPoints.length; i++) {
pointsToUseEnd[i] = { ...endPoints[i] };
if (startPoints[i] === void 0) {
pointsToUseStart[i] = {
...startPoints[startPoints.length - 1],
id: index
};
} else {
pointsToUseStart[i] = { ...startPoints[i], id: index };
}
index = (0, import_editor.getIndexAbove)(index);
}
} else {
for (let i = 0; i < endPoints.length; i++) {
pointsToUseStart[i] = startPoints[i];
pointsToUseEnd[i] = endPoints[i];
}
}
return {
...t > 0.5 ? endShape.props : startShape.props,
points: Object.fromEntries(
pointsToUseStart.map((point, i) => {
const endPoint = pointsToUseEnd[i];
return [
point.id,
{
...point,
x: (0, import_editor.lerp)(point.x, endPoint.x, t),
y: (0, import_editor.lerp)(point.y, endPoint.y, t)
}
];
})
),
scale: (0, import_editor.lerp)(startShape.props.scale, endShape.props.scale, t)
};
}
}
function linePointsToArray(shape) {
return Object.values(shape.props.points).sort(import_editor.sortByIndex);
}
const pathCache = new import_editor.WeakCache();
function getPathForLineShape(shape) {
return pathCache.get(shape, () => {
const points = linePointsToArray(shape).map(import_editor.Vec.From);
switch (shape.props.spline) {
case "cubic": {
return import_PathBuilder.PathBuilder.cubicSplineThroughPoints(points, { endOffsets: 0 });
}
case "line": {
return import_PathBuilder.PathBuilder.lineThroughPoints(points, { endOffsets: 0 });
}
}
});
}
function LineShapeSvg({
shape,
shouldScale = false,
forceSolid = false
}) {
const theme = (0, import_useDefaultColorTheme.useDefaultColorTheme)();
const path = getPathForLineShape(shape);
const { dash, color, size } = shape.props;
const scaleFactor = 1 / shape.props.scale;
const scale = shouldScale ? scaleFactor : 1;
const strokeWidth = import_shared.STROKE_SIZES[size] * shape.props.scale;
return path.toSvg({
style: dash,
strokeWidth,
forceSolid,
randomSeed: shape.id,
props: {
transform: `scale(${scale})`,
stroke: (0, import_editor.getColorValue)(theme, color, "solid"),
fill: "none"
}
});
}
//# sourceMappingURL=LineShapeUtil.js.map