tldraw
Version:
A tiny little drawing editor.
169 lines (168 loc) • 4.94 kB
JavaScript
import {
Vec,
compact,
createShapeId,
toRichText
} from "@tldraw/editor";
const CLONE_HANDLE_MARGIN = 0;
const NOTE_SIZE = 200;
const NOTE_CENTER_OFFSET = new Vec(NOTE_SIZE / 2, NOTE_SIZE / 2);
const NOTE_ADJACENT_POSITION_SNAP_RADIUS = 10;
const BASE_NOTE_POSITIONS = (editor) => [
[
["a1"],
new Vec(NOTE_SIZE * 0.5, NOTE_SIZE * -0.5 - editor.options.adjacentShapeMargin)
],
// t
[
["a2"],
new Vec(NOTE_SIZE * 1.5 + editor.options.adjacentShapeMargin, NOTE_SIZE * 0.5)
],
// r
[
["a3"],
new Vec(NOTE_SIZE * 0.5, NOTE_SIZE * 1.5 + editor.options.adjacentShapeMargin)
],
// b
[
["a4"],
new Vec(NOTE_SIZE * -0.5 - editor.options.adjacentShapeMargin, NOTE_SIZE * 0.5)
]
// l
];
function getBaseAdjacentNotePositions(editor, scale) {
if (scale === 1) return BASE_NOTE_POSITIONS(editor);
const s = NOTE_SIZE * scale;
const m = editor.options.adjacentShapeMargin * scale;
return [
[["a1"], new Vec(s * 0.5, s * -0.5 - m)],
// t
[["a2"], new Vec(s * 1.5 + m, s * 0.5)],
// r
[["a3"], new Vec(s * 0.5, s * 1.5 + m)],
// b
[["a4"], new Vec(s * -0.5 - m, s * 0.5)]
// l
];
}
function getNoteAdjacentPositions(editor, pagePoint, pageRotation, growY, extraHeight, scale) {
return Object.fromEntries(
getBaseAdjacentNotePositions(editor, scale).map(([id, v], i) => {
const point = v.clone();
if (i === 0 && extraHeight) {
point.y -= extraHeight;
} else if (i === 2 && growY) {
point.y += growY;
}
return [id, point.rot(pageRotation).add(pagePoint)];
})
);
}
function getAvailableNoteAdjacentPositions(editor, rotation, scale, extraHeight) {
const selectedShapeIds = new Set(editor.getSelectedShapeIds());
const minSize = (NOTE_SIZE + editor.options.adjacentShapeMargin + extraHeight) ** 2;
const allCenters = /* @__PURE__ */ new Map();
const positions = [];
for (const shape of editor.getCurrentPageShapes()) {
if (!editor.isShapeOfType(shape, "note") || scale !== shape.props.scale || selectedShapeIds.has(shape.id)) {
continue;
}
const transform = editor.getShapePageTransform(shape.id);
if (rotation !== transform.rotation()) continue;
allCenters.set(shape, editor.getShapePageBounds(shape).center);
positions.push(
...Object.values(
getNoteAdjacentPositions(
editor,
transform.point(),
rotation,
shape.props.growY,
extraHeight,
scale
)
)
);
}
const len = positions.length;
let position;
for (const [shape, center] of allCenters) {
for (let i = 0; i < len; i++) {
position = positions[i];
if (!position) continue;
if (Vec.Dist2(center, position) > minSize) continue;
if (editor.isPointInShape(shape, position)) {
positions[i] = void 0;
}
}
}
return compact(positions);
}
function getNoteShapeForAdjacentPosition(editor, shape, center, pageRotation, forceNew = false) {
let nextNote;
const allShapesOnPage = editor.getCurrentPageShapesSorted();
const minDistance = (NOTE_SIZE + editor.options.adjacentShapeMargin ** 2) ** shape.props.scale;
for (let i = allShapesOnPage.length - 1; i >= 0; i--) {
const otherNote = allShapesOnPage[i];
if (otherNote.type === "note" && otherNote.id !== shape.id) {
const otherBounds = editor.getShapePageBounds(otherNote);
if (otherBounds && Vec.Dist2(otherBounds.center, center) < minDistance && editor.isPointInShape(otherNote, center)) {
nextNote = otherNote;
break;
}
}
}
editor.complete();
if (!nextNote || forceNew) {
editor.markHistoryStoppingPoint("creating note shape");
const id = createShapeId();
editor.createShape({
id,
type: "note",
x: center.x,
y: center.y,
rotation: pageRotation,
opacity: shape.opacity,
props: {
// Use the props of the shape we're cloning
...shape.props,
richText: toRichText(""),
growY: 0,
fontSizeAdjustment: 0,
url: ""
}
});
const createdShape = editor.getShape(id);
if (!createdShape) return;
const topLeft = editor.getPointInParentSpace(
createdShape,
Vec.Sub(
center,
Vec.Rot(NOTE_CENTER_OFFSET.clone().mul(createdShape.props.scale), pageRotation)
)
);
editor.updateShape({
id,
type: "note",
x: topLeft.x,
y: topLeft.y
});
nextNote = editor.getShape(id);
}
editor.zoomToSelectionIfOffscreen(16, {
animation: {
duration: editor.options.animationMediumMs
},
inset: 0
});
return nextNote;
}
export {
CLONE_HANDLE_MARGIN,
NOTE_ADJACENT_POSITION_SNAP_RADIUS,
NOTE_CENTER_OFFSET,
NOTE_SIZE,
getAvailableNoteAdjacentPositions,
getNoteAdjacentPositions,
getNoteShapeForAdjacentPosition
};
//# sourceMappingURL=noteHelpers.mjs.map