diff --git a/packages/board/src/index.ts b/packages/board/src/index.ts index ceece4e89..75f1ed8fe 100644 --- a/packages/board/src/index.ts +++ b/packages/board/src/index.ts @@ -70,6 +70,19 @@ export class Board { this.#resetActiveMiddlewareObjs(); } + destroy() { + // #opts + // #middlewareMap + // #middlewares + // #activeMiddlewareObjs + this.#watcher.destroy(); + this.#renderer.destroy(); + // this.#sharer.destroy(); + // #viewer: Viewer; + this.#calculator.destroy(); + this.#eventHub.destroy(); + } + #init() { this.#watcher.on('pointStart', this.#handlePointStart.bind(this)); this.#watcher.on('pointEnd', this.#handlePointEnd.bind(this)); diff --git a/packages/board/src/lib/calculator.ts b/packages/board/src/lib/calculator.ts index 465b84791..4d39c82ab 100644 --- a/packages/board/src/lib/calculator.ts +++ b/packages/board/src/lib/calculator.ts @@ -2,10 +2,14 @@ import type { Point, Data, Element, ElementType, ViewCalculator, ViewCalculatorO import { calcViewElementSize, isViewPointInElement, getViewPointAtElement, isElementInView } from '@idraw/util'; export class Calculator implements ViewCalculator { - private _opts: ViewCalculatorOptions; + #opts: ViewCalculatorOptions; constructor(opts: ViewCalculatorOptions) { - this._opts = opts; + this.#opts = opts; + } + + destroy() { + this.#opts = null as any; } elementSize(size: ElementSize, viewScaleInfo: ViewScaleInfo, viewSizeInfo: ViewSizeInfo): ElementSize { @@ -17,7 +21,7 @@ export class Calculator implements ViewCalculator { } isPointInElement(p: Point, elem: Element, viewScaleInfo: ViewScaleInfo, viewSizeInfo: ViewSizeInfo): boolean { - const context2d = this._opts.viewContext; + const context2d = this.#opts.viewContext; return isViewPointInElement(p, { context2d, element: elem, @@ -30,7 +34,7 @@ export class Calculator implements ViewCalculator { p: Point, opts: { data: Data; viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo } ): { index: number; element: null | Element; groupQueueIndex: number } { - const context2d = this._opts.viewContext; + const context2d = this.#opts.viewContext; return getViewPointAtElement(p, { ...opts, ...{ context2d } }); } } diff --git a/packages/board/src/lib/sharer.ts b/packages/board/src/lib/sharer.ts index 07d2f406d..038c67d90 100644 --- a/packages/board/src/lib/sharer.ts +++ b/packages/board/src/lib/sharer.ts @@ -16,8 +16,8 @@ const defaultActiveStorage: ActiveStore = { }; export class Sharer implements StoreSharer> { - private _activeStore: Store; - private _sharedStore: Store<{ + #activeStore: Store; + #sharedStore: Store<{ [string: string | number | symbol]: any; }>; @@ -28,71 +28,71 @@ export class Sharer implements StoreSharer const sharedStore = new Store({ defaultStorage: {} }); - this._activeStore = activeStore; - this._sharedStore = sharedStore; + this.#activeStore = activeStore; + this.#sharedStore = sharedStore; } getActiveStorage(key: T): ActiveStore[T] { - return this._activeStore.get(key); + return this.#activeStore.get(key); } setActiveStorage(key: T, storage: ActiveStore[T]) { - return this._activeStore.set(key, storage); + return this.#activeStore.set(key, storage); } getActiveStoreSnapshot(): ActiveStore { - return this._activeStore.getSnapshot(); + return this.#activeStore.getSnapshot(); } getSharedStorage(key: string | number | symbol): any { - return this._sharedStore.get(key); + return this.#sharedStore.get(key); } setSharedStorage(key: string | number | symbol, storage: any) { - return this._sharedStore.set(key, storage); + return this.#sharedStore.set(key, storage); } getSharedStoreSnapshot(): Record { - return this._sharedStore.getSnapshot(); + return this.#sharedStore.getSnapshot(); } // get/set active info getActiveViewScaleInfo(): ViewScaleInfo { const viewScaleInfo: ViewScaleInfo = { - scale: this._activeStore.get('scale'), - offsetTop: this._activeStore.get('offsetTop'), - offsetBottom: this._activeStore.get('offsetBottom'), - offsetLeft: this._activeStore.get('offsetLeft'), - offsetRight: this._activeStore.get('offsetRight') + scale: this.#activeStore.get('scale'), + offsetTop: this.#activeStore.get('offsetTop'), + offsetBottom: this.#activeStore.get('offsetBottom'), + offsetLeft: this.#activeStore.get('offsetLeft'), + offsetRight: this.#activeStore.get('offsetRight') }; return viewScaleInfo; } setActiveViewScaleInfo(viewScaleInfo: ViewScaleInfo) { const { scale, offsetTop, offsetBottom, offsetLeft, offsetRight } = viewScaleInfo; - this._activeStore.set('scale', scale); - this._activeStore.set('offsetTop', offsetTop); - this._activeStore.set('offsetBottom', offsetBottom); - this._activeStore.set('offsetLeft', offsetLeft); - this._activeStore.set('offsetRight', offsetRight); + this.#activeStore.set('scale', scale); + this.#activeStore.set('offsetTop', offsetTop); + this.#activeStore.set('offsetBottom', offsetBottom); + this.#activeStore.set('offsetLeft', offsetLeft); + this.#activeStore.set('offsetRight', offsetRight); } setActiveViewSizeInfo(size: ViewSizeInfo) { - this._activeStore.set('width', size.width); - this._activeStore.set('height', size.height); - this._activeStore.set('devicePixelRatio', size.devicePixelRatio); - this._activeStore.set('contextWidth', size.contextWidth); - this._activeStore.set('contextHeight', size.contextHeight); + this.#activeStore.set('width', size.width); + this.#activeStore.set('height', size.height); + this.#activeStore.set('devicePixelRatio', size.devicePixelRatio); + this.#activeStore.set('contextWidth', size.contextWidth); + this.#activeStore.set('contextHeight', size.contextHeight); } getActiveViewSizeInfo(): ViewSizeInfo { const sizeInfo: ViewSizeInfo = { - width: this._activeStore.get('width'), - height: this._activeStore.get('height'), - devicePixelRatio: this._activeStore.get('devicePixelRatio'), - contextWidth: this._activeStore.get('contextWidth'), - contextHeight: this._activeStore.get('contextHeight') + width: this.#activeStore.get('width'), + height: this.#activeStore.get('height'), + devicePixelRatio: this.#activeStore.get('devicePixelRatio'), + contextWidth: this.#activeStore.get('contextWidth'), + contextHeight: this.#activeStore.get('contextHeight') }; return sizeInfo; } diff --git a/packages/board/src/lib/watcher.ts b/packages/board/src/lib/watcher.ts index 4e58c8c0a..a5cb20d92 100644 --- a/packages/board/src/lib/watcher.ts +++ b/packages/board/src/lib/watcher.ts @@ -6,142 +6,166 @@ function isBoardAvailableNum(num: any): boolean { } export class BoardWatcher extends EventEmitter { - private _opts: BoardWatcherOptions; - private _store: Store; + #opts: BoardWatcherOptions; + #store: Store; constructor(opts: BoardWatcherOptions) { super(); const store = new Store({ defaultStorage: { hasPointDown: false, prevClickPoint: null } }); - this._store = store; - this._opts = opts; - this._init(); + this.#store = store; + this.#opts = opts; + this.#init(); } - private _init() { + #init() { const container = window; - container.addEventListener('mousemove', (e: MouseEvent) => { - if (!this._isInTarget(e)) { - return; - } - // if (!this._store.get('hasPointDown')) { - // return; - // } - e.preventDefault(); - const point = this._getPoint(e); - if (!this._isVaildPoint(point)) { - return; - } - this.trigger('hover', { point }); - }); - container.addEventListener('mousedown', (e: MouseEvent) => { - if (!this._isInTarget(e)) { - return; - } - e.preventDefault(); - const point = this._getPoint(e); - if (!this._isVaildPoint(point)) { - return; - } - this._store.set('hasPointDown', true); - this.trigger('pointStart', { point }); - }); - container.addEventListener('mousemove', (e: MouseEvent) => { - if (!this._isInTarget(e)) { - return; - } - e.preventDefault(); - e.stopPropagation(); - const point = this._getPoint(e); - if (!this._isVaildPoint(point)) { - if (this._store.get('hasPointDown')) { - this.trigger('pointLeave', { point }); - this._store.set('hasPointDown', false); - } - return; - } - if (this._store.get('hasPointDown') !== true) { - return; - } - this.trigger('pointMove', { point }); - }); - container.addEventListener('mouseup', (e: MouseEvent) => { - this._store.set('hasPointDown', false); - if (!this._isInTarget(e)) { - return; - } - e.preventDefault(); - const point = this._getPoint(e); - this.trigger('pointEnd', { point }); - }); - container.addEventListener('mouseleave', (e: MouseEvent) => { - this._store.set('hasPointDown', false); - if (!this._isInTarget(e)) { - return; - } - e.preventDefault(); - const point = this._getPoint(e); - this.trigger('pointLeave', { point }); - }); - container.addEventListener( - 'wheel', - (e: WheelEvent) => { - if (!this._isInTarget(e)) { - return; - } - const point = this._getPoint(e); - if (!this._isVaildPoint(point)) { - return; - } - e.preventDefault(); - e.stopPropagation(); - const deltaX = e.deltaX > 0 || e.deltaX < 0 ? e.deltaX : 0; - const deltaY = e.deltaY > 0 || e.deltaY < 0 ? e.deltaY : 0; - - if (e.ctrlKey === true && this.has('wheelScale')) { - this.trigger('wheelScale', { deltaX, deltaY, point }); - } else if (this.has('wheel')) { - this.trigger('wheel', { deltaX, deltaY, point }); - } - }, - { passive: false } - ); - container.addEventListener('click', (e: MouseEvent) => { - if (!this._isInTarget(e)) { - return; - } - e.preventDefault(); - const point = this._getPoint(e); - if (!this._isVaildPoint(point)) { - return; - } - const maxLimitTime = 500; - const t = Date.now(); - const preClickPoint = this._store.get('prevClickPoint'); - if (preClickPoint && t - preClickPoint.t <= maxLimitTime && Math.abs(preClickPoint.x - point.x) <= 5 && Math.abs(preClickPoint.y - point.y) <= 5) { - this.trigger('doubleClick', { point }); - } else { - this._store.set('prevClickPoint', point); - } - }); + container.addEventListener('mousemove', this.#onHover); + container.addEventListener('mousedown', this.#onPointStart); + container.addEventListener('mousemove', this.#onPointMove); + container.addEventListener('mouseup', this.#onPointEnd); + container.addEventListener('mouseleave', this.#onPointLeave); + container.addEventListener('wheel', this.#onWheel, { passive: false }); + container.addEventListener('click', this.#onClick); + container.addEventListener('contextmenu', this.#onContextMenu); + } - container.addEventListener('contextmenu', (e: MouseEvent) => { - if (!this._isInTarget(e)) { - return; - } - e.preventDefault(); - const point = this._getPoint(e); - if (!this._isVaildPoint(point)) { - return; - } - // TODO - }); + destroy() { + const container = window; + container.removeEventListener('mousemove', this.#onHover); + container.removeEventListener('mousedown', this.#onPointStart); + container.removeEventListener('mousemove', this.#onPointMove); + container.removeEventListener('mouseup', this.#onPointEnd); + container.removeEventListener('mouseleave', this.#onPointLeave); + container.removeEventListener('wheel', this.#onWheel); + container.removeEventListener('click', this.#onClick); + container.removeEventListener('contextmenu', this.#onContextMenu); + this.destroy(); } - private _isInTarget(e: MouseEvent | WheelEvent) { - return e.target === this._opts.boardContent.boardContext.canvas; + #onWheel = (e: WheelEvent) => { + if (!this.#isInTarget(e)) { + return; + } + const point = this.#getPoint(e); + if (!this.#isVaildPoint(point)) { + return; + } + e.preventDefault(); + e.stopPropagation(); + const deltaX = e.deltaX > 0 || e.deltaX < 0 ? e.deltaX : 0; + const deltaY = e.deltaY > 0 || e.deltaY < 0 ? e.deltaY : 0; + + if (e.ctrlKey === true && this.has('wheelScale')) { + this.trigger('wheelScale', { deltaX, deltaY, point }); + } else if (this.has('wheel')) { + this.trigger('wheel', { deltaX, deltaY, point }); + } + }; + + #onContextMenu = (e: MouseEvent) => { + if (!this.#isInTarget(e)) { + return; + } + e.preventDefault(); + const point = this.#getPoint(e); + if (!this.#isVaildPoint(point)) { + return; + } + // TODO + }; + + #onClick = (e: MouseEvent) => { + if (!this.#isInTarget(e)) { + return; + } + e.preventDefault(); + const point = this.#getPoint(e); + if (!this.#isVaildPoint(point)) { + return; + } + const maxLimitTime = 500; + const t = Date.now(); + const preClickPoint = this.#store.get('prevClickPoint'); + if (preClickPoint && t - preClickPoint.t <= maxLimitTime && Math.abs(preClickPoint.x - point.x) <= 5 && Math.abs(preClickPoint.y - point.y) <= 5) { + this.trigger('doubleClick', { point }); + } else { + this.#store.set('prevClickPoint', point); + } + }; + + #onPointLeave = (e: MouseEvent) => { + this.#store.set('hasPointDown', false); + if (!this.#isInTarget(e)) { + return; + } + e.preventDefault(); + const point = this.#getPoint(e); + this.trigger('pointLeave', { point }); + }; + + #onPointEnd = (e: MouseEvent) => { + this.#store.set('hasPointDown', false); + if (!this.#isInTarget(e)) { + return; + } + e.preventDefault(); + const point = this.#getPoint(e); + this.trigger('pointEnd', { point }); + }; + + #onPointMove = (e: MouseEvent) => { + if (!this.#isInTarget(e)) { + return; + } + e.preventDefault(); + e.stopPropagation(); + const point = this.#getPoint(e); + if (!this.#isVaildPoint(point)) { + if (this.#store.get('hasPointDown')) { + this.trigger('pointLeave', { point }); + this.#store.set('hasPointDown', false); + } + return; + } + if (this.#store.get('hasPointDown') !== true) { + return; + } + this.trigger('pointMove', { point }); + }; + + #onPointStart = (e: MouseEvent) => { + if (!this.#isInTarget(e)) { + return; + } + e.preventDefault(); + const point = this.#getPoint(e); + if (!this.#isVaildPoint(point)) { + return; + } + this.#store.set('hasPointDown', true); + this.trigger('pointStart', { point }); + }; + + #onHover = (e: MouseEvent) => { + if (!this.#isInTarget(e)) { + return; + } + // if (!this.#store.get('hasPointDown')) { + // return; + // } + e.preventDefault(); + const point = this.#getPoint(e); + if (!this.#isVaildPoint(point)) { + return; + } + this.trigger('hover', { point }); + }; + + #isInTarget(e: MouseEvent | WheelEvent) { + return e.target === this.#opts.boardContent.boardContext.canvas; } - private _getPoint(e: MouseEvent): Point { - const boardCanvas = this._opts.boardContent.boardContext.canvas; + #getPoint(e: MouseEvent): Point { + const boardCanvas = this.#opts.boardContent.boardContext.canvas; const rect = boardCanvas.getBoundingClientRect(); const p: Point = { x: e.clientX - rect.left, @@ -151,8 +175,8 @@ export class BoardWatcher extends EventEmitter { return p; } - private _isVaildPoint(p: Point): boolean { - const viewSize = this._opts.sharer.getActiveViewSizeInfo(); + #isVaildPoint(p: Point): boolean { + const viewSize = this.#opts.sharer.getActiveViewSizeInfo(); const { width, height } = viewSize; if (isBoardAvailableNum(p.x) && isBoardAvailableNum(p.y) && p.x <= width && p.y <= height) { return true; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 4f9f563ee..4386ea0b3 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -44,6 +44,10 @@ export class Core { }); } + destroy() { + this.#board.destroy(); + } + #initContainer() { const container = this.#container; container.style.position = 'relative'; diff --git a/packages/core/src/middleware/dragger/index.ts b/packages/core/src/middleware/dragger/index.ts index 67ee1f7fb..233c541f5 100644 --- a/packages/core/src/middleware/dragger/index.ts +++ b/packages/core/src/middleware/dragger/index.ts @@ -12,6 +12,7 @@ export const MiddlewareDragger: BoardMiddleware let isDragging = false; return { + name: '@middleware/dragger', hover() { if (isDragging === true) { return; diff --git a/packages/core/src/middleware/ruler/index.ts b/packages/core/src/middleware/ruler/index.ts index d5403c993..44470d3aa 100644 --- a/packages/core/src/middleware/ruler/index.ts +++ b/packages/core/src/middleware/ruler/index.ts @@ -24,6 +24,7 @@ export const MiddlewareRuler: BoardMiddleware, CoreEvent> = }; return { + name: '@middleware/ruler', use() { eventHub.on(middlewareEventRuler, rulerCallback); }, diff --git a/packages/core/src/middleware/scaler/index.ts b/packages/core/src/middleware/scaler/index.ts index 34766d2db..4b60912ac 100644 --- a/packages/core/src/middleware/scaler/index.ts +++ b/packages/core/src/middleware/scaler/index.ts @@ -9,6 +9,7 @@ export const MiddlewareScaler: BoardMiddleware, CoreEvent> = const minScale = 0.05; return { + name: '@middleware/scaler', wheelScale(e) { const { deltaY, point } = e; const { scale } = sharer.getActiveViewScaleInfo(); diff --git a/packages/core/src/middleware/scroller/index.ts b/packages/core/src/middleware/scroller/index.ts index 06b050525..367051241 100644 --- a/packages/core/src/middleware/scroller/index.ts +++ b/packages/core/src/middleware/scroller/index.ts @@ -54,6 +54,7 @@ export const MiddlewareScroller: BoardMiddleware = (opts) => { }; return { + name: '@middleware/scroller', wheel: (e: BoardWatherWheelEvent) => { viewer.scroll({ moveX: 0 - e.deltaX, diff --git a/packages/core/src/middleware/selector/index.ts b/packages/core/src/middleware/selector/index.ts index c6e25000f..e02afcb0c 100644 --- a/packages/core/src/middleware/selector/index.ts +++ b/packages/core/src/middleware/selector/index.ts @@ -183,6 +183,7 @@ export const MiddlewareSelector: BoardMiddleware, CoreEven }; return { + name: '@middleware/text-editor', use() { eventHub.on(middlewareEventTextEdit, textEditCallback); }, diff --git a/packages/renderer/src/index.ts b/packages/renderer/src/index.ts index 67e3b42c8..e030bf751 100644 --- a/packages/renderer/src/index.ts +++ b/packages/renderer/src/index.ts @@ -22,6 +22,12 @@ export class Renderer extends EventEmitter implements BoardRen this.#init(); } + destroy() { + this.#opts = null as any; + this.#loader.destroy(); + this.#loader = null as any; + } + #init() { const loader = this.#loader; loader.on('load', (e) => { diff --git a/packages/renderer/src/loader.ts b/packages/renderer/src/loader.ts index 8adfb9841..f37b02ed6 100644 --- a/packages/renderer/src/loader.ts +++ b/packages/renderer/src/loader.ts @@ -59,6 +59,13 @@ export class Loader extends EventEmitter implements RendererLoad }; }); } + + destroy() { + this.#loadFuncMap = null as any; + this.#currentLoadItemMap = null as any; + this.#storageLoadItemMap = null as any; + } + #registerLoadFunc(type: T, func: LoadFunc) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore diff --git a/packages/types/src/lib/board.ts b/packages/types/src/lib/board.ts index f7c16fc9c..f4e4a4766 100644 --- a/packages/types/src/lib/board.ts +++ b/packages/types/src/lib/board.ts @@ -54,6 +54,7 @@ export interface BoardWatcherEventMap = any> } export interface BoardMiddlewareObject = any> { + name?: string; use?: () => void; disuse?: () => void; // action @@ -132,6 +133,7 @@ export interface BoardRenderer extends UtilEventEmitter { updateOptions(opts: RendererOptions): void; drawData(data: Data, opts: RendererDrawOptions): void; scale(num: number): void; + destroy(): void; } export interface BoardWatcherOptions { diff --git a/packages/types/src/lib/renderer.ts b/packages/types/src/lib/renderer.ts index aa3e057e5..0527c2bde 100644 --- a/packages/types/src/lib/renderer.ts +++ b/packages/types/src/lib/renderer.ts @@ -25,6 +25,7 @@ export interface RendererLoader extends UtilEventEmitter { getContent(element: Element): LoadContent | null; getLoadItemMap(): LoadItemMap; setLoadItemMap(itemMap: LoadItemMap): void; + destroy(): void; } export interface RendererDrawOptions { diff --git a/packages/util/src/lib/event.ts b/packages/util/src/lib/event.ts index 0d88d5976..63dcca89d 100644 --- a/packages/util/src/lib/event.ts +++ b/packages/util/src/lib/event.ts @@ -1,25 +1,25 @@ import type { UtilEventEmitter } from '@idraw/types'; export class EventEmitter> implements UtilEventEmitter { - private _listeners: Map void)[]>; + #listeners: Map void)[]>; constructor() { - this._listeners = new Map void)[]>(); + this.#listeners = new Map void)[]>(); } on(eventKey: K, callback: (e: T[K]) => void) { - if (this._listeners.has(eventKey)) { - const callbacks: Array<(e: T[K]) => void> = this._listeners.get(eventKey) || []; + if (this.#listeners.has(eventKey)) { + const callbacks: Array<(e: T[K]) => void> = this.#listeners.get(eventKey) || []; callbacks?.push(callback); - this._listeners.set(eventKey, callbacks); + this.#listeners.set(eventKey, callbacks); } else { - this._listeners.set(eventKey, [callback]); + this.#listeners.set(eventKey, [callback]); } } off(eventKey: K, callback: (e: T[K]) => void) { - if (this._listeners.has(eventKey)) { - const callbacks = this._listeners.get(eventKey); + if (this.#listeners.has(eventKey)) { + const callbacks = this.#listeners.get(eventKey); if (Array.isArray(callbacks)) { for (let i = 0; i < callbacks?.length; i++) { if (callbacks[i] === callback) { @@ -28,12 +28,12 @@ export class EventEmitter> implements UtilEventEmi } } } - this._listeners.set(eventKey, callbacks || []); + this.#listeners.set(eventKey, callbacks || []); } } trigger(eventKey: K, e: T[K]) { - const callbacks = this._listeners.get(eventKey); + const callbacks = this.#listeners.get(eventKey); if (Array.isArray(callbacks)) { callbacks.forEach((cb) => { cb(e); @@ -45,12 +45,16 @@ export class EventEmitter> implements UtilEventEmi } has(name: K | string): boolean { - if (this._listeners.has(name)) { - const list: ((p: T[K]) => void)[] | undefined = this._listeners.get(name); + if (this.#listeners.has(name)) { + const list: ((p: T[K]) => void)[] | undefined = this.#listeners.get(name); if (Array.isArray(list) && list.length > 0) { return true; } } return false; } + + destroy() { + this.#listeners.clear(); + } } diff --git a/packages/util/src/lib/store.ts b/packages/util/src/lib/store.ts index 415f97cae..87cf33689 100644 --- a/packages/util/src/lib/store.ts +++ b/packages/util/src/lib/store.ts @@ -1,31 +1,35 @@ import { deepClone } from './data'; -export class Store> { - private _temp: T; - private _backUpDefaultStorage: T; +export class Store = Record> { + #temp: T; + #backUpDefaultStorage: T; constructor(opts: { defaultStorage: T }) { - this._backUpDefaultStorage = deepClone(opts.defaultStorage); - this._temp = this._createTempStorage(); + this.#backUpDefaultStorage = deepClone(opts.defaultStorage); + this.#temp = this.#createTempStorage(); } set(name: K, value: T[K]) { - this._temp[name] = value; + this.#temp[name] = value; } get(name: K): T[K] { - return this._temp[name]; + return this.#temp[name]; } getSnapshot(): T { - return deepClone(this._temp); + return deepClone(this.#temp); } clear() { - this._temp = this._createTempStorage(); + this.#temp = this.#createTempStorage(); } - private _createTempStorage() { - return deepClone(this._backUpDefaultStorage); + destroy() { + this.#temp = null as any; + } + + #createTempStorage() { + return deepClone(this.#backUpDefaultStorage); } }