import { Point } from "@noya-app/noya-geometry"; import { createCanvasPlugin } from "./baseCanvas"; import { BaseContextProperties, ElementProperties } from "./contextProperties"; import { modeSwitch, NoneMode } from "./PluginSystem"; import { PointerData } from "./pointerPlugin"; export type EraserMode = NoneMode | { type: "erasing" }; export type EraserData = { markedForErasureElementIds: string[]; } & PointerData; export interface EraserContextProperties extends BaseContextProperties, ElementProperties { canErase?: (event: React.PointerEvent) => boolean; adjustErasePoint?: (event: React.PointerEvent, point: Point) => Point; onEraseElements?: (elementIds: string[]) => void; } export function eraserPlugin() { return createCanvasPlugin({ name: "eraser", data: { markedForErasureElementIds: [], }, handlers: ({ getMode, setMode, getData, setData, canErase = () => true, adjustErasePoint = (_, point) => point, getElementId, ...context }: EraserContextProperties) => ({ onPointerDown(event: React.PointerEvent) { modeSwitch(getMode(), { none: () => { const data = getData(); if (!data.pointerDown || !canErase(event)) return; setMode({ type: "erasing" }); const elements = context.filterElements({ at: data.pointerDown.head, predicate: () => true, }); if (elements.length > 0) { setData({ ...data, markedForErasureElementIds: elements.map(getElementId), }); } event.preventDefault(); }, }); }, onPointerMove(event: React.PointerEvent) { modeSwitch(getMode(), { erasing: () => { const data = getData(); if (!data.pointerDown) return; const point = adjustErasePoint(event, data.pointerDown.head); const elements = context.filterElements({ at: point, predicate: () => true, }); if (elements.length > 0) { setData({ ...data, markedForErasureElementIds: mergeUnique( data.markedForErasureElementIds, elements.map(getElementId) ), }); } event.preventDefault(); }, }); }, onPointerUp(event: React.PointerEvent) { modeSwitch(getMode(), { erasing: () => { const data = getData(); if (!data.pointerDown) return; const elements = context.filterElements({ at: data.pointerDown.head, predicate: () => true, }); const markedForErasureElementIds = elements.length > 0 ? mergeUnique( data.markedForErasureElementIds, elements.map(getElementId) ) : data.markedForErasureElementIds; context.onEraseElements?.(markedForErasureElementIds); setMode({ type: "none" }); setData({ ...data, markedForErasureElementIds: [] }); event.preventDefault(); }, }); }, }), }); } function mergeUnique(a: T[], b: T[]) { return Array.from(new Set([...a, ...b])); }