Files
excalidraw/src/utils.ts
Gasim Gasimzada 16263e942b Feature: Multi Point Arrows (#338)
* Add points to arrow on double click

* Use line generator instead of path to generate line segments

* Switch color of the circle when it is on an existing point in the segment

* Check point against both ends of the line segment to find collinearity

* Keep drawing the arrow based on mouse position until shape is changed

* Always select the arrow when in multi element mode

* Use curves instead of lines when drawing arrow points

* Add basic collision detection with some debug points

* Use roughjs shape when performing hit testing

* Draw proper handler rectangles for arrows

* Add argument to renderScene in export

* Globally resize all points on the arrow when bounds are resized

* Hide handler rectangles if an arrow has no size

- Allow continuing adding arrows when selected element is deleted

* Add dragging functionality to arrows

* Add SHIFT functionality to two point arrows

- Fix arrow positions when scrolling
- Revert the element back to selection when not in multi select mode

* Clean app state for export (JSON)

* Set curve options manually instead of using global options

- For some reason, this fixed the flickering issue in all shapes when arrows are rendered

* Set proper options for the arrow

* Increaase accuracy of hit testing arrows

- Additionally, skip testing if point is outside the domain of arrow and each curve

* Calculate bounding box of arrow based on roughjs curves

- Remove domain check per curve

* Change bounding box threshold to 10 and remove unnecessary code

* Fix handler rectangles for 2 and multi point arrows

- Fix margins of handler rectangles when using arrows
- Show handler rectangles in endpoints of 2-point arrows

* Remove unnecessary values from app state for export

* Use `resetTransform` instead of "retranslating" canvas space after each element rendering

* Allow resizing 2-point arrows

- Fix position of one of the handler rectangles

* refactor variable initialization

* Refactored to extract out mult-point generation to the abstracted function

* prevent dragging on arrow creation if under threshold

* Finalize selection during multi element mode when ENTER or ESC is clicked

* Set dragging element to null when finalizing

* Remove pathSegmentCircle from code

* Check if element is any "non-value" instead of NULL

* Show two points on any two point arrow and fix visibility of arrows during scroll

* Resume recording when done with drawing

- When deleting a multi select element, revert back to selection element type

* Resize arrow starting points perfectly

* Fix direction of arrow resize based for NW

* Resume recording history when there is more than one arrow

* Set dragging element to NULL when element is not locked

* Blur active element when finalizing

* Disable undo/redo for multielement, editingelement, and resizing element

- Allow undoing parts of the arrow

* Disable element visibility for arrow

* Use points array for arrow bounds when bezier curve shape is not available

Co-authored-by: David Luzar <luzar.david@gmail.com>
Co-authored-by: Preet <833927+pshihn@users.noreply.github.com>
2020-01-31 18:16:33 +01:00

112 lines
3.0 KiB
TypeScript

export const SVG_NS = "http://www.w3.org/2000/svg";
export function getDateTime() {
const date = new Date();
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const hr = date.getHours();
const min = date.getMinutes();
const secs = date.getSeconds();
return `${year}${month}${day}${hr}${min}${secs}`;
}
export function capitalizeString(str: string) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
export function isToolIcon(
target: Element | EventTarget | null,
): target is HTMLElement {
return target instanceof HTMLElement && target.className.includes("ToolIcon");
}
export function isInputLike(
target: Element | EventTarget | null,
): target is
| HTMLInputElement
| HTMLTextAreaElement
| HTMLSelectElement
| HTMLDivElement {
return (
((target instanceof HTMLElement && target.dataset.type === "wysiwyg") ||
target instanceof HTMLInputElement ||
target instanceof HTMLTextAreaElement ||
target instanceof HTMLSelectElement) &&
!isToolIcon(target)
);
}
// https://github.com/grassator/canvas-text-editor/blob/master/lib/FontMetrics.js
export function measureText(text: string, font: string) {
const line = document.createElement("div");
const body = document.body;
line.style.position = "absolute";
line.style.whiteSpace = "nowrap";
line.style.font = font;
body.appendChild(line);
// Now we can measure width and height of the letter
line.innerText = text;
const width = line.offsetWidth;
const height = line.offsetHeight;
// Now creating 1px sized item that will be aligned to baseline
// to calculate baseline shift
const span = document.createElement("span");
span.style.display = "inline-block";
span.style.overflow = "hidden";
span.style.width = "1px";
span.style.height = "1px";
line.appendChild(span);
// Baseline is important for positioning text on canvas
const baseline = span.offsetTop + span.offsetHeight;
document.body.removeChild(line);
return { width, height, baseline };
}
export function debounce<T extends any[]>(
fn: (...args: T) => void,
timeout: number,
) {
let handle = 0;
let lastArgs: T;
const ret = (...args: T) => {
lastArgs = args;
clearTimeout(handle);
handle = window.setTimeout(() => fn(...args), timeout);
};
ret.flush = () => {
clearTimeout(handle);
fn(...lastArgs);
};
return ret;
}
export function selectNode(node: Element) {
const selection = window.getSelection();
if (selection) {
const range = document.createRange();
range.selectNodeContents(node);
selection.removeAllRanges();
selection.addRange(range);
}
}
export function removeSelection() {
const selection = window.getSelection();
if (selection) {
selection.removeAllRanges();
}
}
export function distance(x: number, y: number) {
return Math.abs(x - y);
}
export function distance2d(x1: number, y1: number, x2: number, y2: number) {
const xd = x2 - x1;
const yd = y2 - y1;
return Math.sqrt(xd * xd + yd * yd);
}