mirror of
https://github.com/idrawjs/idraw
synced 2026-05-24 10:08:34 +00:00
refactor: refactor framework for v0.4
This commit is contained in:
parent
aaa217ed58
commit
e6dc4c1ab0
88 changed files with 1138 additions and 7238 deletions
|
|
@ -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<T extends keyof TypeBoardEventArgMap>(
|
||||
name: T,
|
||||
callback: (p: TypeBoardEventArgMap[T]) => void
|
||||
) {
|
||||
this._watcher.on(name, callback);
|
||||
}
|
||||
|
||||
off<T extends keyof TypeBoardEventArgMap>(
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
99
packages/board/src/lib/calculator.ts
Normal file
99
packages/board/src/lib/calculator.ts
Normal file
|
|
@ -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<ElementType>, 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<ElementType>, 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<ElementType> } {
|
||||
const result: { index: number; element: null | Element<ElementType> } = {
|
||||
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 {};
|
||||
}
|
||||
}
|
||||
|
|
@ -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<T extends keyof TypeBoardEventArgMap>(
|
||||
key: T,
|
||||
callback: (p: TypeBoardEventArgMap[T]) => void
|
||||
): void;
|
||||
off<T extends keyof TypeBoardEventArgMap>(
|
||||
key: T,
|
||||
callback: (p: TypeBoardEventArgMap[T]) => void
|
||||
): void;
|
||||
trigger<T extends keyof TypeBoardEventArgMap>(
|
||||
key: T,
|
||||
p: TypeBoardEventArgMap[T]
|
||||
): void;
|
||||
}
|
||||
|
||||
export class BoardEvent implements TypeBoardEvent {
|
||||
private _listeners: Map<string, ((p: any) => void)[]>;
|
||||
|
||||
constructor() {
|
||||
this._listeners = new Map();
|
||||
}
|
||||
|
||||
on<T extends keyof TypeBoardEventArgMap>(
|
||||
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<T extends keyof TypeBoardEventArgMap>(
|
||||
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<T extends keyof TypeBoardEventArgMap>(
|
||||
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<T extends keyof TypeBoardEventArgMap>(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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
import { istype } from '@idraw/util';
|
||||
|
||||
export default istype;
|
||||
|
|
@ -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<T extends keyof TypeBoardEventArgMap>(
|
||||
name: T,
|
||||
callback: (p: TypeBoardEventArgMap[T]) => void
|
||||
): void {
|
||||
this._event.on(name, callback);
|
||||
}
|
||||
|
||||
off<T extends keyof TypeBoardEventArgMap>(
|
||||
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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<BoardScrollConfig> }
|
||||
>;
|
||||
|
||||
const minScrollerWidth = 12;
|
||||
const scrollerAlpha = 0.12;
|
||||
const scrollerThumbAlpha = 0.36;
|
||||
|
||||
const defaultScrollConfig: Partial<BoardScrollConfig> & {
|
||||
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();
|
||||
}
|
||||
61
packages/board/src/lib/sharer.ts
Normal file
61
packages/board/src/lib/sharer.ts
Normal file
|
|
@ -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<ActiveStore>;
|
||||
private _sharedStore: Store<{
|
||||
[string: string]: any;
|
||||
}>;
|
||||
|
||||
constructor() {
|
||||
const activeStore = new Store<ActiveStore>({
|
||||
defaultStorage: defaultActiveStorage
|
||||
});
|
||||
const sharedStore = new Store({
|
||||
defaultStorage: {}
|
||||
});
|
||||
this._activeStore = activeStore;
|
||||
this._sharedStore = sharedStore;
|
||||
}
|
||||
|
||||
drawFrame(): void {
|
||||
// TODO
|
||||
}
|
||||
|
||||
getActiveStorage<T extends keyof ActiveStore>(key: T): ActiveStore[T] {
|
||||
return this._activeStore.get(key);
|
||||
}
|
||||
|
||||
setActiveStorage<T extends keyof ActiveStore>(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<string, any> {
|
||||
return this._sharedStore.getSnapshot();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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<T extends keyof TempDataDesc>(name: T, value: TempDataDesc[T]) {
|
||||
this._temp[name] = value;
|
||||
}
|
||||
|
||||
get<T extends keyof TempDataDesc>(name: T): TempDataDesc[T] {
|
||||
return this._temp[name];
|
||||
}
|
||||
|
||||
clear(opts: BoardOptions) {
|
||||
this._temp = createDefaultData(opts);
|
||||
}
|
||||
}
|
||||
77
packages/board/src/lib/viewer.ts
Normal file
77
packages/board/src/lib/viewer.ts
Normal file
|
|
@ -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<BoardViewerEventMap> 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<string, any> = sharer.getSharedStoreSnapshot();
|
||||
this._drawFrameSnapshotQueue.push({
|
||||
activeStore,
|
||||
sharedStore
|
||||
});
|
||||
this._drawAnimationFrame();
|
||||
}
|
||||
}
|
||||
|
|
@ -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<T extends keyof TempDataDesc>(name: T, value: TempDataDesc[T]) {
|
||||
this._temp[name] = value;
|
||||
}
|
||||
|
||||
get<T extends keyof TempDataDesc>(name: T): TempDataDesc[T] {
|
||||
return this._temp[name];
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._temp = createTempData();
|
||||
}
|
||||
}
|
||||
|
|
@ -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<BoardWatcherEventMap> {
|
||||
private _opts: WatcherOptions;
|
||||
constructor(opts: WatcherOptions) {
|
||||
super();
|
||||
this._opts = opts;
|
||||
this._init();
|
||||
}
|
||||
|
||||
on<T extends keyof TypeBoardEventArgMap>(
|
||||
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<T extends keyof TypeBoardEventArgMap>(
|
||||
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<ElementType> | 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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',
|
||||
}
|
||||
|
|
@ -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<keyof DataElemDesc>) {
|
||||
return updateElement(this, elem);
|
||||
}
|
||||
|
||||
addElement(elem: DataElementBase<keyof DataElemDesc>): string | null {
|
||||
return addElement(this, elem);
|
||||
}
|
||||
|
||||
deleteElement(uuid: string) {
|
||||
return deleteElement(this, uuid);
|
||||
}
|
||||
|
||||
insertElementBefore(
|
||||
elem: DataElementBase<keyof DataElemDesc>,
|
||||
beforeUUID: string
|
||||
) {
|
||||
return insertElementBefore(this, elem, beforeUUID);
|
||||
}
|
||||
|
||||
insertElementBeforeIndex(
|
||||
elem: DataElementBase<keyof DataElemDesc>,
|
||||
index: number
|
||||
) {
|
||||
return insertElementBeforeIndex(this, elem, index);
|
||||
}
|
||||
|
||||
getSelectedElements() {
|
||||
return getSelectedElements(this);
|
||||
}
|
||||
|
||||
insertElementAfter(
|
||||
elem: DataElementBase<keyof DataElemDesc>,
|
||||
beforeUUID: string
|
||||
) {
|
||||
return insertElementAfter(this, elem, beforeUUID);
|
||||
}
|
||||
|
||||
insertElementAfterIndex(
|
||||
elem: DataElementBase<keyof DataElemDesc>,
|
||||
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<T extends keyof TypeCoreEventArgMap>(
|
||||
key: T,
|
||||
callback: (p: TypeCoreEventArgMap[T]) => void
|
||||
) {
|
||||
this._coreEvent.on(key, callback);
|
||||
}
|
||||
|
||||
off<T extends keyof TypeCoreEventArgMap>(
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<keyof DataElemDesc>
|
||||
): 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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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 };
|
||||
|
|
@ -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<keyof DataElemDesc>;
|
||||
};
|
||||
mouseLeaveElement: TypeCoreEventSelectBaseArg & {
|
||||
element: DataElement<keyof DataElemDesc>;
|
||||
};
|
||||
screenClickElement: TypeCoreEventSelectBaseArg & {
|
||||
element: DataElement<keyof DataElemDesc>;
|
||||
};
|
||||
screenDoubleClickElement: TypeCoreEventSelectBaseArg & {
|
||||
element: DataElement<keyof DataElemDesc>;
|
||||
};
|
||||
screenSelectElement: TypeCoreEventSelectBaseArg & {
|
||||
element: DataElement<keyof DataElemDesc>;
|
||||
};
|
||||
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<T extends keyof TypeCoreEventArgMap>(
|
||||
key: T,
|
||||
callback: (p: TypeCoreEventArgMap[T]) => void
|
||||
): void;
|
||||
off<T extends keyof TypeCoreEventArgMap>(
|
||||
key: T,
|
||||
callback: (p: TypeCoreEventArgMap[T]) => void
|
||||
): void;
|
||||
trigger<T extends keyof TypeCoreEventArgMap>(
|
||||
key: T,
|
||||
p: TypeCoreEventArgMap[T]
|
||||
): void;
|
||||
}
|
||||
|
||||
export class CoreEvent implements TypeCoreEvent {
|
||||
private _listeners: Map<string, ((p: any) => void)[]>;
|
||||
|
||||
constructor() {
|
||||
this._listeners = new Map();
|
||||
}
|
||||
|
||||
on<T extends keyof TypeCoreEventArgMap>(
|
||||
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<T extends keyof TypeCoreEventArgMap>(
|
||||
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<T extends keyof TypeCoreEventArgMap>(
|
||||
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<T extends keyof TypeCoreEventArgMap>(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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
import { DataElement, IDrawData, DataElemDesc } from '@idraw/types';
|
||||
|
||||
type DataElementMap = {
|
||||
[uuid: string]: DataElement<keyof DataElemDesc>;
|
||||
};
|
||||
|
||||
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<keyof DataElemDesc>,
|
||||
after: DataElement<keyof DataElemDesc>
|
||||
): 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;
|
||||
}
|
||||
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -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<keyof DataElemDesc>) {
|
||||
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<keyof DataElemDesc>,
|
||||
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);
|
||||
}
|
||||
|
|
@ -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<T extends keyof TempDataDesc>(name: T, value: TempDataDesc[T]) {
|
||||
this._temp[name] = value;
|
||||
}
|
||||
|
||||
get<T extends keyof TempDataDesc>(name: T): TempDataDesc[T] {
|
||||
return this._temp[name];
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._temp = createData();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<keyof DataElemDesc>, 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<keyof DataElemDesc>,
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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';
|
||||
|
|
@ -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' &&
|
||||
/^(<svg[\s]{1,}|<svg>)/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 };
|
||||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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<keyof DataElemDesc>): 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;
|
||||
}
|
||||
|
|
@ -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<T extends keyof TempDataDesc >(name: T, value: TempDataDesc[T]) {
|
||||
this._temp[name] = value;
|
||||
}
|
||||
|
||||
get<T extends keyof TempDataDesc >(name: T): TempDataDesc[T] {
|
||||
return this._temp[name];
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._temp = createData();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
import { IDrawContext, Point, DataElement, DataElemDesc } from '@idraw/types';
|
||||
import { calcElementCenter, parseAngleToRadian } from './calculate';
|
||||
|
||||
function rotateElement(
|
||||
ctx: IDrawContext,
|
||||
elem: DataElement<keyof DataElemDesc>,
|
||||
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 };
|
||||
|
|
@ -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);
|
||||
}
|
||||
52
packages/core/src/middleware/select/draw-wrapper.ts
Normal file
52
packages/core/src/middleware/select/draw-wrapper.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import type { Element, ElementType, RendererDrawElementOptions } from '@idraw/types';
|
||||
|
||||
export function drawPointWrapper(ctx: CanvasRenderingContext2D, elem: Element<ElementType>, opts?: Omit<RendererDrawElementOptions, 'loader'>) {
|
||||
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<ElementType>, opts?: Omit<RendererDrawElementOptions, 'loader'>) {
|
||||
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();
|
||||
}
|
||||
112
packages/core/src/middleware/select/index.ts
Normal file
112
packages/core/src/middleware/select/index.ts
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
@ -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<keyof DataElemDesc>[] {
|
||||
const elems: DataElement<keyof DataElemDesc>[] = [];
|
||||
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<keyof DataElemDesc> | null {
|
||||
let elem: DataElement<keyof DataElemDesc> | 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<keyof DataElemDesc> | null {
|
||||
let elem: DataElement<keyof DataElemDesc> | null = null;
|
||||
if (index >= 0 && core.$data.elements[index]) {
|
||||
elem = deepClone(core.$data.elements[index]);
|
||||
}
|
||||
return elem;
|
||||
}
|
||||
|
||||
export function updateElement(
|
||||
core: Core,
|
||||
elem: DataElement<keyof DataElemDesc>
|
||||
) {
|
||||
const _elem = deepClone(elem) as DataElement<keyof DataElemDesc>;
|
||||
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<keyof DataElemDesc>
|
||||
): 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<keyof DataElemDesc>,
|
||||
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<keyof DataElemDesc>,
|
||||
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<keyof DataElemDesc>,
|
||||
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<keyof DataElemDesc>,
|
||||
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;
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
import {
|
||||
InterfaceHelperPlugin,
|
||||
HelperPluginEventDetail,
|
||||
HelperPluginEventResult
|
||||
} from '@idraw/types';
|
||||
import { createUUID } from '@idraw/util';
|
||||
|
||||
export class HelperPlugin implements Required<InterfaceHelperPlugin> {
|
||||
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 {}
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
export function filterScript(html: string) {
|
||||
return html.replace(/<script[\s\S]*?<\/script>/ig, '');
|
||||
}
|
||||
|
|
@ -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: '<svg t="1622524892065" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9337" width="200" height="200"><path d="M511.6 76.3C264.3 76.2 64 276.4 64 523.5 64 718.9 189.3 885 363.8 946c23.5 5.9 19.9-10.8 19.9-22.2v-77.5c-135.7 15.9-141.2-73.9-150.3-88.9C215 726 171.5 718 184.5 703c30.9-15.9 62.4 4 98.9 57.9 26.4 39.1 77.9 32.5 104 26 5.7-23.5 17.9-44.5 34.7-60.8-140.6-25.2-199.2-111-199.2-213 0-49.5 16.3-95 48.3-131.7-20.4-60.5 1.9-112.3 4.9-120 58.1-5.2 118.5 41.6 123.2 45.3 33-8.9 70.7-13.6 112.9-13.6 42.4 0 80.2 4.9 113.5 13.9 11.3-8.6 67.3-48.8 121.3-43.9 2.9 7.7 24.7 58.3 5.5 118 32.4 36.8 48.9 82.7 48.9 132.3 0 102.2-59 188.1-200 212.9 23.5 23.2 38.1 55.4 38.1 91v112.5c0.8 9 0 17.9 15 17.9 177.1-59.7 304.6-227 304.6-424.1 0-247.2-200.4-447.3-447.5-447.3z" p-id="9338"></path></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: '<svg t="1622524892065" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9337" width="200" height="200"><path d="M511.6 76.3C264.3 76.2 64 276.4 64 523.5 64 718.9 189.3 885 363.8 946c23.5 5.9 19.9-10.8 19.9-22.2v-77.5c-135.7 15.9-141.2-73.9-150.3-88.9C215 726 171.5 718 184.5 703c30.9-15.9 62.4 4 98.9 57.9 26.4 39.1 77.9 32.5 104 26 5.7-23.5 17.9-44.5 34.7-60.8-140.6-25.2-199.2-111-199.2-213 0-49.5 16.3-95 48.3-131.7-20.4-60.5 1.9-112.3 4.9-120 58.1-5.2 118.5 41.6 123.2 45.3 33-8.9 70.7-13.6 112.9-13.6 42.4 0 80.2 4.9 113.5 13.9 11.3-8.6 67.3-48.8 121.3-43.9 2.9 7.7 24.7 58.3 5.5 118 32.4 36.8 48.9 82.7 48.9 132.3 0 102.2-59 188.1-200 212.9 23.5 23.2 38.1 55.4 38.1 91v112.5c0.8 9 0 17.9 15 17.9 177.1-59.7 304.6-227 304.6-424.1 0-247.2-200.4-447.3-447.5-447.3z" p-id="9338"></path></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'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
BIN
packages/idraw/dev/images/github.png
Normal file
BIN
packages/idraw/dev/images/github.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 197 KiB |
BIN
packages/idraw/dev/images/lena.png
Normal file
BIN
packages/idraw/dev/images/lena.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 143 KiB |
|
|
@ -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 = `<img width="300" src="${dataURL}">`;
|
||||
})
|
||||
.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 = `<img width="300" src="${dataURL}">`;
|
||||
// })
|
||||
// .catch((err) => {
|
||||
// console.log(err);
|
||||
// });
|
||||
// });
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
|
@ -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<string> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<T extends keyof TypeKeyboardEventArgMap >(key: T, callback: (p: TypeKeyboardEventArgMap[T]) => void): void
|
||||
off<T extends keyof TypeKeyboardEventArgMap >(key: T, callback: (p: TypeKeyboardEventArgMap[T]) => void): void
|
||||
// trigger<T extends keyof TypeKeyboardEventArgMap >(key: T, p: TypeKeyboardEventArgMap[T]): void
|
||||
}
|
||||
|
||||
|
||||
export class KeyboardWatcher implements TypeKeyboardEvent {
|
||||
|
||||
private _listeners: Map<string, ((p: any) => 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<T extends keyof TypeKeyboardEventArgMap >(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<T extends keyof TypeKeyboardEventArgMap >(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<T extends keyof TypeKeyboardEventArgMap >(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<T extends keyof TypeKeyboardEventArgMap> (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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
import Core from '@idraw/core';
|
||||
|
||||
export function copySelectedElement(core: Core) {
|
||||
console.log('core ====', core);
|
||||
// console.log(core.getSelectedElements());
|
||||
}
|
||||
|
|
@ -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<keyof DataElemDesc>[];
|
||||
};
|
||||
|
||||
function createDefaultData() {
|
||||
return {
|
||||
isFocus: false,
|
||||
doRecords: [],
|
||||
unDoRecords: [],
|
||||
clipboardElements: [],
|
||||
isDownloading: false
|
||||
};
|
||||
}
|
||||
|
||||
export class TempData {
|
||||
private _temp: TempDataDesc;
|
||||
|
||||
constructor() {
|
||||
this._temp = createDefaultData();
|
||||
}
|
||||
|
||||
set<T extends keyof TempDataDesc>(name: T, value: TempDataDesc[T]) {
|
||||
this._temp[name] = value;
|
||||
}
|
||||
|
||||
get<T extends keyof TempDataDesc>(name: T): TempDataDesc[T] {
|
||||
return this._temp[name];
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._temp = createDefaultData();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
import iDraw from './../index';
|
||||
|
||||
export async function exportDataURL(
|
||||
idraw: iDraw,
|
||||
type: 'image/png' | 'image/jpeg',
|
||||
quality?: number
|
||||
): Promise<string> {
|
||||
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;
|
||||
}
|
||||
|
|
@ -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<keyof DataElemDesc>) => {
|
||||
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<keyof DataElemDesc>) => {
|
||||
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<keyof DataElemDesc>) => {
|
||||
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<keyof DataElemDesc>) => {
|
||||
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<keyof DataElemDesc>) => {
|
||||
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<keyof DataElemDesc>) => {
|
||||
elem.x += keyArrowMoveDistance;
|
||||
idraw.updateElement(elem);
|
||||
});
|
||||
} else {
|
||||
const { scrollLeft } = idraw.getScreenTransform();
|
||||
idraw.scrollLeft(scrollLeft + keyArrowMoveDistance);
|
||||
}
|
||||
}
|
||||
|
||||
export function keyUndo(idraw: iDraw) {
|
||||
idraw.undo();
|
||||
}
|
||||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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;
|
||||
|
|
@ -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',
|
||||
}
|
||||
31
packages/renderer/src/draw/circle.ts
Normal file
31
packages/renderer/src/draw/circle.ts
Normal file
|
|
@ -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();
|
||||
}
|
||||
42
packages/renderer/src/draw/elements.ts
Normal file
42
packages/renderer/src/draw/elements.ts
Normal file
|
|
@ -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<ElementType>, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
packages/renderer/src/draw/image.ts
Normal file
14
packages/renderer/src/draw/image.ts
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
4
packages/renderer/src/draw/index.ts
Normal file
4
packages/renderer/src/draw/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export { drawCircle } from './circle';
|
||||
export { drawRect } from './rect';
|
||||
export { drawImage } from './image';
|
||||
export { drawElementList, drawElement } from './elements';
|
||||
22
packages/renderer/src/draw/rect.ts
Normal file
22
packages/renderer/src/draw/rect.ts
Normal file
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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<RendererEventMap> 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<keyof DataElemDesc>) => {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<keyof DataElemDesc>
|
||||
): 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;
|
||||
}
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
import { DataElement, IDrawData, DataElemDesc } from '@idraw/types';
|
||||
|
||||
type DataElementMap = {
|
||||
[uuid: string]: DataElement<keyof DataElemDesc>;
|
||||
};
|
||||
|
||||
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<keyof DataElemDesc>,
|
||||
after: DataElement<keyof DataElemDesc>
|
||||
): 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;
|
||||
}
|
||||
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
|
@ -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();
|
||||
// }
|
||||
});
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -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);
|
||||
// }
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
// }
|
||||
|
|
@ -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 = `
|
||||
// <svg xmlns="http://www.w3.org/2000/svg" width="${elem.w}" height = "${elem.h}">
|
||||
// <foreignObject width="100%" height="100%">
|
||||
// <div xmlns = "http://www.w3.org/1999/xhtml" style="font-size: ${elem.desc.size}px; color:${elem.desc.color};">
|
||||
// <span>${elem.desc.text || ''}</span>
|
||||
// </div>
|
||||
// </foreignObject>
|
||||
// </svg>
|
||||
// `;
|
||||
// return svg;
|
||||
// }
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
export * from './diff';
|
||||
export * from './loader-event';
|
||||
export * from './loader';
|
||||
export * from './parse';
|
||||
export * from './temp';
|
||||
export * from './value';
|
||||
|
|
@ -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<keyof DataElemDesc>;
|
||||
error?: any;
|
||||
};
|
||||
|
||||
export type TypeLoadData = {
|
||||
[uuid: string]: TypeLoadDataItem;
|
||||
};
|
||||
|
||||
export type TypeLoaderEventArgMap = {
|
||||
complete: void;
|
||||
load: TypeLoadData[string];
|
||||
error: TypeLoadData[string];
|
||||
};
|
||||
|
||||
export interface TypeLoaderEvent {
|
||||
on<T extends keyof TypeLoaderEventArgMap>(
|
||||
key: T,
|
||||
callback: (p: TypeLoaderEventArgMap[T]) => void
|
||||
): void;
|
||||
off<T extends keyof TypeLoaderEventArgMap>(
|
||||
key: T,
|
||||
callback: (p: TypeLoaderEventArgMap[T]) => void
|
||||
): void;
|
||||
trigger<T extends keyof TypeLoaderEventArgMap>(
|
||||
key: T,
|
||||
p: TypeLoaderEventArgMap[T]
|
||||
): void;
|
||||
}
|
||||
|
||||
export class LoaderEvent implements TypeLoaderEvent {
|
||||
private _listeners: Map<string, ((p: any) => void)[]>;
|
||||
|
||||
constructor() {
|
||||
this._listeners = new Map();
|
||||
}
|
||||
|
||||
on<T extends keyof TypeLoaderEventArgMap>(
|
||||
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<T extends keyof TypeLoaderEventArgMap>(
|
||||
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<T extends keyof TypeLoaderEventArgMap>(
|
||||
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<T extends keyof TypeLoaderEventArgMap>(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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<T extends keyof TypeLoaderEventArgMap>(
|
||||
name: T,
|
||||
callback: (arg: TypeLoaderEventArgMap[T]) => void
|
||||
) {
|
||||
this._event.on(name, callback);
|
||||
}
|
||||
|
||||
off<T extends keyof TypeLoaderEventArgMap>(
|
||||
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<keyof DataElemDesc>,
|
||||
// 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<HTMLImageElement> {
|
||||
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!");
|
||||
}
|
||||
}
|
||||
|
|
@ -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<keyof DataElemDesc>): 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;
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
import { DataElement, DataElemDesc } from '@idraw/types';
|
||||
|
||||
export type TypeRendererEventArgMap = {
|
||||
drawFrame: { t: number };
|
||||
drawFrameComplete: { t: number };
|
||||
load: { element: DataElement<keyof DataElemDesc> };
|
||||
loadComplete: { t: number };
|
||||
error: { element: DataElement<keyof DataElemDesc>; error: any };
|
||||
};
|
||||
|
||||
export interface TypeRendererEvent {
|
||||
on<T extends keyof TypeRendererEventArgMap>(
|
||||
key: T,
|
||||
callback: (p: TypeRendererEventArgMap[T]) => void
|
||||
): void;
|
||||
off<T extends keyof TypeRendererEventArgMap>(
|
||||
key: T,
|
||||
callback: (p: TypeRendererEventArgMap[T]) => void
|
||||
): void;
|
||||
trigger<T extends keyof TypeRendererEventArgMap>(
|
||||
key: T,
|
||||
p: TypeRendererEventArgMap[T]
|
||||
): void;
|
||||
}
|
||||
|
||||
export class RendererEvent implements TypeRendererEvent {
|
||||
private _listeners: Map<string, ((p: any) => void)[]>;
|
||||
|
||||
constructor() {
|
||||
this._listeners = new Map();
|
||||
}
|
||||
|
||||
on<T extends keyof TypeRendererEventArgMap>(
|
||||
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<T extends keyof TypeRendererEventArgMap>(
|
||||
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<T extends keyof TypeRendererEventArgMap>(
|
||||
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<T extends keyof TypeRendererEventArgMap>(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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<T extends keyof TempDataDesc>(name: T, value: TempDataDesc[T]) {
|
||||
this._temp[name] = value;
|
||||
}
|
||||
|
||||
get<T extends keyof TempDataDesc>(name: T): TempDataDesc[T] {
|
||||
return this._temp[name];
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._temp = createData();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
import { IDrawContext, Point, DataElement, DataElemDesc } from '@idraw/types';
|
||||
import { calcElementCenter, parseAngleToRadian } from './calculate';
|
||||
|
||||
function rotateElement(
|
||||
ctx: IDrawContext,
|
||||
elem: DataElement<keyof DataElemDesc>,
|
||||
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 };
|
||||
|
|
@ -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);
|
||||
}
|
||||
118
packages/renderer/src/loader.ts
Normal file
118
packages/renderer/src/loader.ts
Normal file
|
|
@ -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<LoaderEventMap> implements RendererLoader {
|
||||
private _loadFuncMap: Record<LoadElementType | string, LoadFunc<LoadElementType, LoadContent>> = {};
|
||||
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<T extends LoadElementType>(type: T, func: LoadFunc<T, LoadContent>) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
this._loadFuncMap[type] = func;
|
||||
}
|
||||
|
||||
private _createLoadItem(element: Element<LoadElementType>): 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<LoadElementType>) {
|
||||
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<LoadElementType>) {
|
||||
if (supportElementTypes.includes(element.type)) {
|
||||
this._loadResource(element);
|
||||
}
|
||||
}
|
||||
|
||||
getContent(uuid: string): LoadContent | null {
|
||||
return this._storageLoadItemMap?.[uuid]?.content || null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
export function filterScript(html: string) {
|
||||
return html.replace(/<script[\s\S]*?<\/script>/ig, '');
|
||||
}
|
||||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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<BoardViewerEventMap> {
|
||||
drawFrame(): void;
|
||||
}
|
||||
|
||||
export interface BoardRenderer extends UtilEventEmitter<RendererEventMap> {
|
||||
updateOptions(opts: RendererOptions): void;
|
||||
drawData(data: Data, opts: RendererDrawOptions): void;
|
||||
scale(num: number): void;
|
||||
}
|
||||
|
|
|
|||
7
packages/types/src/lib/core.ts
Normal file
7
packages/types/src/lib/core.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export interface CoreOptions {
|
||||
width: number;
|
||||
height: number;
|
||||
contextWidth?: number;
|
||||
contextHeight?: number;
|
||||
onlyRender?: boolean;
|
||||
}
|
||||
3
packages/types/src/lib/idraw.ts
Normal file
3
packages/types/src/lib/idraw.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import type { CoreOptions } from './core';
|
||||
|
||||
export type IDrawOptions = CoreOptions;
|
||||
0
packages/types/src/lib/middleware.ts
Normal file
0
packages/types/src/lib/middleware.ts
Normal file
|
|
@ -23,7 +23,7 @@ export interface RendererLoader extends UtilEventEmitter<LoaderEventMap> {
|
|||
getContent(uuid: string): LoadContent | null;
|
||||
}
|
||||
|
||||
export interface RendererDrawOptions extends ViewScaleInfo {}
|
||||
export type RendererDrawOptions = ViewScaleInfo;
|
||||
|
||||
export interface RendererDrawElementOptions extends RendererDrawOptions {
|
||||
loader: RendererLoader;
|
||||
|
|
|
|||
5
packages/types/src/lib/watcher.ts
Normal file
5
packages/types/src/lib/watcher.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import type { Point } from './point';
|
||||
|
||||
export interface PointWatcherEvent {
|
||||
point: Point;
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in a new issue