UNPKG

@webwriter/geometry-cloze

Version:

Create and view geometry exercises with coloring, styling and labeling options.

160 lines (141 loc) 4.34 kB
import { ContextMenuItem } from '../../../types/ContextMenu'; import Shape from '../Shape'; import IDManager from '../../CanvasManager/IDManager'; import Manager from '../../CanvasManager/Abstracts'; export interface NamedElement { name?: string; id?: number; } export default class Element { public name = '[unset]'; private _id = IDManager.getID(); public get id() { return this._id; } constructor( protected manager: Manager, data?: NamedElement ) { if (data?.name) this.name = data.name; if (data?.id) this._id = data.id; } private _hidden = false; public get hidden() { return this._hidden; } public hide(value: boolean = true) { this._hidden = value; this.requestRedraw(); } protected parent: Shape | Manager | null = null; private _children: Element[] = []; protected get children(): readonly Element[] { return this._children; } registerParent(element: Shape | Manager) { this.parent = element; } unregisterParent() { this.parent = null; } protected addChildAt(this: Shape, child: Element, index: number) { this._children.splice(index, 0, child); child.addEventListener('request-redraw', this.requestRedraw.bind(this)); child.registerParent(this); } protected addChild(...children: Element[]) { this._children.push(...children); children.forEach((child) => { if (this instanceof Shape) child.registerParent(this); child.addEventListener('request-redraw', this.requestRedraw.bind(this)); }); } protected removeChild(child: Element) { const index = this._children.indexOf(child); if (index === -1) return; this._children.splice(index, 1); child.removeEventListener('request-redraw', this.requestRedraw.bind(this)); child.unregisterParent(); } protected resortChildren(sort: (children: Element[]) => Element[]) { this._children = sort(this._children); } public hasChild(child: Element) { return this._children.includes(child); } protected listeners: Map<string, ((ele: Element, ...args: any[]) => void)[]> = new Map(); delete(this: Shape) { if (!this.parent) return; if (this.parent instanceof Element) this.parent.removeChild(this); else this.parent.removeChild(this); this.fireEvent('delete'); } draw(ctx: CanvasRenderingContext2D) { if (this._hidden) return; this._children.forEach((child) => child.draw(ctx)); } addEventListener( event: string, callback: (ele: Element, ...args: any[]) => void ) { const callbacks = this.listeners.get(event); if (callbacks) callbacks.push(callback); else this.listeners.set(event, [callback]); } removeEventListener( event: string, callback?: (ele: Element, ...args: any[]) => void ) { const callbacks = this.listeners.get(event); if (callbacks) { if (callback === undefined) this.listeners.delete(event); else { const index = callbacks.indexOf(callback); if (index !== -1) callbacks.splice(index, 1); } } } public fireEvent(event: string, ...args: any[]) { const callbacks = this.listeners.get(event); if (callbacks) callbacks.forEach((callback) => callback(this, ...args)); } protected requestRedraw() { this.fireEvent('request-redraw'); } public getContextMenuItems(this: Shape): ContextMenuItem[] { return [ { key: 'delete', type: 'button', label: 'Delete', action: this.delete.bind(this), badge: 'Del/Backspace' } ]; } public getChildByID(id: number): Element | null { if (this._id === id) return this; for (const child of this._children) { const found = child.getChildByID(id); if (found) return found; } return null; } public export(): { _type: 'element' | 'point' | 'line'; id: number; children?: ({ _type: 'point' | 'line' | 'element' } & Object)[]; } { if (this.children.length) return { _type: 'element', id: this.id, children: this._children.map((child) => child.export()) }; return { _type: 'element', id: this.id }; } }