UNPKG

tldraw

Version:

A tiny little drawing editor.

307 lines (306 loc) • 9.94 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 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