2020-03-07 10:20:38 -05:00
|
|
|
import { FlooredNumber } from "./types";
|
|
|
|
import { getZoomOrigin } from "./scene";
|
2020-04-06 22:26:54 +02:00
|
|
|
import { CURSOR_TYPE } from "./constants";
|
2020-03-07 10:20:38 -05:00
|
|
|
|
2020-01-28 12:25:13 -08:00
|
|
|
export const SVG_NS = "http://www.w3.org/2000/svg";
|
|
|
|
|
2020-03-23 16:38:41 -07:00
|
|
|
let mockDateTime: string | null = null;
|
|
|
|
|
|
|
|
export function setDateTimeForTests(dateTime: string) {
|
|
|
|
mockDateTime = dateTime;
|
|
|
|
}
|
|
|
|
|
2020-04-08 01:31:28 +03:00
|
|
|
export const getDateTime = () => {
|
2020-03-23 16:38:41 -07:00
|
|
|
if (mockDateTime) {
|
|
|
|
return mockDateTime;
|
|
|
|
}
|
|
|
|
|
2020-01-06 20:24:54 +04:00
|
|
|
const date = new Date();
|
|
|
|
const year = date.getFullYear();
|
2020-04-08 01:31:28 +03:00
|
|
|
const month = `${date.getMonth() + 1}`.padStart(2, "0");
|
|
|
|
const day = `${date.getDate()}`.padStart(2, "0");
|
|
|
|
const hr = `${date.getHours()}`.padStart(2, "0");
|
|
|
|
const min = `${date.getMinutes()}`.padStart(2, "0");
|
2020-01-06 20:24:54 +04:00
|
|
|
|
2020-04-08 01:31:28 +03:00
|
|
|
return `${year}-${month}-${day}-${hr}${min}`;
|
|
|
|
};
|
2020-01-06 20:24:54 +04:00
|
|
|
|
|
|
|
export function capitalizeString(str: string) {
|
|
|
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
|
|
}
|
|
|
|
|
2020-01-27 22:14:35 +01:00
|
|
|
export function isToolIcon(
|
|
|
|
target: Element | EventTarget | null,
|
|
|
|
): target is HTMLElement {
|
|
|
|
return target instanceof HTMLElement && target.className.includes("ToolIcon");
|
|
|
|
}
|
|
|
|
|
2020-01-06 20:24:54 +04:00
|
|
|
export function isInputLike(
|
2020-01-24 12:04:54 +02:00
|
|
|
target: Element | EventTarget | null,
|
2020-01-27 22:14:35 +01:00
|
|
|
): target is
|
|
|
|
| HTMLInputElement
|
|
|
|
| HTMLTextAreaElement
|
|
|
|
| HTMLSelectElement
|
2020-02-04 11:50:18 +01:00
|
|
|
| HTMLBRElement
|
2020-01-27 22:14:35 +01:00
|
|
|
| HTMLDivElement {
|
2020-01-06 20:24:54 +04:00
|
|
|
return (
|
2020-02-01 02:58:16 +00:00
|
|
|
(target instanceof HTMLElement && target.dataset.type === "wysiwyg") ||
|
2020-02-01 04:06:27 +00:00
|
|
|
target instanceof HTMLBRElement || // newline in wysiwyg
|
2020-02-01 02:58:16 +00:00
|
|
|
target instanceof HTMLInputElement ||
|
|
|
|
target instanceof HTMLTextAreaElement ||
|
|
|
|
target instanceof HTMLSelectElement
|
2020-01-06 20:24:54 +04:00
|
|
|
);
|
|
|
|
}
|
2020-01-09 00:06:25 +05:00
|
|
|
|
2020-02-04 11:50:18 +01:00
|
|
|
export function isWritableElement(
|
|
|
|
target: Element | EventTarget | null,
|
|
|
|
): target is
|
|
|
|
| HTMLInputElement
|
|
|
|
| HTMLTextAreaElement
|
|
|
|
| HTMLBRElement
|
|
|
|
| HTMLDivElement {
|
|
|
|
return (
|
|
|
|
(target instanceof HTMLElement && target.dataset.type === "wysiwyg") ||
|
|
|
|
target instanceof HTMLBRElement || // newline in wysiwyg
|
|
|
|
target instanceof HTMLTextAreaElement ||
|
|
|
|
(target instanceof HTMLInputElement &&
|
|
|
|
(target.type === "text" || target.type === "number"))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-01-09 00:06:25 +05:00
|
|
|
// 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";
|
2020-03-13 15:10:44 +01:00
|
|
|
line.style.whiteSpace = "pre";
|
2020-01-09 00:06:25 +05:00
|
|
|
line.style.font = font;
|
|
|
|
body.appendChild(line);
|
|
|
|
// Now we can measure width and height of the letter
|
2020-01-12 02:25:33 +04:00
|
|
|
line.innerText = text;
|
2020-01-09 00:06:25 +05:00
|
|
|
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 };
|
|
|
|
}
|
2020-01-11 20:15:41 -08:00
|
|
|
|
|
|
|
export function debounce<T extends any[]>(
|
|
|
|
fn: (...args: T) => void,
|
2020-01-24 12:04:54 +02:00
|
|
|
timeout: number,
|
2020-01-11 20:15:41 -08:00
|
|
|
) {
|
|
|
|
let handle = 0;
|
2020-01-20 18:37:42 +01:00
|
|
|
let lastArgs: T;
|
|
|
|
const ret = (...args: T) => {
|
|
|
|
lastArgs = args;
|
2020-01-11 20:15:41 -08:00
|
|
|
clearTimeout(handle);
|
|
|
|
handle = window.setTimeout(() => fn(...args), timeout);
|
|
|
|
};
|
2020-01-20 18:37:42 +01:00
|
|
|
ret.flush = () => {
|
|
|
|
clearTimeout(handle);
|
|
|
|
fn(...lastArgs);
|
|
|
|
};
|
|
|
|
return ret;
|
2020-01-11 20:15:41 -08:00
|
|
|
}
|
2020-01-15 20:42:02 +05:00
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
2020-01-24 20:45:52 +01:00
|
|
|
|
|
|
|
export function distance(x: number, y: number) {
|
2020-01-26 20:15:08 +01:00
|
|
|
return Math.abs(x - y);
|
2020-01-24 20:45:52 +01:00
|
|
|
}
|
2020-02-01 15:49:18 +04:00
|
|
|
|
2020-02-10 15:09:50 +01:00
|
|
|
export function resetCursor() {
|
|
|
|
document.documentElement.style.cursor = "";
|
|
|
|
}
|
2020-03-07 10:20:38 -05:00
|
|
|
|
2020-04-06 22:26:54 +02:00
|
|
|
export function setCursorForShape(shape: string) {
|
|
|
|
if (shape === "selection") {
|
|
|
|
resetCursor();
|
|
|
|
} else {
|
|
|
|
document.documentElement.style.cursor = CURSOR_TYPE.CROSSHAIR;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-06 03:17:13 +05:30
|
|
|
export const isFullScreen = () =>
|
|
|
|
document.fullscreenElement?.nodeName === "HTML";
|
|
|
|
|
|
|
|
export const allowFullScreen = () =>
|
|
|
|
document.documentElement.requestFullscreen();
|
|
|
|
|
|
|
|
export const exitFullScreen = () => document.exitFullscreen();
|
|
|
|
|
2020-04-07 14:39:06 +03:00
|
|
|
export const getShortcutKey = (shortcut: string): string => {
|
2020-03-09 15:06:35 +02:00
|
|
|
const isMac = /Mac|iPod|iPhone|iPad/.test(window.navigator.platform);
|
|
|
|
if (isMac) {
|
2020-04-07 14:39:06 +03:00
|
|
|
return `${shortcut
|
2020-05-11 00:29:35 +02:00
|
|
|
.replace(/CtrlOrCmd/i, "Cmd")
|
|
|
|
.replace(/Alt/i, "Option")
|
|
|
|
.replace(/Del/i, "Delete")
|
|
|
|
.replace(/Enter|Return/i, "Enter")}`;
|
2020-03-09 15:06:35 +02:00
|
|
|
}
|
2020-05-11 00:29:35 +02:00
|
|
|
return `${shortcut.replace(/CtrlOrCmd/i, "Ctrl")}`;
|
2020-03-09 15:06:35 +02:00
|
|
|
};
|
2020-03-07 10:20:38 -05:00
|
|
|
export function viewportCoordsToSceneCoords(
|
|
|
|
{ clientX, clientY }: { clientX: number; clientY: number },
|
|
|
|
{
|
|
|
|
scrollX,
|
|
|
|
scrollY,
|
|
|
|
zoom,
|
|
|
|
}: {
|
|
|
|
scrollX: FlooredNumber;
|
|
|
|
scrollY: FlooredNumber;
|
|
|
|
zoom: number;
|
|
|
|
},
|
|
|
|
canvas: HTMLCanvasElement | null,
|
2020-03-15 12:25:18 -07:00
|
|
|
scale: number,
|
2020-03-07 10:20:38 -05:00
|
|
|
) {
|
2020-03-15 12:25:18 -07:00
|
|
|
const zoomOrigin = getZoomOrigin(canvas, scale);
|
2020-03-07 10:20:38 -05:00
|
|
|
const clientXWithZoom = zoomOrigin.x + (clientX - zoomOrigin.x) / zoom;
|
|
|
|
const clientYWithZoom = zoomOrigin.y + (clientY - zoomOrigin.y) / zoom;
|
|
|
|
|
|
|
|
const x = clientXWithZoom - scrollX;
|
|
|
|
const y = clientYWithZoom - scrollY;
|
|
|
|
|
|
|
|
return { x, y };
|
|
|
|
}
|
|
|
|
|
|
|
|
export function sceneCoordsToViewportCoords(
|
|
|
|
{ sceneX, sceneY }: { sceneX: number; sceneY: number },
|
|
|
|
{
|
|
|
|
scrollX,
|
|
|
|
scrollY,
|
|
|
|
zoom,
|
|
|
|
}: {
|
|
|
|
scrollX: FlooredNumber;
|
|
|
|
scrollY: FlooredNumber;
|
|
|
|
zoom: number;
|
|
|
|
},
|
|
|
|
canvas: HTMLCanvasElement | null,
|
2020-03-15 12:25:18 -07:00
|
|
|
scale: number,
|
2020-03-07 10:20:38 -05:00
|
|
|
) {
|
2020-03-15 12:25:18 -07:00
|
|
|
const zoomOrigin = getZoomOrigin(canvas, scale);
|
2020-03-07 10:20:38 -05:00
|
|
|
const sceneXWithZoomAndScroll =
|
|
|
|
zoomOrigin.x - (zoomOrigin.x - sceneX - scrollX) * zoom;
|
|
|
|
const sceneYWithZoomAndScroll =
|
|
|
|
zoomOrigin.y - (zoomOrigin.y - sceneY - scrollY) * zoom;
|
|
|
|
|
|
|
|
const x = sceneXWithZoomAndScroll;
|
|
|
|
const y = sceneYWithZoomAndScroll;
|
|
|
|
|
|
|
|
return { x, y };
|
|
|
|
}
|
2020-03-18 11:31:40 -04:00
|
|
|
|
|
|
|
export function getGlobalCSSVariable(name: string) {
|
|
|
|
return getComputedStyle(document.documentElement).getPropertyValue(
|
|
|
|
`--${name}`,
|
|
|
|
);
|
|
|
|
}
|