diff --git a/packages/board/src/index.ts b/packages/board/src/index.ts index 8cb0348..9ac4982 100644 --- a/packages/board/src/index.ts +++ b/packages/board/src/index.ts @@ -1,408 +1,148 @@ -import { - ScreenPosition, - ScreenSize, - ScreenContext, - Point, - PointCursor, - BoardOptions, - BoardSizeOptions, - IDrawContext -} from '@idraw/types'; -import util from '@idraw/util'; -import { ScreenWatcher } from './lib/screen-watcher'; -import { setStyle } from './lib/style'; -import { TypeBoardEventArgMap } from './lib/event'; -import { Scroller } from './lib/scroller'; -import { Screen } from './lib/screen'; -// import { TempData } from './lib/temp'; +import { Renderer } from '@idraw/renderer'; +import { throttle } from '@idraw/util'; +import type { Data, BoardMode, BoardOptions, BoardMiddleware, BoardMiddlewareObject, BoardWatcherEventMap } from '@idraw/types'; +import { Calculator } from './lib/calculator'; +import { BoardWatcher } from './lib/watcher'; +import { Sharer } from './lib/sharer'; +import { Viewer } from './lib/viewer'; -const { throttle, Context } = util; +const frameTime = 16; // ms -type PrivateOptions = BoardOptions & { - devicePixelRatio: number; -}; +const LOCK_MODES: BoardMode[] = ['RULER']; -export default class Board { - private _hasRendered = false; - - private _canvas: HTMLCanvasElement; - private _helperCanvas: HTMLCanvasElement; - private _displayCanvas: HTMLCanvasElement; - private _mount: HTMLDivElement; - private _opts: PrivateOptions; - private _ctx: IDrawContext; - private _helperCtx: IDrawContext; - // private _watcher: Watcher; - private _watcher: ScreenWatcher; - private _scroller: Scroller; - private _screen: Screen; - // private _tempData: TempData; - - constructor(mount: HTMLDivElement, opts: BoardOptions) { - // this._tempData = new TempData(opts); - - this._mount = mount; - this._canvas = document.createElement('canvas'); - this._helperCanvas = document.createElement('canvas'); - this._displayCanvas = document.createElement('canvas'); - this._mount.appendChild(this._displayCanvas); - this._opts = this._parsePrivateOptions(opts); - - const originCtx2d = this._canvas.getContext( - '2d' - ) as CanvasRenderingContext2D; - const displayCtx2d = this._displayCanvas.getContext( - '2d' - ) as CanvasRenderingContext2D; - const helperCtx2d = this._helperCanvas.getContext( - '2d' - ) as CanvasRenderingContext2D; - this._ctx = new Context(originCtx2d, this._opts); - this._helperCtx = new Context(helperCtx2d, this._opts); - this._screen = new Screen(this._ctx, this._opts); - // this._watcher = new Watcher(this._displayCanvas); - this._watcher = new ScreenWatcher(this._displayCanvas, this._ctx); - this._scroller = new Scroller(displayCtx2d, { - width: opts.width, - height: opts.height, - devicePixelRatio: opts.devicePixelRatio || 1, - scrollConfig: opts.scrollConfig +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 _activeMode: BoardMode = 'SELECT'; + constructor(opts: BoardOptions) { + const { viewContent } = opts; + const sharer = new Sharer(); + const calculator = new Calculator({ viewContent }); + const watcher = new BoardWatcher({ + viewContent }); - this._render(); - } - - getDisplayContext2D(): CanvasRenderingContext2D { - return this._displayCanvas.getContext('2d') as CanvasRenderingContext2D; - } - - getOriginContext2D(): CanvasRenderingContext2D { - return this._ctx.getContext(); - } - - getHelperContext2D(): CanvasRenderingContext2D { - return this._helperCtx.getContext(); - } - - getContext(): IDrawContext { - return this._ctx; - } - - getHelperContext(): IDrawContext { - return this._helperCtx; - } - - scale(scaleRatio: number): ScreenContext { - if (scaleRatio > 0) { - this._ctx.setTransform({ scale: scaleRatio }); - this._helperCtx.setTransform({ scale: scaleRatio }); - } - const { position, size } = this._screen.calcScreen(); - return { position, size }; - } - - scrollX(x: number) { - this._watcher.setStatusMap({ - canScrollYPrev: true, - canScrollYNext: true, - canScrollXPrev: true, - canScrollXNext: true + const renderer = new Renderer({ + viewContent, + sharer, + calculator }); - if (x >= 0 || x < 0) { - this._ctx.setTransform({ scrollX: x }); - this._helperCtx.setTransform({ scrollX: x }); - } - const { - position, - size, - canScrollXNext, - canScrollYNext, - canScrollXPrev, - canScrollYPrev - } = this._screen.calcScreen(); - this._watcher.setStatusMap({ - canScrollYPrev, - canScrollYNext, - canScrollXPrev, - canScrollXNext + + this._opts = opts; + this._sharer = sharer; + this._renderer = renderer; + this._watcher = watcher; + this._calculator = calculator; + this._viewer = new Viewer({ + viewContent: opts.viewContent, + sharer, + renderer, + beforeDrawFrame: (e) => { + this._handleBeforeDrawFrame(e); + }, + afterDrawFrame: (e) => { + this._handleAfterDrawFrame(e); + } }); - return { position, size }; + this._init(); + this._resetActiveMiddlewareObjs(); } - scrollY(y: number): ScreenContext { - this._watcher.setStatusMap({ - canScrollYPrev: true, - canScrollYNext: true, - canScrollXPrev: true, - canScrollXNext: true - }); - if (y >= 0 || y < 0) { - this._ctx.setTransform({ scrollY: y }); - this._helperCtx.setTransform({ scrollY: y }); - } - const { - position, - size, - canScrollXNext, - canScrollYNext, - canScrollXPrev, - canScrollYPrev - } = this._screen.calcScreen(); - this._watcher.setStatusMap({ - canScrollYPrev, - canScrollYNext, - canScrollXPrev, - canScrollXNext - }); - return { position, size }; - } - - getTransform() { - return this._ctx.getTransform(); - } - - draw(): ScreenContext { - this.clear(); - const { position, deviceSize, size } = this._screen.calcScreen(); - const displayCtx = this._displayCanvas.getContext('2d'); - displayCtx?.drawImage( - this._canvas, - deviceSize.x, - deviceSize.y, - deviceSize.w, - deviceSize.h + private _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); + }, frameTime) ); - displayCtx?.drawImage( - this._helperCanvas, - deviceSize.x, - deviceSize.y, - deviceSize.w, - deviceSize.h - ); - if (this._opts.canScroll === true) { - this._scroller.draw(position); - } - return { position, size }; - } - - clear() { - const displayCtx = this._displayCanvas.getContext('2d'); - displayCtx?.clearRect( - 0, - 0, - this._displayCanvas.width, - this._displayCanvas.height + this._watcher.on( + 'hover', + throttle((e) => { + this._handleHover(e); + }, frameTime) ); } - on( - name: T, - callback: (p: TypeBoardEventArgMap[T]) => void - ) { - this._watcher.on(name, callback); - } - - off( - name: T, - callback: (p: TypeBoardEventArgMap[T]) => void - ) { - this._watcher.off(name, callback); - } - - getScreenInfo(): { - size: ScreenSize; - position: ScreenPosition; - deviceSize: ScreenSize; - width: number; - height: number; - devicePixelRatio: number; - // eslint-disable-next-line indent - } { - return this._screen.calcScreen(); - } - - setCursor(cursor: PointCursor) { - this._displayCanvas.style.cursor = cursor; - } - - resetCursor() { - this._displayCanvas.style.cursor = 'auto'; - } - - resetSize(opts: BoardSizeOptions) { - this._opts = { ...this._opts, ...opts }; - this._resetContext(); - this._ctx.resetSize(opts); - this._helperCtx.resetSize(opts); - this._screen.resetSize(opts); - this._scroller.resetSize({ - width: this._opts.width, - height: this._opts.height, - devicePixelRatio: this._opts.devicePixelRatio - }); - this.draw(); - } - - getScrollLineWidth(): number { - let lineWidth = 0; - if (this._opts.canScroll === true) { - lineWidth = this._scroller.getLineWidth(); - } - return lineWidth; - } - - pointScreenToContext(screenPoint: Point): Point { - const { scrollX, scrollY, scale } = this.getTransform(); - const ctxPoint = { - x: (screenPoint.x - scrollX) / scale, - y: (screenPoint.y - scrollY) / scale - }; - return ctxPoint; - } - - pointContextToScreen(ctxPoint: Point): Point { - const { scrollX, scrollY, scale } = this.getTransform(); - const screenPoint = { - x: ctxPoint.x * scale + scrollX, - y: ctxPoint.y * scale + scrollY - }; - return screenPoint; - } - - private _render() { - if (this._hasRendered === true) { - return; - } - this._resetContext(); - this._initEvent(); - this._hasRendered = true; - } - - private _resetContext() { - const { width, height, contextWidth, contextHeight, devicePixelRatio } = - this._opts; - this._canvas.width = contextWidth * devicePixelRatio; - this._canvas.height = contextHeight * devicePixelRatio; - - this._helperCanvas.width = contextWidth * devicePixelRatio; - this._helperCanvas.height = contextHeight * devicePixelRatio; - - this._displayCanvas.width = width * devicePixelRatio; - this._displayCanvas.height = height * devicePixelRatio; - - setStyle(this._displayCanvas, { - width: `${width}px`, - height: `${height}px` + private _handlePointStart(e: BoardWatcherEventMap['pointStart']) { + this._activeMiddlewareObjs.forEach((obj) => { + obj?.pointStart?.(e); }); } - private _parsePrivateOptions(opts: BoardOptions): PrivateOptions { - const defaultOpts = { - devicePixelRatio: 1 - }; - return { ...defaultOpts, ...opts }; + private _handlePointEnd(e: BoardWatcherEventMap['pointEnd']) { + this._activeMiddlewareObjs.forEach((obj) => { + obj?.pointEnd?.(e); + }); } - private _initEvent() { - if (this._hasRendered === true) { - return; - } - if (this._opts.canScroll === true) { - this.on( - 'wheelX', - throttle((deltaX) => { - this._doScrollX(deltaX); - }, 16) - ); - this.on( - 'wheelY', - throttle((deltaY: number) => { - this._doScrollY(deltaY); - }, 16) - ); - - let scrollType: 'x' | 'y' | null = null; - this.on( - 'moveStart', - throttle((p: Point) => { - if (this._scroller.isPointAtScrollX(p)) { - scrollType = 'x'; - } else if (this._scroller.isPointAtScrollY(p)) { - scrollType = 'y'; - } - }, 16) - ); - - this.on( - 'move', - throttle((p: Point) => { - if (scrollType) { - this._doMoveScroll(scrollType, p); - } - }, 16) - ); - - this.on( - 'moveEnd', - throttle((p: Point) => { - if (scrollType) { - this._doMoveScroll(scrollType, p); - } - scrollType = null; - }, 16) - ); - - // this.on('doubleClick', (p: Point) => {}) - } + private _handlePointMove(e: BoardWatcherEventMap['pointMove']) { + this._activeMiddlewareObjs.forEach((obj) => { + obj?.pointMove?.(e); + }); } - private _doScrollX(dx: number, prevScrollX?: number) { - const { width } = this._opts; - let scrollX = prevScrollX; - if (!(typeof scrollX === 'number' && (scrollX > 0 || scrollX <= 0))) { - scrollX = this._ctx.getTransform().scrollX; - } - const { position } = this._screen.calcScreen(); - const { xSize } = this._scroller.calc(position); - const moveX = this._screen.calcScreenScroll( - position.left, - position.right, - xSize, - width, - dx - ); - this.scrollX(scrollX + moveX); - this.draw(); + private _handleHover(e: BoardWatcherEventMap['hover']) { + this._activeMiddlewareObjs.forEach((obj) => { + obj?.hover?.(e); + }); } - private _doScrollY(dy: number, prevScrollY?: number) { - const { height } = this._opts; - let scrollY = prevScrollY; - if (!(typeof scrollY === 'number' && (scrollY > 0 || scrollY <= 0))) { - scrollY = this._ctx.getTransform().scrollY; - } - const { position } = this._screen.calcScreen(); - const { ySize } = this._scroller.calc(position); - const moveY = this._screen.calcScreenScroll( - position.top, - position.bottom, - ySize, - height, - dy - ); - this.scrollY(scrollY + moveY); - this.draw(); + private _handleBeforeDrawFrame(e: BoardWatcherEventMap['beforeDrawFrame']) { + this._activeMiddlewareObjs.forEach((obj) => { + obj?.beforeDrawFrame?.(e); + }); } - private _doMoveScroll(scrollType: 'x' | 'y', point: Point) { - if (!scrollType) { - return; - } - const { position } = this._screen.calcScreen(); - const { xSize, ySize } = this._scroller.calc(position); - if (scrollType === 'x') { - this._doScrollX(point.x - xSize / 2, 0); - } else if (scrollType === 'y') { - this._doScrollY(point.y - ySize / 2, 0); - } + private _handleAfterDrawFrame(e: BoardWatcherEventMap['afterDrawFrame']) { + this._activeMiddlewareObjs.forEach((obj) => { + obj?.afterDrawFrame?.(e); + }); + } + + private _resetActiveMiddlewareObjs() { + const { _activeMode: activeMode } = this; + const modes: BoardMode[] = [...LOCK_MODES, activeMode]; + const activeMiddlewareObjs: BoardMiddlewareObject[] = []; + this._middlewareObjs.forEach((m) => { + if (modes.includes(m.mode)) { + activeMiddlewareObjs.push(m); + } + }); + this._activeMiddlewareObjs = activeMiddlewareObjs; + } + + setData(data: Data) { + this._sharer.setActiveStorage('data', data); + this._viewer.drawFrame(); + } + + use(middleware: BoardMiddleware) { + const { viewContent } = this._opts; + const { _sharer: sharer, _viewer: viewer, _calculator: calculator } = this; + const obj = middleware({ viewContent, sharer, viewer, calculator }); + this._middlewares.push(middleware); + this._activeMiddlewareObjs.push(obj); + } + + scale(num: number) { + const { _viewer: viewer, _renderer: renderer } = this; + renderer.scale(num); + viewer.drawFrame(); + } + + scrollX(num: number) { + // TODO + } + + scrollY(num: number) { + // TODO } } diff --git a/packages/board/src/lib/calculator.ts b/packages/board/src/lib/calculator.ts new file mode 100644 index 0000000..3e862c9 --- /dev/null +++ b/packages/board/src/lib/calculator.ts @@ -0,0 +1,99 @@ +import type { Data, PointSize, Point, Element, ElementType, ViewCalculator, ViewCalculatorOptions, ViewScaleInfo, ElementSize } from '../types'; + +export class Calculator implements ViewCalculator { + private _opts: ViewCalculatorOptions; + + constructor(opts: ViewCalculatorOptions) { + this._opts = opts; + } + + private _getBoardSize(): { width: number; height: number } { + return { + width: this._opts.viewContent.boardContext.canvas.width, + height: this._opts.viewContent.boardContext.canvas.height + }; + } + + viewScale(num: number, prevScaleInfo?: ViewScaleInfo): ViewScaleInfo { + // TODO + } + + elementSize(size: ElementSize, scaleInfo: ViewScaleInfo): ElementSize { + const { x, y, w, h } = size; + const { scale, offsetTop, offsetLeft } = scaleInfo; + return { + x: x * scale + offsetLeft, + y: y * scale + offsetTop, + w: w * scale, + h: h * scale + }; + } + + isElementInView(elem: Element, scaleInfo: ViewScaleInfo): boolean { + // TODO + const { width, height } = this._getBoardSize(); + const { scale = 1, offsetTop = 0, offsetLeft = 0 } = scaleInfo; + + // Virtual View Point + // const vvp0: PointSize = { x: offsetLeft, y: offsetTop }; + // const vvp1: PointSize = { x: offsetLeft + width, y: offsetTop }; + // const vvp2: PointSize = { x: offsetLeft + width, y: offsetTop + height }; + // const vvp3: PointSize = { x: offsetLeft, y: offsetTop + height }; + const vvpStart: PointSize = { x: offsetLeft, y: offsetTop }; + const vvpEnd: PointSize = { x: offsetLeft + width, y: offsetTop + height }; + + // Virtual Element Point + const vep0: PointSize = { x: elem.x * scale, y: elem.y * scale }; + const vep1: PointSize = { x: (elem.x + elem.w) * scale, y: elem.y * scale }; + const vep2: PointSize = { x: (elem.x + elem.w) * scale, y: (elem.y + elem.h) * scale }; + const vep3: PointSize = { x: elem.x * scale, y: (elem.y + elem.h) * scale }; + // const vepStart: PointSize = { x: elem.x * scale, y: elem.y * scale }; + // const vepEnd: PointSize = { x: (elem.x + elem.w) * scale, y: (elem.y + elem.w) * scale }; + + const isPointInRect = (p: PointSize) => { + return p.x >= vvpStart.x && p.x <= vvpEnd.x && p.y >= vvpStart.y && p.y <= vvpEnd.y; + }; + if (isPointInRect(vep0) || isPointInRect(vep1) || isPointInRect(vep2) || isPointInRect(vep3)) { + return true; + } + return false; + } + + isPointInElement(p: Point, elem: Element, scaleInfo: ViewScaleInfo): boolean { + const { scale = 1, offsetTop = 0, offsetLeft = 0 } = scaleInfo; + // Virtual Point + const vp: PointSize = { + x: p.x + offsetLeft, + y: p.y + offsetTop + }; + + // Virtual Element Point + const vepStart: PointSize = { x: elem.x * scale, y: elem.y * scale }; + const vepEnd: PointSize = { x: (elem.x + elem.w) * scale, y: (elem.y + elem.w) * scale }; + if (vp.x >= vepStart.x && vp.x <= vepEnd.x && vp.y >= vepStart.y && vp.y <= vepEnd.y) { + return true; + } + return false; + } + + getPointElement(p: Point, data: Data, scaleInfo: ViewScaleInfo): { index: number; element: null | Element } { + const result: { index: number; element: null | Element } = { + index: -1, + element: null + }; + for (let i = 0; i < data.elements.length; i++) { + const elem = data.elements[i]; + if (this.isPointInElement(p, elem, scaleInfo)) { + result.index = i; + result.element = elem; + break; + } + } + return result; + } + + pointToViewPoint(p: Point): Point { + // TODO + return {}; + } +} diff --git a/packages/board/src/lib/event.ts b/packages/board/src/lib/event.ts deleted file mode 100644 index abc85bb..0000000 --- a/packages/board/src/lib/event.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { Point } from '@idraw/types'; - -export interface TypeBoardEventArgMap { - doubleClick: Point; - hover: Point; - leave: void; - point: Point; - move: Point; - moveStart: Point; - moveEnd: Point; - wheelX: number; - wheelY: number; -} - -export interface TypeBoardEvent { - on( - key: T, - callback: (p: TypeBoardEventArgMap[T]) => void - ): void; - off( - key: T, - callback: (p: TypeBoardEventArgMap[T]) => void - ): void; - trigger( - key: T, - p: TypeBoardEventArgMap[T] - ): void; -} - -export class BoardEvent implements TypeBoardEvent { - private _listeners: Map void)[]>; - - constructor() { - this._listeners = new Map(); - } - - on( - eventKey: T, - callback: (p: TypeBoardEventArgMap[T]) => void - ) { - if (this._listeners.has(eventKey)) { - const callbacks = this._listeners.get(eventKey); - callbacks?.push(callback); - this._listeners.set(eventKey, callbacks || []); - } else { - this._listeners.set(eventKey, [callback]); - } - } - - off( - eventKey: T, - callback: (p: TypeBoardEventArgMap[T]) => void - ) { - 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) { - callbacks.splice(i, 1); - break; - } - } - } - this._listeners.set(eventKey, callbacks || []); - } - } - - trigger( - eventKey: T, - arg: TypeBoardEventArgMap[T] - ) { - const callbacks = this._listeners.get(eventKey); - if (Array.isArray(callbacks)) { - callbacks.forEach((cb) => { - cb(arg); - }); - return true; - } else { - return false; - } - } - - has(name: string) { - if (this._listeners.has(name)) { - const list: ((p: TypeBoardEventArgMap[T]) => void)[] | undefined = - this._listeners.get(name); - if (Array.isArray(list) && list.length > 0) { - return true; - } - } - return false; - } -} diff --git a/packages/board/src/lib/istype.ts b/packages/board/src/lib/istype.ts deleted file mode 100644 index 71dc7f4..0000000 --- a/packages/board/src/lib/istype.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { istype } from '@idraw/util'; - -export default istype; \ No newline at end of file diff --git a/packages/board/src/lib/screen-watcher.ts b/packages/board/src/lib/screen-watcher.ts deleted file mode 100644 index 513abfa..0000000 --- a/packages/board/src/lib/screen-watcher.ts +++ /dev/null @@ -1,366 +0,0 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment */ -import { Point, IDrawContext } from '@idraw/types'; -import { BoardEvent, TypeBoardEventArgMap } from './event'; -import { TempData } from './watcher-temp'; - -// const { throttle } = util.time; - -// const isInIframe = window.self === window.top; - -export class ScreenWatcher { - private _canvas: HTMLCanvasElement; - private _isMoving = false; - // private _onMove?: TypeWatchCallback; - // private _onMoveStart?: TypeWatchCallback; - // private _onMoveEnd?: TypeWatchCallback; - private _event: BoardEvent; - private _temp: TempData = new TempData(); - private _container: HTMLElement | Window = window; - // private _ctx: IDrawContext; - - constructor(canvas: HTMLCanvasElement, ctx: IDrawContext) { - this._canvas = canvas; - this._isMoving = false; - this._initEvent(); - this._event = new BoardEvent(); - // this._ctx = ctx; - } - - setStatusMap(statusMap: { - canScrollYPrev: boolean; - canScrollYNext: boolean; - canScrollXPrev: boolean; - canScrollXNext: boolean; - }) { - this._temp.set('statusMap', statusMap); - } - - on( - name: T, - callback: (p: TypeBoardEventArgMap[T]) => void - ): void { - this._event.on(name, callback); - } - - off( - name: T, - callback: (p: TypeBoardEventArgMap[T]) => void - ): void { - this._event.off(name, callback); - } - - _initEvent(): void { - const canvas = this._canvas; - const container = this._container; - // container.addEventListener('mousemove', this._listenWindowHover.bind(this), false); - // container.addEventListener('mousedown', this._listenWindowMoveStart.bind(this), false); - container.addEventListener( - 'mousemove', - this._listenWindowMove.bind(this), - false - ); - container.addEventListener( - 'mouseup', - this._listenWindowMoveEnd.bind(this), - false - ); - - canvas.addEventListener('mousemove', this._listenHover.bind(this), false); - canvas.addEventListener( - 'mousedown', - this._listenMoveStart.bind(this), - false - ); - canvas.addEventListener('mousemove', this._listenMove.bind(this), false); - canvas.addEventListener('mouseup', this._listenMoveEnd.bind(this), false); - - canvas.addEventListener('click', this._listenCanvasClick.bind(this), false); - canvas.addEventListener('wheel', this._listenCanvasWheel.bind(this), false); - canvas.addEventListener( - 'mousedown', - this._listenCanvasMoveStart.bind(this), - true - ); - canvas.addEventListener( - 'mouseup', - this._listenCanvasMoveEnd.bind(this), - true - ); - canvas.addEventListener( - 'mouseover', - this._listenCanvasMoveOver.bind(this), - true - ); - canvas.addEventListener( - 'mouseleave', - this._listenCanvasMoveLeave.bind(this), - true - ); - this._initParentEvent(); - - // container.addEventListener('touchstart', this._listenMoveStart.bind(this), true); - // container.addEventListener('touchmove', this._listenMove.bind(this), true); - // container.addEventListener('touchend', this._listenMoveEnd.bind(this), true); - } - - _initParentEvent() { - try { - let target = window; - const targetOrigin = target.origin; - while (target.self !== target.top) { - // If in iframe - if (target.self !== target.parent) { - // If in same origin - if (target.origin === targetOrigin) { - // window.parent.window.addEventListener( - // 'mousemove', - // throttle(this._listSameOriginParentWindow.bind(this), 16), - // false); - target.parent.window.addEventListener( - 'mousemove', - this._listSameOriginParentWindow.bind(this), - false - ); - } - } - // @ts-ignore - target = target.parent; - if (!target) { - break; - } - } - } catch (err) { - console.warn(err); - } - } - - _listenHover(e: MouseEvent | TouchEvent): void { - e.preventDefault(); - const p = this._getPosition(e); - if (this._isVaildPoint(p)) { - if (this._event.has('hover')) { - this._event.trigger('hover', p); - } - } - this._isMoving = true; - } - - // _listenLeave(e: MouseEvent|TouchEvent): void { - // e.preventDefault(); - // if (this._event.has('leave')) { - // this._event.trigger('leave', undefined); - // } - // } - - _listenMoveStart(e: MouseEvent | TouchEvent): void { - e.preventDefault(); - const p = this._getPosition(e); - if (this._isVaildPoint(p)) { - if (this._event.has('point')) { - this._event.trigger('point', p); - } - if (this._event.has('moveStart')) { - this._event.trigger('moveStart', p); - } - } - this._isMoving = true; - } - - _listenMove(e: MouseEvent | TouchEvent): void { - e.preventDefault(); - e.stopPropagation(); - if (this._event.has('move') && this._isMoving === true) { - const p = this._getPosition(e); - if (this._isVaildPoint(p)) { - this._event.trigger('move', p); - } - } - } - - _listenMoveEnd(e: MouseEvent | TouchEvent): void { - e.preventDefault(); - if (this._event.has('moveEnd')) { - const p = this._getPosition(e); - if (this._isVaildPoint(p)) { - this._event.trigger('moveEnd', p); - } - } - this._isMoving = false; - } - - _listSameOriginParentWindow() { - if (this._temp.get('isHoverCanvas')) { - if (this._event.has('leave')) { - this._event.trigger('leave', undefined); - } - } - if (this._temp.get('isDragCanvas')) { - if (this._event.has('moveEnd')) { - this._event.trigger('moveEnd', { x: NaN, y: NaN }); - } - } - this._isMoving = false; - this._temp.set('isDragCanvas', false); - this._temp.set('isHoverCanvas', false); - } - - _listenCanvasMoveStart() { - if (this._temp.get('isHoverCanvas')) { - this._temp.set('isDragCanvas', true); - } - } - - _listenCanvasMoveEnd() { - this._temp.set('isDragCanvas', false); - } - - _listenCanvasMoveOver() { - this._temp.set('isHoverCanvas', true); - } - - _listenCanvasMoveLeave() { - this._temp.set('isHoverCanvas', false); - if (this._event.has('leave')) { - this._event.trigger('leave', undefined); - } - } - - // _listenWindowHover(e: MouseEvent|TouchEvent|Event): void { - // if (this._temp.get('isDragCanvas')) { - // return; - // } - // e.preventDefault(); - // const p = this._getPosition(e as MouseEvent|TouchEvent); - // if (this._isVaildPoint(p)) { - // if (this._event.has('hover')) { - // this._event.trigger('hover', p); - // } - // } - // this._isMoving = true; - // } - - // _listenWindowMoveStart(e: MouseEvent|TouchEvent|Event): void { - // if (this._temp.get('isHoverCanvas') !== true) { - // return; - // } - // e.preventDefault(); - // const p = this._getPosition(e as MouseEvent|TouchEvent); - // if (this._isVaildPoint(p)) { - // if (this._event.has('point')) { - // this._event.trigger('point', p); - // } - // if (this._event.has('moveStart')) { - // this._event.trigger('moveStart', p); - // } - // } - // this._isMoving = true; - // } - - _listenWindowMove(e: MouseEvent | TouchEvent | Event): void { - if (this._temp.get('isDragCanvas') !== true) { - return; - } - e.preventDefault(); - e.stopPropagation(); - if (this._event.has('move') && this._isMoving === true) { - const p = this._getPosition(e as MouseEvent | TouchEvent); - if (this._isVaildPoint(p)) { - this._event.trigger('move', p); - } - } - } - - _listenWindowMoveEnd(e: MouseEvent | TouchEvent | Event): void { - if (!this._temp.get('isDragCanvas') === true) { - return; - } - e.preventDefault(); - if (this._event.has('moveEnd')) { - const p = this._getPosition(e as MouseEvent | TouchEvent); - if (this._isVaildPoint(p)) { - this._event.trigger('moveEnd', p); - } - } - this._temp.set('isDragCanvas', false); - this._isMoving = false; - } - - _listenCanvasWheel(e: WheelEvent) { - // e.preventDefault(); - // const { scrollX, scrollY } = this._ctx.getTransform(); - // const { width, height } = this._ctx.getSize(); - if (this._event.has('wheelX') && (e.deltaX > 0 || e.deltaX < 0)) { - this._event.trigger('wheelX', e.deltaX); - } - if (this._event.has('wheelY') && (e.deltaY > 0 || e.deltaY < 0)) { - this._event.trigger('wheelY', e.deltaY); - } - const { canScrollYNext, canScrollYPrev } = this._temp.get('statusMap'); - - if (e.deltaX > 0 && e.deltaX < 0) { - e.preventDefault(); - } else if (e.deltaY > 0 && canScrollYNext === true) { - e.preventDefault(); - } else if (e.deltaY < 0 && canScrollYPrev === true) { - e.preventDefault(); - } - } - - _listenCanvasClick(e: MouseEvent | TouchEvent | Event) { - e.preventDefault(); - const maxLimitTime = 500; - const p = this._getPosition(e as MouseEvent | TouchEvent); - const t = Date.now(); - if (this._isVaildPoint(p)) { - const preClickPoint = this._temp.get('prevClickPoint'); - if ( - preClickPoint && - t - preClickPoint.t <= maxLimitTime && - Math.abs(preClickPoint.x - p.x) <= 5 && - Math.abs(preClickPoint.y - p.y) <= 5 - ) { - if (this._event.has('doubleClick')) { - this._event.trigger('doubleClick', { x: p.x, y: p.y }); - } - } else { - this._temp.set('prevClickPoint', { x: p.x, y: p.y, t }); - } - } - } - - _getPosition(e: MouseEvent | TouchEvent): Point { - const canvas = this._canvas; - let x = 0; - let y = 0; - - // @ts-ignore - if (e && e.touches && e.touches.length > 0) { - // @ts-ignore - const touch: Touch = e.touches[0]; - if (touch) { - x = touch.clientX; - y = touch.clientY; - } - } else { - // @ts-ignore - x = e.clientX; - // @ts-ignore - y = e.clientY; - } - - const p = { - x: x - canvas.getBoundingClientRect().left, - y: y - canvas.getBoundingClientRect().top, - t: Date.now() - }; - return p; - } - - private _isVaildPoint(p: Point): boolean { - return isAvailableNum(p.x) && isAvailableNum(p.y); - } -} - -function isAvailableNum(num: any): boolean { - return num > 0 || num < 0 || num === 0; -} diff --git a/packages/board/src/lib/screen.ts b/packages/board/src/lib/screen.ts deleted file mode 100644 index fd33457..0000000 --- a/packages/board/src/lib/screen.ts +++ /dev/null @@ -1,172 +0,0 @@ -import { - BoardSizeOptions, - ScreenPosition, - IDrawContext, - ScreenSize -} from '@idraw/types'; - -type Options = { - width: number; - height: number; - contextWidth: number; - contextHeight: number; - devicePixelRatio: number; -}; - -const _opts = Symbol('_opts'); -const _ctx = Symbol('_ctx'); - -export class Screen { - private [_opts]: Options; - private [_ctx]: IDrawContext; - - constructor(ctx: IDrawContext, opts: Options) { - this[_opts] = opts; - this[_ctx] = ctx; - } - - resetSize(opts: BoardSizeOptions) { - this[_opts] = { ...this[_opts], ...opts }; - } - - calcScreen(): { - size: ScreenSize; - position: ScreenPosition; - deviceSize: ScreenSize; - width: number; - height: number; - devicePixelRatio: number; - canScrollXPrev: boolean; - canScrollXNext: boolean; - canScrollYPrev: boolean; - canScrollYNext: boolean; - } { - const scaleRatio = this[_ctx].getTransform().scale; - const { - width, - height, - contextWidth, - contextHeight, - devicePixelRatio: pxRatio - } = this[_opts]; - - let canScrollXPrev: boolean = true; - let canScrollXNext: boolean = true; - let canScrollYPrev: boolean = true; - let canScrollYNext: boolean = true; - - // init scroll - if (contextWidth * scaleRatio <= width) { - // make context center - this[_ctx].setTransform({ - scrollX: (width - contextWidth * scaleRatio) / 2 - }); - canScrollXPrev = false; - canScrollXNext = false; - } - - if (contextHeight * scaleRatio <= height) { - // make context center - this[_ctx].setTransform({ - scrollY: (height - contextHeight * scaleRatio) / 2 - }); - canScrollYPrev = false; - canScrollYNext = false; - } - - if ( - contextWidth * scaleRatio >= width && - this[_ctx].getTransform().scrollX > 0 - ) { - this[_ctx].setTransform({ - scrollX: 0 - }); - canScrollXPrev = false; - } - if ( - contextHeight * scaleRatio >= height && - this[_ctx].getTransform().scrollY > 0 - ) { - this[_ctx].setTransform({ - scrollY: 0 - }); - canScrollYPrev = false; - } - - const { scrollX: _scrollX, scrollY: _scrollY } = this[_ctx].getTransform(); - - // reset scroll - if ( - _scrollX < 0 && - Math.abs(_scrollX) > Math.abs(contextWidth * scaleRatio - width) - ) { - this[_ctx].setTransform({ - scrollX: 0 - Math.abs(contextWidth * scaleRatio - width) - }); - canScrollXNext = false; - } - if ( - _scrollY < 0 && - Math.abs(_scrollY) > Math.abs(contextHeight * scaleRatio - height) - ) { - this[_ctx].setTransform({ - scrollY: 0 - Math.abs(contextHeight * scaleRatio - height) - }); - canScrollYNext = false; - } - - // result size - const { scrollX, scrollY } = this[_ctx].getTransform(); - const size = { - x: scrollX * scaleRatio, - y: scrollY * scaleRatio, - w: contextWidth * scaleRatio, - h: contextHeight * scaleRatio - }; - const deviceSize = { - x: scrollX * pxRatio, - y: scrollY * pxRatio, - w: contextWidth * pxRatio * scaleRatio, - h: contextHeight * pxRatio * scaleRatio - }; - const position = { - top: scrollY, - bottom: height - (contextHeight * scaleRatio + scrollY), - left: scrollX, - right: width - (contextWidth * scaleRatio + scrollX) - }; - - return { - size, - position, - deviceSize, - width: this[_opts].width, - height: this[_opts].height, - devicePixelRatio: this[_opts].devicePixelRatio, - canScrollYPrev, - canScrollYNext, - canScrollXPrev, - canScrollXNext - }; - } - - calcScreenScroll( - start: number, - end: number, - sliderSize: number, - limitLen: number, - moveDistance: number - ): number { - let scrollDistance = start; - let scrollLen = limitLen - sliderSize; - if (start <= 0 && end <= 0) { - scrollLen = Math.abs(start) + Math.abs(end); - } - let unit = 1; - if (scrollLen > 0) { - unit = scrollLen / (limitLen - sliderSize); - } - scrollDistance = 0 - unit * moveDistance; - return scrollDistance; - } -} diff --git a/packages/board/src/lib/scroller.ts b/packages/board/src/lib/scroller.ts deleted file mode 100644 index 872fcfa..0000000 --- a/packages/board/src/lib/scroller.ts +++ /dev/null @@ -1,275 +0,0 @@ -import { Point, ScreenPosition, BoardScrollConfig } from '@idraw/types'; -import { isColorStr } from '@idraw/util'; - -type TypeOptions = { - width: number; - height: number; - devicePixelRatio: number; - scrollConfig?: BoardScrollConfig; -}; - -type TypePrivateOptions = Required< - TypeOptions & { scrollConfig: Required } ->; - -const minScrollerWidth = 12; -const scrollerAlpha = 0.12; -const scrollerThumbAlpha = 0.36; - -const defaultScrollConfig: Partial & { - width: number; - color: string; -} = { - width: minScrollerWidth, - color: '#000000', - showBackground: true -}; - -export class Scroller { - private _displayCtx: CanvasRenderingContext2D; - private _opts: TypePrivateOptions; - - constructor(ctx: CanvasRenderingContext2D, opts: TypeOptions) { - this._displayCtx = ctx; - this._opts = this._getOpts(opts); - } - - draw(position: ScreenPosition) { - const { width, height, scrollConfig } = this._opts; - const wrapper = this.calc(position); - const ctx = this._displayCtx; - - if (wrapper.xSize > 0) { - if (scrollConfig.showBackground === true) { - ctx.globalAlpha = scrollerAlpha; - ctx.fillStyle = wrapper.color; - // x-line - ctx.fillRect( - 0, - this._doSize(height - wrapper.lineSize), - this._doSize(width), - this._doSize(wrapper.lineSize) - ); - } - - // ctx.globalAlpha = 1; - // x-slider - drawBoxScrollerThumb(ctx, { - axis: 'X', - x: this._doSize(wrapper.translateX), - y: this._doSize(height - wrapper.lineSize), - w: this._doSize(wrapper.xSize), - h: this._doSize(wrapper.lineSize), - r: this._doSize(wrapper.lineSize / 2), - color: wrapper.color - }); - } - - if (wrapper.ySize > 0) { - if (scrollConfig.showBackground === true) { - ctx.globalAlpha = scrollerAlpha; - ctx.fillStyle = wrapper.color; - // y-line - ctx.fillRect( - this._doSize(width - wrapper.lineSize), - 0, - this._doSize(wrapper.lineSize), - this._doSize(height) - ); - } - - // ctx.globalAlpha = 1; - // y-slider - drawBoxScrollerThumb(ctx, { - axis: 'Y', - x: this._doSize(width - wrapper.lineSize), - y: this._doSize(wrapper.translateY), - w: this._doSize(wrapper.lineSize), - h: this._doSize(wrapper.ySize), - r: this._doSize(wrapper.lineSize / 2), - color: wrapper.color - }); - } - - ctx.globalAlpha = 1; - } - - resetSize(opts: { width: number; height: number; devicePixelRatio: number }) { - this._opts = { ...this._opts, ...opts }; - } - - isPointAtScrollY(p: Point): boolean { - const { width, height, scrollConfig } = this._opts; - const ctx = this._displayCtx; - ctx.beginPath(); - ctx.rect( - this._doSize(width - scrollConfig.width), - 0, - this._doSize(scrollConfig.width), - this._doSize(height) - ); - ctx.closePath(); - if (ctx.isPointInPath(this._doSize(p.x), this._doSize(p.y))) { - return true; - } - return false; - } - - isPointAtScrollX(p: Point): boolean { - const { width, height, scrollConfig } = this._opts; - const ctx = this._displayCtx; - ctx.beginPath(); - ctx.rect( - 0, - this._doSize(height - scrollConfig.width), - this._doSize(width - scrollConfig.width), - this._doSize(scrollConfig.width) - ); - ctx.closePath(); - if (ctx.isPointInPath(this._doSize(p.x), this._doSize(p.y))) { - return true; - } - return false; - } - - getLineWidth(): number { - const lineWidth = this._opts.scrollConfig.width; - return lineWidth; - } - - calc(position: ScreenPosition) { - const { width, height, scrollConfig } = this._opts; - const sliderMinSize = scrollConfig.width * 2.5; - const lineSize = scrollConfig.width; - let xSize = 0; - let ySize = 0; - if (position.left <= 0 && position.right <= 0) { - xSize = Math.max( - sliderMinSize, - width - (Math.abs(position.left) + Math.abs(position.right)) - ); - if (xSize >= width) xSize = 0; - } - if (position.top <= 0 || position.bottom <= 0) { - ySize = Math.max( - sliderMinSize, - height - (Math.abs(position.top) + Math.abs(position.bottom)) - ); - if (ySize >= height) ySize = 0; - } - - let translateX = 0; - if (xSize > 0) { - translateX = - xSize / 2 + - ((width - xSize) * Math.abs(position.left)) / - (Math.abs(position.left) + Math.abs(position.right)); - translateX = Math.min(Math.max(0, translateX - xSize / 2), width - xSize); - // const xUnit = this.calcScreenScrollUnit(position.left, position.right, xSize, width); - // translateX = translateX * xUnit; - } - - let translateY = 0; - if (ySize > 0) { - translateY = - ySize / 2 + - ((height - ySize) * Math.abs(position.top)) / - (Math.abs(position.top) + Math.abs(position.bottom)); - translateY = Math.min( - Math.max(0, translateY - ySize / 2), - height - ySize - ); - // const yUnit = this.calcScreenScrollUnit(position.top, position.bottom, ySize, height); - // translateY = translateY * yUnit; - } - const scrollWrapper = { - lineSize, - xSize, - ySize, - translateY, - translateX, - color: this._opts.scrollConfig.color - }; - return scrollWrapper; - } - - private _doSize(num: number) { - return num * this._opts.devicePixelRatio; - } - - private _getOpts(opts: TypeOptions): TypePrivateOptions { - const options: TypePrivateOptions = { - ...opts, - ...{ - scrollConfig: { ...defaultScrollConfig, ...(opts.scrollConfig || {}) } - } - } as TypePrivateOptions; - if (!options.scrollConfig) { - options.scrollConfig = - defaultScrollConfig as TypePrivateOptions['scrollConfig']; - } - if (!(options?.scrollConfig?.width > 0)) { - options.scrollConfig.width = defaultScrollConfig.width; - } - options.scrollConfig.width = Math.max( - options.scrollConfig.width, - defaultScrollConfig.width - ); - - if (isColorStr(options.scrollConfig.color) !== true) { - options.scrollConfig.color = options.scrollConfig.color; - } - return options; - } -} - -function drawBoxScrollerThumb( - ctx: CanvasRenderingContext2D, - opts: { - axis: 'X' | 'Y'; - x: number; - y: number; - w: number; - h: number; - r: number; - color: string; - } -): void { - let { x, y, h, w } = opts; - const { color, axis } = opts; - if (axis === 'X') { - y = y + h / 4 + 1; - h = h / 2; - } else if (axis === 'Y') { - x = x + w / 4 + 1; - w = w / 2; - } - - let r = opts.r; - r = Math.min(r, w / 2, h / 2); - if (w < r * 2 || h < r * 2) { - r = 0; - } - ctx.globalAlpha = scrollerThumbAlpha; - ctx.beginPath(); - ctx.moveTo(x + r, y); - ctx.arcTo(x + w, y, x + w, y + h, r); - ctx.arcTo(x + w, y + h, x, y + h, r); - ctx.arcTo(x, y + h, x, y, r); - ctx.arcTo(x, y, x + w, y, r); - ctx.closePath(); - ctx.fillStyle = color; - ctx.fill(); - - ctx.globalAlpha = 1; - ctx.beginPath(); - ctx.lineWidth = 1; - ctx.strokeStyle = color; - ctx.moveTo(x + r, y); - ctx.arcTo(x + w, y, x + w, y + h, r); - ctx.arcTo(x + w, y + h, x, y + h, r); - ctx.arcTo(x, y + h, x, y, r); - ctx.arcTo(x, y, x + w, y, r); - ctx.closePath(); - ctx.stroke(); -} diff --git a/packages/board/src/lib/sharer.ts b/packages/board/src/lib/sharer.ts new file mode 100644 index 0000000..1bd31fa --- /dev/null +++ b/packages/board/src/lib/sharer.ts @@ -0,0 +1,61 @@ +import type { ActiveStore, StoreSharer } from '@idraw/types'; +import { Store } from '@idraw/util'; + +const defaultActiveStorage: ActiveStore = { + contextWidth: 0, + contextHeight: 0, + data: null, + selectedUUIDs: [] as string[], + selectedIndexs: [] as number[], + scale: 1, + offsetLeft: 0, + offsetRight: 0, + offsetTop: 0, + offsetBottom: 0 +}; + +export class Sharer implements StoreSharer { + private _activeStore: Store; + private _sharedStore: Store<{ + [string: string]: any; + }>; + + constructor() { + const activeStore = new Store({ + defaultStorage: defaultActiveStorage + }); + const sharedStore = new Store({ + defaultStorage: {} + }); + this._activeStore = activeStore; + this._sharedStore = sharedStore; + } + + drawFrame(): void { + // TODO + } + + getActiveStorage(key: T): ActiveStore[T] { + return this._activeStore.get(key); + } + + setActiveStorage(key: T, storage: ActiveStore[T]) { + return this._activeStore.set(key, storage); + } + + getActiveStoreSnapshot(): ActiveStore { + return this._activeStore.getSnapshot(); + } + + getSharedStorage(key: string): any { + return this._sharedStore.get(key); + } + + setSharedStorage(key: string, storage: any) { + return this._sharedStore.set(key, storage); + } + + getSharedStoreSnapshot(): Record { + return this._sharedStore.getSnapshot(); + } +} diff --git a/packages/board/src/lib/style.ts b/packages/board/src/lib/style.ts deleted file mode 100644 index e232250..0000000 --- a/packages/board/src/lib/style.ts +++ /dev/null @@ -1,95 +0,0 @@ -import istype from './istype'; - -export const mergeCSS2StyleAttr = function( - cssMap: {[key: string]: string} = {} -): string { - const cssList = []; - if (istype.json(cssMap) === true) { - for (const key in cssMap) { - let cssKey = `${key}`; - let cssVal = `${cssMap[key]}`; - cssKey = cssKey.trim(); - cssVal = cssVal.trim(); - cssList.push(`${cssKey}:${cssVal}`); - } - } - const styleAttr = cssList.join('; ') + ';'; - return styleAttr; -}; - - -export function setStyle( - dom: HTMLElement, - style: {[key: string]: string} -): void { - const originStyle = getStyle(dom); - const _style = {...originStyle, ...style}; - const keys: string[] = Object.keys(_style); - let styleStr = ''; - keys.forEach((key: string) => { - styleStr += `${key}:${_style[key] || ''};`; - }); - dom.setAttribute('style', styleStr); -} - -export function getStyle(dom: HTMLElement): {[key: string]: string} { - const styleObj: {[key: string]: string} = {}; - const style = dom.getAttribute('style') || ''; - const styleList = style.split(';'); - styleList.forEach((item: string) => { - const dataList = item.split(':'); - if (dataList[0] && typeof dataList[0] === 'string') { - styleObj[dataList[0]] = dataList[1] || ''; - } - }); - - return styleObj; -} - -export function getDomTransform(dom: HTMLElement): { - scaleX: number; - skewY: number; - skewX: number; - scaleY: number; - translateX: number; - translateY: number; -} { - // transform: matrix( scaleX(), skewY(), skewX(), scaleY(), translateX(), translateY() ) - // matrix(1, 0, 0, 1, 0, 0) - const style = getComputedStyle(dom) || {}; - const { transform } = style; - const matrixStr = transform.replace(/^matrix\(|\)$/ig, ''); - const matrixList = matrixStr.split(',').map((str, i) => { - const val = parseFloat(str); - if ([0, 3].indexOf(i) >= 0) { - return isNaN(val) ? 1 : val; - } else { - return isNaN(val) ? 0 : val; - } - }); - const matrix = { - scaleX: matrixList[0], - skewY: matrixList[1] || 0, - skewX: matrixList[2] || 0, - scaleY: matrixList[3] || 1, - translateX: matrixList[4] || 0, - translateY: matrixList[5] || 0, - }; - return matrix; -} - - -export function setDomTransform(dom: HTMLElement, matrix: { - scaleX: number; - skewY: number; - skewX: number; - scaleY: number; - translateX: number; - translateY: number; -}): void { - // transform: matrix( scaleX(), skewY(), skewX(), scaleY(), translateX(), translateY() ) - // matrix(1, 2, -1, 1, 80, 80) - - const transform = `matrix(${matrix.scaleX}, ${matrix.skewY}, ${matrix.skewX}, ${matrix.scaleY}, ${matrix.translateX}, ${matrix.translateY})`; - dom.style.setProperty('transform', transform); -} diff --git a/packages/board/src/lib/temp.ts b/packages/board/src/lib/temp.ts deleted file mode 100644 index 5d7755b..0000000 --- a/packages/board/src/lib/temp.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { BoardOptions, IDrawContext } from '@idraw/types'; -import { Context } from '@idraw/util'; - -type TempDataDesc = { - ctx: IDrawContext; -}; - -function createDefaultData(opts: BoardOptions) { - const canvas = document.createElement('canvas'); - const ctx2d = canvas.getContext('2d') as CanvasRenderingContext2D; - const ctx = new Context(ctx2d, { - width: opts.width, - height: opts.height, - contextWidth: opts.contextWidth, - contextHeight: opts.contextHeight, - devicePixelRatio: opts.devicePixelRatio || window.devicePixelRatio || 1 - }); - - return { - plugins: [], - ctx: ctx - }; -} - -export class TempData { - private _temp: TempDataDesc; - - constructor(opts: BoardOptions) { - this._temp = createDefaultData(opts); - } - - set(name: T, value: TempDataDesc[T]) { - this._temp[name] = value; - } - - get(name: T): TempDataDesc[T] { - return this._temp[name]; - } - - clear(opts: BoardOptions) { - this._temp = createDefaultData(opts); - } -} diff --git a/packages/board/src/lib/viewer.ts b/packages/board/src/lib/viewer.ts new file mode 100644 index 0000000..7239339 --- /dev/null +++ b/packages/board/src/lib/viewer.ts @@ -0,0 +1,77 @@ +import { EventEmitter } from '@idraw/util'; +import type { BoardViewer, BoardViewerEventMap, BoardViewerOptions, ActiveStore, BoardViewerFrameSnapshot } from '@idraw/types'; + +const { requestAnimationFrame } = window; + +type ViewerDrawFrameStatus = 'DRAWING' | 'FREE' | 'COMPLETE'; + +export class Viewer extends EventEmitter implements BoardViewer { + private _opts: BoardViewerOptions; + private _drawFrameSnapshotQueue: BoardViewerFrameSnapshot[] = []; + private _drawFrameStatus: ViewerDrawFrameStatus = 'FREE'; + + constructor(opts: BoardViewerOptions) { + super(); + this._opts = opts; + this._init(); + } + + private _init() { + const { renderer } = this._opts; + renderer.on('load', () => { + this.drawFrame(); + }); + } + + private _drawAnimationFrame() { + if (this._drawFrameStatus === 'DRAWING' || this._drawFrameSnapshotQueue.length === 0) { + return; + } else { + this._drawFrameStatus = 'DRAWING'; + } + const snapshot = this._drawFrameSnapshotQueue.shift(); + const { renderer, viewContent, beforeDrawFrame, afterDrawFrame } = this._opts; + + if (snapshot) { + const { viewContext, helperContext, boardContext } = viewContent; + if (snapshot?.activeStore.data) { + renderer.drawData(snapshot.activeStore.data, { + scale: snapshot?.activeStore.scale, + offsetTop: snapshot?.activeStore.offsetTop, + offsetBottom: snapshot?.activeStore.offsetBottom, + offsetLeft: snapshot?.activeStore.offsetLeft, + offsetRight: snapshot?.activeStore.offsetRight + }); + } + beforeDrawFrame({ snapshot }); + const { width, height } = boardContext.canvas; + boardContext.clearRect(0, 0, width, height); + boardContext.drawImage(viewContext.canvas, 0, 0, width, height); + boardContext.drawImage(helperContext.canvas, 0, 0, width, height); + viewContext.clearRect(0, 0, width, height); + helperContext.clearRect(0, 0, width, height); + afterDrawFrame({ snapshot }); + } + + if (this._drawFrameSnapshotQueue.length === 0) { + this._drawFrameStatus = 'COMPLETE'; + return; + } + if ((this._drawFrameStatus = 'DRAWING')) { + requestAnimationFrame(() => { + this._drawAnimationFrame(); + }); + } + } + + drawFrame(): void { + const { sharer } = this._opts; + const activeStore: ActiveStore = sharer.getActiveStoreSnapshot(); + const sharedStore: Record = sharer.getSharedStoreSnapshot(); + this._drawFrameSnapshotQueue.push({ + activeStore, + sharedStore + }); + this._drawAnimationFrame(); + } +} diff --git a/packages/board/src/lib/watcher-temp.ts b/packages/board/src/lib/watcher-temp.ts deleted file mode 100644 index b9dca12..0000000 --- a/packages/board/src/lib/watcher-temp.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Point } from '@idraw/types'; - -type TempDataDesc = { - prevClickPoint: (Point & { t: number }) | null; - isHoverCanvas: boolean; - isDragCanvas: boolean; - statusMap: { - canScrollYPrev: boolean; - canScrollYNext: boolean; - canScrollXPrev: boolean; - canScrollXNext: boolean; - }; -}; - -function createTempData() { - return { - prevClickPoint: null, - isHoverCanvas: false, - isDragCanvas: false, - statusMap: { - canScrollYPrev: true, - canScrollYNext: true, - canScrollXPrev: true, - canScrollXNext: true - } - }; -} - -export class TempData { - private _temp: TempDataDesc; - - constructor() { - this._temp = createTempData(); - } - - set(name: T, value: TempDataDesc[T]) { - this._temp[name] = value; - } - - get(name: T): TempDataDesc[T] { - return this._temp[name]; - } - - clear() { - this._temp = createTempData(); - } -} diff --git a/packages/board/src/lib/watcher.ts b/packages/board/src/lib/watcher.ts index fc1f7b9..127d721 100644 --- a/packages/board/src/lib/watcher.ts +++ b/packages/board/src/lib/watcher.ts @@ -1,177 +1,69 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment */ -import { Point } from '@idraw/types'; -import { BoardEvent, TypeBoardEventArgMap } from './event'; -import { TempData } from './watcher-temp'; +import type { Point, BoardWatcherEventMap, ViewContent, Data, Element, ElementType } from '@idraw/types'; +import { EventEmitter } from '@idraw/util'; -export class Watcher { - private _canvas: HTMLCanvasElement; - private _isMoving = false; - // private _onMove?: TypeWatchCallback; - // private _onMoveStart?: TypeWatchCallback; - // private _onMoveEnd?: TypeWatchCallback; - private _event: BoardEvent; - private _temp: TempData = new TempData(); +type WatcherOptions = { viewContent: ViewContent }; - constructor(canvas: HTMLCanvasElement) { - this._canvas = canvas; - this._isMoving = false; - this._initEvent(); - this._event = new BoardEvent(); +export class BoardWatcher extends EventEmitter { + private _opts: WatcherOptions; + constructor(opts: WatcherOptions) { + super(); + this._opts = opts; + this._init(); } - on( - name: T, - callback: (p: TypeBoardEventArgMap[T]) => void - ): void { - this._event.on(name, callback); + private _init() { + const boardCanvas = this._opts.viewContent.boardContext.canvas; + boardCanvas.addEventListener('mousemove', (e: MouseEvent) => { + const point = this._getPoint(e); + this.trigger('hover', { point }); + }); + boardCanvas.addEventListener('mousedown', (e: MouseEvent) => { + const point = this._getPoint(e); + this.trigger('pointStart', { point }); + }); + boardCanvas.addEventListener('mousemove', (e: MouseEvent) => { + const point = this._getPoint(e); + this.trigger('pointMove', { point }); + }); + boardCanvas.addEventListener('mouseup', (e: MouseEvent) => { + const point = this._getPoint(e); + this.trigger('pointEnd', { point }); + }); + boardCanvas.addEventListener('mouseleave', (e: MouseEvent) => { + const point = this._getPoint(e); + this.trigger('pointLeave', { point }); + }); } - off( - name: T, - callback: (p: TypeBoardEventArgMap[T]) => void - ): void { - this._event.off(name, callback); - } - - _initEvent(): void { - const canvas = this._canvas; - canvas.addEventListener('mousemove', this._listenHover.bind(this), true); - canvas.addEventListener( - 'mousedown', - this._listenMoveStart.bind(this), - true - ); - canvas.addEventListener('mousemove', this._listenMove.bind(this), true); - canvas.addEventListener('mouseup', this._listenMoveEnd.bind(this), true); - canvas.addEventListener('mouseleave', this._listenMoveEnd.bind(this), true); - canvas.addEventListener('mouseleave', this._listenLeave.bind(this), true); - canvas.addEventListener('click', this._listenClick.bind(this), true); - canvas.addEventListener('wheel', this._listenWheel.bind(this), true); - - canvas.addEventListener( - 'touchstart', - this._listenMoveStart.bind(this), - true - ); - canvas.addEventListener('touchmove', this._listenMove.bind(this), true); - canvas.addEventListener('touchend', this._listenMoveEnd.bind(this), true); - } - - _listenHover(e: MouseEvent | TouchEvent): void { - e.preventDefault(); - const p = this._getPosition(e); - if (this._isVaildPoint(p)) { - if (this._event.has('hover')) { - this._event.trigger('hover', p); - } - } - this._isMoving = true; - } - - _listenLeave(e: MouseEvent | TouchEvent): void { - e.preventDefault(); - if (this._event.has('leave')) { - this._event.trigger('leave', undefined); - } - } - - _listenMoveStart(e: MouseEvent | TouchEvent): void { - e.preventDefault(); - const p = this._getPosition(e); - if (this._isVaildPoint(p)) { - if (this._event.has('point')) { - this._event.trigger('point', p); - } - if (this._event.has('moveStart')) { - this._event.trigger('moveStart', p); - } - } - this._isMoving = true; - } - - _listenMove(e: MouseEvent | TouchEvent): void { - e.preventDefault(); - e.stopPropagation(); - if (this._event.has('move') && this._isMoving === true) { - const p = this._getPosition(e); - if (this._isVaildPoint(p)) { - this._event.trigger('move', p); - } - } - } - - _listenMoveEnd(e: MouseEvent | TouchEvent): void { - e.preventDefault(); - if (this._event.has('moveEnd')) { - const p = this._getPosition(e); - if (this._isVaildPoint(p)) { - this._event.trigger('moveEnd', p); - } - } - this._isMoving = false; - } - - _listenWheel(e: WheelEvent) { - e.preventDefault(); - if (this._event.has('wheelX') && (e.deltaX > 0 || e.deltaX < 0)) { - this._event.trigger('wheelX', e.deltaX); - } - if (this._event.has('wheelY') && (e.deltaY > 0 || e.deltaY < 0)) { - this._event.trigger('wheelY', e.deltaY); - } - } - - _listenClick(e: MouseEvent | TouchEvent) { - e.preventDefault(); - const maxLimitTime = 500; - const p = this._getPosition(e); - const t = Date.now(); - if (this._isVaildPoint(p)) { - const preClickPoint = this._temp.get('prevClickPoint'); - if ( - preClickPoint && - t - preClickPoint.t <= maxLimitTime && - Math.abs(preClickPoint.x - p.x) <= 5 && - Math.abs(preClickPoint.y - p.y) <= 5 - ) { - if (this._event.has('doubleClick')) { - this._event.trigger('doubleClick', { x: p.x, y: p.y }); - } - } else { - this._temp.set('prevClickPoint', { x: p.x, y: p.y, t }); - } - } - } - - _getPosition(e: MouseEvent | TouchEvent): Point { - const canvas = this._canvas; - let x = 0; - let y = 0; - - // @ts-ignore - if (e && e.touches && e.touches.length > 0) { - // @ts-ignore - const touch: Touch = e.touches[0]; - if (touch) { - x = touch.clientX; - y = touch.clientY; - } - } else { - // @ts-ignore - x = e.clientX; - // @ts-ignore - y = e.clientY; - } - - const p = { - x: x - canvas.getBoundingClientRect().left, - y: y - canvas.getBoundingClientRect().top, + private _getPoint(e: MouseEvent): Point { + const boardCanvas = this._opts.viewContent.boardContext.canvas; + const rect = boardCanvas.getBoundingClientRect(); + const p: Point = { + x: e.clientX - rect.left, + y: e.clientY - rect.top, t: Date.now() }; return p; } - - private _isVaildPoint(p: Point): boolean { - return p.x > 0 && p.y > 0; - } +} + +interface PointResult { + index: number; + element: Element | null; +} + +export function getPointResult(p: Point, data: Data): PointResult { + const result: PointResult = { + index: -1, + element: null + }; + for (let i = 0; i < data.elements.length; i++) { + const elem = data.elements[i]; + if (p.x >= elem.x && p.x <= elem.x + elem.w && p.y >= elem.y && p.y <= elem.y + elem.h) { + result.index = i; + result.element = elem; + break; + } + } + return result; } diff --git a/packages/core/src/constant/element.ts b/packages/core/src/constant/element.ts deleted file mode 100644 index b2bbcdb..0000000 --- a/packages/core/src/constant/element.ts +++ /dev/null @@ -1,15 +0,0 @@ - -const elementTypes = { - 'text': {}, // TODO - 'rect': {}, // TODO - 'image': {}, // TODO - 'svg': {}, // TODO - 'circle': {}, // TODO - 'html': {}, // TODO -}; - -export const elementNames = Object.keys(elementTypes); - - -// limitQbliqueAngle -export const LIMIT_QBLIQUE_ANGLE = 15; \ No newline at end of file diff --git a/packages/core/src/constant/static.ts b/packages/core/src/constant/static.ts deleted file mode 100644 index 80815e8..0000000 --- a/packages/core/src/constant/static.ts +++ /dev/null @@ -1,12 +0,0 @@ -export enum Mode { - NULL = 'null', - SELECT_ELEMENT = 'select-element', - SELECT_ELEMENT_LIST = 'select-element-list', - SELECT_ELEMENT_WRAPPER_CONTROLLER = 'select-element-wrapper-controller', - SELECT_AREA = 'select-area', -} - -export enum CursorStatus { - DRAGGING = 'dragging', - NULL = 'null', -} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 92f6495..6233b15 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,327 +1,32 @@ -import { - IDrawData, - Point, - BoardSizeOptions, - IDrawConfig, - IDrawConfigStrict, - DataElementBase, - DataElement, - DataElemDesc, - IDrawContext, - CoreOptions, - ScreenContext, - ScreenData -} from '@idraw/types'; -import Board from '@idraw/board'; -import { deepClone } from '@idraw/util'; -import Renderer from '@idraw/renderer'; -import is, { IsTypeUtil } from './lib/is'; -import check, { CheckTypeUtil } from './lib/check'; -import { - Element, - mergeConfig, - CoreEvent, - TypeCoreEventArgMap, - parseData, - TempData, - diffElementResourceChangeList -} from './lib'; -import { - getSelectedElements, - updateElement, - selectElementByIndex, - selectElement, - cancelElementByIndex, - cancelElement, - getElement, - getElementByIndex, - moveUpElement, - moveDownElement, - addElement, - deleteElement, - insertElementBefore, - insertElementBeforeIndex, - insertElementAfter, - insertElementAfterIndex -} from './mixins/element'; -// import { initEvent } from './mixins/event'; -import { Engine } from './lib/engine'; -import { - drawElementWrapper, - drawAreaWrapper, - drawElementListWrappers -} from './lib/draw/wrapper'; +import type { Data, BoardOptions, CoreOptions, BoardMiddleware } from '@idraw/types'; +import { Board } from '@idraw/board'; +import { createBoardContexts } from '@idraw/util'; -export default class Core { - $data: IDrawData; +export { MiddlewareSelector } from './middleware/select'; + +export class Core { private _board: Board; private _opts: CoreOptions; - private _config: IDrawConfigStrict; - private _renderer: Renderer; - private _elementHandler: Element; - private _coreEvent: CoreEvent = new CoreEvent(); - private _tempData: TempData = new TempData(); - private _engine: Engine; - - static is: IsTypeUtil = is; - static check: CheckTypeUtil = check; - - constructor(mount: HTMLDivElement, opts: CoreOptions, config?: IDrawConfig) { - this.$data = { elements: [] }; + private _mount: HTMLDivElement; + constructor(mount: HTMLDivElement, opts: CoreOptions) { this._opts = opts; - this._config = mergeConfig(config || {}); - this._board = new Board(mount, { - ...this._opts, - canScroll: config?.scrollWrapper?.use, - scrollConfig: { - color: config?.scrollWrapper?.color || '#000000', - width: config?.scrollWrapper?.width || 12, - ...(config?.scrollWrapper || {}) - } - }); - this._renderer = new Renderer(); - const drawFrame = () => { - const helperCtx = this._board.getHelperContext(); - const helperConfig = this._engine.getHelperConfig(); - this._board.clear(); - const { contextWidth, contextHeight, devicePixelRatio } = this._opts; - helperCtx.clearRect( - 0, - 0, - contextWidth * devicePixelRatio, - contextHeight * devicePixelRatio - ); - drawElementWrapper(helperCtx, helperConfig); - drawAreaWrapper(helperCtx, helperConfig); - drawElementListWrappers(helperCtx, helperConfig); - this._board.draw(); - }; - this._renderer.on('drawFrame', () => { - drawFrame(); - }); - this._renderer.on('drawFrameComplete', () => { - drawFrame(); - }); - this._elementHandler = new Element(this._board.getContext()); - this._engine = new Engine({ - coreEvent: this._coreEvent, - board: this._board, - element: this._elementHandler, - config: this._config, - drawFeekback: this.$draw.bind(this), - getDataFeekback: () => this.$data, - selectElementByIndex: this.selectElementByIndex.bind(this), - emitChangeScreen: this._emitChangeScreen.bind(this), - emitChangeData: this.$emitChangeData.bind(this) - }); - this._engine.init(); + this._mount = mount; + const canvas = document.createElement('canvas'); + canvas.width = opts.width; + canvas.height = opts.height; + mount.appendChild(canvas); - this._renderer.on('drawFrame', () => { - this._coreEvent.trigger('drawFrame', undefined); - }); - this._renderer.on('drawFrameComplete', () => { - this._coreEvent.trigger('drawFrameComplete', undefined); - }); - - this._tempData.set('hasInited', true); + const ctx = canvas.getContext('2d') as CanvasRenderingContext2D; + const viewContent = createBoardContexts(ctx); + const board = new Board({ viewContent }); + this._board = board; } - private _emitChangeScreen() { - if (this._coreEvent.has('changeScreen')) { - this._coreEvent.trigger('changeScreen', { - ...this.getScreenTransform() - }); - } + use(middleware: BoardMiddleware) { + this._board.use(middleware); } - $draw(opts?: { resourceChangeUUIDs?: string[] }): void { - this._engine.updateHelperConfig({ - width: this._opts.width, - height: this._opts.height, - devicePixelRatio: this._opts.devicePixelRatio - }); - - this._renderer.thaw(); - this._renderer.render(this._board.getContext(), this.$data, { - changeResourceUUIDs: opts?.resourceChangeUUIDs || [] - }); - } - - getElement(uuid: string) { - return getElement(this, uuid); - } - - getElementByIndex(index: number) { - return getElementByIndex(this, index); - } - - selectElementByIndex(index: number): void { - return selectElementByIndex(this, index); - } - - selectElement(uuid: string): void { - return selectElement(this, uuid); - } - - cancelElementByIndex(index: number): void { - return cancelElementByIndex(this, index); - } - - cancelElement(uuid: string): void { - return cancelElement(this, uuid); - } - - moveUpElement(uuid: string): void { - return moveUpElement(this, uuid); - } - - moveDownElement(uuid: string): void { - return moveDownElement(this, uuid); - } - - updateElement(elem: DataElement) { - return updateElement(this, elem); - } - - addElement(elem: DataElementBase): string | null { - return addElement(this, elem); - } - - deleteElement(uuid: string) { - return deleteElement(this, uuid); - } - - insertElementBefore( - elem: DataElementBase, - beforeUUID: string - ) { - return insertElementBefore(this, elem, beforeUUID); - } - - insertElementBeforeIndex( - elem: DataElementBase, - index: number - ) { - return insertElementBeforeIndex(this, elem, index); - } - - getSelectedElements() { - return getSelectedElements(this); - } - - insertElementAfter( - elem: DataElementBase, - beforeUUID: string - ) { - return insertElementAfter(this, elem, beforeUUID); - } - - insertElementAfterIndex( - elem: DataElementBase, - index: number - ) { - return insertElementAfterIndex(this, elem, index); - } - - resetSize(opts: BoardSizeOptions) { - this._opts = { ...this._opts, ...opts }; - this._board.resetSize(opts); - this.$draw(); - } - - scale(ratio: number): ScreenContext { - const screen = this._board.scale(ratio); - this.$draw(); - this._emitChangeScreen(); - return screen; - } - - scrollLeft(left: number): ScreenContext { - const screen = this._board.scrollX(0 - left); - this.$draw(); - this._emitChangeScreen(); - return screen; - } - - scrollTop(top: number): ScreenContext { - const screen = this._board.scrollY(0 - top); - this.$draw(); - this._emitChangeScreen(); - return screen; - } - - getScreenTransform(): ScreenData { - const transform = this._board.getTransform(); - return { - scale: transform.scale, - scrollTop: Math.max(0, 0 - transform.scrollY), - scrollLeft: Math.max(0, 0 - transform.scrollX) - }; - } - - getData(): IDrawData { - return deepClone(this.$data); - } - - setData(data: any | IDrawData, opts?: { triggerChangeEvent: boolean }): void { - const resourceChangeUUIDs = diffElementResourceChangeList(this.$data, data); - this.$data = this._elementHandler.initData(deepClone(parseData(data))); - if (opts && opts.triggerChangeEvent === true) { - this.$emitChangeData(); - } - this.$draw({ resourceChangeUUIDs }); - } - - clearOperation() { - this._tempData.clear(); - this.$draw(); - } - - on( - key: T, - callback: (p: TypeCoreEventArgMap[T]) => void - ) { - this._coreEvent.on(key, callback); - } - - off( - key: T, - callback: (p: TypeCoreEventArgMap[T]) => void - ) { - this._coreEvent.off(key, callback); - } - - getEngine() { - return this._engine; - } - - pointScreenToContext(p: Point) { - return this._board.pointScreenToContext(p); - } - - pointContextToScreen(p: Point) { - return this._board.pointContextToScreen(p); - } - - $getBoardContext(): IDrawContext { - return this._board.getContext(); - } - - $getDisplayContext2D(): CanvasRenderingContext2D { - return this._board.getDisplayContext2D(); - } - - $getOriginContext2D(): CanvasRenderingContext2D { - return this._board.getOriginContext2D(); - } - - $emitChangeData() { - if (this._coreEvent.has('changeData')) { - this._coreEvent.trigger('changeData', deepClone(this.$data)); - } - } - - $getElementHandler() { - return this._elementHandler; + setData(data: Data) { + this._board.setData(data); } } diff --git a/packages/core/src/lib/calculate.ts b/packages/core/src/lib/calculate.ts deleted file mode 100644 index 14c8bc4..0000000 --- a/packages/core/src/lib/calculate.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { DataElement, DataElemDesc, Point } from '@idraw/types'; - -export function parseRadianToAngle(radian: number): number { - return (radian / Math.PI) * 180; -} - -export function parseAngleToRadian(angle: number): number { - return (angle / 180) * Math.PI; -} - -export function calcElementCenter( - elem: DataElement -): Point { - const p = { - x: elem.x + elem.w / 2, - y: elem.y + elem.h / 2 - }; - return p; -} - -export function calcRadian(center: Point, start: Point, end: Point): number { - const startAngle = calcLineAngle(center, start); - const endAngle = calcLineAngle(center, end); - if (endAngle !== null && startAngle !== null) { - if (startAngle > (Math.PI * 3) / 2 && endAngle < Math.PI / 2) { - return endAngle + (Math.PI * 2 - startAngle); - } else if (endAngle > (Math.PI * 3) / 2 && startAngle < Math.PI / 2) { - return startAngle + (Math.PI * 2 - endAngle); - } else { - return endAngle - startAngle; - } - } else { - return 0; - } -} - -function calcLineAngle(center: Point, p: Point): number | null { - const x = p.x - center.x; - const y = center.y - p.y; - if (x === 0) { - if (y < 0) { - return Math.PI / 2; - } else if (y > 0) { - return Math.PI * (3 / 2); - } - } else if (y === 0) { - if (x < 0) { - return Math.PI; - } else if (x > 0) { - return 0; - } - } - if (x > 0 && y < 0) { - return Math.atan(Math.abs(y) / Math.abs(x)); - } else if (x < 0 && y < 0) { - return Math.PI - Math.atan(Math.abs(y) / Math.abs(x)); - } else if (x < 0 && y > 0) { - return Math.PI + Math.atan(Math.abs(y) / Math.abs(x)); - } else if (x > 0 && y > 0) { - return Math.PI * 2 - Math.atan(Math.abs(y) / Math.abs(x)); - } - return null; -} diff --git a/packages/core/src/lib/check.ts b/packages/core/src/lib/check.ts deleted file mode 100644 index 7a4adb4..0000000 --- a/packages/core/src/lib/check.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { DataElementAttrs } from '@idraw/types'; -import is from './is'; - -function attrs(attrs: DataElementAttrs): boolean { - const { x, y, w, h, angle } = attrs; - if (!(is.x(x) && is.y(y) && is.w(w) && is.h(h) && is.angle(angle))) { - return false; - } - if (!(angle >= -360 && angle <= 360)) { - return false; - } - return true; -} - -function box(desc: any = {}): boolean { - const { borderColor, borderRadius, borderWidth } = desc; - if (desc.hasOwnProperty('borderColor') && !is.color(borderColor)) { - return false; - } - if (desc.hasOwnProperty('borderRadius') && !is.number(borderRadius)) { - return false; - } - if (desc.hasOwnProperty('borderWidth') && !is.number(borderWidth)) { - return false; - } - return true; -} - -function rectDesc(desc: any): boolean { - const { bgColor } = desc; - if (desc.hasOwnProperty('bgColor') && !is.color(bgColor)) { - return false; - } - if (!box(desc)) { - return false; - } - return true; -} - -function circleDesc(desc: any): boolean { - const { bgColor, borderColor, borderWidth } = desc; - if (desc.hasOwnProperty('bgColor') && !is.color(bgColor)) { - return false; - } - if (desc.hasOwnProperty('borderColor') && !is.color(borderColor)) { - return false; - } - if (desc.hasOwnProperty('borderWidth') && !is.number(borderWidth)) { - return false; - } - return true; -} - -function imageDesc(desc: any): boolean { - const { src } = desc; - if (!is.imageSrc(src)) { - return false; - } - return true; -} - -function svgDesc(desc: any): boolean { - const { svg } = desc; - if (!is.svg(svg)) { - return false; - } - return true; -} - -function htmlDesc(desc: any): boolean { - const { html } = desc; - if (!is.html(html)) { - return false; - } - return true; -} - -function textDesc(desc: any): boolean { - const { - text, - color, - fontSize, - lineHeight, - fontFamily, - textAlign, - fontWeight, - bgColor, - strokeWidth, - strokeColor - } = desc; - if (!is.text(text)) { - return false; - } - if (!is.color(color)) { - return false; - } - if (!is.fontSize(fontSize)) { - return false; - } - if (desc.hasOwnProperty('bgColor') && !is.color(bgColor)) { - return false; - } - if (desc.hasOwnProperty('fontWeight') && !is.fontWeight(fontWeight)) { - return false; - } - if (desc.hasOwnProperty('lineHeight') && !is.lineHeight(lineHeight)) { - return false; - } - if (desc.hasOwnProperty('fontFamily') && !is.fontFamily(fontFamily)) { - return false; - } - if (desc.hasOwnProperty('textAlign') && !is.textAlign(textAlign)) { - return false; - } - if (desc.hasOwnProperty('strokeWidth') && !is.strokeWidth(strokeWidth)) { - return false; - } - if (desc.hasOwnProperty('strokeColor') && !is.color(strokeColor)) { - return false; - } - - if (!box(desc)) { - return false; - } - return true; -} - -const check = { - attrs, - textDesc, - rectDesc, - circleDesc, - imageDesc, - svgDesc, - htmlDesc -}; - -type CheckTypeUtil = { - attrs: (value: any) => boolean; - rectDesc: (value: any) => boolean; - circleDesc: (value: any) => boolean; - imageDesc: (value: any) => boolean; - svgDesc: (value: any) => boolean; - htmlDesc: (value: any) => boolean; - textDesc: (value: any) => boolean; -}; - -export { CheckTypeUtil }; - -export default check; diff --git a/packages/core/src/lib/config.ts b/packages/core/src/lib/config.ts deleted file mode 100644 index db1ce07..0000000 --- a/packages/core/src/lib/config.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { IDrawConfig, IDrawConfigStrict } from '@idraw/types'; -import { deepClone } from '@idraw/util'; - -const defaultConfig: IDrawConfigStrict = { - elementWrapper: { - color: '#0d85da', - lockColor: '#aaaaaa', - controllerSize: 6, - lineWidth: 1, - lineDash: [4, 3] - } -}; - -function mergeConfig(config?: IDrawConfig): IDrawConfigStrict { - const result = deepClone(defaultConfig); - if (config) { - if (config.elementWrapper) { - result.elementWrapper = { - ...result.elementWrapper, - ...config.elementWrapper - }; - } - } - return result; -} - -export { mergeConfig }; diff --git a/packages/core/src/lib/core-event.ts b/packages/core/src/lib/core-event.ts deleted file mode 100644 index fe16e5a..0000000 --- a/packages/core/src/lib/core-event.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { - DataElement, - DataElemDesc, - Point, - IDrawData, - ScreenData -} from '@idraw/types'; - -export type TypeCoreEventSelectBaseArg = { - index: number | null; - uuid: string | null; -}; - -export type TypeCoreEventArgMap = { - error: any; - mouseOverScreen: Point; - mouseLeaveScreen: void; - mouseOverElement: TypeCoreEventSelectBaseArg & { - element: DataElement; - }; - mouseLeaveElement: TypeCoreEventSelectBaseArg & { - element: DataElement; - }; - screenClickElement: TypeCoreEventSelectBaseArg & { - element: DataElement; - }; - screenDoubleClickElement: TypeCoreEventSelectBaseArg & { - element: DataElement; - }; - screenSelectElement: TypeCoreEventSelectBaseArg & { - element: DataElement; - }; - screenMoveElementStart: TypeCoreEventSelectBaseArg & Point; - screenMoveElementEnd: TypeCoreEventSelectBaseArg & Point; - screenChangeElement: TypeCoreEventSelectBaseArg & { - width: number; - height: number; - angle: number; - }; - changeData: IDrawData; - changeScreen: ScreenData; - drawFrameComplete: void; - drawFrame: void; -}; - -export interface TypeCoreEvent { - on( - key: T, - callback: (p: TypeCoreEventArgMap[T]) => void - ): void; - off( - key: T, - callback: (p: TypeCoreEventArgMap[T]) => void - ): void; - trigger( - key: T, - p: TypeCoreEventArgMap[T] - ): void; -} - -export class CoreEvent implements TypeCoreEvent { - private _listeners: Map void)[]>; - - constructor() { - this._listeners = new Map(); - } - - on( - eventKey: T, - callback: (p: TypeCoreEventArgMap[T]) => void - ) { - if (this._listeners.has(eventKey)) { - const callbacks = this._listeners.get(eventKey); - callbacks?.push(callback); - this._listeners.set(eventKey, callbacks || []); - } else { - this._listeners.set(eventKey, [callback]); - } - } - - off( - eventKey: T, - callback: (p: TypeCoreEventArgMap[T]) => void - ) { - 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) { - callbacks.splice(i, 1); - break; - } - } - } - this._listeners.set(eventKey, callbacks || []); - } - } - - trigger( - eventKey: T, - arg: TypeCoreEventArgMap[T] - ) { - const callbacks = this._listeners.get(eventKey); - if (Array.isArray(callbacks)) { - callbacks.forEach((cb) => { - cb(arg); - }); - return true; - } else { - return false; - } - } - - has(name: string) { - if (this._listeners.has(name)) { - const list: ((p: TypeCoreEventArgMap[T]) => void)[] | undefined = - this._listeners.get(name); - if (Array.isArray(list) && list.length > 0) { - return true; - } - } - return false; - } -} diff --git a/packages/core/src/lib/diff.ts b/packages/core/src/lib/diff.ts deleted file mode 100644 index c84ced1..0000000 --- a/packages/core/src/lib/diff.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { DataElement, IDrawData, DataElemDesc } from '@idraw/types'; - -type DataElementMap = { - [uuid: string]: DataElement; -}; - -export function isChangeImageElementResource( - before: DataElement<'image'>, - after: DataElement<'image'> -): boolean { - return before?.desc?.src !== after?.desc?.src; -} - -export function isChangeSVGElementResource( - before: DataElement<'svg'>, - after: DataElement<'svg'> -): boolean { - return before?.desc?.svg !== after?.desc?.svg; -} - -export function isChangeHTMLElementResource( - before: DataElement<'html'>, - after: DataElement<'html'> -): boolean { - return ( - before?.desc?.html !== after?.desc?.html || - before?.desc?.width !== after?.desc?.width || - before?.desc?.height !== after?.desc?.height - ); -} - -export function diffElementResourceChange( - before: DataElement, - after: DataElement -): string | null { - let result = null; - let isChange = false; - switch (after.type) { - case 'image': { - isChange = isChangeImageElementResource( - before as DataElement<'image'>, - after as DataElement<'image'> - ); - break; - } - case 'svg': { - isChange = isChangeSVGElementResource( - before as DataElement<'svg'>, - after as DataElement<'svg'> - ); - break; - } - case 'html': { - isChange = isChangeHTMLElementResource( - before as DataElement<'html'>, - after as DataElement<'html'> - ); - break; - } - default: - break; - } - if (isChange === true) { - result = after.uuid; - } - return result; -} - -export function diffElementResourceChangeList( - before: IDrawData, - after: IDrawData -): string[] { - const uuids: string[] = []; - const beforeMap = parseDataElementMap(before); - const afterMap = parseDataElementMap(after); - for (const uuid in afterMap) { - if (['image', 'svg', 'html'].includes(afterMap[uuid]?.type) !== true) { - continue; - } - if (beforeMap[uuid]) { - let isChange = false; - switch (beforeMap[uuid].type) { - case 'image': { - isChange = isChangeImageElementResource( - beforeMap[uuid] as DataElement<'image'>, - afterMap[uuid] as DataElement<'image'> - ); - break; - } - case 'svg': { - isChange = isChangeSVGElementResource( - beforeMap[uuid] as DataElement<'svg'>, - afterMap[uuid] as DataElement<'svg'> - ); - break; - } - case 'html': { - isChange = isChangeHTMLElementResource( - beforeMap[uuid] as DataElement<'html'>, - afterMap[uuid] as DataElement<'html'> - ); - break; - } - default: - break; - } - if (isChange === true) { - uuids.push(uuid); - } - } else { - uuids.push(uuid); - } - } - return uuids; -} - -function parseDataElementMap(data: IDrawData): DataElementMap { - const elemMap: DataElementMap = {}; - data.elements.forEach((elem) => { - elemMap[elem.uuid] = elem; - }); - return elemMap; -} diff --git a/packages/core/src/lib/draw/base.ts b/packages/core/src/lib/draw/base.ts deleted file mode 100644 index 06bec20..0000000 --- a/packages/core/src/lib/draw/base.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { - IDrawContext, - // DataElemDesc, - DataElement -} from '@idraw/types'; -import { istype, isColorStr } from '@idraw/util'; -import { rotateElement } from './../transform'; -import is from './../is'; - -export function clearContext(ctx: IDrawContext) { - // ctx.setFillStyle('rgb(0 0 0 / 100%)'); - // ctx.setStrokeStyle('rgb(0 0 0 / 100%)'); - ctx.setFillStyle('#000000'); - ctx.setStrokeStyle('#000000'); - ctx.setLineDash([]); - ctx.setGlobalAlpha(1); - ctx.setShadowColor('#00000000'); - ctx.setShadowOffsetX(0); - ctx.setShadowOffsetY(0); - ctx.setShadowBlur(0); -} - -export function drawBgColor(ctx: IDrawContext, color: string) { - const size = ctx.getSize(); - ctx.setFillStyle(color); - ctx.fillRect(0, 0, size.contextWidth, size.contextHeight); -} - -export function drawBox( - ctx: IDrawContext, - elem: DataElement<'text' | 'rect'>, - pattern: string | CanvasPattern | null -): void { - clearContext(ctx); - drawBoxBorder(ctx, elem); - clearContext(ctx); - rotateElement(ctx, elem, () => { - const { x, y, w, h } = elem; - let r: number = elem.desc.borderRadius || 0; - r = Math.min(r, w / 2, h / 2); - if (w < r * 2 || h < r * 2) { - r = 0; - } - ctx.beginPath(); - ctx.moveTo(x + r, y); - ctx.arcTo(x + w, y, x + w, y + h, r); - ctx.arcTo(x + w, y + h, x, y + h, r); - ctx.arcTo(x, y + h, x, y, r); - ctx.arcTo(x, y, x + w, y, r); - ctx.closePath(); - if (typeof pattern === 'string') { - ctx.setFillStyle(pattern); - } else if (['CanvasPattern'].includes(istype.type(pattern))) { - ctx.setFillStyle(pattern as CanvasPattern); - } - ctx.fill(); - }); -} - -export function drawBoxBorder( - ctx: IDrawContext, - elem: DataElement<'text' | 'rect'> -): void { - clearContext(ctx); - rotateElement(ctx, elem, () => { - if (!(elem.desc.borderWidth && elem.desc.borderWidth > 0)) { - return; - } - const bw = elem.desc.borderWidth; - let borderColor = '#000000'; - if (isColorStr(elem.desc.borderColor) === true) { - borderColor = elem.desc.borderColor as string; - } - const x = elem.x - bw / 2; - const y = elem.y - bw / 2; - const w = elem.w + bw; - const h = elem.h + bw; - - let r: number = elem.desc.borderRadius || 0; - r = Math.min(r, w / 2, h / 2); - if (r < w / 2 && r < h / 2) { - r = r + bw / 2; - } - const { desc } = elem; - if (desc.shadowColor !== undefined && isColorStr(desc.shadowColor)) { - ctx.setShadowColor(desc.shadowColor); - } - if (desc.shadowOffsetX !== undefined && is.number(desc.shadowOffsetX)) { - ctx.setShadowOffsetX(desc.shadowOffsetX); - } - if (desc.shadowOffsetY !== undefined && is.number(desc.shadowOffsetY)) { - ctx.setShadowOffsetY(desc.shadowOffsetY); - } - if (desc.shadowBlur !== undefined && is.number(desc.shadowBlur)) { - ctx.setShadowBlur(desc.shadowBlur); - } - ctx.beginPath(); - ctx.setLineWidth(bw); - ctx.setStrokeStyle(borderColor); - ctx.moveTo(x + r, y); - ctx.arcTo(x + w, y, x + w, y + h, r); - ctx.arcTo(x + w, y + h, x, y + h, r); - ctx.arcTo(x, y + h, x, y, r); - ctx.arcTo(x, y, x + w, y, r); - ctx.closePath(); - ctx.stroke(); - }); -} diff --git a/packages/core/src/lib/draw/wrapper.ts b/packages/core/src/lib/draw/wrapper.ts deleted file mode 100644 index feef6a1..0000000 --- a/packages/core/src/lib/draw/wrapper.ts +++ /dev/null @@ -1,269 +0,0 @@ -import { IDrawContext, HelperConfig } from '@idraw/types'; -import { rotateContext } from './../transform'; -import { clearContext } from './base'; - -export function drawElementWrapper(ctx: IDrawContext, config: HelperConfig) { - if (!config?.selectedElementWrapper) { - return; - } - const wrapper = config.selectedElementWrapper; - clearContext(ctx); - rotateContext(ctx, wrapper.translate, wrapper.radian || 0, () => { - // draw wrapper's box - ctx.beginPath(); - ctx.setLineDash(wrapper.lineDash); - ctx.setLineWidth(wrapper.lineWidth); - ctx.setStrokeStyle(wrapper.color); - ctx.moveTo(wrapper.controllers.topLeft.x, wrapper.controllers.topLeft.y); - ctx.lineTo(wrapper.controllers.topRight.x, wrapper.controllers.topRight.y); - ctx.lineTo( - wrapper.controllers.bottomRight.x, - wrapper.controllers.bottomRight.y - ); - ctx.lineTo( - wrapper.controllers.bottomLeft.x, - wrapper.controllers.bottomLeft.y - ); - ctx.lineTo( - wrapper.controllers.topLeft.x, - wrapper.controllers.topLeft.y - wrapper.lineWidth / 2 - ); - ctx.stroke(); - ctx.closePath(); - - if (wrapper.lock !== true) { - if (wrapper.controllers.rotate.invisible !== true) { - // draw wrapper's rotate line - ctx.beginPath(); - ctx.moveTo(wrapper.controllers.top.x, wrapper.controllers.top.y); - ctx.lineTo( - wrapper.controllers.rotate.x, - wrapper.controllers.rotate.y + wrapper.controllerSize - ); - ctx.stroke(); - ctx.closePath(); - - // draw wrapper's rotate - ctx.beginPath(); - ctx.setLineDash([]); - ctx.setLineWidth(wrapper.controllerSize / 1.2); - ctx.arc( - wrapper.controllers.rotate.x, - wrapper.controllers.rotate.y, - wrapper.controllerSize * 0.8, - 0, - Math.PI * 2 - ); - ctx.stroke(); - ctx.closePath(); - - ctx.setStrokeStyle('#FFFFFF'); - ctx.beginPath(); - ctx.setLineDash([]); - ctx.setLineWidth(wrapper.controllerSize / 2.1); - ctx.arc( - wrapper.controllers.rotate.x, - wrapper.controllers.rotate.y, - wrapper.controllerSize * 0.8, - 0, - Math.PI * 2 - ); - ctx.stroke(); - ctx.closePath(); - } - - // draw wrapper's controllers - [ - wrapper.controllers.topLeft, - wrapper.controllers.top, - wrapper.controllers.topRight, - wrapper.controllers.right, - wrapper.controllers.bottomRight, - wrapper.controllers.bottom, - wrapper.controllers.bottomLeft, - wrapper.controllers.left - ].forEach((controller) => { - if (controller.invisible !== true) { - ctx.setFillStyle(wrapper.color); - ctx.beginPath(); - ctx.arc( - controller.x, - controller.y, - wrapper.controllerSize, - 0, - Math.PI * 2 - ); - ctx.fill(); - ctx.closePath(); - - ctx.setFillStyle('#FFFFFF'); - ctx.beginPath(); - ctx.arc( - controller.x, - controller.y, - wrapper.controllerSize - 1, - 0, - Math.PI * 2 - ); - ctx.fill(); - ctx.closePath(); - } - }); - } else { - // draw wrapper's lock controllers, - clearContext(ctx); - ctx.setStrokeStyle(wrapper.color); - [ - wrapper.controllers.topLeft, - wrapper.controllers.top, - wrapper.controllers.topRight, - wrapper.controllers.right, - wrapper.controllers.bottomRight, - wrapper.controllers.bottom, - wrapper.controllers.bottomLeft, - wrapper.controllers.left - ].forEach((controller) => { - ctx.beginPath(); - ctx.moveTo( - controller.x - wrapper.controllerSize / 2, - controller.y - wrapper.controllerSize / 2 - ); - ctx.lineTo( - controller.x + wrapper.controllerSize / 2, - controller.y + wrapper.controllerSize / 2 - ); - ctx.stroke(); - ctx.closePath(); - - ctx.beginPath(); - ctx.moveTo( - controller.x + wrapper.controllerSize / 2, - controller.y - wrapper.controllerSize / 2 - ); - ctx.lineTo( - controller.x - wrapper.controllerSize / 2, - controller.y + wrapper.controllerSize / 2 - ); - ctx.stroke(); - ctx.closePath(); - }); - } - }); -} - -export function drawAreaWrapper(ctx: IDrawContext, config: HelperConfig) { - if (!config?.selectedAreaWrapper) { - return; - } - const wrapper = config.selectedAreaWrapper; - if (wrapper && wrapper.w > 0 && wrapper.h > 0) { - clearContext(ctx); - ctx.setGlobalAlpha(0.3); - ctx.setFillStyle(wrapper.color); - ctx.fillRect(wrapper.x, wrapper.y, wrapper.w, wrapper.h); - - clearContext(ctx); - ctx.beginPath(); - ctx.setLineDash(wrapper.lineDash); - ctx.setLineWidth(wrapper.lineWidth); - ctx.setStrokeStyle(wrapper.color); - ctx.moveTo(wrapper.x, wrapper.y); - ctx.lineTo(wrapper.x + wrapper.w, wrapper.y); - ctx.lineTo(wrapper.x + wrapper.w, wrapper.y + wrapper.h); - ctx.lineTo(wrapper.x, wrapper.y + wrapper.h); - ctx.lineTo(wrapper.x, wrapper.y); - ctx.stroke(); - ctx.closePath(); - } -} - -export function drawElementListWrappers( - ctx: IDrawContext, - config: HelperConfig -) { - if (!Array.isArray(config?.selectedElementListWrappers)) { - return; - } - const wrapperList = config.selectedElementListWrappers; - - wrapperList?.forEach((wrapper) => { - clearContext(ctx); - rotateContext(ctx, wrapper.translate, wrapper.radian || 0, () => { - clearContext(ctx); - ctx.setGlobalAlpha(0.05); - ctx.setFillStyle(wrapper.color); - ctx.fillRect( - wrapper.controllers.topLeft.x, - wrapper.controllers.topLeft.y, - wrapper.controllers.bottomRight.x - wrapper.controllers.topLeft.x, - wrapper.controllers.bottomRight.y - wrapper.controllers.topLeft.y - ); - - clearContext(ctx); - ctx.beginPath(); - ctx.setLineDash(wrapper.lineDash); - ctx.setLineWidth(wrapper.lineWidth); - ctx.setStrokeStyle(wrapper.color); - ctx.moveTo(wrapper.controllers.topLeft.x, wrapper.controllers.topLeft.y); - ctx.lineTo( - wrapper.controllers.topRight.x, - wrapper.controllers.topRight.y - ); - ctx.lineTo( - wrapper.controllers.bottomRight.x, - wrapper.controllers.bottomRight.y - ); - ctx.lineTo( - wrapper.controllers.bottomLeft.x, - wrapper.controllers.bottomLeft.y - ); - ctx.lineTo( - wrapper.controllers.topLeft.x, - wrapper.controllers.topLeft.y - wrapper.lineWidth / 2 - ); - ctx.stroke(); - ctx.closePath(); - - if (wrapper.lock === true) { - // draw wrapper's lock controllers, - clearContext(ctx); - // ctx.setFillStyle(wrapper.color); - ctx.setStrokeStyle(wrapper.color); - [ - wrapper.controllers.topLeft, - wrapper.controllers.top, - wrapper.controllers.topRight, - wrapper.controllers.right, - wrapper.controllers.bottomRight, - wrapper.controllers.bottom, - wrapper.controllers.bottomLeft, - wrapper.controllers.left - ].forEach((controller) => { - ctx.beginPath(); - ctx.moveTo( - controller.x - wrapper.controllerSize / 2, - controller.y - wrapper.controllerSize / 2 - ); - ctx.lineTo( - controller.x + wrapper.controllerSize / 2, - controller.y + wrapper.controllerSize / 2 - ); - ctx.stroke(); - ctx.closePath(); - - ctx.beginPath(); - ctx.moveTo( - controller.x + wrapper.controllerSize / 2, - controller.y - wrapper.controllerSize / 2 - ); - ctx.lineTo( - controller.x - wrapper.controllerSize / 2, - controller.y + wrapper.controllerSize / 2 - ); - ctx.stroke(); - ctx.closePath(); - }); - } - }); - }); -} diff --git a/packages/core/src/lib/element.ts b/packages/core/src/lib/element.ts deleted file mode 100644 index aab6508..0000000 --- a/packages/core/src/lib/element.ts +++ /dev/null @@ -1,622 +0,0 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment */ -import { - IDrawContext, - Point, - IDrawData, - HelperWrapperControllerDirection, - DataElement, - DataElemDesc -} from '@idraw/types'; -import { createUUID } from '@idraw/util'; -import { rotateElement } from './transform'; -import { calcRadian, calcElementCenter, parseRadianToAngle } from './calculate'; -import { limitAngle, limitNum } from './value'; -import { LIMIT_QBLIQUE_ANGLE } from './../constant/element'; - -const limitQbliqueAngle = LIMIT_QBLIQUE_ANGLE; - -export class Element { - private _ctx: IDrawContext; - - constructor(ctx: IDrawContext) { - this._ctx = ctx; - } - - initData(data: IDrawData): IDrawData { - data.elements.forEach((elem) => { - if (!(elem.uuid && typeof elem.uuid === 'string')) { - elem.uuid = createUUID(); - } - }); - return data; - } - - isPointInElement(p: Point, data: IDrawData): [number, string | null] { - const ctx = this._ctx; - let idx = -1; - let uuid = null; - for (let i = data.elements.length - 1; i >= 0; i--) { - const ele = data.elements[i]; - if (ele.operation?.invisible === true) continue; - let bw = 0; - // @ts-ignore - if (ele.desc?.borderWidth > 0) { - // @ts-ignore - bw = ele.desc.borderWidth; - } - - rotateElement(ctx, ele, () => { - ctx.beginPath(); - ctx.moveTo(ele.x - bw, ele.y - bw); - ctx.lineTo(ele.x + ele.w + bw, ele.y - bw); - ctx.lineTo(ele.x + ele.w + bw, ele.y + ele.h + bw); - ctx.lineTo(ele.x - bw, ele.y + ele.h + bw); - ctx.lineTo(ele.x - bw, ele.y - bw); - ctx.closePath(); - if (ctx.isPointInPath(p.x, p.y)) { - idx = i; - uuid = ele.uuid; - } - }); - - if (idx >= 0) { - break; - } - } - return [idx, uuid]; - } - - dragElement( - data: IDrawData, - uuid: string, - point: Point, - prevPoint: Point, - scale: number - ): void { - const index = this.getElementIndex(data, uuid); - if (!data.elements[index]) { - return; - } - const moveX = point.x - prevPoint.x; - const moveY = point.y - prevPoint.y; - data.elements[index].x += moveX / scale; - data.elements[index].y += moveY / scale; - this.limitElementAttrs(data.elements[index]); - } - - transformElement( - data: IDrawData, - uuid: string, - point: Point, - prevPoint: Point, - scale: number, - direction: HelperWrapperControllerDirection - ): null | { - width: number; - height: number; - angle: number; - } { - const index = this.getElementIndex(data, uuid); - if (!data.elements[index]) { - return null; - } - if (data.elements[index]?.operation?.lock === true) { - return null; - } - const moveX = (point.x - prevPoint.x) / scale; - const moveY = (point.y - prevPoint.y) / scale; - const elem = data.elements[index]; - // const { devicePixelRatio } = this._ctx.getSize(); - - // if (typeof elem.angle === 'number' && (elem.angle > 0 || elem.angle < 0)) { - // moveY = (point.y - prevPoint.y) / scale; - // } - - if ( - [ - 'top-left', - 'top', - 'top-right', - 'right', - 'bottom-right', - 'bottom', - 'bottom-left', - 'left' - ].includes(direction) - ) { - const p = calcuScaleElemPosition(elem, moveX, moveY, direction); - elem.x = p.x; - elem.y = p.y; - elem.w = p.w; - elem.h = p.h; - } else if (direction === 'rotate') { - const center = calcElementCenter(elem); - const radian = calcRadian(center, prevPoint, point); - elem.angle = (elem.angle || 0) + parseRadianToAngle(radian); - } - - this.limitElementAttrs(elem); - - return { - width: limitNum(elem.w), - height: limitNum(elem.h), - angle: limitAngle(elem.angle || 0) - }; - } - - getElementIndex(data: IDrawData, uuid: string): number { - let idx = -1; - for (let i = 0; i < data.elements.length; i++) { - if (data.elements[i].uuid === uuid) { - idx = i; - break; - } - } - return idx; - } - - limitElementAttrs(elem: DataElement) { - elem.x = limitNum(elem.x); - elem.y = limitNum(elem.y); - elem.w = limitNum(elem.w); - elem.h = limitNum(elem.h); - elem.angle = limitAngle(elem.angle || 0); - } -} - -function calcuScaleElemPosition( - elem: DataElement, - moveX: number, - moveY: number, - direction: HelperWrapperControllerDirection - // scale: number -): Point & { w: number; h: number } { - const p = { x: elem.x, y: elem.y, w: elem.w, h: elem.h }; - let angle = elem.angle || 0; - if (angle < 0) { - angle = Math.max(0, 360 + angle); - } - if (elem.operation?.limitRatio === true) { - if ( - ['top-left', 'top-right', 'bottom-right', 'bottom-left'].includes( - direction - ) - ) { - const maxDist = Math.max(Math.abs(moveX), Math.abs(moveY)); - moveX = (moveX >= 0 ? 1 : -1) * maxDist; - moveY = (((moveY >= 0 ? 1 : -1) * maxDist) / elem.w) * elem.h; - } - } - - switch (direction) { - case 'top-left': { - // TODO - - // if (elem.angle === 0) { - // // TODO - // } else if (elem.angle > 0 || elem.angle < 0) { - // // const angle = elem.angle > 0 ? elem.angle : Math.max(0, elem.angle + 360); - // if (angle < 90) { - // // TODO - // } else if (angle < 180) { - // // TODO - // } else if (angle < 270) { - // // TODO - // } else if (angle < 360) { - // // TODO - // } - // } else { - // // TODO - // } - - if (elem.w - moveX > 0 && elem.h - moveY > 0) { - p.x += moveX; - p.y += moveY; - p.w -= moveX; - p.h -= moveY; - } - - break; - } - case 'top': { - if (elem.angle === 0 || Math.abs(elem.angle || 0) < limitQbliqueAngle) { - if (p.h - moveY > 0) { - p.y += moveY; - p.h -= moveY; - if (elem.operation?.limitRatio === true) { - p.x += ((moveY / elem.h) * elem.w) / 2; - p.w -= (moveY / elem.h) * elem.w; - } - } - } else if ( - elem.angle !== undefined && - (elem.angle > 0 || elem.angle < 0) - ) { - const angle = - elem.angle > 0 ? elem.angle : Math.max(0, elem.angle + 360); - let moveDist = calcMoveDist(moveX, moveY); - let centerX = p.x + elem.w / 2; - let centerY = p.y + elem.h / 2; - if (angle < 90) { - moveDist = 0 - changeMoveDistDirect(moveDist, moveY); - const radian = parseRadian(angle); - const centerMoveDist = moveDist / 2; - centerX = centerX + centerMoveDist * Math.sin(radian); - centerY = centerY - centerMoveDist * Math.cos(radian); - } else if (angle < 180) { - moveDist = changeMoveDistDirect(moveDist, moveX); - const radian = parseRadian(angle - 90); - const centerMoveDist = moveDist / 2; - centerX = centerX + centerMoveDist * Math.cos(radian); - centerY = centerY + centerMoveDist * Math.sin(radian); - } else if (angle < 270) { - moveDist = changeMoveDistDirect(moveDist, moveY); - const radian = parseRadian(angle - 180); - const centerMoveDist = moveDist / 2; - centerX = centerX - centerMoveDist * Math.sin(radian); - centerY = centerY + centerMoveDist * Math.cos(radian); - } else if (angle < 360) { - moveDist = 0 - changeMoveDistDirect(moveDist, moveX); - const radian = parseRadian(angle - 270); - const centerMoveDist = moveDist / 2; - centerX = centerX - centerMoveDist * Math.cos(radian); - centerY = centerY - centerMoveDist * Math.sin(radian); - } - if (p.h + moveDist > 0) { - if (elem.operation?.limitRatio === true) { - p.w = p.w + (moveDist / elem.h) * elem.w; - } - p.h = p.h + moveDist; - p.x = centerX - p.w / 2; - p.y = centerY - p.h / 2; - } - } else { - if (p.h - moveY > 0) { - p.y += moveY; - p.h -= moveY; - if (elem.operation?.limitRatio === true) { - p.x -= moveX / 2; - p.w += moveX; - } - } - } - break; - } - case 'top-right': { - if (p.h - moveY > 0 && p.w + moveX > 0) { - p.y += moveY; - p.w += moveX; - p.h -= moveY; - } - // // TODO - // if (elem.angle === 0) { - // if (p.h - moveY > 0) { - // p.y += moveY; - // p.h -= moveY; - // } - // } else if (elem.angle > 0 || elem.angle < 0) { - // const angle = elem.angle > 0 ? elem.angle : Math.max(0, elem.angle + 360); - // let moveDist = calcMoveDist(moveX, moveY); - // let centerX = p.x + elem.w / 2; - // let centerY = p.y + elem.h / 2; - // let moveDistW: number = 0; - // let moveDistH: number = 0; - // if (angle < 90) { - // const radianDist = Math.atan(Math.tan(Math.abs(moveY)/Math.abs(moveX))) - // const radian = parseRadian(angle); - // const radianResult = radianDist + radian; - // moveDistH = moveDist * Math.sin(radianResult); - // moveDistW = moveDist * Math.cos(radianResult); - // moveDistH = 0 - changeMoveDistDirect(moveDistH, moveY); - // moveDistW = changeMoveDistDirect(moveDistW, moveX); - // { - // // top direct - // const radian = parseRadian(angle); - // const centerMoveDist = moveDistH / 2; - // centerX = centerX + centerMoveDist * Math.sin(radian); - // centerY = centerY - centerMoveDist * Math.cos(radian); - // } - // { - // // right direct - // const radian = parseRadian(angle); - // const centerMoveDist = moveDistW / 2; - // centerX = centerX + centerMoveDist * Math.cos(radian); - // centerY = centerY + centerMoveDist * Math.sin(radian); - // } - - // } else if (angle < 180) { - // const radianDist = Math.atan(Math.tan(Math.abs(moveX)/Math.abs(moveY))) - // const radian = parseRadian(angle); - // const radianResult = radianDist + radian; - // moveDistH = moveDist * Math.sin(radianResult); - // moveDistW = moveDist * Math.cos(radianResult); - // moveDistH = changeMoveDistDirect(moveDistH, moveY); - // moveDistW = changeMoveDistDirect(moveDistW, moveX); - // { - // // top direct - // const radian = parseRadian(angle - 90); - // const centerMoveDist = moveDistH / 2; - // centerX = centerX + centerMoveDist * Math.cos(radian); - // centerY = centerY + centerMoveDist * Math.sin(radian); - // } - // { - // // right direct TODO - // const radian = parseRadian(angle - 90); - // const centerMoveDist = moveDistW / 2; - // centerX = centerX - centerMoveDist * Math.sin(radian); - // centerY = centerY + centerMoveDist * Math.cos(radian); - // } - - // } else if (angle < 270) { - // // TODO - // } else if (angle < 360) { - // // TODO - // } - // if (p.h + moveDistH > 0 && p.w + moveDistW > 0) { - // p.h = p.h + moveDistH; - // // p.w = p.w + moveDistW; - // p.x = centerX - p.w / 2; - // p.y = centerY - p.h / 2; - // } - // } else { - // if (p.h - moveY > 0) { - // p.y += moveY; - // p.h -= moveY; - // } - // } - break; - } - case 'right': { - if (elem.angle === 0 || Math.abs(elem.angle || 0) < limitQbliqueAngle) { - if (elem.w + moveX > 0) { - p.w += moveX; - if (elem.operation?.limitRatio === true) { - p.y -= (moveX * elem.h) / elem.w / 2; - p.h += (moveX * elem.h) / elem.w; - } - } - } else if ( - elem.angle !== undefined && - (elem.angle > 0 || elem.angle < 0) - ) { - const angle = - elem.angle > 0 ? elem.angle : Math.max(0, elem.angle + 360); - let moveDist = calcMoveDist(moveX, moveY); - let centerX = p.x + elem.w / 2; - let centerY = p.y + elem.h / 2; - if (angle < 90) { - moveDist = changeMoveDistDirect(moveDist, moveY); - const radian = parseRadian(angle); - const centerMoveDist = moveDist / 2; - centerX = centerX + centerMoveDist * Math.cos(radian); - centerY = centerY + centerMoveDist * Math.sin(radian); - } else if (angle < 180) { - moveDist = changeMoveDistDirect(moveDist, moveY); - const radian = parseRadian(angle - 90); - const centerMoveDist = moveDist / 2; - centerX = centerX - centerMoveDist * Math.sin(radian); - centerY = centerY + centerMoveDist * Math.cos(radian); - } else if (angle < 270) { - moveDist = changeMoveDistDirect(moveDist, moveY); - const radian = parseRadian(angle - 180); - const centerMoveDist = moveDist / 2; - centerX = centerX + centerMoveDist * Math.cos(radian); - centerY = centerY + centerMoveDist * Math.sin(radian); - moveDist = 0 - moveDist; - } else if (angle < 360) { - moveDist = changeMoveDistDirect(moveDist, moveX); - const radian = parseRadian(angle - 270); - const centerMoveDist = moveDist / 2; - centerX = centerX + centerMoveDist * Math.sin(radian); - centerY = centerY - centerMoveDist * Math.cos(radian); - } - if (p.w + moveDist > 0) { - if (elem.operation?.limitRatio === true) { - p.h = p.h + (moveDist / elem.w) * elem.h; - } - p.w = p.w + moveDist; - p.x = centerX - p.w / 2; - p.y = centerY - p.h / 2; - } - } else { - if (elem.w + moveX > 0) { - p.w += moveX; - if (elem.operation?.limitRatio === true) { - p.h += (moveX * elem.h) / elem.w; - p.y -= (moveX * elem.h) / elem.w / 2; - } - } - } - break; - } - case 'bottom-right': { - // if (elem.angle === 0) { - // // TODO - // } else if (elem.angle > 0 || elem.angle < 0) { - // // const angle = elem.angle > 0 ? elem.angle : Math.max(0, elem.angle + 360); - // if (angle < 90) { - // // TODO - // } else if (angle < 180) { - // // TODO - // } else if (angle < 270) { - // // TODO - // } else if (angle < 360) { - // // TODO - // } - // } else { - // // TODO - // } - if (elem.w + moveX > 0 && elem.h + moveY > 0) { - p.w += moveX; - p.h += moveY; - } - break; - } - case 'bottom': { - if (elem.angle === 0 || Math.abs(elem.angle || 0) < limitQbliqueAngle) { - if (elem.h + moveY > 0) { - p.h += moveY; - if (elem.operation?.limitRatio === true) { - p.x -= ((moveY / elem.h) * elem.w) / 2; - p.w += (moveY / elem.h) * elem.w; - } - } - } else if ( - elem.angle !== undefined && - (elem.angle > 0 || elem.angle < 0) - ) { - const angle = - elem.angle > 0 ? elem.angle : Math.max(0, elem.angle + 360); - let moveDist = calcMoveDist(moveX, moveY); - let centerX = p.x + elem.w / 2; - let centerY = p.y + elem.h / 2; - if (angle < 90) { - moveDist = changeMoveDistDirect(moveDist, moveY); - const radian = parseRadian(angle); - const centerMoveDist = moveDist / 2; - centerX = centerX - centerMoveDist * Math.sin(radian); - centerY = centerY + centerMoveDist * Math.cos(radian); - } else if (angle < 180) { - moveDist = 0 - changeMoveDistDirect(moveDist, moveX); - const radian = parseRadian(angle - 90); - const centerMoveDist = moveDist / 2; - centerX = centerX - centerMoveDist * Math.cos(radian); - centerY = centerY - centerMoveDist * Math.sin(radian); - } else if (angle < 270) { - moveDist = changeMoveDistDirect(moveDist, moveX); - const radian = parseRadian(angle - 180); - const centerMoveDist = moveDist / 2; - centerX = centerX + centerMoveDist * Math.sin(radian); - centerY = centerY - centerMoveDist * Math.cos(radian); - } else if (angle < 360) { - moveDist = changeMoveDistDirect(moveDist, moveX); - const radian = parseRadian(angle - 270); - const centerMoveDist = moveDist / 2; - centerX = centerX + centerMoveDist * Math.cos(radian); - centerY = centerY + centerMoveDist * Math.sin(radian); - } - if (p.h + moveDist > 0) { - if (elem.operation?.limitRatio === true) { - p.w = p.w + (moveDist / elem.h) * elem.w; - } - p.h = p.h + moveDist; - p.x = centerX - p.w / 2; - p.y = centerY - p.h / 2; - } - } else { - if (elem.h + moveY > 0) { - p.h += moveY; - if (elem.operation?.limitRatio === true) { - p.x -= ((moveY / elem.h) * elem.w) / 2; - p.w += (moveY / elem.h) * elem.w; - } - } - } - break; - } - case 'bottom-left': { - // if (elem.angle === 0) { - // // TODO - // } else if (elem.angle > 0 || elem.angle < 0) { - // // const angle = elem.angle > 0 ? elem.angle : Math.max(0, elem.angle + 360); - // if (angle < 90) { - // // TODO - // } else if (angle < 180) { - // // TODO - // } else if (angle < 270) { - // // TODO - // } else if (angle < 360) { - // // TODO - // } - // } else { - // // TODO - // } - if (elem.w - moveX > 0 && elem.h + moveY > 0) { - p.x += moveX; - p.w -= moveX; - p.h += moveY; - } - break; - } - case 'left': { - if (elem.angle === 0 || Math.abs(elem.angle || 0) < limitQbliqueAngle) { - if (elem.w - moveX > 0) { - p.x += moveX; - p.w -= moveX; - if (elem.operation?.limitRatio === true) { - p.h -= (moveX / elem.w) * elem.h; - p.y += ((moveX / elem.w) * elem.h) / 2; - } - } - } else if ( - elem.angle !== undefined && - (elem.angle > 0 || elem.angle < 0) - ) { - const angle = - elem.angle > 0 ? elem.angle : Math.max(0, elem.angle + 360); - let moveDist = calcMoveDist(moveX, moveY); - let centerX = p.x + elem.w / 2; - let centerY = p.y + elem.h / 2; - if (angle < 90) { - moveDist = 0 - changeMoveDistDirect(moveDist, moveX); - const radian = parseRadian(angle); - const centerMoveDist = moveDist / 2; - centerX = centerX - centerMoveDist * Math.cos(radian); - centerY = centerY - centerMoveDist * Math.sin(radian); - } else if (angle < 180) { - moveDist = changeMoveDistDirect(moveDist, moveX); - const radian = parseRadian(angle - 90); - const centerMoveDist = moveDist / 2; - centerX = centerX + centerMoveDist * Math.sin(radian); - centerY = centerY - centerMoveDist * Math.cos(radian); - } else if (angle < 270) { - moveDist = changeMoveDistDirect(moveDist, moveY); - const radian = parseRadian(angle - 180); - const centerMoveDist = moveDist / 2; - centerX = centerX + centerMoveDist * Math.cos(radian); - centerY = centerY + centerMoveDist * Math.sin(radian); - } else if (angle < 360) { - moveDist = changeMoveDistDirect(moveDist, moveY); - const radian = parseRadian(angle - 270); - const centerMoveDist = moveDist / 2; - centerX = centerX - centerMoveDist * Math.sin(radian); - centerY = centerY + centerMoveDist * Math.cos(radian); - } - if (p.w + moveDist > 0) { - if (elem.operation?.limitRatio === true) { - p.h = p.h + (moveDist / elem.w) * elem.h; - } - p.w = p.w + moveDist; - p.x = centerX - p.w / 2; - p.y = centerY - p.h / 2; - } - } else { - if (elem.w - moveX > 0) { - p.x += moveX; - p.w -= moveX; - if (elem.operation?.limitRatio === true) { - p.h -= (moveX / elem.w) * elem.h; - p.y += ((moveX / elem.w) * elem.h) / 2; - } - } - } - break; - } - default: { - break; - } - } - return p; -} - -function parseRadian(angle: number) { - return (angle * Math.PI) / 180; -} - -function calcMoveDist(moveX: number, moveY: number) { - return Math.sqrt(moveX * moveX + moveY * moveY); -} - -function changeMoveDistDirect(moveDist: number, moveDirect: number) { - return moveDirect > 0 ? Math.abs(moveDist) : 0 - Math.abs(moveDist); -} diff --git a/packages/core/src/lib/engine-temp.ts b/packages/core/src/lib/engine-temp.ts deleted file mode 100644 index 26b2609..0000000 --- a/packages/core/src/lib/engine-temp.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { HelperWrapperControllerDirection, Point } from '@idraw/types'; -import { Mode, CursorStatus } from '../constant/static'; - -type TempDataDesc = { - hasInited: boolean; - mode: Mode; - cursorStatus: CursorStatus; - selectedUUID: string | null; - selectedUUIDList: string[]; - hoverUUID: string | null; - selectedControllerDirection: HelperWrapperControllerDirection | null; - hoverControllerDirection: HelperWrapperControllerDirection | null; - prevPoint: Point | null; - hasChangedElement: boolean; -}; - -function createData(): TempDataDesc { - return { - hasInited: false, - mode: Mode.NULL, - cursorStatus: CursorStatus.NULL, - selectedUUID: null, - selectedUUIDList: [], - hoverUUID: null, - selectedControllerDirection: null, - hoverControllerDirection: null, - prevPoint: null, - hasChangedElement: false - }; -} - -export class TempData { - private _temp: TempDataDesc; - - constructor() { - this._temp = createData(); - } - - set(name: T, value: TempDataDesc[T]) { - this._temp[name] = value; - } - - get(name: T): TempDataDesc[T] { - return this._temp[name]; - } - - clear() { - this._temp = createData(); - } -} diff --git a/packages/core/src/lib/engine.ts b/packages/core/src/lib/engine.ts deleted file mode 100644 index 5da64ef..0000000 --- a/packages/core/src/lib/engine.ts +++ /dev/null @@ -1,430 +0,0 @@ -import { - Point, - HelperWrapperControllerDirection, - InterfaceHelperPlugin, - IDrawConfigStrict, - IDrawData, - HelperConfig -} from '@idraw/types'; -import { deepClone, throttle } from '@idraw/util'; -import Board from '@idraw/board'; -import { Mode, CursorStatus } from './../constant/static'; -import { TempData } from './engine-temp'; -import { Helper } from './helper'; -import { Mapper } from './mapper'; -import { Element } from './element'; -import { CoreEvent } from './core-event'; - -type Options = { - coreEvent: CoreEvent; - board: Board; - element: Element; - config: IDrawConfigStrict; - drawFeekback: () => void; - getDataFeekback: () => IDrawData; - selectElementByIndex: (index: number, opts?: { useMode?: boolean }) => void; - emitChangeScreen: () => void; - emitChangeData: () => void; -}; - -export class Engine { - private _plugins: InterfaceHelperPlugin[] = []; - private _opts: Options; - private _mapper: Mapper; - - public temp: TempData; - public helper: Helper; - - constructor(opts: Options) { - const { board, config, element } = opts; - const helper = new Helper(board, config); - this._opts = opts; - this.temp = new TempData(); - this.helper = helper; - this._mapper = new Mapper({ board, helper, element }); - } - - addPlugin(plugin: InterfaceHelperPlugin) { - this._plugins.push(plugin); - } - - getHelperConfig(): HelperConfig { - return this.helper.getConfig(); - } - - updateHelperConfig(opts: { - width: number; - height: number; - devicePixelRatio: number; - }) { - const { board, getDataFeekback, config } = this._opts; - const data = getDataFeekback(); - const transform = board.getTransform(); - this.helper.updateConfig(data, { - width: opts.width, - height: opts.height, - devicePixelRatio: opts.devicePixelRatio, - canScroll: config?.scrollWrapper?.use === true, - selectedUUID: this.temp.get('selectedUUID'), - selectedUUIDList: this.temp.get('selectedUUIDList'), - scale: transform.scale, - scrollX: transform.scrollX, - scrollY: transform.scrollY - }); - } - - init() { - this._initEvent(); - } - - private _initEvent(): void { - if (this.temp.get('hasInited') === true) { - return; - } - const { board } = this._opts; - - board.on('hover', throttle(this._handleHover.bind(this), 32)); - board.on('leave', throttle(this._handleLeave.bind(this), 32)); - board.on('point', throttle(this._handleClick.bind(this), 16)); - board.on('doubleClick', this._handleDoubleClick.bind(this)); - board.on('point', this._handlePoint.bind(this)); - board.on('moveStart', this._handleMoveStart.bind(this)); - board.on('move', throttle(this._handleMove.bind(this), 16)); - board.on('moveEnd', this._handleMoveEnd.bind(this)); - } - - private _handleDoubleClick(point: Point) { - const { element, getDataFeekback, drawFeekback, coreEvent } = this._opts; - const data = getDataFeekback(); - const [index, uuid] = element.isPointInElement(point, data); - if (index >= 0 && uuid) { - const elem = deepClone(data.elements?.[index]); - if (elem?.operation?.invisible !== true) { - coreEvent.trigger('screenDoubleClickElement', { - index, - uuid, - element: deepClone(data.elements?.[index]) - }); - } - } - drawFeekback(); - } - - _handlePoint(point: Point): void { - if (!this._mapper.isEffectivePoint(point)) { - return; - } - const { - element, - getDataFeekback, - selectElementByIndex, - coreEvent, - emitChangeScreen, - drawFeekback - } = this._opts; - const helper = this.helper; - const data = getDataFeekback(); - if (helper.isPointInElementList(point, data)) { - // Coontroll Element-List - this.temp.set('mode', Mode.SELECT_ELEMENT_LIST); - } else { - const { uuid, selectedControllerDirection } = - helper.isPointInElementWrapperController(point, data); - if (uuid && selectedControllerDirection) { - // Controll Element-Wrapper - this.temp.set('mode', Mode.SELECT_ELEMENT_WRAPPER_CONTROLLER); - this.temp.set( - 'selectedControllerDirection', - selectedControllerDirection - ); - this.temp.set('selectedUUID', uuid); - } else { - const [index, uuid] = element.isPointInElement(point, data); - if (index >= 0 && data.elements[index]?.operation?.invisible !== true) { - // Controll Element - selectElementByIndex(index, { useMode: true }); - if ( - typeof uuid === 'string' && - coreEvent.has('screenSelectElement') - ) { - coreEvent.trigger('screenSelectElement', { - index, - uuid, - element: deepClone(data.elements?.[index]) - }); - emitChangeScreen(); - } - this.temp.set('mode', Mode.SELECT_ELEMENT); - } else { - // Controll Area - this.temp.set('selectedUUIDList', []); - this.temp.set('selectedUUID', null); - this.temp.set('mode', Mode.SELECT_AREA); - } - } - } - drawFeekback(); - } - - private _handleClick(point: Point): void { - const { element, getDataFeekback, coreEvent, drawFeekback } = this._opts; - const data = getDataFeekback(); - const [index, uuid] = element.isPointInElement(point, data); - if (index >= 0 && uuid) { - coreEvent.trigger('screenClickElement', { - index, - uuid, - element: deepClone(data.elements?.[index]) - }); - } - drawFeekback(); - } - - private _handleMoveStart(point: Point): void { - const { element, getDataFeekback, coreEvent } = this._opts; - const data = getDataFeekback(); - const helper = this.helper; - - this.temp.set('prevPoint', point); - const uuid = this.temp.get('selectedUUID'); - - if (this.temp.get('mode') === Mode.SELECT_ELEMENT_LIST) { - // TODO - } else if (this.temp.get('mode') === Mode.SELECT_ELEMENT) { - if (typeof uuid === 'string' && coreEvent.has('screenMoveElementStart')) { - coreEvent.trigger('screenMoveElementStart', { - index: element.getElementIndex(data, uuid), - uuid, - x: point.x, - y: point.y - }); - } - } else if (this.temp.get('mode') === Mode.SELECT_AREA) { - helper.startSelectArea(point); - } - } - - private _handleMove(point: Point): void { - const { drawFeekback } = this._opts; - const helper = this.helper; - if (this.temp.get('mode') === Mode.SELECT_ELEMENT_LIST) { - this.temp.set('hasChangedElement', true); - this._dragElements( - this.temp.get('selectedUUIDList'), - point, - this.temp.get('prevPoint') - ); - drawFeekback(); - this.temp.set('cursorStatus', CursorStatus.DRAGGING); - } else if (typeof this.temp.get('selectedUUID') === 'string') { - if (this.temp.get('mode') === Mode.SELECT_ELEMENT) { - this.temp.set('hasChangedElement', true); - this._dragElements( - [this.temp.get('selectedUUID') as string], - point, - this.temp.get('prevPoint') - ); - drawFeekback(); - this.temp.set('cursorStatus', CursorStatus.DRAGGING); - } else if ( - this.temp.get('mode') === Mode.SELECT_ELEMENT_WRAPPER_CONTROLLER && - this.temp.get('selectedControllerDirection') - ) { - this._transfromElement( - this.temp.get('selectedUUID') as string, - point, - this.temp.get('prevPoint'), - this.temp.get( - 'selectedControllerDirection' - ) as HelperWrapperControllerDirection - ); - this.temp.set('cursorStatus', CursorStatus.DRAGGING); - } - } else if (this.temp.get('mode') === Mode.SELECT_AREA) { - helper.changeSelectArea(point); - drawFeekback(); - } - this.temp.set('prevPoint', point); - } - - private _dragElements( - uuids: string[], - point: Point, - prevPoint: Point | null - ): void { - if (!prevPoint) { - return; - } - const { board, element, getDataFeekback, drawFeekback } = this._opts; - const data = getDataFeekback(); - const helper = this.helper; - uuids.forEach((uuid) => { - const idx = helper.getElementIndexByUUID(uuid); - if (idx === null) return; - const elem = data.elements[idx]; - if ( - elem?.operation?.lock !== true && - elem?.operation?.invisible !== true - ) { - element.dragElement( - data, - uuid, - point, - prevPoint, - board.getContext().getTransform().scale - ); - } - }); - drawFeekback(); - } - - private _transfromElement( - uuid: string, - point: Point, - prevPoint: Point | null, - direction: HelperWrapperControllerDirection - ): null | { width: number; height: number; angle: number } { - if (!prevPoint) { - return null; - } - const { board, element, getDataFeekback, drawFeekback } = this._opts; - const data = getDataFeekback(); - const result = element.transformElement( - data, - uuid, - point, - prevPoint, - board.getContext().getTransform().scale, - direction - ); - drawFeekback(); - return result; - } - - private _handleMoveEnd(point: Point): void { - const { - element, - getDataFeekback, - coreEvent, - drawFeekback, - emitChangeData - } = this._opts; - const data = getDataFeekback(); - const helper = this.helper; - - const uuid = this.temp.get('selectedUUID'); - if (typeof uuid === 'string') { - const index = element.getElementIndex(data, uuid); - const elem = data.elements[index]; - if (elem) { - if (coreEvent.has('screenMoveElementEnd')) { - coreEvent.trigger('screenMoveElementEnd', { - index, - uuid, - x: point.x, - y: point.y - }); - } - if (coreEvent.has('screenChangeElement')) { - coreEvent.trigger('screenChangeElement', { - index, - uuid, - width: elem.w, - height: elem.h, - angle: elem.angle || 0 - }); - } - } - } else if (this.temp.get('mode') === Mode.SELECT_AREA) { - const uuids = helper.calcSelectedElements(data); - if (uuids.length > 0) { - this.temp.set('selectedUUIDList', uuids); - this.temp.set('selectedUUID', null); - } else { - this.temp.set('mode', Mode.NULL); - } - helper.clearSelectedArea(); - drawFeekback(); - } - - if (this.temp.get('mode') !== Mode.SELECT_ELEMENT) { - this.temp.set('selectedUUID', null); - } - this.temp.set('cursorStatus', CursorStatus.NULL); - this.temp.set('mode', Mode.NULL); - - if (this.temp.get('hasChangedElement') === true) { - emitChangeData(); - this.temp.set('hasChangedElement', false); - } - } - - private _handleHover(point: Point): void { - let isMouseOverElement = false; - const { board, getDataFeekback, coreEvent } = this._opts; - const data = getDataFeekback(); - const helper = this.helper; - const mapper = this._mapper; - - if (this.temp.get('mode') === Mode.SELECT_AREA) { - board.resetCursor(); - } else if (this.temp.get('cursorStatus') === CursorStatus.NULL) { - const { cursor, elementUUID } = mapper.judgePointCursor(point, data); - board.setCursor(cursor); - if (elementUUID) { - const index: number | null = helper.getElementIndexByUUID(elementUUID); - if (index !== null && index >= 0) { - const elem = data.elements[index]; - if ( - elem?.operation?.lock === true || - elem?.operation?.invisible === true - ) { - board.resetCursor(); - return; - } - if (this.temp.get('hoverUUID') !== elem.uuid) { - const preIndex = helper.getElementIndexByUUID( - this.temp.get('hoverUUID') || '' - ); - if (preIndex !== null && data.elements[preIndex]) { - coreEvent.trigger('mouseLeaveElement', { - uuid: this.temp.get('hoverUUID'), - index: preIndex, - element: data.elements[preIndex] - }); - } - } - if (elem) { - coreEvent.trigger('mouseOverElement', { - uuid: elem.uuid, - index, - element: elem - }); - this.temp.set('hoverUUID', elem.uuid); - isMouseOverElement = true; - } - } - } - } - if (isMouseOverElement !== true && this.temp.get('hoverUUID') !== null) { - const uuid = this.temp.get('hoverUUID'); - const index: number | null = helper.getElementIndexByUUID(uuid || ''); - if (index !== null) - coreEvent.trigger('mouseLeaveElement', { - uuid, - index, - element: data.elements[index] - }); - this.temp.set('hoverUUID', null); - } - if (coreEvent.has('mouseOverScreen')) - coreEvent.trigger('mouseOverScreen', point); - } - - private _handleLeave(): void { - const { coreEvent } = this._opts; - if (coreEvent.has('mouseLeaveScreen')) { - coreEvent.trigger('mouseLeaveScreen', undefined); - } - } -} diff --git a/packages/core/src/lib/helper.ts b/packages/core/src/lib/helper.ts deleted file mode 100644 index cf152a0..0000000 --- a/packages/core/src/lib/helper.ts +++ /dev/null @@ -1,445 +0,0 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment */ -import { - IDrawData, - HelperConfig, - HelperUpdateOpts, - HelperWrapperControllerDirection, - DataElement, - DataElemDesc, - IDrawContext, - Point, - IDrawConfigStrict, - HeplerSelectedElementWrapper -} from '@idraw/types'; -import Board from '@idraw/board'; -import { deepClone } from '@idraw/util'; -import { parseAngleToRadian, calcElementCenter } from './calculate'; -import { rotateContext, rotateElement } from './transform'; -import { LIMIT_QBLIQUE_ANGLE } from './../constant/element'; - -const limitQbliqueAngle = LIMIT_QBLIQUE_ANGLE; - -export class Helper { - private _helperConfig: HelperConfig; - private _coreConfig: IDrawConfigStrict; - private _ctx: IDrawContext; - private _board: Board; - private _areaStart: Point = { x: 0, y: 0 }; - private _areaEnd: Point = { x: 0, y: 0 }; - - constructor(board: Board, config: IDrawConfigStrict) { - this._board = board; - this._ctx = this._board.getContext(); - this._coreConfig = config; - this._helperConfig = { - elementIndexMap: {} - }; - } - - updateConfig(data: IDrawData, opts: HelperUpdateOpts): void { - this._updateElementIndex(data); - this._updateSelectedElementWrapper(data, opts); - this._updateSelectedElementListWrapper(data, opts); - } - - getConfig(): HelperConfig { - return deepClone(this._helperConfig); - } - - getElementIndexByUUID(uuid: string): number | null { - const index = this._helperConfig.elementIndexMap[uuid]; - if (index >= 0) { - return index; - } - return null; - } - - isPointInElementWrapperController( - p: Point, - data?: IDrawData - ): { - uuid: string | null | undefined; - selectedControllerDirection: HelperWrapperControllerDirection | null; - hoverControllerDirection: HelperWrapperControllerDirection | null; - directIndex: number | null; - } { - const ctx = this._ctx; - const uuid = this._helperConfig?.selectedElementWrapper?.uuid || null; - let directIndex = null; - let selectedControllerDirection: HelperWrapperControllerDirection | null = - null; - let hoverControllerDirection: HelperWrapperControllerDirection | null = - null; - if (!this._helperConfig.selectedElementWrapper) { - return { - uuid, - selectedControllerDirection, - directIndex, - hoverControllerDirection - }; - } - const wrapper = this._helperConfig.selectedElementWrapper; - const controllers = [ - wrapper.controllers.right, - wrapper.controllers.topRight, - wrapper.controllers.top, - wrapper.controllers.topLeft, - wrapper.controllers.left, - wrapper.controllers.bottomLeft, - wrapper.controllers.bottom, - wrapper.controllers.bottomRight - ]; - const directionNames: HelperWrapperControllerDirection[] = [ - 'right', - 'top-right', - 'top', - 'top-left', - 'left', - 'bottom-left', - 'bottom', - 'bottom-right' - ]; - let hoverDirectionNames = deepClone(directionNames); - - let angleMoveNum = 0; - if (data && uuid) { - const elemIdx = this.getElementIndexByUUID(uuid); - if (elemIdx !== null && elemIdx >= 0) { - const elem = data.elements[elemIdx]; - let angle = elem.angle || 0; - if (angle < 0) { - angle += 360; - } - if (angle < 45) { - angleMoveNum = 0; - } else if (angle < 90) { - angleMoveNum = 1; - } else if (angle < 135) { - angleMoveNum = 2; - } else if (angle < 180) { - angleMoveNum = 3; - } else if (angle < 225) { - angleMoveNum = 4; - } else if (angle < 270) { - angleMoveNum = 5; - } else if (angle < 315) { - angleMoveNum = 6; - } - } - } - if (angleMoveNum > 0) { - hoverDirectionNames = hoverDirectionNames - .slice(-angleMoveNum) - .concat(hoverDirectionNames.slice(0, -angleMoveNum)); - } - - rotateContext(ctx, wrapper.translate, wrapper.radian || 0, () => { - for (let i = 0; i < controllers.length; i++) { - const controller = controllers[i]; - if (controller.invisible === true) { - continue; - } - - ctx.beginPath(); - ctx.arc( - controller.x, - controller.y, - wrapper.controllerSize, - 0, - Math.PI * 2 - ); - ctx.closePath(); - - if (ctx.isPointInPath(p.x, p.y)) { - selectedControllerDirection = directionNames[i]; - hoverControllerDirection = hoverDirectionNames[i]; - } - if (selectedControllerDirection) { - directIndex = i; - break; - } - } - }); - - if (selectedControllerDirection === null) { - const controller = wrapper.controllers.rotate; - if (controller.invisible !== true) { - rotateContext(ctx, wrapper.translate, wrapper.radian || 0, () => { - ctx.beginPath(); - ctx.arc( - controller.x, - controller.y, - wrapper.controllerSize, - 0, - Math.PI * 2 - ); - ctx.closePath(); - if (ctx.isPointInPath(p.x, p.y)) { - selectedControllerDirection = 'rotate'; - hoverControllerDirection = 'rotate'; - } - }); - } - } - - return { - uuid, - selectedControllerDirection, - hoverControllerDirection, - directIndex - }; - } - - isPointInElementList(p: Point, data: IDrawData): boolean { - const ctx = this._ctx; - let idx = -1; - let uuid = null; - const wrapperList = this._helperConfig?.selectedElementListWrappers || []; - for (let i = 0; i < wrapperList.length; i++) { - const wrapper = wrapperList[i]; - const elemIdx = this._helperConfig.elementIndexMap[wrapper.uuid]; - const ele = data.elements[elemIdx]; - if (!ele) continue; - if (ele.operation?.invisible === true) continue; - let bw = 0; - // @ts-ignore - if (ele.desc?.borderWidth > 0) { - // @ts-ignore - bw = ele.desc.borderWidth; - } - rotateElement(ctx, ele, () => { - ctx.beginPath(); - ctx.moveTo(ele.x - bw, ele.y - bw); - ctx.lineTo(ele.x + ele.w + bw, ele.y - bw); - ctx.lineTo(ele.x + ele.w + bw, ele.y + ele.h + bw); - ctx.lineTo(ele.x - bw, ele.y + ele.h + bw); - ctx.lineTo(ele.x - bw, ele.y - bw); - ctx.closePath(); - if (ctx.isPointInPath(p.x, p.y)) { - idx = i; - uuid = ele.uuid; - } - }); - if (idx >= 0) { - break; - } - } - if (uuid && idx >= 0) { - return true; - } else { - return false; - } - } - - startSelectArea(p: Point) { - this._areaStart = p; - this._areaEnd = p; - } - - changeSelectArea(p: Point) { - this._areaEnd = p; - this._calcSelectedArea(); - } - - clearSelectedArea() { - this._areaStart = { x: 0, y: 0 }; - this._areaEnd = { x: 0, y: 0 }; - this._calcSelectedArea(); - } - - calcSelectedElements(data: IDrawData) { - const transform = this._ctx.getTransform(); - const { scale = 1, scrollX = 0, scrollY = 0 } = transform; - const start = this._areaStart; - const end = this._areaEnd; - const x = (Math.min(start.x, end.x) - scrollX) / scale; - const y = (Math.min(start.y, end.y) - scrollY) / scale; - const w = Math.abs(end.x - start.x) / scale; - const h = Math.abs(end.y - start.y) / scale; - const uuids: string[] = []; - const ctx = this._ctx; - - ctx.beginPath(); - ctx.moveTo(x, y); - ctx.lineTo(x + w, y); - ctx.lineTo(x + w, y + h); - ctx.lineTo(x, y + h); - ctx.lineTo(x, y); - // ctx.rect(x, y, w, h); - ctx.closePath(); - data.elements.forEach((elem) => { - if (elem?.operation?.invisible !== true) { - const centerX = elem.x + elem.w / 2; - const centerY = elem.y + elem.h / 2; - if (ctx.isPointInPathWithoutScroll(centerX, centerY)) { - uuids.push(elem.uuid); - } - } - }); - return uuids; - } - - private _calcSelectedArea() { - const start = this._areaStart; - const end = this._areaEnd; - - const transform = this._ctx.getTransform(); - const { scale = 1, scrollX = 0, scrollY = 0 } = transform; - const elemWrapper = this._coreConfig.elementWrapper; - const lineWidth = elemWrapper.lineWidth / scale; - const lineDash = elemWrapper.lineDash.map((n) => n / scale); - - this._helperConfig.selectedAreaWrapper = { - x: (Math.min(start.x, end.x) - scrollX) / scale, - y: (Math.min(start.y, end.y) - scrollY) / scale, - w: Math.abs(end.x - start.x) / scale, - h: Math.abs(end.y - start.y) / scale, - startPoint: { x: start.x, y: start.y }, - endPoint: { x: end.x, y: end.y }, - lineWidth: lineWidth, - lineDash: lineDash, - color: elemWrapper.color - }; - } - - private _updateElementIndex(data: IDrawData) { - this._helperConfig.elementIndexMap = {}; - data.elements.forEach((elem: DataElement, i) => { - this._helperConfig.elementIndexMap[elem.uuid] = i; - }); - } - - private _updateSelectedElementWrapper( - data: IDrawData, - opts: HelperUpdateOpts - ) { - const { selectedUUID: uuid } = opts; - if ( - !( - typeof uuid === 'string' && - this._helperConfig.elementIndexMap[uuid] >= 0 - ) - ) { - delete this._helperConfig.selectedElementWrapper; - return; - } - const index: number = this._helperConfig.elementIndexMap[uuid]; - const elem = data.elements[index]; - if (elem?.operation?.invisible === true) { - return; - } - const wrapper = this._createSelectedElementWrapper(elem, opts); - this._helperConfig.selectedElementWrapper = wrapper; - } - - private _updateSelectedElementListWrapper( - data: IDrawData, - opts: HelperUpdateOpts - ) { - const { selectedUUIDList } = opts; - const wrapperList: HeplerSelectedElementWrapper[] = []; - data.elements.forEach((elem) => { - if (selectedUUIDList?.includes(elem.uuid)) { - const wrapper = this._createSelectedElementWrapper(elem, opts); - wrapperList.push(wrapper); - } - }); - this._helperConfig.selectedElementListWrappers = wrapperList; - } - - private _createSelectedElementWrapper( - elem: DataElement, - opts: HelperUpdateOpts - ): HeplerSelectedElementWrapper { - const { scale } = opts; - const elemWrapper = this._coreConfig.elementWrapper; - const controllerSize = elemWrapper.controllerSize / scale; - const lineWidth = elemWrapper.lineWidth / scale; - const lineDash = elemWrapper.lineDash.map((n) => n / scale); - - const rotateLimit = 12; - // @ts-ignore - const bw = elem.desc?.borderWidth || 0; - let hideObliqueDirection = false; - if ( - typeof elem.angle === 'number' && - Math.abs(elem.angle) > limitQbliqueAngle - ) { - hideObliqueDirection = true; - } - // TODO - // const controllerOffset = controllerSize; - const controllerOffset = lineWidth; - - const wrapper: HeplerSelectedElementWrapper = { - uuid: elem.uuid, - controllerSize: controllerSize, - controllerOffset: controllerOffset, - lock: elem?.operation?.lock === true, - controllers: { - topLeft: { - x: elem.x - controllerOffset - bw, - y: elem.y - controllerOffset - bw, - invisible: - hideObliqueDirection || elem?.operation?.disableScale === true - }, - top: { - x: elem.x + elem.w / 2, - y: elem.y - controllerOffset - bw, - invisible: elem?.operation?.disableScale === true - }, - topRight: { - x: elem.x + elem.w + controllerOffset + bw, - y: elem.y - controllerOffset - bw, - invisible: - hideObliqueDirection || elem?.operation?.disableScale === true - }, - right: { - x: elem.x + elem.w + controllerOffset + bw, - y: elem.y + elem.h / 2, - invisible: elem?.operation?.disableScale === true - }, - bottomRight: { - x: elem.x + elem.w + controllerOffset + bw, - y: elem.y + elem.h + controllerOffset + bw, - invisible: - hideObliqueDirection || elem?.operation?.disableScale === true - }, - bottom: { - x: elem.x + elem.w / 2, - y: elem.y + elem.h + controllerOffset + bw, - invisible: elem?.operation?.disableScale === true - }, - bottomLeft: { - x: elem.x - controllerOffset - bw, - y: elem.y + elem.h + controllerOffset + bw, - invisible: - hideObliqueDirection || elem?.operation?.disableScale === true - }, - left: { - x: elem.x - controllerOffset - bw, - y: elem.y + elem.h / 2, - invisible: elem?.operation?.disableScale === true - }, - rotate: { - x: elem.x + elem.w / 2, - y: elem.y - controllerSize - (controllerSize * 2 + rotateLimit) - bw, - invisible: elem?.operation?.disableRotate === true - } - }, - lineWidth: lineWidth, - lineDash: lineDash, - color: - elem?.operation?.lock === true - ? elemWrapper.lockColor - : elemWrapper.color - }; - - if (typeof elem.angle === 'number' && (elem.angle > 0 || elem.angle < 0)) { - wrapper.radian = parseAngleToRadian(elem.angle); - wrapper.translate = calcElementCenter(elem); - } - - return wrapper; - } -} diff --git a/packages/core/src/lib/index.ts b/packages/core/src/lib/index.ts deleted file mode 100644 index d722226..0000000 --- a/packages/core/src/lib/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -export * from './calculate'; -export * from './check'; -export * from './config'; -export * from './core-event'; -export * from './diff'; -export * from './element'; -export * from './helper'; -export * from './is'; -export * from './mapper'; -export * from './parse'; -export * from './temp'; -export * from './transform'; -export * from './value'; diff --git a/packages/core/src/lib/is.ts b/packages/core/src/lib/is.ts deleted file mode 100644 index 578f798..0000000 --- a/packages/core/src/lib/is.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { isColorStr } from '@idraw/util'; - -function number(value: any) { - return typeof value === 'number' && (value > 0 || value <= 0); -} - -function x(value: any) { - return number(value); -} - -function y(value: any) { - return number(value); -} - -function w(value: any) { - return typeof value === 'number' && value >= 0; -} - -function h(value: any) { - return typeof value === 'number' && value >= 0; -} - -function angle(value: any) { - return typeof value === 'number' && value >= -360 && value <= 360; -} - -function borderWidth(value: any) { - return w(value); -} - -function borderRadius(value: any) { - return number(value) && value >= 0; -} - -function color(value: any) { - return isColorStr(value); -} - -function imageURL(value: any) { - return ( - typeof value === 'string' && - /^(http:\/\/|https:\/\/|\.\/|\/)/.test(`${value}`) - ); -} - -function imageBase64(value: any) { - return typeof value === 'string' && /^(data:image\/)/.test(`${value}`); -} - -function imageSrc(value: any) { - return imageBase64(value) || imageURL(value); -} - -function svg(value: any) { - return ( - typeof value === 'string' && - /^()/i.test(`${value}`.trim()) && - /<\/[\s]{0,}svg>$/i.test(`${value}`.trim()) - ); -} - -function html(value: any) { - let result = false; - if (typeof value === 'string') { - let div: null | HTMLDivElement = document.createElement('div'); - div.innerHTML = value; - if (div.children.length > 0) { - result = true; - } - div = null; - } - return result; -} - -function text(value: any) { - return typeof value === 'string'; -} - -function fontSize(value: any) { - return number(value) && value > 0; -} - -function lineHeight(value: any) { - return number(value) && value > 0; -} - -function strokeWidth(value: any) { - return number(value) && value > 0; -} - -function textAlign(value: any) { - return ['center', 'left', 'right'].includes(value); -} - -function fontFamily(value: any) { - return typeof value === 'string' && value.length > 0; -} - -function fontWeight(value: any) { - return ['bold'].includes(value); -} - -const is: IsTypeUtil = { - x, - y, - w, - h, - angle, - number, - borderWidth, - borderRadius, - color, - imageSrc, - imageURL, - imageBase64, - svg, - html, - text, - fontSize, - lineHeight, - textAlign, - fontFamily, - fontWeight, - strokeWidth -}; - -type IsTypeUtil = { - x: (value: any) => boolean; - y: (value: any) => boolean; - w: (value: any) => boolean; - h: (value: any) => boolean; - angle: (value: any) => boolean; - number: (value: any) => boolean; - borderWidth: (value: any) => boolean; - borderRadius: (value: any) => boolean; - color: (value: any) => boolean; - imageSrc: (value: any) => boolean; - imageURL: (value: any) => boolean; - imageBase64: (value: any) => boolean; - svg: (value: any) => boolean; - html: (value: any) => boolean; - text: (value: any) => boolean; - fontSize: (value: any) => boolean; - fontWeight: (value: any) => boolean; - lineHeight: (value: any) => boolean; - textAlign: (value: any) => boolean; - fontFamily: (value: any) => boolean; - strokeWidth: (value: any) => boolean; -}; - -export default is; - -export { IsTypeUtil }; diff --git a/packages/core/src/lib/mapper.ts b/packages/core/src/lib/mapper.ts deleted file mode 100644 index 812b450..0000000 --- a/packages/core/src/lib/mapper.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { IDrawData, Point, PointCursor } from '@idraw/types'; -import Board from '@idraw/board'; -import { Helper } from './helper'; -import { Element } from './element'; - -const _board = Symbol('_displayCtx'); -const _helper = Symbol('_helper'); -const _element = Symbol('_element'); -const _opts = Symbol('_opts'); - -type Options = { - board: Board; - element: Element; - helper: Helper; -}; - -export class Mapper { - private [_opts]: Options; - private [_board]: Board; - private [_helper]: Helper; - private [_element]: Element; - - constructor(opts: Options) { - this[_opts] = opts; - this[_board] = this[_opts].board; - this[_element] = this[_opts].element; - this[_helper] = this[_opts].helper; - } - - isEffectivePoint(p: Point): boolean { - const scrollLineWidth = this[_board].getScrollLineWidth(); - const screenInfo = this[_board].getScreenInfo(); - if ( - p.x <= screenInfo.width - scrollLineWidth && - p.y <= screenInfo.height - scrollLineWidth - ) { - return true; - } - return false; - } - - judgePointCursor( - p: Point, - data: IDrawData - ): { - cursor: PointCursor; - elementUUID: string | null; - } { - let cursor: PointCursor = 'auto'; - let elementUUID: string | null = null; - if (!this.isEffectivePoint(p)) { - return { cursor, elementUUID }; - } - const { uuid, hoverControllerDirection } = this[ - _helper - ].isPointInElementWrapperController(p, data); - const direction = hoverControllerDirection; - if (uuid && direction) { - switch (direction) { - case 'top-right': { - cursor = 'ne-resize'; - break; - } - - case 'top-left': { - cursor = 'nw-resize'; - break; - } - case 'top': { - cursor = 'n-resize'; - break; - } - - case 'right': { - cursor = 'e-resize'; - break; - } - case 'bottom-right': { - cursor = 'se-resize'; - break; - } - case 'bottom': { - cursor = 's-resize'; - break; - } - case 'bottom-left': { - cursor = 'sw-resize'; - break; - } - case 'left': { - cursor = 'w-resize'; - break; - } - case 'rotate': { - cursor = 'grab'; - break; - } - default: { - break; - } - } - if (uuid) { - elementUUID = uuid; - } - } else { - const [index, uuid] = this[_element].isPointInElement(p, data); - if (index >= 0) { - cursor = 'move'; - } - if (uuid) { - elementUUID = uuid; - } - } - return { - cursor, - elementUUID - }; - } -} diff --git a/packages/core/src/lib/parse.ts b/packages/core/src/lib/parse.ts deleted file mode 100644 index 3cdcbae..0000000 --- a/packages/core/src/lib/parse.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { IDrawData, DataElement, DataElemDesc } from '@idraw/types'; -import { elementNames } from './../constant/element'; - -export function parseData(data: any): IDrawData { - const result: IDrawData = { - elements: [] - }; - if (Array.isArray(data?.elements)) { - data?.elements.forEach((elem: any = {}) => { - if (isElement(elem)) { - result.elements.push(elem); - } - }); - } - if (typeof data.bgColor === 'string') { - result.bgColor = data.bgColor; - } - return result; -} - -function isElement(elem: DataElement): boolean { - if ( - !( - isNumber(elem.x) && - isNumber(elem.y) && - isNumber(elem.w) && - isNumber(elem.h) - ) - ) { - return false; - } - if (!(typeof elem.type === 'string' && elementNames.includes(elem.type))) { - return false; - } - return true; -} - -function isNumber(num: any) { - return num >= 0 || num < 0; -} diff --git a/packages/core/src/lib/temp.ts b/packages/core/src/lib/temp.ts deleted file mode 100644 index 9da158b..0000000 --- a/packages/core/src/lib/temp.ts +++ /dev/null @@ -1,31 +0,0 @@ -type TempDataDesc = { - hasInited: boolean; -} - -function createData(): TempDataDesc { - return { - hasInited: false, - } -} - - -export class TempData { - - private _temp: TempDataDesc - - constructor() { - this._temp = createData(); - } - - set(name: T, value: TempDataDesc[T]) { - this._temp[name] = value; - } - - get(name: T): TempDataDesc[T] { - return this._temp[name]; - } - - clear() { - this._temp = createData(); - } -} \ No newline at end of file diff --git a/packages/core/src/lib/transform.ts b/packages/core/src/lib/transform.ts deleted file mode 100644 index 7cfdfbd..0000000 --- a/packages/core/src/lib/transform.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { IDrawContext, Point, DataElement, DataElemDesc } from '@idraw/types'; -import { calcElementCenter, parseAngleToRadian } from './calculate'; - -function rotateElement( - ctx: IDrawContext, - elem: DataElement, - callback: (ctx: IDrawContext) => void -): void { - const center: Point = calcElementCenter(elem); - const radian = parseAngleToRadian(elem.angle || 0); - return rotateContext(ctx, center, radian || 0, callback); -} - -function rotateContext( - ctx: IDrawContext, - center: Point | undefined, - radian: number, - callback: (ctx: IDrawContext) => void -): void { - if (center && (radian > 0 || radian < 0)) { - ctx.translate(center.x, center.y); - ctx.rotate(radian); - ctx.translate(-center.x, -center.y); - } - - callback(ctx); - - if (center && (radian > 0 || radian < 0)) { - ctx.translate(center.x, center.y); - ctx.rotate(-radian); - ctx.translate(-center.x, -center.y); - } -} - -export { rotateContext, rotateElement }; diff --git a/packages/core/src/lib/value.ts b/packages/core/src/lib/value.ts deleted file mode 100644 index c224a9b..0000000 --- a/packages/core/src/lib/value.ts +++ /dev/null @@ -1,9 +0,0 @@ - -export function limitNum(num: number): number { - const numStr: string = num.toFixed(2); - return parseFloat(numStr); -} - -export function limitAngle(angle: number): number { - return limitNum(angle % 360); -} diff --git a/packages/core/src/middleware/select/draw-wrapper.ts b/packages/core/src/middleware/select/draw-wrapper.ts new file mode 100644 index 0000000..b470b41 --- /dev/null +++ b/packages/core/src/middleware/select/draw-wrapper.ts @@ -0,0 +1,52 @@ +import type { Element, ElementType, RendererDrawElementOptions } from '@idraw/types'; + +export function drawPointWrapper(ctx: CanvasRenderingContext2D, elem: Element, opts?: Omit) { + const bw = 2; + let { x, y, w, h } = elem; + + if (opts?.calculator) { + const { calculator } = opts; + const size = calculator.elementSize({ x, y, w, h }, opts); + x = size.x; + y = size.y; + w = size.w; + h = size.h; + } + + ctx.setLineDash([4, 4]); + ctx.lineWidth = 2; + ctx.strokeStyle = '#e91e2f'; + + ctx.beginPath(); + ctx.moveTo(x - bw, y - bw); + ctx.lineTo(x + w + bw, y - bw); + ctx.lineTo(x + w + bw, y + h + bw); + ctx.lineTo(x - bw, y + h + bw); + ctx.lineTo(x - bw, y - bw); + ctx.closePath(); + ctx.stroke(); +} + +export function drawHoverWrapper(ctx: CanvasRenderingContext2D, elem: Element, opts?: Omit) { + const bw = 2; + let { x, y, w, h } = elem; + if (opts?.calculator) { + const { calculator } = opts; + const size = calculator.elementSize({ x, y, w, h }, opts); + x = size.x; + y = size.y; + w = size.w; + h = size.h; + } + ctx.setLineDash([]); + ctx.lineWidth = 2; + ctx.strokeStyle = '#e91e2f'; + ctx.beginPath(); + ctx.moveTo(x - bw, y - bw); + ctx.lineTo(x + w + bw, y - bw); + ctx.lineTo(x + w + bw, y + h + bw); + ctx.lineTo(x - bw, y + h + bw); + ctx.lineTo(x - bw, y - bw); + ctx.closePath(); + ctx.stroke(); +} diff --git a/packages/core/src/middleware/select/index.ts b/packages/core/src/middleware/select/index.ts new file mode 100644 index 0000000..ed41aef --- /dev/null +++ b/packages/core/src/middleware/select/index.ts @@ -0,0 +1,112 @@ +import type { Point, PointWatcherEvent, BoardMiddleware } from '@idraw/types'; +import { createBoardContexts } from '@idraw/util'; +import { drawPointWrapper, drawHoverWrapper } from './draw-wrapper'; + +export const MiddlewareSelector: BoardMiddleware = (opts) => { + const { viewer, sharer, viewContent, calculator } = opts; + const { helperContext } = viewContent; + + const key = 'SELECT'; + const keyHoverElementSize = `${key}_hoverElementSize`; + const keySelectType = `${key}_type`; // 'default' | 'hover' | 'drag' + + const getIndex = () => { + const idx = sharer.getActiveStorage('selectedIndexs')[0]; + return idx >= 0 ? idx : -1; + }; + + const getScaleInfo = () => { + return { + scale: sharer.getActiveStorage('scale'), + offsetLeft: sharer.getActiveStorage('offsetLeft'), + offsetRight: sharer.getActiveStorage('offsetRight'), + offsetTop: sharer.getActiveStorage('offsetTop'), + offsetBottom: sharer.getActiveStorage('offsetBottom') + }; + }; + + const getActiveElem = () => { + const index = getIndex(); + const storeData = sharer.getActiveStorage('data'); + return storeData?.elements?.[index] || null; + }; + + let prevPoint: Point | null = null; + let isDrag = false; + + viewer.drawFrame(); + + return { + mode: key, + hover: (e: PointWatcherEvent) => { + if (!isDrag) { + const data = sharer.getActiveStorage('data'); + if (data) { + const result = calculator.getPointElement(e.point, data, getScaleInfo()); + if (result.element) { + const { x, y, w, h } = result.element; + sharer.setSharedStorage(keySelectType, 'hover'); + sharer.setSharedStorage(keyHoverElementSize, { x, y, w, h }); + viewer.drawFrame(); + return; + } + } + if (sharer.getSharedStorage(keySelectType) === 'hover') { + sharer.setSharedStorage(keySelectType, 'default'); + sharer.setSharedStorage(keyHoverElementSize, null); + viewer.drawFrame(); + } + } + }, + pointStart: (e: PointWatcherEvent) => { + const data = sharer.getActiveStorage('data'); + if (data) { + const result = calculator.getPointElement(e.point, data, getScaleInfo()); + sharer.setActiveStorage('selectedIndexs', result.index >= 0 ? [result.index] : []); + } + if (getIndex() >= 0) { + sharer.setSharedStorage(keySelectType, 'drag'); + isDrag = true; + prevPoint = e.point; + } + }, + pointMove: (e: PointWatcherEvent) => { + if (!isDrag) { + return; + } + const data = sharer.getActiveStorage('data'); + + const index = getIndex(); + const elem = getActiveElem(); + const scale = sharer.getActiveStorage('scale') || 1; + const startPoint = prevPoint; + const endPoint = e.point; + if (data && elem && index >= 0 && startPoint && endPoint) { + data.elements[index].x += (endPoint.x - startPoint.x) / scale; + data.elements[index].y += (endPoint.y - startPoint.y) / scale; + sharer.setActiveStorage('data', data); + prevPoint = e.point; + } else { + prevPoint = null; + } + viewer.drawFrame(); + }, + pointEnd: (e: PointWatcherEvent) => { + sharer.setActiveStorage('selectedIndexs', []); + isDrag = false; + }, + + beforeDrawFrame({ snapshot }) { + const { activeStore, sharedStore } = snapshot; + const { data, selectedIndexs, scale, offsetLeft, offsetTop, offsetRight, offsetBottom } = activeStore; + const selectType = sharedStore[keySelectType]; + const hoverElement = sharedStore[keyHoverElementSize]; + const drawOpts = { calculator, scale, offsetLeft, offsetTop, offsetRight, offsetBottom }; + if (selectType === 'hover' && hoverElement) { + drawHoverWrapper(helperContext, hoverElement, drawOpts); + } else if (selectType === 'drag' && data?.elements?.[selectedIndexs?.[0]]) { + drawPointWrapper(helperContext, data?.elements?.[selectedIndexs?.[0]], drawOpts); + } + } + }; +}; diff --git a/packages/core/src/mixins/element.ts b/packages/core/src/mixins/element.ts deleted file mode 100644 index 5f8b010..0000000 --- a/packages/core/src/mixins/element.ts +++ /dev/null @@ -1,216 +0,0 @@ -import { DataElement, DataElemDesc, DataElementBase } from '@idraw/types'; -import { deepClone, createUUID } from '@idraw/util'; -import { diffElementResourceChange } from '../lib/diff'; -import Core from '../index'; -import { Mode } from '../constant/static'; - -export function getSelectedElements( - core: Core -): DataElement[] { - const elems: DataElement[] = []; - let list: string[] = []; - const uuid = core.getEngine().temp.get('selectedUUID'); - if (typeof uuid === 'string' && uuid) { - list.push(uuid); - } else { - list = core.getEngine().temp.get('selectedUUIDList'); - } - list.forEach((uuid) => { - const index = core.getEngine().helper.getElementIndexByUUID(uuid); - if (index !== null && index >= 0) { - const elem = core.$data.elements[index]; - if (elem) elems.push(elem); - } - }); - return deepClone(elems); -} - -export function getElement( - core: Core, - uuid: string -): DataElement | null { - let elem: DataElement | null = null; - const index = core.getEngine().helper.getElementIndexByUUID(uuid); - if (index !== null && core.$data.elements[index]) { - elem = deepClone(core.$data.elements[index]); - } - return elem; -} - -export function getElementByIndex( - core: Core, - index: number -): DataElement | null { - let elem: DataElement | null = null; - if (index >= 0 && core.$data.elements[index]) { - elem = deepClone(core.$data.elements[index]); - } - return elem; -} - -export function updateElement( - core: Core, - elem: DataElement -) { - const _elem = deepClone(elem) as DataElement; - const data = core.getData(); - const resourceChangeUUIDs: string[] = []; - for (let i = 0; i < data.elements.length; i++) { - if (_elem.uuid === data.elements[i]?.uuid) { - const result = diffElementResourceChange(data.elements[i], _elem); - if (typeof result === 'string') { - resourceChangeUUIDs.push(result); - } - data.elements[i] = _elem; - break; - } - } - core.$emitChangeData(); - core.$draw({ resourceChangeUUIDs }); -} - -export function selectElementByIndex(core: Core, index: number): void { - if (core.$data.elements[index]) { - const uuid = core.$data.elements[index].uuid; - core.getEngine().temp.set('mode', Mode.NULL); - if (typeof uuid === 'string') { - core.getEngine().temp.set('selectedUUID', uuid); - core.getEngine().temp.set('selectedUUIDList', []); - } - core.$draw(); - } -} - -export function selectElement(core: Core, uuid: string): void { - const index = core.getEngine().helper.getElementIndexByUUID(uuid); - if (typeof index === 'number' && index >= 0) { - core.selectElementByIndex(index); - } -} - -export function cancelElementByIndex(core: Core, index: number): void { - if (core.$data.elements[index]) { - const uuid = core.$data.elements[index].uuid; - const selectedUUID = core.getEngine().temp.get('selectedUUID'); - if (typeof uuid === 'string' && uuid === selectedUUID) { - core.getEngine().temp.set('mode', Mode.NULL); - core.getEngine().temp.set('selectedUUID', null); - core.getEngine().temp.set('selectedUUIDList', []); - } - core.$draw(); - } -} - -export function cancelElement(core: Core, uuid: string): void { - const index = core.getEngine().helper.getElementIndexByUUID(uuid); - if (typeof index === 'number' && index >= 0) { - core.cancelElementByIndex(index); - } -} - -export function moveUpElement(core: Core, uuid: string): void { - const index = core.getEngine().helper.getElementIndexByUUID(uuid); - if ( - typeof index === 'number' && - index >= 0 && - index < core.$data.elements.length - 1 - ) { - const temp = core.$data.elements[index]; - core.$data.elements[index] = core.$data.elements[index + 1]; - core.$data.elements[index + 1] = temp; - } - core.$emitChangeData(); - core.$draw(); -} - -export function moveDownElement(core: Core, uuid: string): void { - const index = core.getEngine().helper.getElementIndexByUUID(uuid); - if ( - typeof index === 'number' && - index > 0 && - index < core.$data.elements.length - ) { - const temp = core.$data.elements[index]; - core.$data.elements[index] = core.$data.elements[index - 1]; - core.$data.elements[index - 1] = temp; - } - core.$emitChangeData(); - core.$draw(); -} - -export function addElement( - core: Core, - elem: DataElementBase -): string | null { - const _elem = deepClone(elem); - _elem.uuid = createUUID(); - core.$data.elements.push(_elem); - core.$emitChangeData(); - core.$draw(); - return _elem.uuid; -} - -export function deleteElement(core: Core, uuid: string) { - const index = core.$getElementHandler().getElementIndex(core.getData(), uuid); - if (index >= 0) { - core.$data.elements.splice(index, 1); - core.$emitChangeData(); - core.$draw(); - } -} - -export function insertElementBefore( - core: Core, - elem: DataElementBase, - beforeUUID: string -) { - const index = core.getEngine().helper.getElementIndexByUUID(beforeUUID); - if (index !== null) { - return core.insertElementBeforeIndex(elem, index); - } - return null; -} - -export function insertElementBeforeIndex( - core: Core, - elem: DataElementBase, - index: number -) { - const _elem = deepClone(elem); - _elem.uuid = createUUID(); - if (index >= 0) { - core.$data.elements.splice(index, 0, _elem); - core.$emitChangeData(); - core.$draw(); - return _elem.uuid; - } - return null; -} - -export function insertElementAfter( - core: Core, - elem: DataElementBase, - beforeUUID: string -) { - const index = core.getEngine().helper.getElementIndexByUUID(beforeUUID); - if (index !== null) { - return core.insertElementAfterIndex(elem, index); - } - return null; -} - -export function insertElementAfterIndex( - core: Core, - elem: DataElementBase, - index: number -) { - const _elem = deepClone(elem); - _elem.uuid = createUUID(); - if (index >= 0) { - core.$data.elements.splice(index + 1, 0, _elem); - core.$emitChangeData(); - core.$draw(); - return _elem.uuid; - } - return null; -} diff --git a/packages/core/src/plugins/helper.ts b/packages/core/src/plugins/helper.ts deleted file mode 100644 index d5f7354..0000000 --- a/packages/core/src/plugins/helper.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { - InterfaceHelperPlugin, - HelperPluginEventDetail, - HelperPluginEventResult -} from '@idraw/types'; -import { createUUID } from '@idraw/util'; - -export class HelperPlugin implements Required { - readonly name: string = 'helper-plugin'; - - readonly uuid: string; - - constructor() { - // TODO - this.uuid = createUUID(); - } - - onHover(detail: HelperPluginEventDetail): void | HelperPluginEventResult { - if (detail.controller === null) { - } - } - - onPoint(detail: HelperPluginEventDetail): void | HelperPluginEventResult {} - - onClick(detail: HelperPluginEventDetail): void | HelperPluginEventResult {} - - onMoveStart( - detail: HelperPluginEventDetail - ): void | HelperPluginEventResult {} - - onMove(detail: HelperPluginEventDetail): void | HelperPluginEventResult {} - - onMoveEnd(detail: HelperPluginEventDetail): void | HelperPluginEventResult {} -} diff --git a/packages/core/src/util/filter.ts b/packages/core/src/util/filter.ts deleted file mode 100644 index df84741..0000000 --- a/packages/core/src/util/filter.ts +++ /dev/null @@ -1,4 +0,0 @@ - -export function filterScript(html: string) { - return html.replace(//ig, ''); -} \ No newline at end of file diff --git a/packages/idraw/dev/data.ts b/packages/idraw/dev/data.ts index 62a0a34..2de6732 100644 --- a/packages/idraw/dev/data.ts +++ b/packages/idraw/dev/data.ts @@ -1,90 +1,183 @@ -import type { IDrawDataBase } from '@idraw/types'; +import type { Data } from '@idraw/types'; -const data: IDrawDataBase = { - bgColor: '#ffffff', +// const data: Data = { +// bgColor: '#ffffff', +// elements: [ +// { +// name: 'rect-001', +// x: 5, +// y: 5, +// w: 100, +// h: 50, +// type: 'rect', +// desc: { +// bgColor: '#ffeb3b', +// borderRadius: 10, +// borderWidth: 5, +// borderColor: '#ffc107' +// } +// }, +// { +// name: 'text-002', +// x: 40, +// y: 40, +// w: 100, +// h: 60, +// // angle: 30, +// type: 'text', +// desc: { +// fontSize: 16, +// text: 'Hello Text', +// fontWeight: 'bold', +// color: '#666666', +// borderRadius: 30, +// borderWidth: 4, +// borderColor: '#ff5722' +// } +// }, +// { +// name: 'image-003', +// x: 80, +// y: 80, +// w: 160, +// h: 80, +// type: 'image', +// desc: { +// src: './images/computer.png' +// }, +// operation: { +// // disableRotate: true, +// limitRatio: true +// } +// }, +// { +// name: 'svg-004', +// x: 200 - 5, +// y: 150 - 50, +// w: 100, +// h: 100, +// type: 'svg', +// angle: 135, +// desc: { +// svg: '' +// }, +// operation: { +// // disableRotate: true, +// limitRatio: true +// } +// }, +// { +// name: 'text-002', +// x: 200, +// y: 200, +// w: 300, +// h: 100, +// // angle: 30, +// type: 'text', +// desc: { +// fontSize: 16, +// // text: 'Hello Text Hello Text Hello Text Hello Text Hello Text Hello Text', +// text: 'Hello Text', +// fontWeight: 'bold', +// color: '#666666', +// borderRadius: 30, +// borderWidth: 2, +// borderColor: '#ff5722', +// textAlign: 'center', +// verticalAlign: 'middle' +// } +// } +// ] +// }; + +const data: Data = { elements: [ { - name: 'rect-001', - x: 5, - y: 5, - w: 100, - h: 50, - type: 'rect', - desc: { - bgColor: '#ffeb3b', - borderRadius: 10, - borderWidth: 5, - borderColor: '#ffc107' - } - }, - { - name: 'text-002', - x: 40, - y: 40, - w: 100, - h: 60, - // angle: 30, - type: 'text', - desc: { - fontSize: 16, - text: 'Hello Text', - fontWeight: 'bold', - color: '#666666', - borderRadius: 30, - borderWidth: 4, - borderColor: '#ff5722' - } - }, - { - name: 'image-003', - x: 80, - y: 80, - w: 160, - h: 80, + uuid: 'xxx-0003', type: 'image', - desc: { - src: './images/computer.png' - }, - operation: { - // disableRotate: true, - limitRatio: true - } - }, - { - name: 'svg-004', - x: 200 - 5, - y: 150 - 50, + x: 100, + y: 100, w: 100, h: 100, - type: 'svg', - angle: 135, desc: { - svg: '' - }, - operation: { - // disableRotate: true, - limitRatio: true + src: './images/lena.png' } }, { - name: 'text-002', - x: 200, - y: 200, - w: 300, + uuid: 'xxxx-0001', + x: 2, + y: 2, + w: 100, h: 100, - // angle: 30, - type: 'text', + type: 'circle', desc: { - fontSize: 16, - // text: 'Hello Text Hello Text Hello Text Hello Text Hello Text Hello Text', - text: 'Hello Text', - fontWeight: 'bold', - color: '#666666', - borderRadius: 30, - borderWidth: 2, - borderColor: '#ff5722', - textAlign: 'center', - verticalAlign: 'middle' + bgColor: '#f44336' + } + }, + { + uuid: 'xxx-0002', + type: 'rect', + x: 50, + y: 50, + w: 100, + h: 100, + desc: { + bgColor: '#2196f3' + } + }, + { + uuid: 'xxx-0004', + type: 'image', + x: 250, + y: 250, + w: 100, + h: 100, + desc: { + src: './images/github.png?t=003' + } + }, + { + uuid: 'xxxx-0005', + x: 0, + y: 300, + w: 100, + h: 100, + type: 'circle', + desc: { + bgColor: '#009688' + } + }, + { + uuid: 'xxxx-0006', + x: 300, + y: 300, + w: 100, + h: 100, + type: 'circle', + desc: { + bgColor: '#673ab7' + } + }, + { + uuid: 'xxxx-0007', + x: 300, + y: 0, + w: 100, + h: 100, + type: 'circle', + desc: { + bgColor: '#ffc107' + } + }, + { + uuid: 'xxxx-0008', + x: 150, + y: 150, + w: 100, + h: 100, + type: 'circle', + desc: { + bgColor: '#4caf50' } } ] diff --git a/packages/idraw/dev/images/github.png b/packages/idraw/dev/images/github.png new file mode 100644 index 0000000..3d64592 Binary files /dev/null and b/packages/idraw/dev/images/github.png differ diff --git a/packages/idraw/dev/images/lena.png b/packages/idraw/dev/images/lena.png new file mode 100644 index 0000000..08e54c5 Binary files /dev/null and b/packages/idraw/dev/images/lena.png differ diff --git a/packages/idraw/dev/main.ts b/packages/idraw/dev/main.ts index af00ed5..7e51a51 100644 --- a/packages/idraw/dev/main.ts +++ b/packages/idraw/dev/main.ts @@ -1,21 +1,15 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ -import iDraw from '../src/index'; +import { iDraw } from '../src/index'; import { getData } from './data'; const opts = { - width: 600, + width: 400, height: 400, - contextWidth: 600, + contextWidth: 400, contextHeight: 400, devicePixelRatio: 2 }; -// var config = { -// elementWrapper: { -// controllerSize: 4, -// } -// } - const mount = document.querySelector('#mount') as HTMLDivElement; const data = getData(); const idraw = new iDraw( @@ -23,41 +17,33 @@ const idraw = new iDraw( Object.assign({}, opts, { // contextWidth: 500, // contextHeight: 400 - }), - { - scrollWrapper: { - use: true - // color: 'red' - // showBackground: false - } - } + }) ); idraw.setData(data); // const parseData = idraw.getData(); -idraw.on('changeData', (d) => { - console.log('changeData ======', d); -}); +// idraw.on('changeData', (d) => { +// console.log('changeData ======', d); +// }); -idraw.scale(1.5); +// idraw.scale(1.5); +// idraw.selectElementByIndex(1); -idraw.selectElementByIndex(1); +// setTimeout(() => { +// // idraw.cancelElementByIndex(1); +// // idraw.cancelElement(parseData.elements[1].uuid); +// }, 2000); -setTimeout(() => { - // idraw.cancelElementByIndex(1); - // idraw.cancelElement(parseData.elements[1].uuid); -}, 2000); - -const btn = document.querySelector('#btn') as HTMLButtonElement; -btn.addEventListener('click', () => { - idraw - .exportDataURL({ type: 'image/png' }) - .then((dataURL) => { - const preview = document.querySelector('#preview') as HTMLDivElement; - preview.innerHTML = ``; - }) - .catch((err) => { - console.log(err); - }); -}); +// const btn = document.querySelector('#btn') as HTMLButtonElement; +// btn.addEventListener('click', () => { +// idraw +// .exportDataURL({ type: 'image/png' }) +// .then((dataURL) => { +// const preview = document.querySelector('#preview') as HTMLDivElement; +// preview.innerHTML = ``; +// }) +// .catch((err) => { +// console.log(err); +// }); +// }); diff --git a/packages/idraw/src/config.ts b/packages/idraw/src/config.ts deleted file mode 100644 index 300fbf0..0000000 --- a/packages/idraw/src/config.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { PrivateOptions } from './types'; - -export const defaultOptions: PrivateOptions = { - width: 400, - height: 300, - contextWidth: 400, - contextHeight: 300, - devicePixelRatio: 1, - onlyRender: false, - maxRecords: 10, - disableKeyboard: true -}; diff --git a/packages/idraw/src/index.ts b/packages/idraw/src/index.ts index 20f7b9a..4e9f389 100644 --- a/packages/idraw/src/index.ts +++ b/packages/idraw/src/index.ts @@ -1,113 +1,18 @@ -import Core from '@idraw/core'; -import { IDrawData, IDrawConfig } from '@idraw/types'; -import { Options, PrivateOptions } from './types'; -import { defaultOptions } from './config'; -import { TempData } from './lib/temp'; -import { KeyboardWatcher } from './lib/keyboard-watcher'; +import { Core, MiddlewareSelector } from '@idraw/core'; +import type { IDrawOptions, Data } from '@idraw/types'; -import { redo, undo } from './mixins/record'; -import { exportDataURL, toDataURL } from './mixins/file'; -import { - copyElements, - pasteElements, - cutElements, - deleteElements, - keyArrowUp, - keyArrowDown, - keyArrowLeft, - keyArrowRight, - keyUndo -} from './mixins/keyboard'; -// import { version } from './../package.json'; +export class iDraw { + private _core: Core; + private _opts: IDrawOptions; -export default class iDraw extends Core { - private _opts: PrivateOptions; - private _hasInited = false; - private _tempData = new TempData(); - private _keyboardWatcher = new KeyboardWatcher(); - - // static version = version; - - constructor(mount: HTMLDivElement, opts: Options, config?: IDrawConfig) { - super( - mount, - { - width: opts.width || defaultOptions.width, - height: opts.height || defaultOptions.height, - contextWidth: opts.contextWidth || defaultOptions.contextWidth, - contextHeight: opts.contextHeight || defaultOptions.contextHeight, - devicePixelRatio: - opts.devicePixelRatio || defaultOptions.devicePixelRatio, - onlyRender: opts.onlyRender || defaultOptions.onlyRender - }, - config || {} - ); - this._opts = this._createOpts(opts); - this._initEvent(); + constructor(mount: HTMLDivElement, opts: IDrawOptions) { + const core = new Core(mount, opts); + this._core = core; + this._opts = opts; + core.use(MiddlewareSelector); } - undo(): { doRecordCount: number; data: IDrawData | null } { - return undo(this); - } - - redo(): { undoRecordCount: number; data: IDrawData | null } { - return redo(this); - } - - toDataURL(type: 'image/png' | 'image/jpeg', quality?: number): string { - return toDataURL(this, type, quality); - } - - getTempData() { - return this._tempData; - } - - async exportDataURL( - type: 'image/png' | 'image/jpeg', - quality?: number - ): Promise { - return exportDataURL(this, type, quality); - } - - private _initEvent() { - if (this._hasInited === true) { - return; - } - this.on('changeData', (data: IDrawData) => { - this._pushRecord(data); - }); - this.on('mouseLeaveScreen', () => { - this._tempData.set('isFocus', false); - }); - this.on('mouseOverScreen', () => { - this._tempData.set('isFocus', true); - }); - if (this._opts.disableKeyboard === false) { - this._keyboardWatcher - .on('keyboardCopy', () => copyElements(this)) - .on('keyboardPaste', () => pasteElements(this)) - .on('keyboardCut', () => cutElements(this)) - .on('keyboardDelete', () => deleteElements(this)) - .on('keyboardArrowUp', () => keyArrowUp(this)) - .on('keyboardArrowDown', () => keyArrowDown(this)) - .on('keyboardArrowLeft', () => keyArrowLeft(this)) - .on('keyboardArrowRight', () => keyArrowRight(this)) - .on('keyboardUndo', () => keyUndo(this)); - } - this._hasInited = true; - } - - private _pushRecord(data: IDrawData) { - const doRecords = this._tempData.get('doRecords'); - if (doRecords.length >= this._opts.maxRecords) { - doRecords.shift(); - } - doRecords.push({ data, time: Date.now() }); - this._tempData.set('doRecords', doRecords); - this._tempData.set('unDoRecords', []); - } - - private _createOpts(opts: Options): PrivateOptions { - return { ...{}, ...defaultOptions, ...opts }; + setData(data: Data) { + this._core.setData(data); } } diff --git a/packages/idraw/src/lib/keyboard-watcher.ts b/packages/idraw/src/lib/keyboard-watcher.ts deleted file mode 100644 index 2ea8c84..0000000 --- a/packages/idraw/src/lib/keyboard-watcher.ts +++ /dev/null @@ -1,104 +0,0 @@ - - -export type TypeKeyboardEventArgMap = { - 'keyboardCopy': void; - 'keyboardPaste': void; - 'keyboardCut': void; - 'keyboardDelete': void; - 'keyboardArrowRight': void; - 'keyboardArrowLeft': void; - 'keyboardArrowUp': void; - 'keyboardArrowDown': void; - 'keyboardUndo': void; -} - -export interface TypeKeyboardEvent { - on(key: T, callback: (p: TypeKeyboardEventArgMap[T]) => void): void - off(key: T, callback: (p: TypeKeyboardEventArgMap[T]) => void): void - // trigger(key: T, p: TypeKeyboardEventArgMap[T]): void -} - - -export class KeyboardWatcher implements TypeKeyboardEvent { - - private _listeners: Map void)[]>; - - constructor() { - this._listeners = new Map(); - this._initEvent(); - } - - private _initEvent() { - document.addEventListener('keydown', (e) => { - if ((e.metaKey === true || e.ctrlKey === true) && e.key === 'c') { - this.trigger('keyboardCopy', undefined); - } else if ((e.metaKey === true || e.ctrlKey === true) && e.key === 'v') { - this.trigger('keyboardPaste', undefined); - } else if ((e.metaKey === true || e.ctrlKey === true) && e.key === 'x') { - this.trigger('keyboardCut', undefined); - } else if ((e.metaKey === true || e.ctrlKey === true) && e.key === 'z') { - this.trigger('keyboardUndo', undefined); - } else if (e.key === 'Backspace') { - this.trigger('keyboardDelete', undefined); - } else if (e.key === 'ArrowUp') { - this.trigger('keyboardArrowUp', undefined); - } else if (e.key === 'ArrowDown') { - this.trigger('keyboardArrowDown', undefined); - } else if (e.key === 'ArrowLeft') { - this.trigger('keyboardArrowLeft', undefined); - } else if (e.key === 'ArrowRight') { - this.trigger('keyboardArrowRight', undefined); - } - }); - } - - on(eventKey: T, callback: (p: TypeKeyboardEventArgMap[T]) => void) { - if (this._listeners.has(eventKey)) { - const callbacks = this._listeners.get(eventKey); - callbacks?.push(callback); - this._listeners.set(eventKey, callbacks || []); - } else { - this._listeners.set(eventKey, [callback]); - } - return this; - } - - off(eventKey: T, callback: (p: TypeKeyboardEventArgMap[T]) => void) { - 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) { - callbacks.splice(i, 1); - break; - } - } - } - this._listeners.set(eventKey, callbacks || []); - } - return this; - } - - trigger(eventKey: T, arg: TypeKeyboardEventArgMap[T]) { - const callbacks = this._listeners.get(eventKey); - if (Array.isArray(callbacks)) { - callbacks.forEach((cb) => { - cb(arg); - }); - return true; - } else { - return false; - } - } - - has (name: string) { - if (this._listeners.has(name)) { - const list: ((p: TypeKeyboardEventArgMap[T]) => void)[] | undefined = this._listeners.get(name); - if (Array.isArray(list) && list.length > 0) { - return true; - } - } - return false; - } - -} diff --git a/packages/idraw/src/lib/keyboard.ts b/packages/idraw/src/lib/keyboard.ts deleted file mode 100644 index eb2ed88..0000000 --- a/packages/idraw/src/lib/keyboard.ts +++ /dev/null @@ -1,6 +0,0 @@ -import Core from '@idraw/core'; - -export function copySelectedElement(core: Core) { - console.log('core ====', core); - // console.log(core.getSelectedElements()); -} \ No newline at end of file diff --git a/packages/idraw/src/lib/temp.ts b/packages/idraw/src/lib/temp.ts deleted file mode 100644 index f3d2475..0000000 --- a/packages/idraw/src/lib/temp.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { DataElemDesc, DataElement } from '@idraw/types'; -import { Record } from './../types'; - -type TempDataDesc = { - isDownloading: boolean; - isFocus: boolean; - doRecords: Record[]; - unDoRecords: Record[]; - clipboardElements: DataElement[]; -}; - -function createDefaultData() { - return { - isFocus: false, - doRecords: [], - unDoRecords: [], - clipboardElements: [], - isDownloading: false - }; -} - -export class TempData { - private _temp: TempDataDesc; - - constructor() { - this._temp = createDefaultData(); - } - - set(name: T, value: TempDataDesc[T]) { - this._temp[name] = value; - } - - get(name: T): TempDataDesc[T] { - return this._temp[name]; - } - - clear() { - this._temp = createDefaultData(); - } -} diff --git a/packages/idraw/src/mixins/file.ts b/packages/idraw/src/mixins/file.ts deleted file mode 100644 index b748f2d..0000000 --- a/packages/idraw/src/mixins/file.ts +++ /dev/null @@ -1,41 +0,0 @@ -import iDraw from './../index'; - -export async function exportDataURL( - idraw: iDraw, - type: 'image/png' | 'image/jpeg', - quality?: number -): Promise { - if (idraw.getTempData().get('isDownloading') === true) { - return Promise.reject('Busy!'); - } - - idraw.getTempData().set('isDownloading', true); - return new Promise((resolve, reject) => { - let dataURL = ''; - function listenRenderFrameComplete() { - idraw.off('drawFrameComplete', listenRenderFrameComplete); - idraw.getTempData().set('isDownloading', false); - const ctx = idraw.$getOriginContext2D(); - const canvas = ctx.canvas; - dataURL = canvas.toDataURL(type, quality); - resolve(dataURL); - } - try { - idraw.on('drawFrameComplete', listenRenderFrameComplete); - idraw.clearOperation(); - } catch (err) { - reject(err); - } - }); -} - -export function toDataURL( - idraw: iDraw, - type: 'image/png' | 'image/jpeg', - quality?: number -): string { - const ctx = idraw.$getOriginContext2D(); - const canvas = ctx.canvas; - const dataURL: string = canvas.toDataURL(type, quality); - return dataURL; -} diff --git a/packages/idraw/src/mixins/keyboard.ts b/packages/idraw/src/mixins/keyboard.ts deleted file mode 100644 index a7f2c3c..0000000 --- a/packages/idraw/src/mixins/keyboard.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { deepClone } from '@idraw/util'; -import { DataElement, DataElemDesc } from '@idraw/types'; -import iDraw from './../index'; - -export function copyElements(idraw: iDraw) { - if (idraw.getTempData().get('isFocus') !== true) { - return; - } - const elems = deepClone(idraw.getSelectedElements()); - idraw.getTempData().set('clipboardElements', elems); -} - -export function pasteElements(idraw: iDraw) { - if (idraw.getTempData().get('isFocus') !== true) { - return; - } - const elems = idraw.getTempData().get('clipboardElements'); - const moveRate = 0.1; - elems.forEach((elem) => { - elem.x += elem.w * moveRate; - elem.y += elem.w * moveRate; - idraw.addElement(elem); - }); - idraw.getTempData().set('clipboardElements', []); -} - -export function cutElements(idraw: iDraw) { - if (idraw.getTempData().get('isFocus') !== true) { - return; - } - const elems = deepClone(idraw.getSelectedElements()); - elems.forEach((elem: DataElement) => { - idraw.deleteElement(elem.uuid); - }); - idraw.getTempData().set('clipboardElements', elems); -} - -export function deleteElements(idraw: iDraw) { - if (idraw.getTempData().get('isFocus') !== true) { - return; - } - const elems = deepClone(idraw.getSelectedElements()); - elems.forEach((elem: DataElement) => { - idraw.deleteElement(elem.uuid); - }); -} - -const keyArrowMoveDistance = 4; - -export function keyArrowUp(idraw: iDraw) { - const elems = deepClone(idraw.getSelectedElements()); - if (elems.length > 0) { - elems.forEach((elem: DataElement) => { - elem.y -= keyArrowMoveDistance; - idraw.updateElement(elem); - }); - } else { - const { scrollTop } = idraw.getScreenTransform(); - idraw.scrollTop(scrollTop - keyArrowMoveDistance); - } -} - -export function keyArrowDown(idraw: iDraw) { - const elems = deepClone(idraw.getSelectedElements()); - if (elems.length > 0) { - elems.forEach((elem: DataElement) => { - elem.y += keyArrowMoveDistance; - idraw.updateElement(elem); - }); - } else { - const { scrollTop } = idraw.getScreenTransform(); - idraw.scrollTop(scrollTop + keyArrowMoveDistance); - } -} - -export function keyArrowLeft(idraw: iDraw) { - const elems = deepClone(idraw.getSelectedElements()); - if (elems.length > 0) { - elems.forEach((elem: DataElement) => { - elem.x -= keyArrowMoveDistance; - idraw.updateElement(elem); - }); - } else { - const { scrollLeft } = idraw.getScreenTransform(); - idraw.scrollLeft(scrollLeft - keyArrowMoveDistance); - } -} - -export function keyArrowRight(idraw: iDraw) { - const elems = deepClone(idraw.getSelectedElements()); - if (elems.length > 0) { - elems.forEach((elem: DataElement) => { - elem.x += keyArrowMoveDistance; - idraw.updateElement(elem); - }); - } else { - const { scrollLeft } = idraw.getScreenTransform(); - idraw.scrollLeft(scrollLeft + keyArrowMoveDistance); - } -} - -export function keyUndo(idraw: iDraw) { - idraw.undo(); -} diff --git a/packages/idraw/src/mixins/record.ts b/packages/idraw/src/mixins/record.ts deleted file mode 100644 index 926a8bb..0000000 --- a/packages/idraw/src/mixins/record.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { IDrawData } from '@idraw/types'; -import iDraw from './../index'; - -export function undo(idraw: iDraw): { - doRecordCount: number; - data: IDrawData | null; -} { - const doRecords = idraw.getTempData().get('doRecords'); - const unDoRecords = idraw.getTempData().get('unDoRecords'); - if (!(doRecords.length > 1)) { - return { - doRecordCount: doRecords.length, - data: null - }; - } - const popRecord = doRecords.pop(); - if (popRecord) { - unDoRecords.push(popRecord); - } - const record = doRecords[doRecords.length - 1]; - if (record?.data) { - idraw.setData(record.data); - } - idraw.getTempData().set('doRecords', doRecords); - idraw.getTempData().set('unDoRecords', unDoRecords); - return { - doRecordCount: doRecords.length, - data: record?.data || null - }; -} - -export function redo(idraw: iDraw): { - undoRecordCount: number; - data: IDrawData | null; -} { - const unDoRecords = idraw.getTempData().get('unDoRecords'); - if (!(unDoRecords.length > 0)) { - return { - undoRecordCount: unDoRecords.length, - data: null - }; - } - const record = unDoRecords.pop(); - if (record?.data) { - idraw.setData(record.data); - } - idraw.getTempData().set('unDoRecords', unDoRecords); - return { - undoRecordCount: unDoRecords.length, - data: record?.data || null - }; -} diff --git a/packages/idraw/src/types.ts b/packages/idraw/src/types.ts deleted file mode 100644 index 4105e6f..0000000 --- a/packages/idraw/src/types.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { IDrawData, CoreOptions } from '@idraw/types'; - -export type Options = { - maxRecords?: number; - disableKeyboard?: boolean; -} & CoreOptions; - -export type PrivateOptions = { - maxRecords: number; - disableKeyboard: boolean; -} & Options; - -export type Record = { - data: IDrawData; - time: number; -}; diff --git a/packages/renderer/src/constant/element.ts b/packages/renderer/src/constant/element.ts deleted file mode 100644 index b2bbcdb..0000000 --- a/packages/renderer/src/constant/element.ts +++ /dev/null @@ -1,15 +0,0 @@ - -const elementTypes = { - 'text': {}, // TODO - 'rect': {}, // TODO - 'image': {}, // TODO - 'svg': {}, // TODO - 'circle': {}, // TODO - 'html': {}, // TODO -}; - -export const elementNames = Object.keys(elementTypes); - - -// limitQbliqueAngle -export const LIMIT_QBLIQUE_ANGLE = 15; \ No newline at end of file diff --git a/packages/renderer/src/constant/static.ts b/packages/renderer/src/constant/static.ts deleted file mode 100644 index 80815e8..0000000 --- a/packages/renderer/src/constant/static.ts +++ /dev/null @@ -1,12 +0,0 @@ -export enum Mode { - NULL = 'null', - SELECT_ELEMENT = 'select-element', - SELECT_ELEMENT_LIST = 'select-element-list', - SELECT_ELEMENT_WRAPPER_CONTROLLER = 'select-element-wrapper-controller', - SELECT_AREA = 'select-area', -} - -export enum CursorStatus { - DRAGGING = 'dragging', - NULL = 'null', -} diff --git a/packages/renderer/src/draw/circle.ts b/packages/renderer/src/draw/circle.ts new file mode 100644 index 0000000..da87f2a --- /dev/null +++ b/packages/renderer/src/draw/circle.ts @@ -0,0 +1,31 @@ +import type { Element, RendererDrawElementOptions } from '@idraw/types'; + +export function drawCircle(ctx: CanvasRenderingContext2D, elem: Element<'circle'>, opts: RendererDrawElementOptions) { + const { desc } = elem; + const { bgColor = '#000000', borderColor = '#000000', borderWidth = 0 } = desc; + const { calculator, scale, offsetTop, offsetBottom, offsetLeft, offsetRight } = opts; + const { x, y, w, h } = calculator.elementSize({ x: elem.x, y: elem.y, w: elem.w, h: elem.h }, { scale, offsetTop, offsetBottom, offsetLeft, offsetRight }); + const a = w / 2; + const b = h / 2; + const centerX = x + a; + const centerY = y + b; + + // draw border + if (borderWidth && borderWidth > 0) { + const ba = borderWidth / 2 + a; + const bb = borderWidth / 2 + b; + ctx.beginPath(); + ctx.strokeStyle = borderColor; + ctx.lineWidth = borderWidth; + ctx.ellipse(centerX, centerY, ba, bb, 0, 0, 2 * Math.PI); + ctx.closePath(); + ctx.stroke(); + } + + // draw content + ctx.beginPath(); + ctx.fillStyle = bgColor; + ctx.ellipse(centerX, centerY, a, b, 0, 0, 2 * Math.PI); + ctx.closePath(); + ctx.fill(); +} diff --git a/packages/renderer/src/draw/elements.ts b/packages/renderer/src/draw/elements.ts new file mode 100644 index 0000000..a1427d1 --- /dev/null +++ b/packages/renderer/src/draw/elements.ts @@ -0,0 +1,42 @@ +import type { Element, ElementType, Data, RendererDrawElementOptions } from '@idraw/types'; +import { drawCircle } from './circle'; +import { drawRect } from './rect'; +import { drawImage } from './image'; + +export function drawElement(ctx: CanvasRenderingContext2D, elem: Element, opts: RendererDrawElementOptions) { + try { + switch (elem.type) { + case 'rect': { + drawRect(ctx, elem as Element<'rect'>, opts); + break; + } + case 'circle': { + drawCircle(ctx, elem as Element<'circle'>, opts); + break; + } + case 'image': { + drawImage(ctx, elem as Element<'image'>, opts); + break; + } + default: { + break; + } + } + } catch (err) { + console.error(err); + } +} + +export function drawElementList(ctx: CanvasRenderingContext2D, elements: Data['elements'], opts: RendererDrawElementOptions) { + for (let i = elements.length - 1; i >= 0; i--) { + const elem = elements[i]; + if (!opts.calculator.isElementInView(elem, opts)) { + continue; + } + try { + drawElement(ctx, elem, opts); + } catch (err) { + console.error(err); + } + } +} diff --git a/packages/renderer/src/draw/image.ts b/packages/renderer/src/draw/image.ts new file mode 100644 index 0000000..26b2ae0 --- /dev/null +++ b/packages/renderer/src/draw/image.ts @@ -0,0 +1,14 @@ +import type { Element, RendererDrawElementOptions } from '@idraw/types'; + +export function drawImage(ctx: CanvasRenderingContext2D, elem: Element<'image'>, opts: RendererDrawElementOptions) { + const content = opts.loader.getContent(elem.uuid); + const { calculator, scale, offsetTop, offsetBottom, offsetLeft, offsetRight } = opts; + const { x, y, w, h } = calculator.elementSize({ x: elem.x, y: elem.y, w: elem.w, h: elem.h }, { scale, offsetTop, offsetBottom, offsetLeft, offsetRight }); + + if (!content) { + opts.loader.load(elem as Element<'image'>); + } + if (elem.type === 'image' && content) { + ctx.drawImage(content, x, y, w, h); + } +} diff --git a/packages/renderer/src/draw/index.ts b/packages/renderer/src/draw/index.ts new file mode 100644 index 0000000..db3b81b --- /dev/null +++ b/packages/renderer/src/draw/index.ts @@ -0,0 +1,4 @@ +export { drawCircle } from './circle'; +export { drawRect } from './rect'; +export { drawImage } from './image'; +export { drawElementList, drawElement } from './elements'; diff --git a/packages/renderer/src/draw/rect.ts b/packages/renderer/src/draw/rect.ts new file mode 100644 index 0000000..ef30825 --- /dev/null +++ b/packages/renderer/src/draw/rect.ts @@ -0,0 +1,22 @@ +import type { Element, RendererDrawElementOptions } from '@idraw/types'; + +export function drawRect(ctx: CanvasRenderingContext2D, elem: Element<'rect'>, opts: RendererDrawElementOptions) { + // const { desc } = elem; + const { calculator, scale, offsetTop, offsetBottom, offsetLeft, offsetRight } = opts; + const { x, y, w, h } = calculator.elementSize({ x: elem.x, y: elem.y, w: elem.w, h: elem.h }, { scale, offsetTop, offsetBottom, offsetLeft, offsetRight }); + + let r: number = (elem.desc.borderRadius || 0) * scale; + r = Math.min(r, w / 2, h / 2); + if (w < r * 2 || h < r * 2) { + r = 0; + } + ctx.beginPath(); + ctx.moveTo(x + r, y); + ctx.arcTo(x + w, y, x + w, y + h, r); + ctx.arcTo(x + w, y + h, x, y + h, r); + ctx.arcTo(x, y + h, x, y, r); + ctx.arcTo(x, y, x + w, y, r); + ctx.closePath(); + ctx.fillStyle = elem.desc.bgColor || '#000000'; + ctx.fill(); +} diff --git a/packages/renderer/src/index.ts b/packages/renderer/src/index.ts index fcea7d2..f8c7f6d 100644 --- a/packages/renderer/src/index.ts +++ b/packages/renderer/src/index.ts @@ -1,192 +1,77 @@ -import { - IDrawData, - IDrawContext, - DataElement, - DataElemDesc -} from '@idraw/types'; -import { createUUID, deepClone, Context } from '@idraw/util'; -import { drawContext } from './lib/draw'; -import { TypeLoadDataItem } from './lib/loader-event'; -import Loader from './lib/loader'; -import { RendererEvent } from './lib/renderer-event'; +import { EventEmitter, createOffscreenContext2D } from '@idraw/util'; +import { drawElementList } from './draw'; +import { Loader } from './loader'; +import type { Data, BoardRenderer, RendererOptions, RendererEventMap, RendererDrawOptions } from '@idraw/types'; -const { requestAnimationFrame } = window; +export class Renderer extends EventEmitter implements BoardRenderer { + private _opts: RendererOptions; + private _loader: Loader = new Loader(); + private _draftContextTop: CanvasRenderingContext2D; + private _draftContextMiddle: CanvasRenderingContext2D; + private _draftContextBottom: CanvasRenderingContext2D; -type QueueItem = { data: IDrawData }; -enum DrawStatus { - NULL = 'null', - FREE = 'free', - DRAWING = 'drawing', - FREEZE = 'freeze' - // STOP = 'stop', -} - -type Options = { - width: number; - height: number; - contextWidth?: number; - contextHeight?: number; - devicePixelRatio: number; -}; - -export default class Renderer extends RendererEvent { - private _queue: QueueItem[] = []; - private _ctx: IDrawContext | null = null; - private _status: DrawStatus = DrawStatus.NULL; - private _loader: Loader; - private _opts?: Options; - - constructor(opts?: Options) { + constructor(opts: RendererOptions) { super(); this._opts = opts; - this._loader = new Loader({ - maxParallelNum: 6 + const { width, height } = this._opts.viewContent.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(); + } + + private _init() { + const { _loader: loader } = this; + loader.on('load', (e) => { + this.trigger('load', e); }); - this._loader.on('load', (res: TypeLoadDataItem) => { - this._drawFrame(); - this.trigger('load', { element: res.element }); - }); - this._loader.on('error', (res: TypeLoadDataItem) => { - this.trigger('error', { element: res.element, error: res.error }); - }); - this._loader.on('complete', () => { - this.trigger('loadComplete', { t: Date.now() }); + loader.on('error', () => { + // TODO }); } - render( - target: HTMLCanvasElement | IDrawContext, - originData: IDrawData, - opts?: { - // forceUpdate?: boolean, - changeResourceUUIDs?: string[]; - } - ): void { - // if ([DrawStatus.STOP, DrawStatus.FREEZE].includes(this._status)) { - // return; - // } - // this._status = DrawStatus.FREE; + updateOptions(opts: RendererOptions) { + this._opts = opts; + } - const { changeResourceUUIDs = [] } = opts || {}; - this._status = DrawStatus.FREE; + drawData(data: Data, opts: RendererDrawOptions) { + const { _loader: loader } = this; + const { calculator } = this._opts; + const { viewContext } = this._opts.viewContent; + viewContext.clearRect(0, 0, viewContext.canvas.width, viewContext.canvas.height); + drawElementList(viewContext, data.elements, { loader, calculator, ...opts }); + } - const data = deepClone(originData); - if (Array.isArray(data.elements)) { - data.elements.forEach((elem: DataElement) => { - if (!(typeof elem.uuid === 'string' && elem.uuid)) { - elem.uuid = createUUID(); - } + scale(num: number) { + const { sharer } = this._opts; + const { data, offsetTop, offsetBottom, offsetLeft, offsetRight } = sharer.getActiveStoreSnapshot(); + // TODO calc offset data + if (data) { + this.drawData(data, { + scale: num, + offsetTop, + offsetBottom, + offsetLeft, + offsetRight }); } + sharer.setActiveStorage('scale', num); + } - if (!this._ctx) { - // TODO - if ( - this._opts && - Object.prototype.toString.call(target) === '[object HTMLCanvasElement]' - ) { - const { width, height, contextWidth, contextHeight, devicePixelRatio } = - this._opts as Options; - const canvas = target as HTMLCanvasElement; - canvas.width = width * devicePixelRatio; - canvas.height = height * devicePixelRatio; - const ctx2d = canvas.getContext('2d') as CanvasRenderingContext2D; - this._ctx = new Context(ctx2d, { - width, - height, - contextWidth: contextWidth || width, - contextHeight: contextHeight || height, - devicePixelRatio - }); - } else if (target) { - // TODO - this._ctx = target as IDrawContext; - } + scroll(opts: { offsetTop?: number; offsetLeft?: number }) { + const { sharer } = this._opts; + const { data, scale, offsetTop, offsetBottom, offsetLeft, offsetRight } = sharer.getActiveStoreSnapshot(); + // TODO calc offset data + if (data) { + this.drawData(data, { + scale, + offsetTop, + offsetBottom, + offsetLeft, + offsetRight + }); } - - if ([DrawStatus.FREEZE].includes(this._status)) { - return; - } - const _data: QueueItem = deepClone({ data }) as QueueItem; - this._queue.push(_data); - // if (this._status !== DrawStatus.DRAWING) { - // this._status = DrawStatus.DRAWING; - // this._drawFrame(); - // } - this._drawFrame(); - this._loader.load(data, changeResourceUUIDs || []); - } - - getContext(): IDrawContext | null { - return this._ctx; - } - - thaw() { - this._status = DrawStatus.FREE; - } - - private _freeze() { - this._status = DrawStatus.FREEZE; - } - - private _drawFrame() { - if (this._status === DrawStatus.FREEZE) { - return; - } - requestAnimationFrame(() => { - if (this._status === DrawStatus.FREEZE) { - return; - } - const ctx = this._ctx; - - let item: QueueItem | undefined = this._queue[0]; - let isLastFrame = false; - if (this._queue.length > 1) { - item = this._queue.shift(); - } else { - isLastFrame = true; - } - if (this._loader.isComplete() !== true) { - this._drawFrame(); - if (item && ctx) { - drawContext(ctx, item.data, this._loader); - // this._board.draw(); - // this.trigger('drawFrame', { t: Date.now() }) - } - } else if (item && ctx) { - drawContext(ctx, item.data, this._loader); - // this._board.draw(); - // this.trigger('drawFrame', { t: Date.now() }) - this._retainQueueOneItem(); - if (!isLastFrame) { - this._drawFrame(); - } else { - this._status = DrawStatus.FREE; - } - } else { - this._status = DrawStatus.FREE; - } - this.trigger('drawFrame', { t: Date.now() }); - - if ( - this._loader.isComplete() === true && - this._queue.length === 1 && - this._status === DrawStatus.FREE - ) { - if (ctx && this._queue[0] && this._queue[0].data) { - drawContext(ctx, this._queue[0].data, this._loader); - } - this.trigger('drawFrameComplete', { t: Date.now() }); - this._freeze(); - } - }); - } - - private _retainQueueOneItem() { - if (this._queue.length <= 1) { - return; - } - const lastOne = deepClone(this._queue[this._queue.length - 1]); - this._queue = [lastOne]; + // sharer.setActiveStorage('scale', num); } } diff --git a/packages/renderer/src/lib/calculate.ts b/packages/renderer/src/lib/calculate.ts deleted file mode 100644 index 14c8bc4..0000000 --- a/packages/renderer/src/lib/calculate.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { DataElement, DataElemDesc, Point } from '@idraw/types'; - -export function parseRadianToAngle(radian: number): number { - return (radian / Math.PI) * 180; -} - -export function parseAngleToRadian(angle: number): number { - return (angle / 180) * Math.PI; -} - -export function calcElementCenter( - elem: DataElement -): Point { - const p = { - x: elem.x + elem.w / 2, - y: elem.y + elem.h / 2 - }; - return p; -} - -export function calcRadian(center: Point, start: Point, end: Point): number { - const startAngle = calcLineAngle(center, start); - const endAngle = calcLineAngle(center, end); - if (endAngle !== null && startAngle !== null) { - if (startAngle > (Math.PI * 3) / 2 && endAngle < Math.PI / 2) { - return endAngle + (Math.PI * 2 - startAngle); - } else if (endAngle > (Math.PI * 3) / 2 && startAngle < Math.PI / 2) { - return startAngle + (Math.PI * 2 - endAngle); - } else { - return endAngle - startAngle; - } - } else { - return 0; - } -} - -function calcLineAngle(center: Point, p: Point): number | null { - const x = p.x - center.x; - const y = center.y - p.y; - if (x === 0) { - if (y < 0) { - return Math.PI / 2; - } else if (y > 0) { - return Math.PI * (3 / 2); - } - } else if (y === 0) { - if (x < 0) { - return Math.PI; - } else if (x > 0) { - return 0; - } - } - if (x > 0 && y < 0) { - return Math.atan(Math.abs(y) / Math.abs(x)); - } else if (x < 0 && y < 0) { - return Math.PI - Math.atan(Math.abs(y) / Math.abs(x)); - } else if (x < 0 && y > 0) { - return Math.PI + Math.atan(Math.abs(y) / Math.abs(x)); - } else if (x > 0 && y > 0) { - return Math.PI * 2 - Math.atan(Math.abs(y) / Math.abs(x)); - } - return null; -} diff --git a/packages/renderer/src/lib/diff.ts b/packages/renderer/src/lib/diff.ts deleted file mode 100644 index c84ced1..0000000 --- a/packages/renderer/src/lib/diff.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { DataElement, IDrawData, DataElemDesc } from '@idraw/types'; - -type DataElementMap = { - [uuid: string]: DataElement; -}; - -export function isChangeImageElementResource( - before: DataElement<'image'>, - after: DataElement<'image'> -): boolean { - return before?.desc?.src !== after?.desc?.src; -} - -export function isChangeSVGElementResource( - before: DataElement<'svg'>, - after: DataElement<'svg'> -): boolean { - return before?.desc?.svg !== after?.desc?.svg; -} - -export function isChangeHTMLElementResource( - before: DataElement<'html'>, - after: DataElement<'html'> -): boolean { - return ( - before?.desc?.html !== after?.desc?.html || - before?.desc?.width !== after?.desc?.width || - before?.desc?.height !== after?.desc?.height - ); -} - -export function diffElementResourceChange( - before: DataElement, - after: DataElement -): string | null { - let result = null; - let isChange = false; - switch (after.type) { - case 'image': { - isChange = isChangeImageElementResource( - before as DataElement<'image'>, - after as DataElement<'image'> - ); - break; - } - case 'svg': { - isChange = isChangeSVGElementResource( - before as DataElement<'svg'>, - after as DataElement<'svg'> - ); - break; - } - case 'html': { - isChange = isChangeHTMLElementResource( - before as DataElement<'html'>, - after as DataElement<'html'> - ); - break; - } - default: - break; - } - if (isChange === true) { - result = after.uuid; - } - return result; -} - -export function diffElementResourceChangeList( - before: IDrawData, - after: IDrawData -): string[] { - const uuids: string[] = []; - const beforeMap = parseDataElementMap(before); - const afterMap = parseDataElementMap(after); - for (const uuid in afterMap) { - if (['image', 'svg', 'html'].includes(afterMap[uuid]?.type) !== true) { - continue; - } - if (beforeMap[uuid]) { - let isChange = false; - switch (beforeMap[uuid].type) { - case 'image': { - isChange = isChangeImageElementResource( - beforeMap[uuid] as DataElement<'image'>, - afterMap[uuid] as DataElement<'image'> - ); - break; - } - case 'svg': { - isChange = isChangeSVGElementResource( - beforeMap[uuid] as DataElement<'svg'>, - afterMap[uuid] as DataElement<'svg'> - ); - break; - } - case 'html': { - isChange = isChangeHTMLElementResource( - beforeMap[uuid] as DataElement<'html'>, - afterMap[uuid] as DataElement<'html'> - ); - break; - } - default: - break; - } - if (isChange === true) { - uuids.push(uuid); - } - } else { - uuids.push(uuid); - } - } - return uuids; -} - -function parseDataElementMap(data: IDrawData): DataElementMap { - const elemMap: DataElementMap = {}; - data.elements.forEach((elem) => { - elemMap[elem.uuid] = elem; - }); - return elemMap; -} diff --git a/packages/renderer/src/lib/draw/base.ts b/packages/renderer/src/lib/draw/base.ts deleted file mode 100644 index 877aa02..0000000 --- a/packages/renderer/src/lib/draw/base.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { - IDrawContext, - // DataElemDesc, - DataElement -} from '@idraw/types'; -import { is, istype, isColorStr } from '@idraw/util'; -import { rotateElement } from './../transform'; - -export function clearContext(ctx: IDrawContext) { - // ctx.setFillStyle('rgb(0 0 0 / 100%)'); - // ctx.setStrokeStyle('rgb(0 0 0 / 100%)'); - ctx.setFillStyle('#000000'); - ctx.setStrokeStyle('#000000'); - ctx.setLineDash([]); - ctx.setGlobalAlpha(1); - ctx.setShadowColor('#00000000'); - ctx.setShadowOffsetX(0); - ctx.setShadowOffsetY(0); - ctx.setShadowBlur(0); -} - -export function drawBgColor(ctx: IDrawContext, color: string) { - const size = ctx.getSize(); - ctx.setFillStyle(color); - ctx.fillRect(0, 0, size.contextWidth, size.contextHeight); -} - -export function drawBox( - ctx: IDrawContext, - elem: DataElement<'text' | 'rect'>, - pattern: string | CanvasPattern | null -): void { - clearContext(ctx); - drawBoxBorder(ctx, elem); - clearContext(ctx); - rotateElement(ctx, elem, () => { - const { x, y, w, h } = elem; - let r: number = elem.desc.borderRadius || 0; - r = Math.min(r, w / 2, h / 2); - if (w < r * 2 || h < r * 2) { - r = 0; - } - ctx.beginPath(); - ctx.moveTo(x + r, y); - ctx.arcTo(x + w, y, x + w, y + h, r); - ctx.arcTo(x + w, y + h, x, y + h, r); - ctx.arcTo(x, y + h, x, y, r); - ctx.arcTo(x, y, x + w, y, r); - ctx.closePath(); - if (typeof pattern === 'string') { - ctx.setFillStyle(pattern); - } else if (['CanvasPattern'].includes(istype.type(pattern))) { - ctx.setFillStyle(pattern as CanvasPattern); - } - ctx.fill(); - }); -} - -export function drawBoxBorder( - ctx: IDrawContext, - elem: DataElement<'text' | 'rect'> -): void { - clearContext(ctx); - rotateElement(ctx, elem, () => { - if (!(elem.desc.borderWidth && elem.desc.borderWidth > 0)) { - return; - } - const bw = elem.desc.borderWidth; - let borderColor = '#000000'; - if (isColorStr(elem.desc.borderColor) === true) { - borderColor = elem.desc.borderColor as string; - } - const x = elem.x - bw / 2; - const y = elem.y - bw / 2; - const w = elem.w + bw; - const h = elem.h + bw; - - let r: number = elem.desc.borderRadius || 0; - r = Math.min(r, w / 2, h / 2); - if (r < w / 2 && r < h / 2) { - r = r + bw / 2; - } - const { desc } = elem; - if (desc.shadowColor !== undefined && isColorStr(desc.shadowColor)) { - ctx.setShadowColor(desc.shadowColor); - } - if (desc.shadowOffsetX !== undefined && is.number(desc.shadowOffsetX)) { - ctx.setShadowOffsetX(desc.shadowOffsetX); - } - if (desc.shadowOffsetY !== undefined && is.number(desc.shadowOffsetY)) { - ctx.setShadowOffsetY(desc.shadowOffsetY); - } - if (desc.shadowBlur !== undefined && is.number(desc.shadowBlur)) { - ctx.setShadowBlur(desc.shadowBlur); - } - ctx.beginPath(); - ctx.setLineWidth(bw); - ctx.setStrokeStyle(borderColor); - ctx.moveTo(x + r, y); - ctx.arcTo(x + w, y, x + w, y + h, r); - ctx.arcTo(x + w, y + h, x, y + h, r); - ctx.arcTo(x, y + h, x, y, r); - ctx.arcTo(x, y, x + w, y, r); - ctx.closePath(); - ctx.stroke(); - }); -} diff --git a/packages/renderer/src/lib/draw/circle.ts b/packages/renderer/src/lib/draw/circle.ts deleted file mode 100644 index f5830f9..0000000 --- a/packages/renderer/src/lib/draw/circle.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { IDrawContext, DataElement } from '@idraw/types'; -import { rotateElement } from './../transform'; -import { clearContext } from './base'; - -export function drawCircle(ctx: IDrawContext, elem: DataElement<'circle'>) { - clearContext(ctx); - rotateElement(ctx, elem, (ctx) => { - const { x, y, w, h, desc } = elem; - const { - bgColor = '#000000', - borderColor = '#000000', - borderWidth = 0 - } = desc; - - const a = w / 2; - const b = h / 2; - const centerX = x + a; - const centerY = y + b; - - // draw border - if (borderWidth && borderWidth > 0) { - const ba = borderWidth / 2 + a; - const bb = borderWidth / 2 + b; - ctx.beginPath(); - ctx.setStrokeStyle(borderColor); - ctx.setLineWidth(borderWidth); - ctx.ellipse(centerX, centerY, ba, bb, 0, 0, 2 * Math.PI); - - ctx.closePath(); - ctx.stroke(); - } - - // draw content - ctx.beginPath(); - ctx.setFillStyle(bgColor); - ctx.ellipse(centerX, centerY, a, b, 0, 0, 2 * Math.PI); - ctx.closePath(); - ctx.fill(); - - // // draw shadow - // clearContext(ctx); - // if ((desc.shadowOffsetX !== undefined && is.number(desc.shadowOffsetX)) || desc.shadowOffsetY !== undefined && is.number(desc.shadowOffsetY)) { - - // if (desc.shadowColor !== undefined && util.color.isColorStr(desc.shadowColor)) { - // ctx.setShadowColor(desc.shadowColor); - // } - // if (desc.shadowOffsetX !== undefined && is.number(desc.shadowOffsetX)) { - // ctx.setShadowOffsetX(desc.shadowOffsetX); - // } - // if (desc.shadowOffsetY !== undefined && is.number(desc.shadowOffsetY)) { - // ctx.setShadowOffsetY(desc.shadowOffsetY); - // } - // if (desc.shadowBlur !== undefined && is.number(desc.shadowBlur)) { - // ctx.setShadowBlur(desc.shadowBlur); - // } - - // const a = (w + borderWidth * 2) / 2; - // const b = (h + borderWidth * 2) / 2; - // const centerX = x + a - borderWidth; - // const centerY = y + b - borderWidth; - // const unit = (a > b) ? 1 / a : 1 / b; - - // ctx.beginPath(); - // ctx.setFillStyle('#ffffff6a'); - // ctx.moveTo(centerX + a, centerY); - // for(var i = 0; i < 2 * Math.PI; i += unit) { - // ctx.lineTo(centerX + a * Math.cos(i), centerY + b * Math.sin(i)); - // } - // ctx.closePath(); - // ctx.fill(); - // } - }); -} diff --git a/packages/renderer/src/lib/draw/html.ts b/packages/renderer/src/lib/draw/html.ts deleted file mode 100644 index e69533c..0000000 --- a/packages/renderer/src/lib/draw/html.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { IDrawContext, DataElement } from '@idraw/types'; -import { rotateElement } from '../transform'; -import Loader from '../loader'; - -export function drawHTML( - ctx: IDrawContext, - elem: DataElement<'html'>, - loader: Loader -) { - const content = loader.getContent(elem.uuid); - rotateElement(ctx, elem, () => { - if (content) { - ctx.drawImage(content, elem.x, elem.y, elem.w, elem.h); - } - }); -} diff --git a/packages/renderer/src/lib/draw/image.ts b/packages/renderer/src/lib/draw/image.ts deleted file mode 100644 index aec9682..0000000 --- a/packages/renderer/src/lib/draw/image.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { IDrawContext, DataElement } from '@idraw/types'; -import { rotateElement } from '../transform'; -import Loader from '../loader'; - -export function drawImage( - ctx: IDrawContext, - elem: DataElement<'image'>, - loader: Loader -) { - // const desc = elem.desc as DataElemDesc['rect']; - const content = loader.getContent(elem.uuid); - rotateElement(ctx, elem, () => { - // ctx.setFillStyle(desc.color); - // ctx.fillRect(elem.x, elem.y, elem.w, elem.h); - if (content) { - // ctx.drawImage(content, 0, 0, elem.w, elem.h, elem.x, elem.y, elem.w, elem.h); - ctx.drawImage(content, elem.x, elem.y, elem.w, elem.h); - } - }); -} - -// import { -// IDrawContext, -// DataElement, -// HelperConfig, -// DataElemDesc, -// } from '@idraw/types'; -// import Loader from '../loader'; -// import { drawBox } from './base'; - -// export function drawImage( -// ctx: IDrawContext, -// elem: DataElement<'image'>, -// loader: Loader, -// helperConfig: HelperConfig -// ) { -// const content = loader.getPattern(elem, { -// forceUpdate: helperConfig?.selectedElementWrapper?.uuid === elem.uuid -// }); -// drawBox(ctx, elem, content); -// } diff --git a/packages/renderer/src/lib/draw/index.ts b/packages/renderer/src/lib/draw/index.ts deleted file mode 100644 index 571d219..0000000 --- a/packages/renderer/src/lib/draw/index.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { - IDrawContext, - IDrawData, - DataElement - // Point, -} from '@idraw/types'; -import { isColorStr } from '@idraw/util'; -import Loader from '../loader'; -import { clearContext, drawBgColor } from './base'; -import { drawRect } from './rect'; -import { drawImage } from './image'; -import { drawSVG } from './svg'; -import { drawHTML } from './html'; -import { drawText } from './text'; -import { drawCircle } from './circle'; - -export function drawContext( - ctx: IDrawContext, - data: IDrawData, - loader: Loader -): void { - clearContext(ctx); - const size = ctx.getSize(); - ctx.clearRect(0, 0, size.contextWidth, size.contextHeight); - - if (typeof data.bgColor === 'string' && isColorStr(data.bgColor)) { - drawBgColor(ctx, data.bgColor); - } - - if (!(data.elements.length > 0)) { - return; - } - for (let i = 0; i < data.elements.length; i++) { - const elem = data.elements[i]; - if (elem?.operation?.invisible === true) { - continue; - } - switch (elem.type) { - case 'rect': { - drawRect(ctx, elem as DataElement<'rect'>); - break; - } - case 'text': { - drawText(ctx, elem as DataElement<'text'>, loader); - break; - } - case 'image': { - drawImage(ctx, elem as DataElement<'image'>, loader); - break; - } - case 'svg': { - drawSVG(ctx, elem as DataElement<'svg'>, loader); - break; - } - case 'html': { - drawHTML(ctx, elem as DataElement<'html'>, loader); - break; - } - case 'circle': { - drawCircle(ctx, elem as DataElement<'circle'>); - break; - } - default: { - // nothing - break; - } - } - } -} diff --git a/packages/renderer/src/lib/draw/rect.ts b/packages/renderer/src/lib/draw/rect.ts deleted file mode 100644 index 0d3f98d..0000000 --- a/packages/renderer/src/lib/draw/rect.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { IDrawContext, DataElement } from '@idraw/types'; -import { drawBox } from './base'; - -export function drawRect(ctx: IDrawContext, elem: DataElement<'rect'>) { - drawBox(ctx, elem, elem.desc.bgColor as string); -} diff --git a/packages/renderer/src/lib/draw/svg.ts b/packages/renderer/src/lib/draw/svg.ts deleted file mode 100644 index 406bb39..0000000 --- a/packages/renderer/src/lib/draw/svg.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { IDrawContext, DataElement } from '@idraw/types'; -import { rotateElement } from '../transform'; -import Loader from '../loader'; - -export function drawSVG( - ctx: IDrawContext, - elem: DataElement<'svg'>, - loader: Loader -) { - // const desc = elem.desc as DataElemDesc['rect']; - const content = loader.getContent(elem.uuid); - rotateElement(ctx, elem, () => { - // ctx.setFillStyle(desc.color); - // ctx.fillRect(elem.x, elem.y, elem.w, elem.h); - if (content) { - // ctx.drawImage(content, 0, 0, elem.w, elem.h, elem.x, elem.y, elem.w, elem.h); - ctx.drawImage(content, elem.x, elem.y, elem.w, elem.h); - } - }); -} - -// import { -// IDrawContext, -// DataElement, -// HelperConfig, -// } from '@idraw/types'; -// import Loader from '../loader'; -// import { drawBox } from './base'; - -// export function drawSVG( -// ctx: IDrawContext, -// elem: DataElement<'svg'>, -// loader: Loader, -// helperConfig: HelperConfig -// ) { -// const content = loader.getPattern(elem, { -// forceUpdate: helperConfig?.selectedElementWrapper?.uuid === elem.uuid -// }); -// drawBox(ctx, elem, content); -// } diff --git a/packages/renderer/src/lib/draw/text.ts b/packages/renderer/src/lib/draw/text.ts deleted file mode 100644 index b9e9f7e..0000000 --- a/packages/renderer/src/lib/draw/text.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { IDrawContext, DataElemDescText, DataElement } from '@idraw/types'; -import { is, isColorStr } from '@idraw/util'; -import Loader from '../loader'; -import { clearContext, drawBox } from './base'; -import { rotateElement } from './../transform'; - -export function drawText( - ctx: IDrawContext, - elem: DataElement<'text'>, - loader: Loader -) { - clearContext(ctx); - drawBox(ctx, elem, elem.desc.bgColor || 'transparent'); - rotateElement(ctx, elem, () => { - const desc: DataElemDescText = { - ...{ - fontSize: 12, - fontFamily: 'sans-serif', - textAlign: 'center' - }, - ...elem.desc - }; - ctx.setFillStyle(elem.desc.color); - ctx.setTextBaseline('top'); - ctx.setFont({ - fontWeight: desc.fontWeight, - fontSize: desc.fontSize, - fontFamily: desc.fontFamily - }); - const descText = desc.text.replace(/\r\n/gi, '\n'); - const fontHeight = desc.lineHeight || desc.fontSize; - const descTextList = descText.split('\n'); - const lines: { text: string; width: number }[] = []; - - let lineNum = 0; - descTextList.forEach((tempText: string, idx: number) => { - let lineText = ''; - - if (tempText.length > 0) { - for (let i = 0; i < tempText.length; i++) { - if ( - ctx.measureText(lineText + (tempText[i] || '')).width < - ctx.calcDeviceNum(elem.w) - ) { - lineText += tempText[i] || ''; - } else { - lines.push({ - text: lineText, - width: ctx.calcScreenNum(ctx.measureText(lineText).width) - }); - lineText = tempText[i] || ''; - lineNum++; - } - if ((lineNum + 1) * fontHeight > elem.h) { - break; - } - if (tempText.length - 1 === i) { - if ((lineNum + 1) * fontHeight < elem.h) { - lines.push({ - text: lineText, - width: ctx.calcScreenNum(ctx.measureText(lineText).width) - }); - if (idx < descTextList.length - 1) { - lineNum++; - } - break; - } - } - } - } else { - lines.push({ - text: '', - width: 0 - }); - } - }); - - let startY = 0; - if (lines.length * fontHeight < elem.h) { - if (elem.desc.verticalAlign === 'top') { - startY = 0; - } else if (elem.desc.verticalAlign === 'bottom') { - startY += elem.h - lines.length * fontHeight; - } else { - // middle and default - startY += (elem.h - lines.length * fontHeight) / 2; - } - } - - // draw text lines - { - const _y = elem.y + startY; - if ( - desc.textShadowColor !== undefined && - isColorStr(desc.textShadowColor) - ) { - ctx.setShadowColor(desc.textShadowColor); - } - if ( - desc.textShadowOffsetX !== undefined && - is.number(desc.textShadowOffsetX) - ) { - ctx.setShadowOffsetX(desc.textShadowOffsetX); - } - if ( - desc.textShadowOffsetY !== undefined && - is.number(desc.textShadowOffsetY) - ) { - ctx.setShadowOffsetY(desc.textShadowOffsetY); - } - if (desc.textShadowBlur !== undefined && is.number(desc.textShadowBlur)) { - ctx.setShadowBlur(desc.textShadowBlur); - } - lines.forEach((line, i) => { - let _x = elem.x; - if (desc.textAlign === 'center') { - _x = elem.x + (elem.w - line.width) / 2; - } else if (desc.textAlign === 'right') { - _x = elem.x + (elem.w - line.width); - } - ctx.fillText(line.text, _x, _y + fontHeight * i); - }); - clearContext(ctx); - } - - // draw text stroke - if ( - isColorStr(desc.strokeColor) && - desc.strokeWidth !== undefined && - desc.strokeWidth > 0 - ) { - const _y = elem.y + startY; - lines.forEach((line, i) => { - let _x = elem.x; - if (desc.textAlign === 'center') { - _x = elem.x + (elem.w - line.width) / 2; - } else if (desc.textAlign === 'right') { - _x = elem.x + (elem.w - line.width); - } - if (desc.strokeColor !== undefined) { - ctx.setStrokeStyle(desc.strokeColor); - } - if (desc.strokeWidth !== undefined && desc.strokeWidth > 0) { - ctx.setLineWidth(desc.strokeWidth); - } - ctx.strokeText(line.text, _x, _y + fontHeight * i); - }); - } - }); -} - -// export function createTextSVG(elem: DataElement<'text'>): string { -// const svg = ` -// -// -//
-// ${elem.desc.text || ''} -//
-//
-//
-// `; -// return svg; -// } diff --git a/packages/renderer/src/lib/index.ts b/packages/renderer/src/lib/index.ts deleted file mode 100644 index 4745827..0000000 --- a/packages/renderer/src/lib/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './diff'; -export * from './loader-event'; -export * from './loader'; -export * from './parse'; -export * from './temp'; -export * from './value'; diff --git a/packages/renderer/src/lib/loader-event.ts b/packages/renderer/src/lib/loader-event.ts deleted file mode 100644 index 01d6325..0000000 --- a/packages/renderer/src/lib/loader-event.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { DataElement, DataElemDesc } from '@idraw/types'; - -export type TypeLoadDataItem = { - uuid: string; - type: 'image' | 'svg' | 'html'; - status: 'null' | 'loaded' | 'fail'; - content: null | HTMLImageElement | HTMLCanvasElement; - elemW: number; - elemH: number; - source: string; - element: DataElement; - error?: any; -}; - -export type TypeLoadData = { - [uuid: string]: TypeLoadDataItem; -}; - -export type TypeLoaderEventArgMap = { - complete: void; - load: TypeLoadData[string]; - error: TypeLoadData[string]; -}; - -export interface TypeLoaderEvent { - on( - key: T, - callback: (p: TypeLoaderEventArgMap[T]) => void - ): void; - off( - key: T, - callback: (p: TypeLoaderEventArgMap[T]) => void - ): void; - trigger( - key: T, - p: TypeLoaderEventArgMap[T] - ): void; -} - -export class LoaderEvent implements TypeLoaderEvent { - private _listeners: Map void)[]>; - - constructor() { - this._listeners = new Map(); - } - - on( - eventKey: T, - callback: (p: TypeLoaderEventArgMap[T]) => void - ) { - if (this._listeners.has(eventKey)) { - const callbacks = this._listeners.get(eventKey); - callbacks?.push(callback); - this._listeners.set(eventKey, callbacks || []); - } else { - this._listeners.set(eventKey, [callback]); - } - } - - off( - eventKey: T, - callback: (p: TypeLoaderEventArgMap[T]) => void - ) { - 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) { - callbacks.splice(i, 1); - break; - } - } - } - this._listeners.set(eventKey, callbacks || []); - } - } - - trigger( - eventKey: T, - arg: TypeLoaderEventArgMap[T] - ) { - const callbacks = this._listeners.get(eventKey); - if (Array.isArray(callbacks)) { - callbacks.forEach((cb) => { - cb(arg); - }); - return true; - } else { - return false; - } - } - - has(name: string) { - if (this._listeners.has(name)) { - const list: ((p: TypeLoaderEventArgMap[T]) => void)[] | undefined = - this._listeners.get(name); - if (Array.isArray(list) && list.length > 0) { - return true; - } - } - return false; - } -} diff --git a/packages/renderer/src/lib/loader.ts b/packages/renderer/src/lib/loader.ts deleted file mode 100644 index 8b4ffab..0000000 --- a/packages/renderer/src/lib/loader.ts +++ /dev/null @@ -1,347 +0,0 @@ -import { IDrawData, DataElement } from '@idraw/types'; -import { loadImage, loadSVG, loadHTML, deepClone } from '@idraw/util'; -import { - LoaderEvent, - TypeLoadData, - TypeLoaderEventArgMap -} from './loader-event'; -import { filterScript } from './../util/filter'; - -type Options = { - maxParallelNum: number; -}; - -enum LoaderStatus { - FREE = 'free', - LOADING = 'loading', - COMPLETE = 'complete' -} - -export default class Loader { - private _opts: Options; - private _event: LoaderEvent; - // private _patternMap: {[uuid: string]: CanvasPattern} = {} - private _currentLoadData: TypeLoadData = {}; - private _currentUUIDQueue: string[] = []; - private _storageLoadData: TypeLoadData = {}; - private _status: LoaderStatus = LoaderStatus.FREE; - - private _waitingLoadQueue: Array<{ - uuidQueue: string[]; - loadData: TypeLoadData; - }> = []; - - constructor(opts: Options) { - this._opts = opts; - this._event = new LoaderEvent(); - this._waitingLoadQueue = []; - } - - load(data: IDrawData, changeResourceUUIDs: string[]): void { - const [uuidQueue, loadData] = this._resetLoadData( - data, - changeResourceUUIDs - ); - if ( - this._status === LoaderStatus.FREE || - this._status === LoaderStatus.COMPLETE - ) { - this._currentUUIDQueue = uuidQueue; - this._currentLoadData = loadData; - this._loadTask(); - } else if (this._status === LoaderStatus.LOADING && uuidQueue.length > 0) { - this._waitingLoadQueue.push({ - uuidQueue, - loadData - }); - } - } - - on( - name: T, - callback: (arg: TypeLoaderEventArgMap[T]) => void - ) { - this._event.on(name, callback); - } - - off( - name: T, - callback: (arg: TypeLoaderEventArgMap[T]) => void - ) { - this._event.off(name, callback); - } - - isComplete() { - return this._status === LoaderStatus.COMPLETE; - } - - getContent(uuid: string): null | HTMLImageElement | HTMLCanvasElement { - if (this._storageLoadData[uuid]?.status === 'loaded') { - return this._storageLoadData[uuid].content; - } - return null; - } - - // getPattern( - // elem: DataElement, - // opts?: { - // forceUpdate: boolean - // } - // ): null | CanvasPattern { - // if (this._patternMap[elem.uuid] ) { - // if (!(opts && opts.forceUpdate === true)) { - // return this._patternMap[elem.uuid]; - // } - // } - // const item = this._currentLoadData[elem.uuid]; - // if (item?.status === 'loaded') { - // const board = this._opts.board; - // const tempCanvas = board.createCanvas(); - // const tempCtx = board.createContext(tempCanvas); - // const image = this.getContent(elem.uuid); - // tempCtx.drawImage(image, elem.x, elem.y, elem.w, elem.h); - - // const canvas = board.createCanvas(); - // const ctx = board.createContext(canvas); - // const pattern = ctx.createPattern(tempCanvas, 'no-repeat'); - // if (pattern) this._patternMap[elem.uuid] = pattern; - // return pattern; - // } - // return null; - // } - - private _resetLoadData( - data: IDrawData, - changeResourceUUIDs: string[] - ): [string[], TypeLoadData] { - const loadData: TypeLoadData = {}; - const uuidQueue: string[] = []; - - const storageLoadData = this._storageLoadData; - // const currentUUIDs: string[] = [] - - // add new load-data - for (let i = data.elements.length - 1; i >= 0; i--) { - const elem = data.elements[i] as DataElement<'image' | 'svg' | 'html'>; - // currentUUIDs.push(elem.uuid); - if (['image', 'svg', 'html'].includes(elem.type)) { - if (!storageLoadData[elem.uuid]) { - loadData[elem.uuid] = this._createEmptyLoadItem(elem); - uuidQueue.push(elem.uuid); - } else { - if (changeResourceUUIDs.includes(elem.uuid)) { - loadData[elem.uuid] = this._createEmptyLoadItem(elem); - uuidQueue.push(elem.uuid); - } - // if (elem.type === 'image') { - // const _ele = elem as DataElement<'image'>; - // if (_ele.desc.src !== storageLoadData[elem.uuid].source) { - // loadData[elem.uuid] = this._createEmptyLoadItem(elem); - // uuidQueue.push(elem.uuid); - // } - // } else if (elem.type === 'svg') { - // const _ele = elem as DataElement<'svg'>; - // if (_ele.desc.svg !== storageLoadData[elem.uuid].source) { - // loadData[elem.uuid] = this._createEmptyLoadItem(elem); - // uuidQueue.push(elem.uuid); - // } - // } else if (elem.type === 'html') { - // const _ele = elem as DataElement<'html'>; - // if (filterScript(_ele.desc.html) !== storageLoadData[elem.uuid].source) { - // loadData[elem.uuid] = this._createEmptyLoadItem(elem); - // uuidQueue.push(elem.uuid); - // } - // } - } - } - } - - // const loadDataUUIDs = Object.keys(loadData); - // // clear unuse load-data - // loadDataUUIDs.forEach((loadUUID) => { - // if (currentUUIDs.includes(loadUUID) !== true) { - // delete loadData[loadUUID]; - // } - // }); - return [uuidQueue, loadData]; - } - - private _createEmptyLoadItem( - elem: DataElement<'image' | 'svg' | 'html'> - ): TypeLoadData[string] { - let source = ''; - - const type: TypeLoadData[string]['type'] = - elem.type as TypeLoadData[string]['type']; - let elemW: number = elem.w; - let elemH: number = elem.h; - if (elem.type === 'image') { - const _elem = elem as DataElement<'image'>; - source = _elem.desc.src || ''; - } else if (elem.type === 'svg') { - const _elem = elem as DataElement<'svg'>; - source = _elem.desc.svg || ''; - } else if (elem.type === 'html') { - const _elem = elem as DataElement<'html'>; - source = filterScript(_elem.desc.html || ''); - elemW = _elem.desc.width || elem.w; - elemH = _elem.desc.height || elem.h; - } - return { - uuid: elem.uuid, - type: type, - status: 'null', - content: null, - source, - elemW, - elemH, - element: deepClone(elem) - }; - } - - private _loadTask() { - if (this._status === LoaderStatus.LOADING) { - return; - } - this._status = LoaderStatus.LOADING; - - if (this._currentUUIDQueue.length === 0) { - if (this._waitingLoadQueue.length === 0) { - this._status = LoaderStatus.COMPLETE; - this._event.trigger('complete', undefined); - return; - } else { - const waitingItem = this._waitingLoadQueue.shift(); - if (waitingItem) { - const { uuidQueue, loadData } = waitingItem; - this._currentLoadData = loadData; - this._currentUUIDQueue = uuidQueue; - } - } - } - - const { maxParallelNum } = this._opts; - const uuids = this._currentUUIDQueue.splice(0, maxParallelNum); - const uuidMap: { [uuid: string]: number } = {}; - - uuids.forEach((url, i) => { - uuidMap[url] = i; - }); - const loadUUIDList: string[] = []; - const _loadAction = () => { - if (loadUUIDList.length >= maxParallelNum) { - return false; - } - if (uuids.length === 0) { - return true; - } - - for (let i = loadUUIDList.length; i < maxParallelNum; i++) { - const uuid = uuids.shift(); - if (uuid === undefined) { - break; - } - loadUUIDList.push(uuid); - - this._loadElementSource(this._currentLoadData[uuid]) - .then((image) => { - loadUUIDList.splice(loadUUIDList.indexOf(uuid), 1); - const status = _loadAction(); - - this._storageLoadData[uuid] = { - uuid, - type: this._currentLoadData[uuid].type, - status: 'loaded', - content: image, - source: this._currentLoadData[uuid].source, - elemW: this._currentLoadData[uuid].elemW, - elemH: this._currentLoadData[uuid].elemH, - element: this._currentLoadData[uuid].element - }; - - if ( - loadUUIDList.length === 0 && - uuids.length === 0 && - status === true - ) { - this._status = LoaderStatus.FREE; - this._loadTask(); - } - this._event.trigger('load', { - uuid: this._storageLoadData[uuid]?.uuid, - type: this._storageLoadData[uuid].type, - status: this._storageLoadData[uuid].status, - content: this._storageLoadData[uuid].content, - source: this._storageLoadData[uuid].source, - elemW: this._storageLoadData[uuid].elemW, - elemH: this._storageLoadData[uuid].elemH, - element: this._storageLoadData[uuid]?.element - }); - }) - .catch((err) => { - console.warn(err); - - loadUUIDList.splice(loadUUIDList.indexOf(uuid), 1); - const status = _loadAction(); - - if (this._currentLoadData[uuid]) { - this._storageLoadData[uuid] = { - uuid, - type: this._currentLoadData[uuid]?.type, - status: 'fail', - content: null, - error: err, - source: this._currentLoadData[uuid]?.source, - elemW: this._currentLoadData[uuid]?.elemW, - elemH: this._currentLoadData[uuid]?.elemH, - element: this._currentLoadData[uuid]?.element - }; - } - - if ( - loadUUIDList.length === 0 && - uuids.length === 0 && - status === true - ) { - this._status = LoaderStatus.FREE; - this._loadTask(); - } - - if (this._currentLoadData[uuid]) { - this._event.trigger('error', { - uuid: uuid, - type: this._storageLoadData[uuid]?.type, - status: this._storageLoadData[uuid]?.status, - content: this._storageLoadData[uuid]?.content, - source: this._storageLoadData[uuid]?.source, - elemW: this._storageLoadData[uuid]?.elemW, - elemH: this._storageLoadData[uuid]?.elemH, - element: this._storageLoadData[uuid]?.element - }); - } - }); - } - return false; - }; - _loadAction(); - } - - private async _loadElementSource( - params: TypeLoadData[string] - ): Promise { - if (params && params.type === 'image') { - const image = await loadImage(params.source); - return image; - } else if (params && params.type === 'svg') { - const image = await loadSVG(params.source); - return image; - } else if (params && params.type === 'html') { - const image = await loadHTML(params.source, { - width: params.elemW, - height: params.elemH - }); - return image; - } - throw Error("Element's source is not support!"); - } -} diff --git a/packages/renderer/src/lib/parse.ts b/packages/renderer/src/lib/parse.ts deleted file mode 100644 index 3cdcbae..0000000 --- a/packages/renderer/src/lib/parse.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { IDrawData, DataElement, DataElemDesc } from '@idraw/types'; -import { elementNames } from './../constant/element'; - -export function parseData(data: any): IDrawData { - const result: IDrawData = { - elements: [] - }; - if (Array.isArray(data?.elements)) { - data?.elements.forEach((elem: any = {}) => { - if (isElement(elem)) { - result.elements.push(elem); - } - }); - } - if (typeof data.bgColor === 'string') { - result.bgColor = data.bgColor; - } - return result; -} - -function isElement(elem: DataElement): boolean { - if ( - !( - isNumber(elem.x) && - isNumber(elem.y) && - isNumber(elem.w) && - isNumber(elem.h) - ) - ) { - return false; - } - if (!(typeof elem.type === 'string' && elementNames.includes(elem.type))) { - return false; - } - return true; -} - -function isNumber(num: any) { - return num >= 0 || num < 0; -} diff --git a/packages/renderer/src/lib/renderer-event.ts b/packages/renderer/src/lib/renderer-event.ts deleted file mode 100644 index 7139769..0000000 --- a/packages/renderer/src/lib/renderer-event.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { DataElement, DataElemDesc } from '@idraw/types'; - -export type TypeRendererEventArgMap = { - drawFrame: { t: number }; - drawFrameComplete: { t: number }; - load: { element: DataElement }; - loadComplete: { t: number }; - error: { element: DataElement; error: any }; -}; - -export interface TypeRendererEvent { - on( - key: T, - callback: (p: TypeRendererEventArgMap[T]) => void - ): void; - off( - key: T, - callback: (p: TypeRendererEventArgMap[T]) => void - ): void; - trigger( - key: T, - p: TypeRendererEventArgMap[T] - ): void; -} - -export class RendererEvent implements TypeRendererEvent { - private _listeners: Map void)[]>; - - constructor() { - this._listeners = new Map(); - } - - on( - eventKey: T, - callback: (p: TypeRendererEventArgMap[T]) => void - ) { - if (this._listeners.has(eventKey)) { - const callbacks = this._listeners.get(eventKey); - callbacks?.push(callback); - this._listeners.set(eventKey, callbacks || []); - } else { - this._listeners.set(eventKey, [callback]); - } - } - - off( - eventKey: T, - callback: (p: TypeRendererEventArgMap[T]) => void - ) { - 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) { - callbacks.splice(i, 1); - break; - } - } - } - this._listeners.set(eventKey, callbacks || []); - } - } - - trigger( - eventKey: T, - arg: TypeRendererEventArgMap[T] - ) { - const callbacks = this._listeners.get(eventKey); - if (Array.isArray(callbacks)) { - callbacks.forEach((cb) => { - cb(arg); - }); - return true; - } else { - return false; - } - } - - has(name: string) { - if (this._listeners.has(name)) { - const list: ((p: TypeRendererEventArgMap[T]) => void)[] | undefined = - this._listeners.get(name); - if (Array.isArray(list) && list.length > 0) { - return true; - } - } - return false; - } -} diff --git a/packages/renderer/src/lib/temp.ts b/packages/renderer/src/lib/temp.ts deleted file mode 100644 index 6baff8d..0000000 --- a/packages/renderer/src/lib/temp.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { HelperWrapperControllerDirection, Point } from '@idraw/types'; -import { Mode, CursorStatus } from './../constant/static'; - -type TempDataDesc = { - hasInited: boolean; - onlyRender: boolean; - mode: Mode; - cursorStatus: CursorStatus; - selectedUUID: string | null; - selectedUUIDList: string[]; - hoverUUID: string | null; - selectedControllerDirection: HelperWrapperControllerDirection | null; - hoverControllerDirection: HelperWrapperControllerDirection | null; - prevPoint: Point | null; -}; - -function createData(): TempDataDesc { - return { - onlyRender: false, - hasInited: false, - mode: Mode.NULL, - cursorStatus: CursorStatus.NULL, - selectedUUID: null, - selectedUUIDList: [], - hoverUUID: null, - selectedControllerDirection: null, - hoverControllerDirection: null, - prevPoint: null - }; -} - -export class TempData { - private _temp: TempDataDesc; - - constructor() { - this._temp = createData(); - } - - set(name: T, value: TempDataDesc[T]) { - this._temp[name] = value; - } - - get(name: T): TempDataDesc[T] { - return this._temp[name]; - } - - clear() { - this._temp = createData(); - } -} diff --git a/packages/renderer/src/lib/transform.ts b/packages/renderer/src/lib/transform.ts deleted file mode 100644 index 7cfdfbd..0000000 --- a/packages/renderer/src/lib/transform.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { IDrawContext, Point, DataElement, DataElemDesc } from '@idraw/types'; -import { calcElementCenter, parseAngleToRadian } from './calculate'; - -function rotateElement( - ctx: IDrawContext, - elem: DataElement, - callback: (ctx: IDrawContext) => void -): void { - const center: Point = calcElementCenter(elem); - const radian = parseAngleToRadian(elem.angle || 0); - return rotateContext(ctx, center, radian || 0, callback); -} - -function rotateContext( - ctx: IDrawContext, - center: Point | undefined, - radian: number, - callback: (ctx: IDrawContext) => void -): void { - if (center && (radian > 0 || radian < 0)) { - ctx.translate(center.x, center.y); - ctx.rotate(radian); - ctx.translate(-center.x, -center.y); - } - - callback(ctx); - - if (center && (radian > 0 || radian < 0)) { - ctx.translate(center.x, center.y); - ctx.rotate(-radian); - ctx.translate(-center.x, -center.y); - } -} - -export { rotateContext, rotateElement }; diff --git a/packages/renderer/src/lib/value.ts b/packages/renderer/src/lib/value.ts deleted file mode 100644 index c224a9b..0000000 --- a/packages/renderer/src/lib/value.ts +++ /dev/null @@ -1,9 +0,0 @@ - -export function limitNum(num: number): number { - const numStr: string = num.toFixed(2); - return parseFloat(numStr); -} - -export function limitAngle(angle: number): number { - return limitNum(angle % 360); -} diff --git a/packages/renderer/src/loader.ts b/packages/renderer/src/loader.ts new file mode 100644 index 0000000..b73547e --- /dev/null +++ b/packages/renderer/src/loader.ts @@ -0,0 +1,118 @@ +import type { RendererLoader, LoaderEventMap, LoadFunc, LoadContent, LoadItem, LoadElementType, Element } from '@idraw/types'; +import { loadImage, loadHTML, loadSVG, EventEmitter } from '@idraw/util'; + +interface LoadItemMap { + [uuid: string]: LoadItem; +} + +const supportElementTypes: LoadElementType[] = ['image', 'svg', 'html']; + +export class Loader extends EventEmitter implements RendererLoader { + private _loadFuncMap: Record> = {}; + private _currentLoadItemMap: LoadItemMap = {}; + private _storageLoadItemMap: LoadItemMap = {}; + + constructor() { + super(); + this._registerLoadFunc<'image'>('image', async (elem: Element<'image'>) => { + const content = await loadImage(elem.desc.src); + return { + uuid: elem.uuid, + lastModified: Date.now(), + content + }; + }); + this._registerLoadFunc<'html'>('html', async (elem: Element<'html'>) => { + const content = await loadHTML(elem.desc.html, elem.desc); + return { + uuid: elem.uuid, + lastModified: Date.now(), + content + }; + }); + this._registerLoadFunc<'svg'>('svg', async (elem: Element<'svg'>) => { + const content = await loadSVG(elem.desc.svg); + return { + uuid: elem.uuid, + lastModified: Date.now(), + content + }; + }); + } + private _registerLoadFunc(type: T, func: LoadFunc) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + this._loadFuncMap[type] = func; + } + + private _createLoadItem(element: Element): LoadItem { + return { + element, + status: 'null', + content: null, + error: null, + startTime: -1, + endTime: -1 + }; + } + + private _emitLoad(item: LoadItem) { + const uuid = item.element.uuid; + const storageItem = this._storageLoadItemMap[uuid]; + if (storageItem) { + if (storageItem.startTime < item.startTime) { + this._storageLoadItemMap[uuid] = item; + this.trigger('load', { ...item, countTime: item.endTime - item.startTime }); + } + } else { + this._storageLoadItemMap[uuid] = item; + this.trigger('load', { ...item, countTime: item.endTime - item.startTime }); + } + } + + private _emitError(item: LoadItem) { + const uuid = item.element.uuid; + const storageItem = this._storageLoadItemMap[uuid]; + if (storageItem) { + if (storageItem.startTime < item.startTime) { + this._storageLoadItemMap[uuid] = item; + this.trigger('error', { ...item, countTime: item.endTime - item.startTime }); + } + } else { + this._storageLoadItemMap[uuid] = item; + this.trigger('error', { ...item, countTime: item.endTime - item.startTime }); + } + } + + private _loadResource(element: Element) { + const item = this._createLoadItem(element); + this._currentLoadItemMap[element.uuid] = item; + const loadFunc = this._loadFuncMap[element.type]; + if (typeof loadFunc === 'function') { + item.startTime = Date.now(); + loadFunc(element) + .then((result) => { + item.content = result.content; + item.endTime = Date.now(); + item.status = 'load'; + this._emitLoad(item); + }) + .catch((err: Error) => { + item.endTime = Date.now(); + item.status = 'error'; + item.error = err; + this._emitError(item); + }); + } + } + + load(element: Element) { + if (supportElementTypes.includes(element.type)) { + this._loadResource(element); + } + } + + getContent(uuid: string): LoadContent | null { + return this._storageLoadItemMap?.[uuid]?.content || null; + } +} diff --git a/packages/renderer/src/util/filter.ts b/packages/renderer/src/util/filter.ts deleted file mode 100644 index df84741..0000000 --- a/packages/renderer/src/util/filter.ts +++ /dev/null @@ -1,4 +0,0 @@ - -export function filterScript(html: string) { - return html.replace(//ig, ''); -} \ No newline at end of file diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 773f851..1e364e0 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -7,3 +7,6 @@ export * from './lib/board'; export * from './lib/renderer'; export * from './lib/loader'; export * from './lib/store'; +export * from './lib/watcher'; +export * from './lib/core'; +export * from './lib/idraw'; diff --git a/packages/types/src/lib/board.ts b/packages/types/src/lib/board.ts index 121ed97..f18a789 100644 --- a/packages/types/src/lib/board.ts +++ b/packages/types/src/lib/board.ts @@ -1,8 +1,9 @@ import type { Point } from './point'; import type { ViewContent, ViewCalculator } from './view'; import type { UtilEventEmitter } from './util'; -import type { Renderer } from '../renderer'; import type { ActiveStore, StoreSharer } from './store'; +import type { RendererEventMap, RendererOptions, RendererDrawOptions } from './renderer'; +import type { Data } from './data'; interface BoardWatcherPointEvent { point: Point; @@ -64,12 +65,13 @@ export interface BoardViewerFrameSnapshot { } export interface BoardViewerEventMap { + // eslint-disable-next-line @typescript-eslint/ban-types drawFrame: {}; } export interface BoardViewerOptions { sharer: StoreSharer; - renderer: Renderer; + renderer: BoardRenderer; viewContent: ViewContent; beforeDrawFrame: (e: { snapshot: BoardViewerFrameSnapshot }) => void; afterDrawFrame: (e: { snapshot: BoardViewerFrameSnapshot }) => void; @@ -78,3 +80,9 @@ export interface BoardViewerOptions { export interface BoardViewer extends UtilEventEmitter { drawFrame(): void; } + +export interface BoardRenderer extends UtilEventEmitter { + updateOptions(opts: RendererOptions): void; + drawData(data: Data, opts: RendererDrawOptions): void; + scale(num: number): void; +} diff --git a/packages/types/src/lib/core.ts b/packages/types/src/lib/core.ts new file mode 100644 index 0000000..3ea84a0 --- /dev/null +++ b/packages/types/src/lib/core.ts @@ -0,0 +1,7 @@ +export interface CoreOptions { + width: number; + height: number; + contextWidth?: number; + contextHeight?: number; + onlyRender?: boolean; +} diff --git a/packages/types/src/lib/idraw.ts b/packages/types/src/lib/idraw.ts new file mode 100644 index 0000000..7885ce9 --- /dev/null +++ b/packages/types/src/lib/idraw.ts @@ -0,0 +1,3 @@ +import type { CoreOptions } from './core'; + +export type IDrawOptions = CoreOptions; diff --git a/packages/types/src/lib/middleware.ts b/packages/types/src/lib/middleware.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/types/src/lib/renderer.ts b/packages/types/src/lib/renderer.ts index 9800550..965e7e9 100644 --- a/packages/types/src/lib/renderer.ts +++ b/packages/types/src/lib/renderer.ts @@ -23,7 +23,7 @@ export interface RendererLoader extends UtilEventEmitter { getContent(uuid: string): LoadContent | null; } -export interface RendererDrawOptions extends ViewScaleInfo {} +export type RendererDrawOptions = ViewScaleInfo; export interface RendererDrawElementOptions extends RendererDrawOptions { loader: RendererLoader; diff --git a/packages/types/src/lib/watcher.ts b/packages/types/src/lib/watcher.ts new file mode 100644 index 0000000..d5ab6ef --- /dev/null +++ b/packages/types/src/lib/watcher.ts @@ -0,0 +1,5 @@ +import type { Point } from './point'; + +export interface PointWatcherEvent { + point: Point; +} diff --git a/tsconfig.web.json b/tsconfig.web.json index 3df948f..f974528 100644 --- a/tsconfig.web.json +++ b/tsconfig.web.json @@ -7,11 +7,19 @@ "moduleResolution": "node", "allowJs": false, "strict": true, + "strictNullChecks": true, "experimentalDecorators": true, "resolveJsonModule": true, "esModuleInterop": true, "removeComments": true, - "lib": ["ES2016", "DOM"] + "lib": ["ES2016", "DOM"], + "paths": { + "@idraw/types": ["./packages/types/src/index.ts"], + "@idraw/util": ["./packages/util/src/index.ts"], + "@idraw/renderer": ["./packages/renderer/src/index.ts"], + "@idraw/board": ["./packages/board/src/index.ts"], + "@idraw/core": ["./packages/core/src/index.ts"] + } }, "include": [ "packages/*/src",