diff --git a/packages/board/src/index.ts b/packages/board/src/index.ts index 25693c196..d0a444fe8 100644 --- a/packages/board/src/index.ts +++ b/packages/board/src/index.ts @@ -2,7 +2,6 @@ import { Renderer } from '@idraw/renderer'; import { throttle, calcElementsContextSize, EventEmitter } from '@idraw/util'; import type { Data, - BoardMode, BoardOptions, BoardMiddleware, BoardMiddlewareObject, @@ -19,92 +18,93 @@ import { Viewer } from './lib/viewer'; const throttleTime = 10; // ms -const LOCK_MODES: BoardMode[] = ['RULER']; +interface BoardMiddlewareMapItem { + status: 'enable' | 'disable'; + middlewareObject: BoardMiddlewareObject; +} export class Board { - private _opts: BoardOptions; - private _middlewares: BoardMiddleware[] = []; - private _middlewareObjs: BoardMiddlewareObject[] = []; - private _activeMiddlewareObjs: BoardMiddlewareObject[] = []; - private _watcher: BoardWatcher; - private _sharer: Sharer; - // private _renderer: Renderer; - private _viewer: Viewer; - private _calculator: Calculator; - private _eventHub: EventEmitter = new EventEmitter(); - private _activeMode: BoardMode = 'SELECT'; + #opts: BoardOptions; + #middlewareMap: WeakMap = new WeakMap(); + #middlewares: BoardMiddleware[] = []; + #activeMiddlewareObjs: BoardMiddlewareObject[] = []; + #watcher: BoardWatcher; + #sharer: Sharer; + #viewer: Viewer; + #calculator: Calculator; + #eventHub: EventEmitter = new EventEmitter(); + constructor(opts: BoardOptions) { - const { viewContent } = opts; + const { boardContent } = opts; const sharer = new Sharer(); - const calculator = new Calculator({ viewContent }); + const calculator = new Calculator({ viewContext: boardContent.viewContext }); const watcher = new BoardWatcher({ - viewContent, + boardContent, sharer }); const renderer = new Renderer({ - viewContent, + viewContext: boardContent.viewContext, sharer, calculator }); - this._opts = opts; - this._sharer = sharer; - // this._renderer = renderer; - this._watcher = watcher; - this._calculator = calculator; - this._viewer = new Viewer({ - viewContent: opts.viewContent, + this.#opts = opts; + this.#sharer = sharer; + this.#watcher = watcher; + this.#calculator = calculator; + this.#viewer = new Viewer({ + boardContent: opts.boardContent, sharer, renderer, - calculator, + calculator: this.#calculator, beforeDrawFrame: (e) => { - this._handleBeforeDrawFrame(e); + this.#handleBeforeDrawFrame(e); }, afterDrawFrame: (e) => { - this._handleAfterDrawFrame(e); + this.#handleAfterDrawFrame(e); } }); - this._init(); - this._resetActiveMiddlewareObjs(); + this.#init(); + this.#resetActiveMiddlewareObjs(); } - private _init() { - this._watcher.on('pointStart', this._handlePointStart.bind(this)); - this._watcher.on('pointEnd', this._handlePointEnd.bind(this)); - this._watcher.on( + #init() { + this.#watcher.on('pointStart', this.#handlePointStart.bind(this)); + this.#watcher.on('pointEnd', this.#handlePointEnd.bind(this)); + this.#watcher.on( 'pointMove', throttle((e) => { - this._handlePointMove(e); + this.#handlePointMove(e); }, throttleTime) ); - this._watcher.on( + this.#watcher.on( 'hover', throttle((e) => { - this._handleHover(e); + this.#handleHover(e); }, throttleTime) ); - this._watcher.on( + this.#watcher.on( 'wheel', throttle((e) => { - this._handleWheel(e); + this.#handleWheel(e); }, throttleTime) ); - this._watcher.on( + this.#watcher.on( 'wheelScale', throttle((e) => { - this._handleWheelScale(e); + this.#handleWheelScale(e); }, throttleTime) ); - this._watcher.on('scrollX', this._handleScrollX.bind(this)); - this._watcher.on('scrollY', this._handleScrollY.bind(this)); - this._watcher.on('resize', this._handleResize.bind(this)); - this._watcher.on('doubleClick', this._handleDoubleClick.bind(this)); + this.#watcher.on('scrollX', this.#handleScrollX.bind(this)); + this.#watcher.on('scrollY', this.#handleScrollY.bind(this)); + this.#watcher.on('resize', this.#handleResize.bind(this)); + this.#watcher.on('doubleClick', this.#handleDoubleClick.bind(this)); } - private _handlePointStart(e: BoardWatcherEventMap['pointStart']) { - for (let i = 0; i < this._activeMiddlewareObjs.length; i++) { - const obj = this._activeMiddlewareObjs[i]; + #handlePointStart(e: BoardWatcherEventMap['pointStart']) { + for (let i = 0; i < this.#activeMiddlewareObjs.length; i++) { + const obj = this.#activeMiddlewareObjs[i]; const result = obj?.pointStart?.(e); if (result === false) { return; @@ -112,9 +112,9 @@ export class Board { } } - private _handlePointEnd(e: BoardWatcherEventMap['pointEnd']) { - for (let i = 0; i < this._activeMiddlewareObjs.length; i++) { - const obj = this._activeMiddlewareObjs[i]; + #handlePointEnd(e: BoardWatcherEventMap['pointEnd']) { + for (let i = 0; i < this.#activeMiddlewareObjs.length; i++) { + const obj = this.#activeMiddlewareObjs[i]; const result = obj?.pointEnd?.(e); if (result === false) { return; @@ -122,9 +122,9 @@ export class Board { } } - private _handlePointMove(e: BoardWatcherEventMap['pointMove']) { - for (let i = 0; i < this._activeMiddlewareObjs.length; i++) { - const obj = this._activeMiddlewareObjs[i]; + #handlePointMove(e: BoardWatcherEventMap['pointMove']) { + for (let i = 0; i < this.#activeMiddlewareObjs.length; i++) { + const obj = this.#activeMiddlewareObjs[i]; const result = obj?.pointMove?.(e); if (result === false) { return; @@ -132,9 +132,9 @@ export class Board { } } - private _handleHover(e: BoardWatcherEventMap['hover']) { - for (let i = 0; i < this._activeMiddlewareObjs.length; i++) { - const obj = this._activeMiddlewareObjs[i]; + #handleHover(e: BoardWatcherEventMap['hover']) { + for (let i = 0; i < this.#activeMiddlewareObjs.length; i++) { + const obj = this.#activeMiddlewareObjs[i]; const result = obj?.hover?.(e); if (result === false) { return; @@ -142,9 +142,9 @@ export class Board { } } - private _handleDoubleClick(e: BoardWatcherEventMap['doubleClick']) { - for (let i = 0; i < this._activeMiddlewareObjs.length; i++) { - const obj = this._activeMiddlewareObjs[i]; + #handleDoubleClick(e: BoardWatcherEventMap['doubleClick']) { + for (let i = 0; i < this.#activeMiddlewareObjs.length; i++) { + const obj = this.#activeMiddlewareObjs[i]; const result = obj?.doubleClick?.(e); if (result === false) { return; @@ -152,9 +152,9 @@ export class Board { } } - private _handleWheel(e: BoardWatcherEventMap['wheel']) { - for (let i = 0; i < this._activeMiddlewareObjs.length; i++) { - const obj = this._activeMiddlewareObjs[i]; + #handleWheel(e: BoardWatcherEventMap['wheel']) { + for (let i = 0; i < this.#activeMiddlewareObjs.length; i++) { + const obj = this.#activeMiddlewareObjs[i]; const result = obj?.wheel?.(e); if (result === false) { return; @@ -162,9 +162,9 @@ export class Board { } } - private _handleWheelScale(e: BoardWatcherEventMap['wheelScale']) { - for (let i = 0; i < this._activeMiddlewareObjs.length; i++) { - const obj = this._activeMiddlewareObjs[i]; + #handleWheelScale(e: BoardWatcherEventMap['wheelScale']) { + for (let i = 0; i < this.#activeMiddlewareObjs.length; i++) { + const obj = this.#activeMiddlewareObjs[i]; const result = obj?.wheelScale?.(e); if (result === false) { return; @@ -172,19 +172,9 @@ export class Board { } } - // private _handleScale(e: BoardWatcherEventMap['scale']) { - // for (let i = 0; i < this._activeMiddlewareObjs.length; i++) { - // const obj = this._activeMiddlewareObjs[i]; - // const result = obj?.scale?.(e); - // if (result === false) { - // return; - // } - // } - // } - - private _handleScrollX(e: BoardWatcherEventMap['scrollX']) { - for (let i = 0; i < this._activeMiddlewareObjs.length; i++) { - const obj = this._activeMiddlewareObjs[i]; + #handleScrollX(e: BoardWatcherEventMap['scrollX']) { + for (let i = 0; i < this.#activeMiddlewareObjs.length; i++) { + const obj = this.#activeMiddlewareObjs[i]; const result = obj?.scrollX?.(e); if (result === false) { return; @@ -192,9 +182,9 @@ export class Board { } } - private _handleScrollY(e: BoardWatcherEventMap['scrollY']) { - for (let i = 0; i < this._activeMiddlewareObjs.length; i++) { - const obj = this._activeMiddlewareObjs[i]; + #handleScrollY(e: BoardWatcherEventMap['scrollY']) { + for (let i = 0; i < this.#activeMiddlewareObjs.length; i++) { + const obj = this.#activeMiddlewareObjs[i]; const result = obj?.scrollY?.(e); if (result === false) { return; @@ -202,9 +192,9 @@ export class Board { } } - private _handleResize(e: BoardWatcherEventMap['resize']) { - for (let i = 0; i < this._activeMiddlewareObjs.length; i++) { - const obj = this._activeMiddlewareObjs[i]; + #handleResize(e: BoardWatcherEventMap['resize']) { + for (let i = 0; i < this.#activeMiddlewareObjs.length; i++) { + const obj = this.#activeMiddlewareObjs[i]; const result = obj?.resize?.(e); if (result === false) { return; @@ -212,9 +202,9 @@ export class Board { } } - private _handleClear(e: BoardWatcherEventMap['clear']) { - for (let i = 0; i < this._activeMiddlewareObjs.length; i++) { - const obj = this._activeMiddlewareObjs[i]; + #handleClear(e: BoardWatcherEventMap['clear']) { + for (let i = 0; i < this.#activeMiddlewareObjs.length; i++) { + const obj = this.#activeMiddlewareObjs[i]; const result = obj?.clear?.(e); if (result === false) { return; @@ -223,9 +213,9 @@ export class Board { } // draw event - private _handleBeforeDrawFrame(e: BoardWatcherEventMap['beforeDrawFrame']) { - for (let i = 0; i < this._activeMiddlewareObjs.length; i++) { - const obj = this._activeMiddlewareObjs[i]; + #handleBeforeDrawFrame(e: BoardWatcherEventMap['beforeDrawFrame']) { + for (let i = 0; i < this.#activeMiddlewareObjs.length; i++) { + const obj = this.#activeMiddlewareObjs[i]; const result = obj?.beforeDrawFrame?.(e); if (result === false) { return; @@ -233,9 +223,9 @@ export class Board { } } - private _handleAfterDrawFrame(e: BoardWatcherEventMap['afterDrawFrame']) { - for (let i = 0; i < this._activeMiddlewareObjs.length; i++) { - const obj = this._activeMiddlewareObjs[i]; + #handleAfterDrawFrame(e: BoardWatcherEventMap['afterDrawFrame']) { + for (let i = 0; i < this.#activeMiddlewareObjs.length; i++) { + const obj = this.#activeMiddlewareObjs[i]; const result = obj?.afterDrawFrame?.(e); if (result === false) { return; @@ -243,31 +233,29 @@ export class Board { } } - private _resetActiveMiddlewareObjs() { - const { _activeMode: activeMode } = this; - const modes: BoardMode[] = [...LOCK_MODES, activeMode]; + #resetActiveMiddlewareObjs() { const activeMiddlewareObjs: BoardMiddlewareObject[] = []; - this._middlewareObjs.forEach((m) => { - if (m.isDefault === true) { - activeMiddlewareObjs.push(m); - } else if (modes.includes(m.mode)) { - activeMiddlewareObjs.push(m); + const middlewareMap = this.#middlewareMap; + this.#middlewares.forEach((middleware: BoardMiddleware) => { + const item = middlewareMap.get(middleware); + if (item?.status === 'enable' && item?.middlewareObject) { + activeMiddlewareObjs.push(item.middlewareObject); } }); - this._activeMiddlewareObjs = activeMiddlewareObjs; + this.#activeMiddlewareObjs = activeMiddlewareObjs; } getSharer() { - return this._sharer; + return this.#sharer; } getViewer() { - return this._viewer; + return this.#viewer; } setData(data: Data): { viewSizeInfo: ViewSizeInfo } { - const sharer = this._sharer; - this._sharer.setActiveStorage('data', data); + const sharer = this.#sharer; + this.#sharer.setActiveStorage('data', data); const viewSizeInfo = sharer.getActiveViewSizeInfo(); // const currentScaleInfo = sharer.getActiveViewScaleInfo(); const newViewContextSize = calcElementsContextSize(data.elements, { @@ -275,66 +263,99 @@ export class Board { viewHeight: viewSizeInfo.height, extend: true }); - this._viewer.drawFrame(); + this.#viewer.drawFrame(); const newViewSizeInfo = { ...viewSizeInfo, ...newViewContextSize }; - this._sharer.setActiveViewSizeInfo(newViewSizeInfo); + this.#sharer.setActiveViewSizeInfo(newViewSizeInfo); return { viewSizeInfo: newViewSizeInfo }; } getData(): Data | null { - const { data } = this._sharer.getActiveStoreSnapshot(); + const { data } = this.#sharer.getActiveStoreSnapshot(); return data; } use(middleware: BoardMiddleware) { - const { viewContent, container } = this._opts; - const { _sharer: sharer, _viewer: viewer, _calculator: calculator, _eventHub: eventHub } = this; - const obj = middleware({ viewContent, sharer, viewer, calculator, eventHub: eventHub as UtilEventEmitter, container }); - this._middlewares.push(middleware); - this._activeMiddlewareObjs.push(obj); + if (this.#middlewareMap.has(middleware)) { + const item = this.#middlewareMap.get(middleware); + if (item) { + item.middlewareObject.use?.(); + item.status = 'enable'; + this.#middlewareMap.set(middleware, item); + this.#resetActiveMiddlewareObjs(); + return; + } + } + const { boardContent, container } = this.#opts; + const sharer = this.#sharer; + const viewer = this.#viewer; + const calculator = this.#calculator; + const eventHub = this.#eventHub; + + const obj = middleware({ boardContent, sharer, viewer, calculator, eventHub: eventHub as UtilEventEmitter, container }); + obj.use?.(); + this.#middlewares.push(middleware); + this.#activeMiddlewareObjs.push(obj); + + this.#middlewareMap.set(middleware, { + status: 'enable', + middlewareObject: obj + }); + this.#resetActiveMiddlewareObjs(); + } + + disuse(middleware: BoardMiddleware) { + const item = this.#middlewareMap.get(middleware); + if (item) { + item.middlewareObject.disuse?.(); + item.status = 'disable'; + this.#middlewareMap.set(middleware, item); + this.#resetActiveMiddlewareObjs(); + } } scale(opts: { scale: number; point: PointSize }) { - const { _viewer: viewer } = this; + const viewer = this.#viewer; const { moveX, moveY } = viewer.scale(opts); viewer.scroll({ moveX, moveY }); } scroll(opts: { moveX: number; moveY: number }) { - return this._viewer.scroll(opts); + return this.#viewer.scroll(opts); } updateViewScaleInfo(opts: { scale: number; offsetX: number; offsetY: number }) { - return this._viewer.updateViewScaleInfo(opts); + return this.#viewer.updateViewScaleInfo(opts); } resize(newViewSize: ViewSizeInfo) { - const viewSize = this._viewer.resize(newViewSize); + const viewSize = this.#viewer.resize(newViewSize); const { width, height, devicePixelRatio } = newViewSize; - const { viewContent } = this._opts; - viewContent.viewContext.$resize({ width, height, devicePixelRatio }); - viewContent.helperContext.$resize({ width, height, devicePixelRatio }); - viewContent.boardContext.$resize({ width, height, devicePixelRatio }); - this._viewer.drawFrame(); - this._watcher.trigger('resize', viewSize); - this._sharer.setActiveViewSizeInfo(newViewSize); + const { boardContent } = this.#opts; + boardContent.viewContext.$resize({ width, height, devicePixelRatio }); + boardContent.helperContext.$resize({ width, height, devicePixelRatio }); + boardContent.boardContext.$resize({ width, height, devicePixelRatio }); + this.#viewer.drawFrame(); + this.#watcher.trigger('resize', viewSize); + this.#sharer.setActiveViewSizeInfo(newViewSize); } clear() { - const { viewContent } = this._opts; - const { underContext, helperContext, viewContext, boardContext } = viewContent; + const { boardContent } = this.#opts; + const { underContext, helperContext, viewContext, boardContext } = boardContent; underContext.clearRect(0, 0, underContext.canvas.width, underContext.canvas.height); helperContext.clearRect(0, 0, helperContext.canvas.width, helperContext.canvas.height); viewContext.clearRect(0, 0, viewContext.canvas.width, viewContext.canvas.height); boardContext.clearRect(0, 0, boardContext.canvas.width, boardContext.canvas.height); - this._handleClear(); + this.#handleClear(); } getEventHub(): EventEmitter { - return this._eventHub; + return this.#eventHub; } } + +export { Sharer, Calculator }; diff --git a/packages/board/src/lib/calculator.ts b/packages/board/src/lib/calculator.ts index 0f5563d84..465b84791 100644 --- a/packages/board/src/lib/calculator.ts +++ b/packages/board/src/lib/calculator.ts @@ -17,7 +17,7 @@ export class Calculator implements ViewCalculator { } isPointInElement(p: Point, elem: Element, viewScaleInfo: ViewScaleInfo, viewSizeInfo: ViewSizeInfo): boolean { - const context2d = this._opts.viewContent.boardContext; + const context2d = this._opts.viewContext; return isViewPointInElement(p, { context2d, element: elem, @@ -30,7 +30,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.viewContent.boardContext; + const context2d = this._opts.viewContext; return getViewPointAtElement(p, { ...opts, ...{ context2d } }); } } diff --git a/packages/board/src/lib/viewer.ts b/packages/board/src/lib/viewer.ts index 6d80d51ee..b26dc40db 100644 --- a/packages/board/src/lib/viewer.ts +++ b/packages/board/src/lib/viewer.ts @@ -40,7 +40,7 @@ export class Viewer extends EventEmitter implements BoardVi } const snapshot = this._drawFrameSnapshotQueue.shift(); - const { renderer, viewContent, beforeDrawFrame, afterDrawFrame } = this._opts; + const { renderer, boardContent, beforeDrawFrame, afterDrawFrame } = this._opts; if (snapshot) { const { scale, offsetTop, offsetBottom, offsetLeft, offsetRight, width, height, contextHeight, contextWidth, devicePixelRatio } = snapshot.activeStore; @@ -65,7 +65,7 @@ export class Viewer extends EventEmitter implements BoardVi } beforeDrawFrame({ snapshot }); - viewContent.drawView(); + boardContent.drawView(); afterDrawFrame({ snapshot }); } @@ -161,7 +161,7 @@ export class Viewer extends EventEmitter implements BoardVi const newViewSize = { ...originViewSize, ...viewSize }; const { width, height, devicePixelRatio } = newViewSize; - const { underContext, boardContext, helperContext, viewContext } = this._opts.viewContent; + const { underContext, boardContext, helperContext, viewContext } = this._opts.boardContent; boardContext.canvas.width = width * devicePixelRatio; boardContext.canvas.height = height * devicePixelRatio; boardContext.canvas.style.width = `${width}px`; diff --git a/packages/board/src/lib/watcher.ts b/packages/board/src/lib/watcher.ts index e097ce8ab..93dc8c589 100644 --- a/packages/board/src/lib/watcher.ts +++ b/packages/board/src/lib/watcher.ts @@ -136,11 +136,11 @@ export class BoardWatcher extends EventEmitter { } private _isInTarget(e: MouseEvent | WheelEvent) { - return e.target === this._opts.viewContent.boardContext.canvas; + return e.target === this._opts.boardContent.boardContext.canvas; } private _getPoint(e: MouseEvent): Point { - const boardCanvas = this._opts.viewContent.boardContext.canvas; + const boardCanvas = this._opts.boardContent.boardContext.canvas; const rect = boardCanvas.getBoundingClientRect(); const p: Point = { x: e.clientX - rect.left, diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 24f896000..be1f61ea3 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,6 +1,6 @@ import type { Data, PointSize, CoreOptions, BoardMiddleware, ViewSizeInfo, CoreEvent, ViewScaleInfo } from '@idraw/types'; import { Board } from '@idraw/board'; -import { createViewContent, validateElements } from '@idraw/util'; +import { createBoardContent, validateElements } from '@idraw/util'; import { Cursor } from './lib/cursor'; // export { MiddlewareSelector } from './middleware/selector'; @@ -9,6 +9,7 @@ export { MiddlewareScroller } from './middleware/scroller'; export { MiddlewareScaler, middlewareEventScale } from './middleware/scaler'; export { MiddlewareRuler, middlewareEventRuler } from './middleware/ruler'; export { MiddlewareTextEditor, middlewareEventTextEdit } from './middleware/text-editor'; +export { MiddlewareDragger } from './middleware/dragger'; export class Core { #board: Board; @@ -25,8 +26,8 @@ export class Core { this.#initContainer(); container.appendChild(canvas); - const viewContent = createViewContent(canvas, { width, height, devicePixelRatio, offscreen: true }); - const board = new Board({ viewContent, container }); + const boardContent = createBoardContent(canvas, { width, height, devicePixelRatio, offscreen: true }); + const board = new Board({ boardContent, container }); const sharer = board.getSharer(); sharer.setActiveViewSizeInfo({ width, @@ -52,6 +53,10 @@ export class Core { this.#board.use(middleware); } + disuse(middleware: BoardMiddleware) { + this.#board.disuse(middleware); + } + setData(data: Data) { validateElements(data?.elements || []); this.#board.setData(data); diff --git a/packages/core/src/lib/cursor-image.ts b/packages/core/src/lib/cursor-image.ts index 77d1a067c..936ff316d 100644 --- a/packages/core/src/lib/cursor-image.ts +++ b/packages/core/src/lib/cursor-image.ts @@ -2,3 +2,7 @@ export const CURSOR = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAACXBIWXMAAAsTAAALEwEAmpwYAAAF92lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNi4wLWMwMDYgNzkuMTY0NzUzLCAyMDIxLzAyLzE1LTExOjUyOjEzICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjIuMyAoTWFjaW50b3NoKSIgeG1wOkNyZWF0ZURhdGU9IjIwMjMtMDktMTdUMTY6MDc6MjYrMDg6MDAiIHhtcDpNb2RpZnlEYXRlPSIyMDIzLTA5LTE3VDE2OjEyOjUwKzA4OjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDIzLTA5LTE3VDE2OjEyOjUwKzA4OjAwIiBkYzpmb3JtYXQ9ImltYWdlL3BuZyIgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyIgcGhvdG9zaG9wOklDQ1Byb2ZpbGU9InNSR0IgSUVDNjE5NjYtMi4xIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjliMGM0MzI2LWU4ZTQt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'; export const CURSOR_RESIZE = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAApCAYAAABHomvIAAAACXBIWXMAAAsTAAALEwEAmpwYAAAF92lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNi4wLWMwMDYgNzkuMTY0NzUzLCAyMDIxLzAyLzE1LTExOjUyOjEzICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjIuMyAoTWFjaW50b3NoKSIgeG1wOkNyZWF0ZURhdGU9IjIwMjMtMDktMTdUMTY6MzE6MjMrMDg6MDAiIHhtcDpNb2RpZnlEYXRlPSIyMDIzLTA5LTE3VDE2OjQ0OjIyKzA4OjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDIzLTA5LTE3VDE2OjQ0OjIyKzA4OjAwIiBkYzpmb3JtYXQ9ImltYWdlL3BuZyIgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyIgcGhvdG9zaG9wOklDQ1Byb2ZpbGU9InNSR0IgSUVDNjE5NjYtMi4xIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjY0MTBhYjUzLWM0ZjEtNDVhNS04MjhkLTIxOTczOWFjOTk3MSIgeG1wTU06RG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOjBkMDNmNjM5LTE5MzctY2Y0MC1hMTg0LTIyMjg0NzczNWNmYSIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjgyYjQwZGRmLWE0ZGEtNDY3MC1iYzc2LTBhYjY3ZmI5M2I0ZSI+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6ODJiNDBkZGYtYTRkYS00NjcwLWJjNzYtMGFiNjdmYjkzYjRlIiBzdEV2dDp3aGVuPSIyMDIzLTA5LTE3VDE2OjMxOjIzKzA4OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgMjIuMyAoTWFjaW50b3NoKSIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6NjQxMGFiNTMtYzRmMS00NWE1LTgyOGQtMjE5NzM5YWM5OTcxIiBzdEV2dDp3aGVuPSIyMDIzLTA5LTE3VDE2OjQ0OjIyKzA4OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgMjIuMyAoTWFjaW50b3NoKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz57vRudAAAEk0lEQVRYhe3ZW0jbVxzA8e8/MX+NYnG9uI4xE3bvoLt0FzradRfGBtsYo32YdAhb6WQyBqV7KOylpYjzZShDGfjmyxgbgjjwyRm16SYMhgiNKDhbL3VtNF4xJOnf3x7+59i/Wf4aTbInf3AwJMdzPjnnf/n9/jFEhGzDMIxMb3uAIsDs6ek5urS05Dtz5syE+uwekAQS6u89YD19gC0NIpJ1c8GZQHlXV9fJRCIxGo/HxxoaGj4CngWOAEGgEihXfT07MeQC3MB1dna+lkgkRkXF6urq3xcuXPgUOAE8DzwGPOiGLARwEy4ej4+JiITD4elr167NiIgsLi7eqq2trQPeBI4Bj7sh8w10xZmmeds0zdn+/v5/RERisdjUuXPnvgLeAl50Q+YTaAA+oKy7u/uE3laNAwSQ4uLiu6FQ6G4G5DG13YeAMjWWkU+gBygJhULHNe769etTTpwDGXUiz58//yXwujp5qoAHgBLAk0+gNxKJHEulUiMKN2ma5gwgPp/vjhOXjlxYWJisq6urBV5RW30IKAW8eQPGYrGjlmXdEBEZHBy8aZrmFCCmac729fVtAHt7e6MO5N2+vr47IiJLS0s3L126dBZ4Sh2LZUBRwVdwYGBgVuwOYh/zsoF0bnPBVzDTMRgOh6dFhROokSIi8/Pz0+pEeaPQx+DGWdzV1XVSX2LcgCIic3NzMzU1NV8D7wIvq9WrLNRZvOk62NHRccqJTAdGo9Hb1dXV3wAfYt9VjgAPFfI66EQWAxU9PT0fuwEvXrzYBJwF3gFeAAJAhfrfrO4k/7lxZxnr2JlJqry8POnWyePx6H4JR0vhktVkHGOXQI20SkpKLLcOhmGsA5YCaZiVLS5XoADi9XpdkznDMERhnE0fCgUHZhvOW+CO4/8A5hR7wFxjD5hr7AFzjZyBlmVlrOYdYaS1HUUuQA/gWV9fd51URDyqn1c1j6MVFGjoidfW1oq2ABrYj0V82OmVzwHNajVdB88C5wOKTdM87NaxsrKyQsFKHC2BnTDo+/TWt8Bd5INeVC44NDT0xXYZdXNz8w/AaeyS8yjwCPdzQu92ht2m/OUjIyOfS1pkAoqItLS0fA+8D7wKPA0cxs6qC1O4T0xMfKYnb21tnXEDNjc3z+nXbW1t3wFvYz9dCAL7KUThHovFPtGTNjU1jQFSX18/lg68cuXKLUAaGxs3vkB7e/u3wHHgCQpUdnpTqdQvesJgMDisUVevXh3Xry9fvnxTv66qqprQ/cfHx/vVNj/J/couv0DAv7q6+pMDeYPNSalkwkUikX7s4ukl4FHgAODPN1CXnPsWFxd/dCAjW+GGhoZCwAfYpeczwMPAPjVW3gv3IvXN98disZ8dyBGNCwQC4/r94eHhfuy6+JS6zATUCeJXY+W9cNfIUuDAwsLCr05kIBDYeBQ8Ojr6h8Lpx25BtbWlGpfv62BG5PLy8m+SFpOTk38C76mVe84NVyhgOvLgysrK7xoXjUb/Uqt2XG1rEDiYCbcd0MgwsWtk+J1EI03An0wmw5Zlefx+/2n1eRKIO5r+rWTTpFsZ/gWFrGMmeObuqwAAAABJRU5ErkJggg=='; + +export const CURSOR_DRAG_DEFAULT = `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAApCAYAAABHomvIAAAEvUlEQVRYhc2Y20/cVRDHP8v+uKzAWkpDCtZq8BYhJkq8PBoJxgj7I2m0ryaSyFN9IT74pI8+8WJiggRj/APApFkSTGRJ+qKGUiyxbGPEWsUSuVhYC12Wy8+HmeMeYPe3d3WSye/k/M7le+bMzJkZ+J9ToEJreGVYN+vihcytstjQocUlAw0WCSoIVCv3Ae8CXwIt+u8XHVeOG8qLqgAHqAFCQCNwGugCthBJGd7S/tM6LqTzHI5KuqzgaoAHgEvAtwrkG2AG8FzXvep5nue67lX9N6P/PR1/SefXlBtkFXKNDUA/RyX1D9uUbYzOb9D1ygbSQU7eDMxhSSsSiVz3AxiJRK57nuf19/fPad8coqONFCBJPyUOIKc1OvcbgiY9ICDT8+0DHge2gR0gBeyTtviM5HeKAGlrrfMZVwhdAVaAr4F3KNF4gkA9cBbowOc68+3LwDn1Mhdy4/OcYk54nPr6+m54nofruvPa9SFQ6wfQTweNgTwInAGuQWk6mEUvHwb+Au4Dexx7ffK9+0q+CHWIBIOZ9skHYKWfK9tQ8gZoLDjIyWCg3ORY+2T8aYOyg4EaRIFrkCs4QU1NTSvabC20zyIDzuyfkYzEahHX8jHwE/LwXwHeJ4P7KIVIu5ouxFDC+HgLAy4MfEF2v1UpgOezATSiNVf6EvAWsD01NXVtdnb2bk9Pz81sp/q3KIi8t83Ae4DX0tJy0z7t8PDwD4FAYIX/WILVwKMAzc3Nu/agoaGhzmg0ugcsV0hIvlSNoH8IuIiebHJycrls4sotwaxGYvueAPAjEg3jum79yMjIr5WUjFLOxMpBIopWoBN4GfhOJ+4NDg4ulVt6i4uLm7r+BvAscA4fCXrAAfJQJ5Fg8gPgMuCMjo62d3V1LW1sbKRKlpXSxMTEmjZ/5qQrOwHwUAGmkIjiHhJdfKrM/Pz8Y+3t7RvT09Ob5QAYi8X2tbmke2cEZwAaCdoAt5QvIzEbiUSitbe3tyoej++UCnBhYSGkzdu6d1aQxkgOkfwgieQMCeCu8iwSnpNKpcIdHR3BZDJ5UCy4SCSyvL6+/giiSou67wE+eYmhAKKkIaAJUdynkdflNcQF3dFTpooxjmg0aozDAz4DXkB8bxPy1OYM7QzIOuAU0AY8pQu9CrwJ3AK8UCj0RyHgZmZmli1w3wOvAM/oHo1kiZiygbSDh7PAE8DzQA/wBqKnnuM4a2NjY7dygbtw4cICRy31deBFJA09o7eWsU7kJ1KTLJmyRyOSn5zS70fAkwBtbW2/DwwMHHR3d9d3dnY2rK6u7o2Pj2/GYrH9eDzurK2tndM1vwI+QXT7T2BTD5skQz6SCyAcLRoZkGEFGQbeRqpbubK+BPA5MI14h00L3I6Cy2h4+eQbJsKuRa6iQcGFFfB5oBt4DpFsI+Ky7iDBxW3gBhBH/GtC+R7i1lKk3UxRAG2QpvxWr0AbtB3SA5jkx36djH/dVlDbyibN3M8GDvJPyI2PSulixm/u6kZ1OQCaJ/S+tndJX6tvsFBIxcCUdA3vW5ubxMrObc0hDMhd/Rqp5QQHxeW8dubnHGM7tzVv/IECMlxQ/bpcRfRMqaNRheNcUGG9XFWDACfzWs/6Fl3t/xtO//8gpbCORQAAAABJRU5ErkJggg==`; + +export const CURSOR_DRAG_ACTIVE = `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAER0lEQVRYhe2YT2hjRRjAf8lL22xsNsm6EWKrSKvuIkIh+O9QRFxEW18KUsoe7FHoRaWCN1FPetOrIHgVKS0q9P5OxaJbodkalgVrtVZjS7Ntd02z6abPw3yzmaT585q+elj2g2HmvZn35jffN/PNNwP35R6XgM/fuif4n+dO2klQvgsaZRc4NJJvoJbHdhrIAkJAN2ADHwFfAw9J3ZoB/b9I0AA6A0SBc0Aa2EVpSqddeZ+QdmfkO+u0gIPSQQR4HfhRQH4AHMDNZDJXXNd1M5nMFalzdB3wJTAOPAD0yEB9066G6wXepVZTd5MpTdporZ6jVqsatmMJoTR3HvgJQ1u2bS+3ArRte9l1XXdsbGyJo1pdBN6Wf3d3ChlAmSQO9LeC8fquQRpDWaerHWSjSr1iu4BkJyOsF9u2s67rkslkluTVxygltAVsJBZqdCngEj5osIlW+4EYytRNF04jeu3vulCT+7QkLH20dEOhumft97pQI4s3+iiRSPwtxVSbd39J8eEGvzFXc1NAs8KSFAZeBt4AHgNeBFDWObkEAne7HAK2gT2gCFQatdca1GbtBj4E3veFprVYVLXXcg4GqM6588BbAMlkcm1qamqzr6/v6ikBet5RgiizJoDPAXdgYGDZXHkzMzPrrk9CdRWngUeAsxxdCzVwpgZ/BigWiz1mo4mJif7jqMajeJrU5hywgGvAej6fvzA0NLThN1Eul9uT4g5VTbYFNKUIfAbsZ7PZvuHh4Wt+As7Ozu5IcY2j219TQB0NV4A7qODgO4CFhYWLg4ODOb8AHccpG4A68m6pRQ1YAQ6A28A+8BXwBcDq6upTqVTquh+AuVyuW4q/opRRaQdZD1gCbgE3ge+BT4HdfD7/ZDwe/z2bzRY6hRsfH1/e3Nx8FDX/sgbgoZfvg6jo4ixqW7oIPA+8CrwHrAJuJBLJd+JaHMf5k6qmPgGeQe1SCenXk0/U21xcIC8AzwGvAJPAEuAmEon1xcXFba9w8/Pz5oqdB14CnpY+oij35km0qwmjwqAU8ISM9hIwBeQA17KsG9PT07+1gxsZGdmgdqW+BjwLDAAPoo4ALU+W9arVwWoIpXp9kouKZpPAO8AwQCwW+2d0dLQ0OTkZSafT0UKhUJ6bm9t2HKeysrIS3tra0g7+KvABUJC0g5rrJdRcbLpImtleRzYashc1P2OSXwbebDVykX3gW+Ab1AHqhuQ3pe6AJlFMO0CoPROHDcio5I8DL1A9C8dQbmod+APYAK4DvwjQnsDdErgyVTfTEaCG1GFYGHXG7TVSo2OkdvhlAflXoHSuNdfStFqaRhEi2kfdprrj6M5LAt0I8EDaaMdflPal48CB95hMr3Bt8h4jD0kyL5E0pN6dysZzW7N2AqjbmhdIZjJvufTOpE19x3g+9s1XJ/ck5tVbfdhu+rxDfLiSO+lFToCjZwrXyH2/0Lwv95z8B1jAqXmDnj4YAAAAAElFTkSuQmCC`; diff --git a/packages/core/src/lib/cursor.ts b/packages/core/src/lib/cursor.ts index fe1597fc1..db336e410 100644 --- a/packages/core/src/lib/cursor.ts +++ b/packages/core/src/lib/cursor.ts @@ -1,6 +1,6 @@ import type { UtilEventEmitter, CoreEvent } from '@idraw/types'; import { limitAngle, loadImage, parseAngleToRadian } from '@idraw/util'; -import { CURSOR, CURSOR_RESIZE } from './cursor-image'; +import { CURSOR, CURSOR_RESIZE, CURSOR_DRAG_DEFAULT, CURSOR_DRAG_ACTIVE } from './cursor-image'; export class Cursor { private _eventHub: UtilEventEmitter; @@ -9,6 +9,8 @@ export class Cursor { private _resizeCursorBaseImage: HTMLImageElement | null = null; private _cursorImageMap: Record = { auto: CURSOR, + 'drag-default': CURSOR_DRAG_DEFAULT, + 'drag-active': CURSOR_DRAG_ACTIVE, 'rotate-0': CURSOR_RESIZE }; constructor( @@ -31,8 +33,11 @@ export class Cursor { this._resetCursor('auto'); } else if (typeof e.type === 'string' && e.type?.startsWith('resize-')) { this._setCursorResize(e); + } else if (e.type === 'drag-default') { + this._resetCursor('drag-default'); + } else if (e.type === 'drag-active') { + this._resetCursor('drag-active'); } else { - // TODO this._resetCursor('auto'); } }); diff --git a/packages/core/src/middleware/dragger/index.ts b/packages/core/src/middleware/dragger/index.ts new file mode 100644 index 000000000..67ee1f7fb --- /dev/null +++ b/packages/core/src/middleware/dragger/index.ts @@ -0,0 +1,53 @@ +import type { BoardMiddleware, CoreEvent, Point } from '@idraw/types'; + +const key = 'DRAG'; +const keyPrevPoint = Symbol(`${key}_prevPoint`); + +type DraggerSharedStorage = { + [keyPrevPoint]: Point | null; +}; + +export const MiddlewareDragger: BoardMiddleware = (opts) => { + const { eventHub, sharer, viewer } = opts; + let isDragging = false; + + return { + hover() { + if (isDragging === true) { + return; + } + eventHub.trigger('cursor', { + type: 'drag-default' + }); + }, + + pointStart(e) { + const { point } = e; + sharer.setSharedStorage(keyPrevPoint, point); + isDragging = true; + eventHub.trigger('cursor', { + type: 'drag-active' + }); + }, + + pointMove(e) { + const { point } = e; + const prevPoint = sharer.getSharedStorage(keyPrevPoint); + if (point && prevPoint) { + const moveX = point.x - prevPoint.x; + const moveY = point.y - prevPoint.y; + viewer.scroll({ moveX, moveY }); + viewer.drawFrame(); + } + sharer.setSharedStorage(keyPrevPoint, point); + }, + + pointEnd() { + isDragging = false; + sharer.setSharedStorage(keyPrevPoint, null); + eventHub.trigger('cursor', { + type: 'drag-default' + }); + } + }; +}; diff --git a/packages/core/src/middleware/ruler/index.ts b/packages/core/src/middleware/ruler/index.ts index 66b86eff9..d5403c993 100644 --- a/packages/core/src/middleware/ruler/index.ts +++ b/packages/core/src/middleware/ruler/index.ts @@ -5,13 +5,12 @@ import { drawRulerBackground, drawXRuler, drawYRuler, calcXRulerScaleList, calcY export const middlewareEventRuler = '@middleware/show-ruler'; export const MiddlewareRuler: BoardMiddleware, CoreEvent> = (opts) => { - const key = 'RULE'; - const { viewContent, viewer, eventHub } = opts; - const { helperContext, underContext } = viewContent; + const { boardContent, viewer, eventHub } = opts; + const { helperContext, underContext } = boardContent; let show: boolean = true; let showGrid: boolean = true; - eventHub.on(middlewareEventRuler, (e: { show: boolean; showGrid: boolean }) => { + const rulerCallback = (e: { show: boolean; showGrid: boolean }) => { if (typeof e?.show === 'boolean') { show = e.show; } @@ -22,10 +21,15 @@ export const MiddlewareRuler: BoardMiddleware, CoreEvent> = if (typeof e?.show === 'boolean' || typeof e?.showGrid === 'boolean') { viewer.drawFrame(); } - }); + }; + return { - mode: key, - isDefault: true, + use() { + eventHub.on(middlewareEventRuler, rulerCallback); + }, + disuse() { + eventHub.off(middlewareEventRuler, rulerCallback); + }, beforeDrawFrame: ({ snapshot }) => { if (show === true) { const viewScaleInfo = getViewScaleInfoFromSnapshot(snapshot); diff --git a/packages/core/src/middleware/scaler/index.ts b/packages/core/src/middleware/scaler/index.ts index c3e84b7d5..34766d2db 100644 --- a/packages/core/src/middleware/scaler/index.ts +++ b/packages/core/src/middleware/scaler/index.ts @@ -4,14 +4,11 @@ import { formatNumber } from '@idraw/util'; export const middlewareEventScale = '@middleware/scale'; export const MiddlewareScaler: BoardMiddleware, CoreEvent> = (opts) => { - const key = 'SCALE'; const { viewer, sharer, eventHub } = opts; const maxScale = 50; const minScale = 0.05; return { - mode: key, - isDefault: true, 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 e78e8a154..06b050525 100644 --- a/packages/core/src/middleware/scroller/index.ts +++ b/packages/core/src/middleware/scroller/index.ts @@ -1,11 +1,11 @@ import type { Point, BoardMiddleware, PointWatcherEvent, BoardWatherWheelEvent } from '@idraw/types'; import { drawScroller, isPointInScrollThumb } from './util'; // import type { ScrollbarThumbType } from './util'; -import { key, keyXThumbRect, keyYThumbRect, keyPrevPoint, keyActivePoint, keyActiveThumbType } from './config'; +import { keyXThumbRect, keyYThumbRect, keyPrevPoint, keyActivePoint, keyActiveThumbType } from './config'; export const MiddlewareScroller: BoardMiddleware = (opts) => { - const { viewer, viewContent, sharer } = opts; - const { helperContext } = viewContent; + const { viewer, boardContent, sharer } = opts; + const { helperContext } = boardContent; sharer.setSharedStorage(keyXThumbRect, null); // null | ElementSize sharer.setSharedStorage(keyYThumbRect, null); // null | ElementSize @@ -54,8 +54,6 @@ export const MiddlewareScroller: BoardMiddleware = (opts) => { }; return { - mode: key, - wheel: (e: BoardWatherWheelEvent) => { viewer.scroll({ moveX: 0 - e.deltaX, @@ -87,7 +85,7 @@ export const MiddlewareScroller: BoardMiddleware = (opts) => { return false; } }, - pointEnd: (e: PointWatcherEvent) => { + pointEnd: () => { const activeThumbType = sharer.getSharedStorage(keyActiveThumbType); clear(); if (activeThumbType === 'X' || activeThumbType === 'Y') { diff --git a/packages/core/src/middleware/selector/index.ts b/packages/core/src/middleware/selector/index.ts index 6f39d1d06..11643023e 100644 --- a/packages/core/src/middleware/selector/index.ts +++ b/packages/core/src/middleware/selector/index.ts @@ -8,7 +8,7 @@ import { findElementsFromList, findElementsFromListByPositions } from '@idraw/util'; -import type { ViewRectVertexes, CoreEvent } from '@idraw/types'; +import type { ViewRectVertexes, CoreEvent, ElementPosition } from '@idraw/types'; import type { Point, PointSize, @@ -33,7 +33,6 @@ import { calcMoveInGroup } from './util'; import { - key, keyActionType, keyResizeType, keyAreaStart, @@ -57,40 +56,11 @@ import { middlewareEventTextEdit } from '../text-editor'; export const middlewareEventSelect: string = '@middleware/select'; export const MiddlewareSelector: BoardMiddleware = (opts) => { - const { viewer, sharer, viewContent, calculator, eventHub } = opts; - const { helperContext } = viewContent; + const { viewer, sharer, boardContent, calculator, eventHub } = opts; + const { helperContext } = boardContent; let prevPoint: Point | null = null; let inBusyMode: 'resize' | 'drag' | 'drag-list' | 'area' | null = null; - eventHub.on(middlewareEventSelect, ({ uuids, positions }) => { - let elements: Element[] = []; - - const actionType = sharer.getSharedStorage(keyActionType); - const data = sharer.getActiveStorage('data'); - if (positions && Array.isArray(positions)) { - elements = findElementsFromListByPositions(positions, data?.elements || []); - } else { - elements = findElementsFromList(uuids, data?.elements || []); - } - - let needRefresh = false; - if (!actionType && elements.length === 1) { - // TODO - sharer.setSharedStorage(keyActionType, 'select'); - needRefresh = true; - } else if (actionType === 'select' && elements.length === 1) { - // TODO - needRefresh = true; - } - if (needRefresh) { - const elem = elements[0]; - const groupQueue = getGroupQueueFromList(elem.uuid, data?.elements || []); - sharer.setSharedStorage(keyGroupQueue, groupQueue); - updateSelectedElementList(elements); - viewer.drawFrame(); - } - }); - sharer.setSharedStorage(keyActionType, null); const getActiveElements = () => { @@ -176,15 +146,50 @@ export const MiddlewareSelector: BoardMiddleware { + let elements: Element[] = []; + const actionType = sharer.getSharedStorage(keyActionType); + const data = sharer.getActiveStorage('data'); + if (positions && Array.isArray(positions)) { + elements = findElementsFromListByPositions(positions, data?.elements || []); + } else { + elements = findElementsFromList(uuids, data?.elements || []); + } + + let needRefresh = false; + if (!actionType && elements.length === 1) { + // TODO + sharer.setSharedStorage(keyActionType, 'select'); + needRefresh = true; + } else if (actionType === 'select' && elements.length === 1) { + // TODO + needRefresh = true; + } + if (needRefresh) { + const elem = elements[0]; + const groupQueue = getGroupQueueFromList(elem.uuid, data?.elements || []); + sharer.setSharedStorage(keyGroupQueue, groupQueue); + updateSelectedElementList(elements); + viewer.drawFrame(); + } + }; + return { - mode: key, + use() { + eventHub.on(middlewareEventSelect, selectCallback); + }, + + disuse() { + eventHub.off(middlewareEventSelect, selectCallback); + }, + hover: (e: PointWatcherEvent) => { const resizeType = sharer.getSharedStorage(keyResizeType); const actionType = sharer.getSharedStorage(keyActionType); const groupQueue = sharer.getSharedStorage(keyGroupQueue); const triggerCursor = (target: PointTarget) => { - let cursor: string | null = target.type; + const cursor: string | null = target.type; if (inBusyMode === null) { eventHub.trigger('cursor', { type: cursor, diff --git a/packages/core/src/middleware/selector/util.ts b/packages/core/src/middleware/selector/util.ts index 5f4dc5898..49d112ce9 100644 --- a/packages/core/src/middleware/selector/util.ts +++ b/packages/core/src/middleware/selector/util.ts @@ -26,7 +26,7 @@ import type { AreaSize, ViewSizeInfo } from './types'; -import { keyDebugElemCenter, keyDebugEnd0, keyDebugEndHorizontal, keyDebugEndVertical, keyDebugStartHorizontal, keyDebugStartVertical } from './config'; +// import { keyDebugElemCenter, keyDebugEnd0, keyDebugEndHorizontal, keyDebugEndVertical, keyDebugStartHorizontal, keyDebugStartVertical } from './config'; function parseRadian(angle: number) { return (angle * Math.PI) / 180; @@ -229,12 +229,14 @@ export function resizeElement( moveHorizontalY = (endHorizontal.y - startHorizontal.y) / scale; moveHorizontalDist = calcMoveDist(moveHorizontalX, moveHorizontalY); moveHorizontalDist = changeMoveDistDirect(moveHorizontalDist, moveHorizontalY); + // eslint-disable-next-line @typescript-eslint/no-unused-vars centerMoveHorizontalDist = moveHorizontalDist / 2; moveVerticalX = (endVertical.x - startVertical.x) / scale; moveVerticalY = (endVertical.y - startVertical.y) / scale; moveVerticalDist = calcMoveDist(moveVerticalX, moveVerticalY); moveVerticalDist = changeMoveDistDirect(moveVerticalDist, moveVerticalY); + // eslint-disable-next-line @typescript-eslint/no-unused-vars centerMoveVerticalDist = moveVerticalDist / 2; } diff --git a/packages/core/src/middleware/text-editor/index.ts b/packages/core/src/middleware/text-editor/index.ts index a6fab194e..4d196c136 100644 --- a/packages/core/src/middleware/text-editor/index.ts +++ b/packages/core/src/middleware/text-editor/index.ts @@ -11,10 +11,8 @@ type TextEditEvent = { const defaultElementDetail = getDefaultElementDetailConfig(); export const MiddlewareTextEditor: BoardMiddleware, CoreEvent> = (opts) => { - const key = 'SELECT'; - - const { eventHub, viewContent, viewer } = opts; - const canvas = viewContent.boardContext.canvas; + const { eventHub, boardContent, viewer } = opts; + const canvas = boardContent.boardContext.canvas; const textarea = document.createElement('textarea'); const canvasWrapper = document.createElement('div'); const container = opts.container || document.body; @@ -160,15 +158,19 @@ export const MiddlewareTextEditor: BoardMiddleware, CoreEven hideTextArea(); }); - eventHub.on(middlewareEventTextEdit, (e: TextEditEvent) => { + const textEditCallback = (e: TextEditEvent) => { if (e?.element && e?.element?.type === 'text') { activeElem = e.element; } showTextArea(e); - }); + }; return { - mode: key, - isDefault: true + use() { + eventHub.on(middlewareEventTextEdit, textEditCallback); + }, + disuse() { + eventHub.off(middlewareEventTextEdit, textEditCallback); + } }; }; diff --git a/packages/idraw/src/config.ts b/packages/idraw/src/config.ts new file mode 100644 index 000000000..3f59234a2 --- /dev/null +++ b/packages/idraw/src/config.ts @@ -0,0 +1,10 @@ +import { IDrawSettings } from '@idraw/types'; + +export const defaultSettings: Required = { + enableScroll: true, + enableSelect: true, + enableScale: true, + enableRuler: true, + enableTextEdit: true, + enableDrag: false +}; diff --git a/packages/idraw/src/idraw.ts b/packages/idraw/src/idraw.ts index df878c9fc..22ae92bb4 100644 --- a/packages/idraw/src/idraw.ts +++ b/packages/idraw/src/idraw.ts @@ -1,5 +1,25 @@ -import { Core, MiddlewareSelector, MiddlewareScroller, MiddlewareScaler, MiddlewareRuler, MiddlewareTextEditor, middlewareEventSelect } from '@idraw/core'; -import type { PointSize, IDrawOptions, Data, ViewSizeInfo, ElementType, Element, RecursivePartial, ElementPosition } from '@idraw/types'; +import { + Core, + MiddlewareSelector, + MiddlewareScroller, + MiddlewareScaler, + MiddlewareRuler, + MiddlewareTextEditor, + middlewareEventSelect, + MiddlewareDragger +} from '@idraw/core'; +import type { + PointSize, + IDrawOptions, + IDrawSettings, + Data, + ViewSizeInfo, + ViewScaleInfo, + ElementType, + Element, + RecursivePartial, + ElementPosition +} from '@idraw/types'; import type { IDrawEvent } from './event'; import { createElement, @@ -9,12 +29,14 @@ import { moveElementPosition, getElementPositionFromList } from '@idraw/util'; +import { defaultSettings } from './config'; export class iDraw { #core: Core; #opts: IDrawOptions; - constructor(mount: HTMLDivElement, opts: IDrawOptions) { + constructor(mount: HTMLDivElement, options: IDrawOptions) { + const opts = { ...defaultSettings, ...options }; const { width, height, devicePixelRatio } = opts; const core = new Core(mount, { width, height, devicePixelRatio }); this.#core = core; @@ -23,13 +45,61 @@ export class iDraw { } #init() { - const { disableRuler, disableScale, disableScroll, disableSelect, disableTextEdit } = this.#opts; + const { enableRuler, enableScale, enableScroll, enableSelect, enableTextEdit, enableDrag } = this.#opts; const core = this.#core; - disableScroll !== true && core.use(MiddlewareScroller); - disableSelect !== true && core.use(MiddlewareSelector); - disableScale !== true && core.use(MiddlewareScaler); - disableRuler !== true && core.use(MiddlewareRuler); - disableTextEdit !== true && core.use(MiddlewareTextEditor); + enableScroll === true && core.use(MiddlewareScroller); + enableSelect === true && core.use(MiddlewareSelector); + enableScale === true && core.use(MiddlewareScaler); + enableRuler === true && core.use(MiddlewareRuler); + enableTextEdit === true && core.use(MiddlewareTextEditor); + enableDrag === true && core.use(MiddlewareTextEditor); + } + + reset(opts: IDrawSettings) { + const core = this.#core; + const { enableRuler, enableScale, enableScroll, enableSelect, enableTextEdit, enableDrag } = opts; + if (enableScroll === true) { + core.use(MiddlewareScroller); + } else if (enableScroll === false) { + core.disuse(MiddlewareScroller); + } + + if (enableSelect === true) { + core.use(MiddlewareSelector); + } else if (enableSelect === false) { + core.disuse(MiddlewareSelector); + } + + if (enableScale === true) { + core.use(MiddlewareScaler); + } else if (enableScale === false) { + core.disuse(MiddlewareScaler); + } + + if (enableRuler === true) { + core.use(MiddlewareRuler); + } else if (enableRuler === false) { + core.disuse(MiddlewareRuler); + } + + if (enableTextEdit === true) { + core.use(MiddlewareTextEditor); + } else if (enableTextEdit === false) { + core.disuse(MiddlewareTextEditor); + } + + if (enableDrag === true) { + core.use(MiddlewareDragger); + } else if (enableDrag === false) { + core.disuse(MiddlewareDragger); + } + + core.refresh(); + + this.#opts = { + ...this.#opts, + ...opts + }; } setData(data: Data) { @@ -42,6 +112,13 @@ export class iDraw { return this.#core.getData(); } + getViewInfo(): { + viewSizeInfo: ViewSizeInfo; + viewScaleInfo: ViewScaleInfo; + } { + return this.#core.getViewInfo(); + } + scale(opts: { scale: number; point: PointSize }) { this.#core.scale(opts); } diff --git a/packages/idraw/src/index.ts b/packages/idraw/src/index.ts index ec7cca33a..5cb259ab2 100644 --- a/packages/idraw/src/index.ts +++ b/packages/idraw/src/index.ts @@ -12,6 +12,7 @@ export { middlewareEventRuler, MiddlewareTextEditor } from '@idraw/core'; +export { Sharer, Calculator } from '@idraw/board'; export { Renderer } from '@idraw/renderer'; export { delay, @@ -39,7 +40,7 @@ export { loadHTML, is, check, - createViewContent, + createBoardContent, createContext2D, createOffscreenContext2D, EventEmitter, @@ -67,11 +68,14 @@ export { validateElements, calcElementsContextSize, calcElementsViewInfo, + calcElementListSize, getElemenetsAssetIds, findElementFromList, findElementsFromList, findElementFromListByPosition, findElementsFromListByPositions, + findElementQueueFromListByPosition, + getElementPositionFromList, updateElementInList, getGroupQueueFromList, getElementSize, diff --git a/packages/renderer/src/draw/box.ts b/packages/renderer/src/draw/box.ts index faa13a52f..af58980f6 100644 --- a/packages/renderer/src/draw/box.ts +++ b/packages/renderer/src/draw/box.ts @@ -11,7 +11,7 @@ export function drawBox( originElem: Element; calcElemSize: ElementSize; pattern?: string | CanvasPattern | null; - renderContent: Function; + renderContent: () => void; viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo; } @@ -45,7 +45,7 @@ function drawClipPath( opts: { originElem?: Element; calcElemSize?: ElementSize; - renderContent: Function; + renderContent: () => void; viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo; } @@ -60,8 +60,8 @@ function drawClipPath( const scaleH = h / originH; const viewOriginX = originX * scaleW; const viewOriginY = originY * scaleH; - let internalX = x - viewOriginX; - let internalY = y - viewOriginY; + const internalX = x - viewOriginX; + const internalY = y - viewOriginY; ctx.save(); ctx.translate(internalX as number, internalY as number); @@ -88,10 +88,12 @@ function drawBoxBackground( opts: { pattern?: string | CanvasPattern | null; viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo } ): void { const { pattern, viewScaleInfo, viewSizeInfo } = opts; - let transform: TransformAction[] = []; - let { borderRadius, borderWidth } = viewElem.detail; + const transform: TransformAction[] = []; + let { borderRadius } = viewElem.detail; + const { borderWidth } = viewElem.detail; if (typeof borderWidth !== 'number') { // TODO: If borderWidth is an array, borderRadius will not take effect and will become 0. + // eslint-disable-next-line @typescript-eslint/no-unused-vars borderRadius = 0; } if (viewElem.detail.background || pattern) { @@ -321,7 +323,7 @@ function drawBoxBorder(ctx: ViewContext2D, viewElem: Element, opts: export function drawBoxShadow( ctx: ViewContext2D, viewElem: Element, - opts: { viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo; renderContent: Function } + opts: { viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo; renderContent: () => void } ): void { const { detail } = viewElem; const { viewScaleInfo, renderContent } = opts; diff --git a/packages/renderer/src/draw/circle.ts b/packages/renderer/src/draw/circle.ts index bd4e89a75..5629a88cf 100644 --- a/packages/renderer/src/draw/circle.ts +++ b/packages/renderer/src/draw/circle.ts @@ -8,7 +8,7 @@ export function drawCircle(ctx: ViewContext2D, elem: Element<'circle'>, opts: Re const { background = '#000000', borderColor = '#000000', borderWidth = 0 } = detail; const { calculator, viewScaleInfo, viewSizeInfo } = opts; // const { scale, offsetTop, offsetBottom, offsetLeft, offsetRight } = viewScaleInfo; - const { x, y, w, h } = calculator.elementSize({ x: elem.x, y: elem.y, w: elem.w, h: elem.h }, viewScaleInfo, viewSizeInfo); + const { x, y, w, h } = calculator?.elementSize({ x: elem.x, y: elem.y, w: elem.w, h: elem.h }, viewScaleInfo, viewSizeInfo) || elem; const viewElem = { ...elem, ...{ x, y, w, h, angle } }; rotateElement(ctx, { x, y, w, h, angle }, () => { diff --git a/packages/renderer/src/draw/elements.ts b/packages/renderer/src/draw/elements.ts index 66cc48a39..bbf57f77d 100644 --- a/packages/renderer/src/draw/elements.ts +++ b/packages/renderer/src/draw/elements.ts @@ -17,10 +17,12 @@ export function drawElementList(ctx: ViewContext2D, data: Data, opts: RendererDr } } }; - // TODO - if (!opts.calculator.isElementInView(elem, opts.viewScaleInfo, opts.viewSizeInfo)) { - continue; + if (opts.forceDrawAll !== true) { + if (!opts.calculator?.isElementInView(elem, opts.viewScaleInfo, opts.viewSizeInfo)) { + continue; + } } + try { drawElement(ctx, elem, opts); } catch (err) { diff --git a/packages/renderer/src/draw/group.ts b/packages/renderer/src/draw/group.ts index 83e6d85fa..071f0236f 100644 --- a/packages/renderer/src/draw/group.ts +++ b/packages/renderer/src/draw/group.ts @@ -65,7 +65,7 @@ export function drawElement(ctx: ViewContext2D, elem: Element, opts export function drawGroup(ctx: ViewContext2D, elem: Element<'group'>, opts: RendererDrawElementOptions) { const { calculator, viewScaleInfo, viewSizeInfo } = opts; - const { x, y, w, h, angle } = calculator.elementSize({ x: elem.x, y: elem.y, w: elem.w, h: elem.h, angle: elem.angle }, viewScaleInfo, viewSizeInfo); + const { x, y, w, h, angle } = calculator?.elementSize({ x: elem.x, y: elem.y, w: elem.w, h: elem.h, angle: elem.angle }, viewScaleInfo, viewSizeInfo) || elem; const viewElem = { ...elem, ...{ x, y, w, h, angle } }; rotateElement(ctx, { x, y, w, h, angle }, () => { drawBoxShadow(ctx, viewElem, { @@ -116,9 +116,12 @@ export function drawGroup(ctx: ViewContext2D, elem: Element<'group'>, opts: Rend y: newParentSize.y + child.y } }; - if (!calculator.isElementInView(child, opts.viewScaleInfo, opts.viewSizeInfo)) { - continue; + if (opts.forceDrawAll !== true) { + if (!calculator?.isElementInView(child, opts.viewScaleInfo, opts.viewSizeInfo)) { + continue; + } } + try { drawElement(ctx, child, { ...opts }); } catch (err) { diff --git a/packages/renderer/src/draw/html.ts b/packages/renderer/src/draw/html.ts index 7df20402c..17634fc0b 100644 --- a/packages/renderer/src/draw/html.ts +++ b/packages/renderer/src/draw/html.ts @@ -4,7 +4,7 @@ import { rotateElement } from '@idraw/util'; export function drawHTML(ctx: ViewContext2D, elem: Element<'html'>, opts: RendererDrawElementOptions) { const content = opts.loader.getContent(elem); const { calculator, viewScaleInfo, viewSizeInfo } = opts; - const { x, y, w, h, angle } = calculator.elementSize(elem, viewScaleInfo, viewSizeInfo); + const { x, y, w, h, angle } = calculator?.elementSize(elem, viewScaleInfo, viewSizeInfo) || elem; rotateElement(ctx, { x, y, w, h, angle }, () => { if (!content) { opts.loader.load(elem as Element<'html'>, opts.elementAssets || {}); diff --git a/packages/renderer/src/draw/image.ts b/packages/renderer/src/draw/image.ts index d827ffa3b..3dd7ef55d 100644 --- a/packages/renderer/src/draw/image.ts +++ b/packages/renderer/src/draw/image.ts @@ -5,7 +5,7 @@ import { drawBox, drawBoxShadow } from './box'; export function drawImage(ctx: ViewContext2D, elem: Element<'image'>, opts: RendererDrawElementOptions) { const content = opts.loader.getContent(elem); const { calculator, viewScaleInfo, viewSizeInfo } = opts; - const { x, y, w, h, angle } = calculator.elementSize(elem, viewScaleInfo, viewSizeInfo); + const { x, y, w, h, angle } = calculator?.elementSize(elem, viewScaleInfo, viewSizeInfo) || elem; const viewElem = { ...elem, ...{ x, y, w, h, angle } }; rotateElement(ctx, { x, y, w, h, angle }, () => { diff --git a/packages/renderer/src/draw/path.ts b/packages/renderer/src/draw/path.ts index 2f47f7865..74a61387a 100644 --- a/packages/renderer/src/draw/path.ts +++ b/packages/renderer/src/draw/path.ts @@ -6,7 +6,7 @@ export function drawPath(ctx: ViewContext2D, elem: Element<'path'>, opts: Render const { detail } = elem; const { originX, originY, originW, originH } = detail; const { calculator, viewScaleInfo, viewSizeInfo } = opts; - const { x, y, w, h, angle } = calculator.elementSize(elem, viewScaleInfo, viewSizeInfo); + const { x, y, w, h, angle } = calculator?.elementSize(elem, viewScaleInfo, viewSizeInfo) || elem; const scaleW = w / originW; const scaleH = h / originH; const viewOriginX = originX * scaleW; diff --git a/packages/renderer/src/draw/rect.ts b/packages/renderer/src/draw/rect.ts index 11600ad66..741d60996 100644 --- a/packages/renderer/src/draw/rect.ts +++ b/packages/renderer/src/draw/rect.ts @@ -4,7 +4,7 @@ import { drawBox, drawBoxShadow } from './box'; export function drawRect(ctx: ViewContext2D, elem: Element<'rect'>, opts: RendererDrawElementOptions) { const { calculator, viewScaleInfo, viewSizeInfo } = opts; - let { x, y, w, h, angle } = calculator.elementSize(elem, viewScaleInfo, viewSizeInfo); + const { x, y, w, h, angle } = calculator?.elementSize(elem, viewScaleInfo, viewSizeInfo) || elem; const viewElem = { ...elem, ...{ x, y, w, h, angle } }; rotateElement(ctx, { x, y, w, h, angle }, () => { diff --git a/packages/renderer/src/draw/svg.ts b/packages/renderer/src/draw/svg.ts index 477315915..b476f7190 100644 --- a/packages/renderer/src/draw/svg.ts +++ b/packages/renderer/src/draw/svg.ts @@ -4,7 +4,7 @@ import { rotateElement } from '@idraw/util'; export function drawSVG(ctx: ViewContext2D, elem: Element<'svg'>, opts: RendererDrawElementOptions) { const content = opts.loader.getContent(elem); const { calculator, viewScaleInfo, viewSizeInfo } = opts; - const { x, y, w, h, angle } = calculator.elementSize(elem, viewScaleInfo, viewSizeInfo); + const { x, y, w, h, angle } = calculator?.elementSize(elem, viewScaleInfo, viewSizeInfo) || elem; rotateElement(ctx, { x, y, w, h, angle }, () => { if (!content) { opts.loader.load(elem as Element<'svg'>, opts.elementAssets || {}); diff --git a/packages/renderer/src/draw/text.ts b/packages/renderer/src/draw/text.ts index adc80d3b4..e9824fd54 100644 --- a/packages/renderer/src/draw/text.ts +++ b/packages/renderer/src/draw/text.ts @@ -7,7 +7,7 @@ const detailConfig = getDefaultElementDetailConfig(); export function drawText(ctx: ViewContext2D, elem: Element<'text'>, opts: RendererDrawElementOptions) { const { calculator, viewScaleInfo, viewSizeInfo } = opts; - const { x, y, w, h, angle } = calculator.elementSize(elem, viewScaleInfo, viewSizeInfo); + const { x, y, w, h, angle } = calculator?.elementSize(elem, viewScaleInfo, viewSizeInfo) || elem; const viewElem = { ...elem, ...{ x, y, w, h, angle } }; rotateElement(ctx, { x, y, w, h, angle }, () => { drawBox(ctx, viewElem, { diff --git a/packages/renderer/src/index.ts b/packages/renderer/src/index.ts index 607709928..23fae28e1 100644 --- a/packages/renderer/src/index.ts +++ b/packages/renderer/src/index.ts @@ -4,25 +4,25 @@ import { Loader } from './loader'; import type { Data, BoardRenderer, RendererOptions, RendererEventMap, RendererDrawOptions } from '@idraw/types'; export class Renderer extends EventEmitter implements BoardRenderer { - private _opts: RendererOptions; - private _loader: Loader = new Loader(); + #opts: RendererOptions; + #loader: Loader = new Loader(); // private _draftContextTop: CanvasRenderingContext2D; // private _draftContextMiddle: CanvasRenderingContext2D; // private _draftContextBottom: CanvasRenderingContext2D; constructor(opts: RendererOptions) { super(); - this._opts = opts; - // const { width, height } = this._opts.viewContent.viewContext.canvas; + this.#opts = opts; + // const { width, height } = this.#opts.boardContent.viewContext.canvas; // this._draftContextTop = createOffscreenContext2D({ width, height }) as CanvasRenderingContext2D; // this._draftContextMiddle = createOffscreenContext2D({ width, height }) as CanvasRenderingContext2D; // this._draftContextBottom = createOffscreenContext2D({ width, height }) as CanvasRenderingContext2D; - this._init(); + this.#init(); } - private _init() { - const { _loader: loader } = this; + #init() { + const loader = this.#loader; loader.on('load', (e) => { this.trigger('load', e); }); @@ -32,13 +32,13 @@ export class Renderer extends EventEmitter implements BoardRen } updateOptions(opts: RendererOptions) { - this._opts = opts; + this.#opts = opts; } drawData(data: Data, opts: RendererDrawOptions) { - const { _loader: loader } = this; - const { calculator } = this._opts; - const { viewContext } = this._opts.viewContent; + const loader = this.#loader; + const { calculator } = this.#opts; + const viewContext = this.#opts.viewContext; viewContext.clearRect(0, 0, viewContext.canvas.width, viewContext.canvas.height); const parentElementSize = { x: 0, @@ -56,7 +56,11 @@ export class Renderer extends EventEmitter implements BoardRen } scale(num: number) { - const { sharer } = this._opts; + const { sharer } = this.#opts; + if (!sharer) { + // TODO + return; + } const { data, offsetTop, offsetBottom, offsetLeft, offsetRight, width, height, contextHeight, contextWidth, devicePixelRatio } = sharer.getActiveStoreSnapshot(); if (data) { diff --git a/packages/types/src/lib/board.ts b/packages/types/src/lib/board.ts index eebeb0db0..f7c16fc9c 100644 --- a/packages/types/src/lib/board.ts +++ b/packages/types/src/lib/board.ts @@ -1,5 +1,5 @@ import type { Point, PointSize } from './point'; -import type { ViewContent, ViewCalculator, ViewScaleInfo, ViewSizeInfo } from './view'; +import type { BoardContent, ViewCalculator, ViewScaleInfo, ViewSizeInfo } from './view'; import type { UtilEventEmitter } from './util'; import type { ActiveStore, StoreSharer } from './store'; import type { RendererEventMap, RendererOptions, RendererDrawOptions } from './renderer'; @@ -53,12 +53,9 @@ export interface BoardWatcherEventMap = any> clear: void; } -export type BoardMode = 'SELECT' | 'SCROLL' | 'RULE' | 'CONNECT' | 'PENCIL' | 'PEN' | string; - export interface BoardMiddlewareObject = any> { - mode: BoardMode; - isDefault?: boolean; - created?: () => void; + use?: () => void; + disuse?: () => void; // action hover?: (e: BoardWatcherEventMap['hover']) => void | boolean; pointStart?: (e: BoardWatcherEventMap['pointStart']) => void | boolean; @@ -84,7 +81,7 @@ export interface BoardMiddlewareObject = any } export interface BoardMiddlewareOptions = Record, E extends BoardExtendEvent = Record> { - viewContent: ViewContent; + boardContent: BoardContent; sharer: StoreSharer; viewer: BoardViewer; calculator: ViewCalculator; @@ -98,7 +95,7 @@ export type BoardMiddleware = any, E extends ) => BoardMiddlewareObject; export interface BoardOptions { - viewContent: ViewContent; + boardContent: BoardContent; container?: HTMLDivElement; } @@ -116,7 +113,7 @@ export interface BoardViewerOptions { sharer: StoreSharer>; renderer: BoardRenderer; calculator: ViewCalculator; - viewContent: ViewContent; + boardContent: BoardContent; beforeDrawFrame: (e: { snapshot: BoardViewerFrameSnapshot> }) => void; afterDrawFrame: (e: { snapshot: BoardViewerFrameSnapshot> }) => void; } @@ -138,7 +135,7 @@ export interface BoardRenderer extends UtilEventEmitter { } export interface BoardWatcherOptions { - viewContent: ViewContent; + boardContent: BoardContent; sharer: StoreSharer>; } @@ -147,4 +144,4 @@ export interface BoardWatcherStore { prevClickPoint: Point | null; } -export type BoardExtendEvent = Record & {}; +export type BoardExtendEvent = Record; diff --git a/packages/types/src/lib/core.ts b/packages/types/src/lib/core.ts index eb5f84257..30fc9a5fd 100644 --- a/packages/types/src/lib/core.ts +++ b/packages/types/src/lib/core.ts @@ -15,12 +15,14 @@ export type CursorType = | 'resize-top-left' | 'resize-top-right' | 'resize-bottom-left' - | 'resize-bottom-right'; + | 'resize-bottom-right' + | 'drag-default' + | 'drag-active'; export interface CoreEventCursor { type: CursorType | string | null; - groupQueue: Element<'group'>[]; - element: Element; + groupQueue?: Element<'group'>[]; + element?: Element; } export interface CoreEventSelect { diff --git a/packages/types/src/lib/idraw.ts b/packages/types/src/lib/idraw.ts index dd3074766..62d35f481 100644 --- a/packages/types/src/lib/idraw.ts +++ b/packages/types/src/lib/idraw.ts @@ -1,9 +1,12 @@ import type { CoreOptions } from './core'; -export type IDrawOptions = CoreOptions & { - disableScroll?: boolean; - disableSelect?: boolean; - disableScale?: boolean; - disableRuler?: boolean; - disableTextEdit?: boolean; -}; +export interface IDrawSettings { + enableScroll?: boolean; + enableSelect?: boolean; + enableScale?: boolean; + enableRuler?: boolean; + enableTextEdit?: boolean; + enableDrag?: boolean; +} + +export type IDrawOptions = CoreOptions & IDrawSettings; diff --git a/packages/types/src/lib/renderer.ts b/packages/types/src/lib/renderer.ts index 1f67db0ae..fd58505f3 100644 --- a/packages/types/src/lib/renderer.ts +++ b/packages/types/src/lib/renderer.ts @@ -1,17 +1,18 @@ -import type { ViewContent, ViewScaleInfo, ViewCalculator, ViewSizeInfo } from './view'; +import type { ViewScaleInfo, ViewCalculator, ViewSizeInfo } from './view'; import type { Element, ElementSize, ElementAssets } from './element'; import type { LoaderEventMap, LoadElementType, LoadContent } from './loader'; import type { UtilEventEmitter } from './util'; import type { StoreSharer } from './store'; +import { ViewContext2D } from '@idraw/types'; export interface RendererOptions { - viewContent: ViewContent; - sharer: StoreSharer; - calculator: ViewCalculator; + viewContext: ViewContext2D; + sharer?: StoreSharer; + calculator?: ViewCalculator; } export interface RendererEvent { - viewContext: ViewContent['viewContext']; + viewContext: ViewContext2D; } export interface RendererEventMap { @@ -27,11 +28,12 @@ export interface RendererLoader extends UtilEventEmitter { export interface RendererDrawOptions { viewSizeInfo: ViewSizeInfo; viewScaleInfo: ViewScaleInfo; + forceDrawAll?: boolean; } export interface RendererDrawElementOptions extends RendererDrawOptions { loader: RendererLoader; - calculator: ViewCalculator; + calculator?: ViewCalculator; viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo; parentElementSize: ElementSize; diff --git a/packages/types/src/lib/view.ts b/packages/types/src/lib/view.ts index d4318beaa..65d57d4b4 100644 --- a/packages/types/src/lib/view.ts +++ b/packages/types/src/lib/view.ts @@ -22,7 +22,7 @@ export interface ViewSizeInfo extends ViewContextSize { devicePixelRatio: number; } -export interface ViewContent { +export interface BoardContent { boardContext: ViewContext2D; viewContext: ViewContext2D; helperContext: ViewContext2D; @@ -31,7 +31,8 @@ export interface ViewContent { } export interface ViewCalculatorOptions { - viewContent: ViewContent; + // boardContent?: BoardContent; + viewContext: ViewContext2D; } export interface ViewCalculator { diff --git a/packages/util/src/index.ts b/packages/util/src/index.ts index 3dbef12c2..69925844f 100644 --- a/packages/util/src/index.ts +++ b/packages/util/src/index.ts @@ -7,7 +7,7 @@ export { istype } from './lib/istype'; export { loadImage, loadSVG, loadHTML } from './lib/load'; export { is } from './lib/is'; export { check } from './lib/check'; -export { createViewContent, createContext2D, createOffscreenContext2D } from './lib/canvas'; +export { createBoardContent, createContext2D, createOffscreenContext2D } from './lib/canvas'; export { EventEmitter } from './lib/event'; export { calcDistance, calcSpeed, equalPoint, equalTouchPoint, vaildPoint, vaildTouchPoint, getCenterFromTwoPoints } from './lib/point'; export { Store } from './lib/store'; @@ -33,13 +33,15 @@ export { findElementFromList, findElementsFromList, findElementFromListByPosition, + findElementQueueFromListByPosition, findElementsFromListByPositions, getGroupQueueFromList, getElementSize, mergeElementAsset, filterElementAsset, isResourceElement, - getElementPositionFromList + getElementPositionFromList, + calcElementListSize } from './lib/element'; export { checkRectIntersect } from './lib/rect'; export { diff --git a/packages/util/src/lib/canvas.ts b/packages/util/src/lib/canvas.ts index 2df8199e7..06baa70d4 100644 --- a/packages/util/src/lib/canvas.ts +++ b/packages/util/src/lib/canvas.ts @@ -1,4 +1,4 @@ -import type { ViewContent } from '@idraw/types'; +import type { BoardContent } from '@idraw/types'; import { Context2D } from './context2d'; export function createContext2D(opts: { ctx?: CanvasRenderingContext2D; width: number; height: number; devicePixelRatio: number }): Context2D { @@ -28,10 +28,10 @@ export function createOffscreenContext2D(opts: { width: number; height: number; return context2d; } -export function createViewContent( +export function createBoardContent( canvas: HTMLCanvasElement, opts: { width: number; height: number; devicePixelRatio: number; offscreen?: boolean } -): ViewContent { +): BoardContent { const { width, height, devicePixelRatio, offscreen } = opts; const ctxOpts = { width, @@ -58,7 +58,7 @@ export function createViewContent( helperContext.clearRect(0, 0, w, h); }; - const content: ViewContent = { + const content: BoardContent = { underContext, viewContext, helperContext, @@ -83,7 +83,7 @@ export function createViewContent( helperContext.clearRect(0, 0, width, height); }; - const content: ViewContent = { + const content: BoardContent = { underContext, viewContext, helperContext, diff --git a/packages/util/src/lib/element.ts b/packages/util/src/lib/element.ts index 71bbcf0e7..76e301ad3 100644 --- a/packages/util/src/lib/element.ts +++ b/packages/util/src/lib/element.ts @@ -79,6 +79,64 @@ export function validateElements(elements: Array>): boolean type AreaSize = ElementSize; +export function calcElementListSize(list: Elements): ElementSize { + const area: AreaSize = { x: 0, y: 0, w: 0, h: 0 }; + let prevElemSize: ElementSize | null = null; + + for (let i = 0; i < list.length; i++) { + const elem = list[i]; + if (elem?.operations?.invisible) { + continue; + } + const elemSize = { + x: elem.x, + y: elem.y, + w: elem.w, + h: elem.h, + angle: elem.angle || 0 + }; + + if (elemSize.angle && (elemSize.angle > 0 || elemSize.angle < 0)) { + const ves = rotateElementVertexes(elemSize); + if (ves.length === 4) { + const xList = [ves[0].x, ves[1].x, ves[2].x, ves[3].x]; + const yList = [ves[0].y, ves[1].y, ves[2].y, ves[3].y]; + elemSize.x = Math.min(...xList); + elemSize.y = Math.min(...yList); + elemSize.w = Math.abs(Math.max(...xList) - Math.min(...xList)); + elemSize.h = Math.abs(Math.max(...yList) - Math.min(...yList)); + } + } + if (prevElemSize) { + const areaStartX = Math.min(elemSize.x, area.x); + const areaStartY = Math.min(elemSize.y, area.y); + + const areaEndX = Math.max(elemSize.x + elemSize.w, area.x + area.w); + const areaEndY = Math.max(elemSize.y + elemSize.h, area.y + area.h); + + area.x = areaStartX; + area.y = areaStartY; + area.w = Math.abs(areaEndX - areaStartX); + area.h = Math.abs(areaEndY - areaStartY); + } else { + area.x = elemSize.x; + area.y = elemSize.y; + area.w = elemSize.w; + area.h = elemSize.h; + } + prevElemSize = elemSize; + } + + const listSize: ElementSize = { + x: Math.floor(area.x), + y: Math.floor(area.y), + w: Math.ceil(area.w), + h: Math.ceil(area.h) + }; + + return listSize; +} + export function calcElementsContextSize( elements: Array>, opts?: { viewWidth: number; viewHeight: number; extend?: boolean } @@ -120,7 +178,7 @@ export function calcElementsContextSize( area.y = Math.min(area.y, 0); } - const ctxSize: ViewContextSize = { + const ctxSize = { contextWidth: area.w, contextHeight: area.h }; @@ -343,8 +401,29 @@ export function findElementFromListByPosition(position: ElementPosition, list: E return result; } +export function findElementQueueFromListByPosition(position: ElementPosition, list: Element[]): Element[] { + const result: Element[] = []; + let tempList: Element[] = list; + for (let i = 0; i < position.length; i++) { + const pos = position[i]; + const item = tempList[pos]; + if (item) { + result.push(item); + } else { + break; + } + + if (i < position.length - 1 && item.type === 'group') { + tempList = (item as Element<'group'>).detail.children; + } else { + break; + } + } + return result; +} + export function getElementPositionFromList(uuid: string, elements: Element[]): ElementPosition { - let result: ElementPosition = []; + const result: ElementPosition = []; let over = false; const _loop = (list: Element[]) => { for (let i = 0; i < list.length; i++) { diff --git a/packages/util/src/lib/handle-element.ts b/packages/util/src/lib/handle-element.ts index f53250469..290d4b771 100644 --- a/packages/util/src/lib/handle-element.ts +++ b/packages/util/src/lib/handle-element.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ import type { RecursivePartial, Element, Elements, ElementPosition, ElementSize, ElementType, ViewScaleInfo, ViewSizeInfo } from '@idraw/types'; import { createUUID } from './uuid'; import { @@ -226,7 +227,7 @@ function mergeElement = Element>(ori } else if (['detail', 'operations'].includes(commonKey)) { // @ts-ignore if (istype.json(updateContent[commonKey] as any)) { - if (!(originElem as Object)?.hasOwnProperty(commonKey)) { + if (!(originElem as unknown)?.hasOwnProperty(commonKey)) { // @ts-ignore originElem[commonKey] = {}; } @@ -237,7 +238,7 @@ function mergeElement = Element>(ori } // @ts-ignore } else if (istype.array(updateContent[commonKey] as any)) { - if (!(originElem as Object)?.hasOwnProperty(commonKey)) { + if (!(originElem as unknown)?.hasOwnProperty(commonKey)) { // @ts-ignore originElem[commonKey] = []; } diff --git a/packages/util/src/lib/view-box.ts b/packages/util/src/lib/view-box.ts index 1dcacc613..4bc3a7da4 100644 --- a/packages/util/src/lib/view-box.ts +++ b/packages/util/src/lib/view-box.ts @@ -5,7 +5,9 @@ const defaultElemConfig = getDefaultElementDetailConfig(); export function calcViewBoxSize(viewElem: Element, opts: { viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo }): ViewBoxSize { const { viewScaleInfo } = opts; const { scale } = viewScaleInfo; - let { borderRadius, boxSizing = defaultElemConfig.boxSizing, borderWidth } = viewElem.detail; + let { borderRadius } = viewElem.detail; + const { boxSizing = defaultElemConfig.boxSizing, borderWidth } = viewElem.detail; + if (typeof borderWidth !== 'number') { // TODO: If borderWidth is an array, borderRadius will not take effect and will become 0. borderRadius = 0;