import { MIME_TYPES } from "../../constants"; import type { Bounds } from "../../element/bounds"; import { getElementAbsoluteCoords } from "../../element/bounds"; import { hitElementBoundingBox } from "../../element/collision"; import type { ElementsMap, NonDeletedExcalidrawElement, } from "../../element/types"; import { rotate } from "../../math"; import { DEFAULT_LINK_SIZE } from "../../renderer/renderElement"; import type { AppState, Point, UIAppState } from "../../types"; export const EXTERNAL_LINK_IMG = document.createElement("img"); EXTERNAL_LINK_IMG.src = `data:${MIME_TYPES.svg}, ${encodeURIComponent( ``, )}`; export const getLinkHandleFromCoords = ( [x1, y1, x2, y2]: Bounds, angle: number, appState: Pick, ): Bounds => { const size = DEFAULT_LINK_SIZE; const linkWidth = size / appState.zoom.value; const linkHeight = size / appState.zoom.value; const linkMarginY = size / appState.zoom.value; const centerX = (x1 + x2) / 2; const centerY = (y1 + y2) / 2; const centeringOffset = (size - 8) / (2 * appState.zoom.value); const dashedLineMargin = 4 / appState.zoom.value; // Same as `ne` resize handle const x = x2 + dashedLineMargin - centeringOffset; const y = y1 - dashedLineMargin - linkMarginY + centeringOffset; const [rotatedX, rotatedY] = rotate( x + linkWidth / 2, y + linkHeight / 2, centerX, centerY, angle, ); return [ rotatedX - linkWidth / 2, rotatedY - linkHeight / 2, linkWidth, linkHeight, ]; }; export const isPointHittingLinkIcon = ( element: NonDeletedExcalidrawElement, elementsMap: ElementsMap, appState: AppState, [x, y]: Point, ) => { const threshold = 4 / appState.zoom.value; const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap); const [linkX, linkY, linkWidth, linkHeight] = getLinkHandleFromCoords( [x1, y1, x2, y2], element.angle, appState, ); const hitLink = x > linkX - threshold && x < linkX + threshold + linkWidth && y > linkY - threshold && y < linkY + linkHeight + threshold; return hitLink; }; export const isPointHittingLink = ( element: NonDeletedExcalidrawElement, elementsMap: ElementsMap, appState: AppState, [x, y]: Point, isMobile: boolean, ) => { if (!element.link || appState.selectedElementIds[element.id]) { return false; } if ( !isMobile && appState.viewModeEnabled && hitElementBoundingBox(x, y, element, elementsMap) ) { return true; } return isPointHittingLinkIcon(element, elementsMap, appState, [x, y]); };