mirror of
https://github.com/idrawjs/idraw
synced 2026-05-24 10:08:34 +00:00
refactor: refactor mode of idraw
This commit is contained in:
parent
a5059f51c1
commit
79f380aef2
18 changed files with 597 additions and 159 deletions
|
|
@ -1,5 +1,9 @@
|
|||
import { Renderer } from '@idraw/renderer';
|
||||
import { throttle, calcElementsContextSize, EventEmitter } from '@idraw/util';
|
||||
import {
|
||||
// throttle,
|
||||
calcElementsContextSize,
|
||||
EventEmitter
|
||||
} from '@idraw/util';
|
||||
import type {
|
||||
Data,
|
||||
BoardOptions,
|
||||
|
|
@ -16,7 +20,7 @@ import { BoardWatcher } from './lib/watcher';
|
|||
import { Sharer } from './lib/sharer';
|
||||
import { Viewer } from './lib/viewer';
|
||||
|
||||
const throttleTime = 10; // ms
|
||||
// const throttleTime = 10; // ms
|
||||
|
||||
interface BoardMiddlewareMapItem {
|
||||
status: 'enable' | 'disable';
|
||||
|
|
@ -91,31 +95,34 @@ export class Board<T extends BoardExtendEventMap = BoardExtendEventMap> {
|
|||
#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);
|
||||
}, throttleTime)
|
||||
);
|
||||
this.#watcher.on(
|
||||
'hover',
|
||||
throttle((e) => {
|
||||
this.#handleHover(e);
|
||||
}, throttleTime)
|
||||
);
|
||||
|
||||
this.#watcher.on(
|
||||
'wheel',
|
||||
throttle((e) => {
|
||||
this.#handleWheel(e);
|
||||
}, throttleTime)
|
||||
);
|
||||
this.#watcher.on(
|
||||
'wheelScale',
|
||||
throttle((e) => {
|
||||
this.#handleWheelScale(e);
|
||||
}, throttleTime)
|
||||
);
|
||||
// this.#watcher.on(
|
||||
// 'pointMove',
|
||||
// throttle((e) => {
|
||||
// this.#handlePointMove(e);
|
||||
// }, throttleTime)
|
||||
// );
|
||||
// this.#watcher.on(
|
||||
// 'hover',
|
||||
// throttle((e) => {
|
||||
// this.#handleHover(e);
|
||||
// }, throttleTime)
|
||||
// );
|
||||
// this.#watcher.on(
|
||||
// 'wheel',
|
||||
// throttle((e) => {
|
||||
// this.#handleWheel(e);
|
||||
// }, throttleTime)
|
||||
// );
|
||||
// this.#watcher.on(
|
||||
// 'wheelScale',
|
||||
// throttle((e) => {
|
||||
// this.#handleWheelScale(e);
|
||||
// }, throttleTime)
|
||||
// );
|
||||
this.#watcher.on('pointMove', this.#handlePointMove.bind(this));
|
||||
this.#watcher.on('hover', this.#handleHover.bind(this));
|
||||
this.#watcher.on('wheel', this.#handleWheel.bind(this));
|
||||
this.#watcher.on('wheelScale', this.#handleWheelScale.bind(this));
|
||||
this.#watcher.on('scrollX', this.#handleScrollX.bind(this));
|
||||
this.#watcher.on('scrollY', this.#handleScrollY.bind(this));
|
||||
this.#watcher.on('resize', this.#handleResize.bind(this));
|
||||
|
|
|
|||
18
packages/core/src/middleware/selector/auxiliary.ts
Normal file
18
packages/core/src/middleware/selector/auxiliary.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import type { Element, ElementSize, ViewRectInfo, ViewScaleInfo } from '@idraw/types';
|
||||
import { calcElementViewRectInfo } from '@idraw/util';
|
||||
|
||||
export function getElementViewRectInfo(
|
||||
elem: ElementSize,
|
||||
opts: {
|
||||
groupQueue: Element<'group'>[];
|
||||
viewScaleInfo: ViewScaleInfo;
|
||||
}
|
||||
): ViewRectInfo {
|
||||
const { groupQueue, viewScaleInfo } = opts;
|
||||
const viewRectInfo = calcElementViewRectInfo(elem, {
|
||||
groupQueue,
|
||||
viewScaleInfo,
|
||||
range: true
|
||||
});
|
||||
return viewRectInfo;
|
||||
}
|
||||
|
|
@ -30,3 +30,5 @@ export const lockColor = '#5b5959b5';
|
|||
|
||||
export const controllerSize = 10;
|
||||
// export const wrapperColor = '#1890ff';
|
||||
|
||||
export const auxiliaryColor = '#f7276e';
|
||||
|
|
|
|||
|
|
@ -1,69 +1,43 @@
|
|||
import type { ViewContext2D, ViewRectVertexes, Element } from '@idraw/types';
|
||||
import { drawLine } from './draw-base';
|
||||
import type { ViewContext2D, ViewRectVertexes, Element, ViewScaleInfo } from '@idraw/types';
|
||||
import { getElementViewRectInfo } from './auxiliary';
|
||||
import { auxiliaryColor } from './config';
|
||||
import { drawLine, drawCrossByCenter } from './draw-base';
|
||||
|
||||
const auxiliaryColor = '#f7276e';
|
||||
// const auxiliaryTextColor = '#FFFFFF';
|
||||
// const fontSize = 12;
|
||||
// const fontFamily = 'Consolas,Monaco,monospace';
|
||||
// const fontWeight = 600;
|
||||
|
||||
// function drawLabel(
|
||||
// ctx,
|
||||
// opts: {
|
||||
// text: string;
|
||||
// start: PointSize;
|
||||
// textColor: string;
|
||||
// background: string;
|
||||
// }
|
||||
// ) {
|
||||
// // TODO
|
||||
// }
|
||||
|
||||
export function drawSizeAuxiliaryLines(
|
||||
export function drawAuxiliaryLines(
|
||||
ctx: ViewContext2D,
|
||||
opts: {
|
||||
vertexes: ViewRectVertexes;
|
||||
element: Element | null;
|
||||
groupQueue: Element<'group'>[];
|
||||
viewScaleInfo: ViewScaleInfo;
|
||||
}
|
||||
) {
|
||||
const { vertexes, element, groupQueue } = opts;
|
||||
const point = vertexes[0];
|
||||
const { element, groupQueue, viewScaleInfo } = opts;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
const viewRectInfo = getElementViewRectInfo(element, {
|
||||
groupQueue,
|
||||
viewScaleInfo
|
||||
});
|
||||
const lineOpts = {
|
||||
borderColor: auxiliaryColor,
|
||||
borderWidth: 1,
|
||||
lineDash: []
|
||||
// lineDash: [4, 4]
|
||||
};
|
||||
if (groupQueue.length > 0) {
|
||||
// TODO
|
||||
} else {
|
||||
if (!element?.angle) {
|
||||
drawLine(ctx, { x: point.x, y: 0 }, point, lineOpts);
|
||||
drawLine(ctx, { x: 0, y: point.y }, point, lineOpts);
|
||||
drawLine(ctx, viewRectInfo.topLeft, viewRectInfo.topRight, lineOpts);
|
||||
drawLine(ctx, viewRectInfo.topRight, viewRectInfo.bottomRight, lineOpts);
|
||||
drawLine(ctx, viewRectInfo.bottomRight, viewRectInfo.bottomLeft, lineOpts);
|
||||
drawLine(ctx, viewRectInfo.bottomLeft, viewRectInfo.topLeft, lineOpts);
|
||||
|
||||
// {
|
||||
// const xNum = `${element?.x}`;
|
||||
// const w = xNum.length * fontSize;
|
||||
// const h = fontSize;
|
||||
// const x = point.x / 2 - xNum.length * fontSize;
|
||||
// const y = point.y + fontSize / 2;
|
||||
// ctx.$setFont({
|
||||
// fontSize,
|
||||
// fontFamily,
|
||||
// fontWeight
|
||||
// });
|
||||
// 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.fillStyle = auxiliaryColor;
|
||||
// ctx.fill();
|
||||
const crossOpts = { ...lineOpts, size: 6 };
|
||||
|
||||
// ctx.fillStyle = auxiliaryTextColor;
|
||||
// ctx.fillText(xNum, x, y, w);
|
||||
// }
|
||||
}
|
||||
}
|
||||
drawCrossByCenter(ctx, viewRectInfo.topLeft, crossOpts);
|
||||
drawCrossByCenter(ctx, viewRectInfo.topRight, crossOpts);
|
||||
drawCrossByCenter(ctx, viewRectInfo.bottomLeft, crossOpts);
|
||||
drawCrossByCenter(ctx, viewRectInfo.bottomRight, crossOpts);
|
||||
drawCrossByCenter(ctx, viewRectInfo.top, crossOpts);
|
||||
drawCrossByCenter(ctx, viewRectInfo.right, crossOpts);
|
||||
drawCrossByCenter(ctx, viewRectInfo.bottom, crossOpts);
|
||||
drawCrossByCenter(ctx, viewRectInfo.left, crossOpts);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,16 +85,12 @@ export function drawCircleController(
|
|||
// ctx.fill();
|
||||
}
|
||||
|
||||
export function drawCrossVertexes(
|
||||
ctx: ViewContext2D,
|
||||
vertexes: ViewRectVertexes,
|
||||
opts: { borderColor: string; borderWidth: number; background: string; lineDash: number[] }
|
||||
) {
|
||||
const { borderColor, borderWidth, background, lineDash } = opts;
|
||||
export function drawCrossVertexes(ctx: ViewContext2D, vertexes: ViewRectVertexes, opts: { borderColor: string; borderWidth: number; lineDash: number[] }) {
|
||||
const { borderColor, borderWidth, lineDash } = opts;
|
||||
ctx.setLineDash([]);
|
||||
ctx.lineWidth = borderWidth;
|
||||
ctx.strokeStyle = borderColor;
|
||||
ctx.fillStyle = background;
|
||||
// ctx.fillStyle = background;
|
||||
ctx.setLineDash(lineDash);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(vertexes[0].x, vertexes[0].y);
|
||||
|
|
@ -107,3 +103,34 @@ export function drawCrossVertexes(
|
|||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
export function drawCrossByCenter(ctx: ViewContext2D, center: PointSize, opts: { size: number; borderColor: string; borderWidth: number; lineDash: number[] }) {
|
||||
const { size, borderColor, borderWidth, lineDash } = opts;
|
||||
const minX = center.x - size / 2;
|
||||
const maxX = center.x + size / 2;
|
||||
const minY = center.y - size / 2;
|
||||
const maxY = center.y + size / 2;
|
||||
const vertexes: ViewRectVertexes = [
|
||||
{
|
||||
x: minX,
|
||||
y: minY
|
||||
},
|
||||
{
|
||||
x: maxX,
|
||||
y: minY
|
||||
},
|
||||
{
|
||||
x: maxX,
|
||||
y: maxY
|
||||
},
|
||||
{
|
||||
x: minX,
|
||||
y: maxY
|
||||
}
|
||||
];
|
||||
drawCrossVertexes(ctx, vertexes, {
|
||||
borderColor,
|
||||
borderWidth,
|
||||
lineDash
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { rotateElementVertexes, calcViewPointSize, calcViewVertexes } from '@idr
|
|||
import type { AreaSize } from './types';
|
||||
import { resizeControllerBorderWidth, areaBorderWidth, wrapperColor, selectWrapperBorderWidth, lockColor, controllerSize } from './config';
|
||||
import { drawVertexes, drawLine, drawCircleController, drawCrossVertexes } from './draw-base';
|
||||
// import { drawSizeAuxiliaryLines } from './draw-auxiliary';
|
||||
// import { drawAuxiliaryLines } from './draw-auxiliary';
|
||||
|
||||
export function drawHoverVertexesWrapper(
|
||||
ctx: ViewContext2D,
|
||||
|
|
@ -77,7 +77,7 @@ export function drawSelectedElementControllersVertexes(
|
|||
return;
|
||||
}
|
||||
const { hideControllers } = opts;
|
||||
// const { element, groupQueue } = opts;
|
||||
// const { element, groupQueue, viewScaleInfo } = opts;
|
||||
const { elementWrapper, topLeft, topRight, bottomLeft, bottomRight, top, rotate } = controller;
|
||||
const wrapperOpts = { borderColor: wrapperColor, borderWidth: selectWrapperBorderWidth, background: 'transparent', lineDash: [] };
|
||||
const ctrlOpts = { ...wrapperOpts, borderWidth: resizeControllerBorderWidth, background: '#FFFFFF' };
|
||||
|
|
@ -96,7 +96,7 @@ export function drawSelectedElementControllersVertexes(
|
|||
drawCircleController(ctx, calcViewPointSize(rotate.center, opts), { ...ctrlOpts, size: controllerSize, borderWidth: 2 });
|
||||
}
|
||||
|
||||
// drawSizeAuxiliaryLines(ctx, {
|
||||
// drawAuxiliaryLines(ctx, {
|
||||
// vertexes: [
|
||||
// calcViewPointSize(topLeft.center, opts),
|
||||
// calcViewPointSize(topRight.center, opts),
|
||||
|
|
@ -104,7 +104,8 @@ export function drawSelectedElementControllersVertexes(
|
|||
// calcViewPointSize(bottomLeft.center, opts)
|
||||
// ],
|
||||
// element,
|
||||
// groupQueue
|
||||
// groupQueue,
|
||||
// viewScaleInfo
|
||||
// });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -220,6 +220,19 @@ export const MiddlewareTextEditor: BoardMiddleware<ExtendEventMap, CoreEventMap
|
|||
|
||||
hideTextArea();
|
||||
});
|
||||
textarea.addEventListener('keydown', (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
textarea.addEventListener('keypress', (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
textarea.addEventListener('keyup', (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
textarea.addEventListener('wheel', (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
const textEditCallback = (e: TextEditEvent) => {
|
||||
if (e?.position && e?.element && e?.element?.type === 'text') {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,20 @@
|
|||
import { IDrawSettings } from '@idraw/types';
|
||||
import type { IDrawSettings, IDrawStorage, IDrawMode } from '@idraw/types';
|
||||
|
||||
export const defaultMode: IDrawMode = 'select';
|
||||
|
||||
export const defaultSettings: Required<IDrawSettings> = {
|
||||
enableScroll: true,
|
||||
enableSelect: true,
|
||||
enableScale: true,
|
||||
enableRuler: true,
|
||||
enableTextEdit: true,
|
||||
enableDrag: false
|
||||
mode: defaultMode
|
||||
};
|
||||
|
||||
export function getDefaultStorage(): IDrawStorage {
|
||||
const storage: IDrawStorage = {
|
||||
mode: defaultMode,
|
||||
enableRuler: false,
|
||||
enableScale: false,
|
||||
enableScroll: false,
|
||||
enableSelect: false,
|
||||
enableTextEdit: false,
|
||||
enableDrag: false
|
||||
};
|
||||
return storage;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,18 @@
|
|||
import { Core, MiddlewareSelector, MiddlewareScroller, MiddlewareScaler, MiddlewareRuler, MiddlewareTextEditor, MiddlewareDragger } from '@idraw/core';
|
||||
import { Core } from '@idraw/core';
|
||||
import type {
|
||||
PointSize,
|
||||
IDrawOptions,
|
||||
IDrawSettings,
|
||||
IDrawFeature,
|
||||
IDrawMode,
|
||||
Data,
|
||||
ViewSizeInfo,
|
||||
ViewScaleInfo,
|
||||
ElementType,
|
||||
Element,
|
||||
RecursivePartial,
|
||||
ElementPosition
|
||||
ElementPosition,
|
||||
IDrawStorage
|
||||
} from '@idraw/types';
|
||||
import type { IDrawEvent } from './event';
|
||||
import {
|
||||
|
|
@ -22,16 +25,21 @@ import {
|
|||
calcElementListSize,
|
||||
filterCompactData,
|
||||
calcViewCenterContent,
|
||||
calcViewCenter
|
||||
calcViewCenter,
|
||||
Store
|
||||
} from '@idraw/util';
|
||||
import { defaultSettings } from './config';
|
||||
import { defaultSettings, getDefaultStorage, defaultMode } from './config';
|
||||
import { exportImageFileBlobURL } from './file';
|
||||
import type { ExportImageFileBaseOptions, ExportImageFileResult } from './file';
|
||||
import { eventKeys } from './event';
|
||||
import { changeMode } from './mode';
|
||||
|
||||
export class iDraw {
|
||||
#core: Core<IDrawEvent>;
|
||||
#opts: IDrawOptions;
|
||||
#store: Store<IDrawStorage> = new Store<IDrawStorage>({
|
||||
defaultStorage: getDefaultStorage()
|
||||
});
|
||||
|
||||
constructor(mount: HTMLDivElement, options: IDrawOptions) {
|
||||
const opts = { ...defaultSettings, ...options };
|
||||
|
|
@ -43,63 +51,48 @@ export class iDraw {
|
|||
}
|
||||
|
||||
#init() {
|
||||
const { enableRuler, enableScale, enableScroll, enableSelect, enableTextEdit, enableDrag } = this.#opts;
|
||||
const core = this.#core;
|
||||
enableScroll === true && core.use(MiddlewareScroller);
|
||||
enableSelect === true && core.use(MiddlewareSelector);
|
||||
enableScale === true && core.use(MiddlewareScaler);
|
||||
enableRuler === true && core.use(MiddlewareRuler);
|
||||
enableTextEdit === true && core.use(MiddlewareTextEditor);
|
||||
enableDrag === true && core.use(MiddlewareDragger);
|
||||
const store = this.#store;
|
||||
changeMode('select', core, store);
|
||||
this.enable('ruler');
|
||||
}
|
||||
|
||||
#setFeature(feat: IDrawFeature, status: boolean) {
|
||||
if (feat === 'ruler') {
|
||||
const store = this.#store;
|
||||
store.set('enableRuler', !!status);
|
||||
const currentMode = store.get('mode');
|
||||
this.setMode(currentMode);
|
||||
}
|
||||
}
|
||||
|
||||
reset(opts: IDrawSettings) {
|
||||
const core = this.#core;
|
||||
const { enableRuler, enableScale, enableScroll, enableSelect, enableTextEdit, enableDrag } = opts;
|
||||
if (enableScroll === true) {
|
||||
core.use(MiddlewareScroller);
|
||||
} else if (enableScroll === false) {
|
||||
core.disuse(MiddlewareScroller);
|
||||
}
|
||||
|
||||
if (enableSelect === true) {
|
||||
core.use(MiddlewareSelector);
|
||||
} else if (enableSelect === false) {
|
||||
core.disuse(MiddlewareSelector);
|
||||
}
|
||||
|
||||
if (enableScale === true) {
|
||||
core.use(MiddlewareScaler);
|
||||
} else if (enableScale === false) {
|
||||
core.disuse(MiddlewareScaler);
|
||||
}
|
||||
|
||||
if (enableRuler === true) {
|
||||
core.use(MiddlewareRuler);
|
||||
} else if (enableRuler === false) {
|
||||
core.disuse(MiddlewareRuler);
|
||||
}
|
||||
|
||||
if (enableTextEdit === true) {
|
||||
core.use(MiddlewareTextEditor);
|
||||
} else if (enableTextEdit === false) {
|
||||
core.disuse(MiddlewareTextEditor);
|
||||
}
|
||||
|
||||
if (enableDrag === true) {
|
||||
core.use(MiddlewareDragger);
|
||||
} else if (enableDrag === false) {
|
||||
core.disuse(MiddlewareDragger);
|
||||
}
|
||||
|
||||
const store = this.#store;
|
||||
store.clear();
|
||||
changeMode(opts.mode || defaultMode, core, store);
|
||||
core.refresh();
|
||||
|
||||
this.#opts = {
|
||||
...this.#opts,
|
||||
...opts
|
||||
};
|
||||
}
|
||||
|
||||
setMode(mode: IDrawMode) {
|
||||
const core = this.#core;
|
||||
const store = this.#store;
|
||||
changeMode(mode || defaultMode, core, store);
|
||||
core.refresh();
|
||||
}
|
||||
|
||||
enable(feat: IDrawFeature) {
|
||||
this.#setFeature(feat, true);
|
||||
}
|
||||
|
||||
disable(feat: IDrawFeature) {
|
||||
this.#setFeature(feat, false);
|
||||
}
|
||||
|
||||
setData(data: Data) {
|
||||
const core = this.#core;
|
||||
core.setData(data);
|
||||
|
|
@ -273,7 +266,9 @@ export class iDraw {
|
|||
|
||||
destroy() {
|
||||
const core = this.#core;
|
||||
const store = this.#store;
|
||||
core.destroy();
|
||||
store.destroy();
|
||||
}
|
||||
|
||||
getViewCenter() {
|
||||
|
|
|
|||
|
|
@ -118,7 +118,11 @@ export {
|
|||
deepCloneElement,
|
||||
calcViewCenterContent,
|
||||
calcViewCenter,
|
||||
modifyElement
|
||||
modifyElement,
|
||||
calcElementViewRectInfo,
|
||||
calcElementOriginRectInfo,
|
||||
calcElementViewRectInfoMap,
|
||||
sortElementsViewVisiableInfoMap
|
||||
} from '@idraw/util';
|
||||
export { iDraw } from './idraw';
|
||||
export { eventKeys } from './event';
|
||||
|
|
|
|||
95
packages/idraw/src/mode.ts
Normal file
95
packages/idraw/src/mode.ts
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import type { IDrawMode, IDrawStorage } from '@idraw/types';
|
||||
import { Store } from '@idraw/util';
|
||||
import { Core, MiddlewareSelector, MiddlewareScroller, MiddlewareScaler, MiddlewareRuler, MiddlewareTextEditor, MiddlewareDragger } from '@idraw/core';
|
||||
import { IDrawEvent } from './event';
|
||||
|
||||
function isValidMode(mode: string | IDrawMode) {
|
||||
return ['select', 'drag', 'readOnly'].includes(mode);
|
||||
}
|
||||
|
||||
function runMiddlewares(core: Core<IDrawEvent>, store: Store<IDrawStorage>) {
|
||||
const { enableRuler, enableScale, enableScroll, enableSelect, enableTextEdit, enableDrag } = store.getSnapshot();
|
||||
if (enableScroll === true) {
|
||||
core.use(MiddlewareScroller);
|
||||
} else if (enableScroll === false) {
|
||||
core.disuse(MiddlewareScroller);
|
||||
}
|
||||
|
||||
if (enableSelect === true) {
|
||||
core.use(MiddlewareSelector);
|
||||
} else if (enableSelect === false) {
|
||||
core.disuse(MiddlewareSelector);
|
||||
}
|
||||
|
||||
if (enableScale === true) {
|
||||
core.use(MiddlewareScaler);
|
||||
} else if (enableScale === false) {
|
||||
core.disuse(MiddlewareScaler);
|
||||
}
|
||||
|
||||
if (enableRuler === true) {
|
||||
core.use(MiddlewareRuler);
|
||||
} else if (enableRuler === false) {
|
||||
core.disuse(MiddlewareRuler);
|
||||
}
|
||||
|
||||
if (enableTextEdit === true) {
|
||||
core.use(MiddlewareTextEditor);
|
||||
} else if (enableTextEdit === false) {
|
||||
core.disuse(MiddlewareTextEditor);
|
||||
}
|
||||
|
||||
if (enableDrag === true) {
|
||||
core.use(MiddlewareDragger);
|
||||
} else if (enableDrag === false) {
|
||||
core.disuse(MiddlewareDragger);
|
||||
}
|
||||
}
|
||||
|
||||
export function changeMode(mode: IDrawMode, core: Core<IDrawEvent>, store: Store<IDrawStorage>) {
|
||||
let enableScale: boolean = false;
|
||||
let enableScroll: boolean = false;
|
||||
let enableSelect: boolean = false;
|
||||
let enableTextEdit: boolean = false;
|
||||
let enableDrag: boolean = false;
|
||||
let enableRuler = store.get('enableRuler');
|
||||
|
||||
let innerMode: IDrawMode = 'select';
|
||||
store.set('mode', innerMode);
|
||||
if (isValidMode(mode)) {
|
||||
innerMode = mode;
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(`${mode} is invalid mode of iDraw.js`);
|
||||
}
|
||||
|
||||
if (innerMode === 'select') {
|
||||
enableScale = true;
|
||||
enableScroll = true;
|
||||
enableSelect = true;
|
||||
enableTextEdit = true;
|
||||
enableDrag = false;
|
||||
} else if (innerMode === 'drag') {
|
||||
enableScale = true;
|
||||
enableScroll = true;
|
||||
enableSelect = false;
|
||||
enableTextEdit = false;
|
||||
enableDrag = true;
|
||||
} else if (innerMode === 'readOnly') {
|
||||
enableScale = false;
|
||||
enableScroll = false;
|
||||
enableSelect = false;
|
||||
enableTextEdit = false;
|
||||
enableDrag = false;
|
||||
enableRuler = false;
|
||||
}
|
||||
|
||||
store.set('enableScale', enableScale);
|
||||
store.set('enableScroll', enableScroll);
|
||||
store.set('enableSelect', enableSelect);
|
||||
store.set('enableTextEdit', enableTextEdit);
|
||||
store.set('enableDrag', enableDrag);
|
||||
store.set('enableRuler', enableRuler);
|
||||
|
||||
runMiddlewares(core, store);
|
||||
}
|
||||
|
|
@ -1,12 +1,21 @@
|
|||
import type { CoreOptions } from './core';
|
||||
|
||||
export type IDrawMode = 'select' | 'drag' | 'readOnly';
|
||||
|
||||
export type IDrawFeature = 'ruler' | string; // TODO other feature
|
||||
|
||||
export interface IDrawSettings {
|
||||
enableScroll?: boolean;
|
||||
enableSelect?: boolean;
|
||||
enableScale?: boolean;
|
||||
enableRuler?: boolean;
|
||||
enableTextEdit?: boolean;
|
||||
enableDrag?: boolean;
|
||||
mode?: IDrawMode;
|
||||
}
|
||||
|
||||
export type IDrawOptions = CoreOptions & IDrawSettings;
|
||||
|
||||
export interface IDrawStorage {
|
||||
mode: IDrawMode;
|
||||
enableRuler: boolean;
|
||||
enableScale: boolean;
|
||||
enableScroll: boolean;
|
||||
enableSelect: boolean;
|
||||
enableTextEdit: boolean;
|
||||
enableDrag: boolean;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { Element, ElementType, ElementSize } from './element';
|
||||
import type { Element, ElementType, ElementSize, ElementPosition } from './element';
|
||||
import type { Point, PointSize } from './point';
|
||||
import type { Data } from './data';
|
||||
import type { ViewContext2D } from './context2d';
|
||||
|
|
@ -54,3 +54,31 @@ export interface ViewBoxSize {
|
|||
h: number;
|
||||
radiusList: [number, number, number, number];
|
||||
}
|
||||
|
||||
export type ViewRectInfo = {
|
||||
topLeft: PointSize;
|
||||
topRight: PointSize;
|
||||
bottomRight: PointSize;
|
||||
bottomLeft: PointSize;
|
||||
top: PointSize;
|
||||
right: PointSize;
|
||||
bottom: PointSize;
|
||||
left: PointSize;
|
||||
center: PointSize;
|
||||
};
|
||||
|
||||
export type ViewRectInfoMap = {
|
||||
originRectInfo: ViewRectInfo;
|
||||
viewRectInfo: ViewRectInfo | null;
|
||||
rangeRectInfo: ViewRectInfo | null;
|
||||
};
|
||||
|
||||
export type ViewVisibleInfo = ViewRectInfoMap & {
|
||||
isVisibleInView: boolean;
|
||||
isGroup: boolean;
|
||||
position: ElementPosition;
|
||||
};
|
||||
|
||||
export type ViewVisibleInfoMap = {
|
||||
[uuid: string]: ViewVisibleInfo;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ export {
|
|||
findElementQueueFromListByPosition,
|
||||
findElementsFromListByPositions,
|
||||
getGroupQueueFromList,
|
||||
getGroupQueueByElementPosition,
|
||||
getElementSize,
|
||||
mergeElementAsset,
|
||||
filterElementAsset,
|
||||
|
|
@ -54,8 +55,12 @@ export {
|
|||
isViewPointInElement,
|
||||
getViewPointAtElement,
|
||||
isElementInView,
|
||||
calcViewScaleInfo
|
||||
calcViewScaleInfo,
|
||||
calcElementViewRectInfo,
|
||||
calcElementOriginRectInfo,
|
||||
calcElementViewRectInfoMap
|
||||
} from './lib/view-calc';
|
||||
export { sortElementsViewVisiableInfoMap } from './lib/view-visible';
|
||||
export { rotatePoint, rotateVertexes, rotateByCenter } from './lib/rotate';
|
||||
export { getElementVertexes, calcElementVertexesInGroup, calcElementVertexesQueueInGroup, calcElementQueueVertexesQueueInGroup } from './lib/vertex';
|
||||
export { calcElementSizeController } from './lib/controller';
|
||||
|
|
|
|||
|
|
@ -293,6 +293,23 @@ export function getGroupQueueFromList(uuid: string, elements: Element<ElementTyp
|
|||
return groupQueue;
|
||||
}
|
||||
|
||||
export function getGroupQueueByElementPosition(elements: Element<ElementType>[], position: ElementPosition): Element<'group'>[] | null {
|
||||
const groupQueue: Element<'group'>[] = [];
|
||||
let currentElements: Element[] = elements;
|
||||
if (position.length > 1) {
|
||||
for (let i = 0; i < position.length - 1; i++) {
|
||||
const group = currentElements[i] as Element<'group'>;
|
||||
if (group?.type === 'group' && Array.isArray(group?.detail?.children)) {
|
||||
groupQueue.push(group);
|
||||
currentElements = group.detail.children;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return groupQueue;
|
||||
}
|
||||
|
||||
export function getElementSize(elem: Element): ElementSize {
|
||||
const { x, y, w, h, angle } = elem;
|
||||
const size: ElementSize = { x, y, w, h, angle };
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@ export class Store<T extends Record<string | symbol, any> = Record<string | symb
|
|||
}
|
||||
|
||||
getSnapshot(): T {
|
||||
return deepClone(this.#temp);
|
||||
// return deepClone(this.#temp);
|
||||
return this.#temp;
|
||||
}
|
||||
|
||||
clear() {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,21 @@
|
|||
import { Point, PointSize, Data, ViewScaleInfo, ViewSizeInfo, Element, ElementType, ElementSize, ViewContext2D, ViewRectVertexes } from '@idraw/types';
|
||||
import {
|
||||
Point,
|
||||
PointSize,
|
||||
Data,
|
||||
ViewScaleInfo,
|
||||
ViewSizeInfo,
|
||||
Element,
|
||||
ElementType,
|
||||
ElementSize,
|
||||
ViewContext2D,
|
||||
ViewRectVertexes,
|
||||
ViewRectInfo,
|
||||
ViewRectInfoMap
|
||||
} from '@idraw/types';
|
||||
import { rotateElementVertexes } from './rotate';
|
||||
import { checkRectIntersect } from './rect';
|
||||
import { calcElementVertexesInGroup } from './vertex';
|
||||
import { getCenterFromTwoPoints } from './point';
|
||||
|
||||
export function calcViewScaleInfo(info: { scale: number; offsetX: number; offsetY: number }, opts: { viewSizeInfo: ViewSizeInfo }): ViewScaleInfo {
|
||||
const { scale, offsetX, offsetY } = info;
|
||||
|
|
@ -83,7 +98,7 @@ export function calcViewElementSize(size: ElementSize, opts: { viewScaleInfo: Vi
|
|||
return newSize;
|
||||
}
|
||||
|
||||
export function calcViewPointSize(size: PointSize, opts: { viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo }): PointSize {
|
||||
export function calcViewPointSize(size: PointSize, opts: { viewScaleInfo: ViewScaleInfo }): PointSize {
|
||||
const { viewScaleInfo } = opts;
|
||||
const { x, y } = size;
|
||||
const { scale, offsetTop, offsetLeft } = viewScaleInfo;
|
||||
|
|
@ -224,3 +239,171 @@ export function isElementInView(elem: ElementSize, opts: { viewScaleInfo: ViewSc
|
|||
const elemSize = { x: elemStartX, y: elemStartY, w: elemEndX - elemStartX, h: elemEndY - elemStartY };
|
||||
return checkRectIntersect(viewSize, elemSize);
|
||||
}
|
||||
|
||||
export function calcElementOriginRectInfo(
|
||||
elemSize: ElementSize,
|
||||
opts: {
|
||||
groupQueue: Element<'group'>[];
|
||||
}
|
||||
): ViewRectInfo {
|
||||
const { groupQueue } = opts;
|
||||
|
||||
const vertexes = calcElementVertexesInGroup(elemSize, { groupQueue }) as ViewRectVertexes;
|
||||
|
||||
const top = getCenterFromTwoPoints(vertexes[0], vertexes[1]);
|
||||
const right = getCenterFromTwoPoints(vertexes[1], vertexes[2]);
|
||||
const bottom = getCenterFromTwoPoints(vertexes[2], vertexes[3]);
|
||||
const left = getCenterFromTwoPoints(vertexes[3], vertexes[0]);
|
||||
|
||||
const topLeft = vertexes[0];
|
||||
const topRight = vertexes[1];
|
||||
const bottomRight = vertexes[2];
|
||||
const bottomLeft = vertexes[3];
|
||||
|
||||
const maxX = Math.max(topLeft.x, topRight.x, bottomRight.x, bottomLeft.x);
|
||||
const maxY = Math.max(topLeft.y, topRight.y, bottomRight.y, bottomLeft.y);
|
||||
const minX = Math.min(topLeft.x, topRight.x, bottomRight.x, bottomLeft.x);
|
||||
const minY = Math.min(topLeft.y, topRight.y, bottomRight.y, bottomLeft.y);
|
||||
const center: PointSize = {
|
||||
x: (maxX + minX) / 2,
|
||||
y: (maxY + minY) / 2
|
||||
};
|
||||
|
||||
const rectInfo: ViewRectInfo = {
|
||||
center,
|
||||
topLeft,
|
||||
topRight,
|
||||
bottomLeft,
|
||||
bottomRight,
|
||||
top,
|
||||
right,
|
||||
left,
|
||||
bottom
|
||||
};
|
||||
|
||||
return rectInfo;
|
||||
}
|
||||
|
||||
export function calcElementViewRectInfo(
|
||||
elemSize: ElementSize,
|
||||
opts: {
|
||||
groupQueue: Element<'group'>[];
|
||||
viewScaleInfo: ViewScaleInfo;
|
||||
range?: boolean;
|
||||
}
|
||||
): ViewRectInfo {
|
||||
const { groupQueue, viewScaleInfo, range } = opts;
|
||||
|
||||
// Original RectInfo
|
||||
const originRectInfo = calcElementOriginRectInfo(elemSize, { groupQueue });
|
||||
const { center, top, bottom, left, right, topLeft, topRight, bottomLeft, bottomRight } = originRectInfo;
|
||||
|
||||
// View RectInfo
|
||||
const viewRectInfo: ViewRectInfo = {
|
||||
center: calcViewPointSize(center, { viewScaleInfo }),
|
||||
topLeft: calcViewPointSize(topLeft, { viewScaleInfo }),
|
||||
topRight: calcViewPointSize(topRight, { viewScaleInfo }),
|
||||
bottomLeft: calcViewPointSize(bottomLeft, { viewScaleInfo }),
|
||||
bottomRight: calcViewPointSize(bottomRight, { viewScaleInfo }),
|
||||
top: calcViewPointSize(top, { viewScaleInfo }),
|
||||
right: calcViewPointSize(right, { viewScaleInfo }),
|
||||
left: calcViewPointSize(left, { viewScaleInfo }),
|
||||
bottom: calcViewPointSize(bottom, { viewScaleInfo })
|
||||
};
|
||||
|
||||
if (range === true) {
|
||||
// Range RectInfo
|
||||
const viewMaxX = Math.max(viewRectInfo.topLeft.x, viewRectInfo.topRight.x, viewRectInfo.bottomRight.x, viewRectInfo.bottomLeft.x);
|
||||
const viewMaxY = Math.max(viewRectInfo.topLeft.y, viewRectInfo.topRight.y, viewRectInfo.bottomRight.y, viewRectInfo.bottomLeft.y);
|
||||
const viewMinX = Math.min(viewRectInfo.topLeft.x, viewRectInfo.topRight.x, viewRectInfo.bottomRight.x, viewRectInfo.bottomLeft.x);
|
||||
const viewMinY = Math.min(viewRectInfo.topLeft.y, viewRectInfo.topRight.y, viewRectInfo.bottomRight.y, viewRectInfo.bottomLeft.y);
|
||||
|
||||
const rangeCenter = { x: viewRectInfo.center.x, y: viewRectInfo.center.y };
|
||||
const rangeTopLeft = { x: viewMinX, y: viewMinY };
|
||||
const rangeTopRight = { x: viewMaxX, y: viewMinY };
|
||||
const rangeBottomRight = { x: viewMaxX, y: viewMaxY };
|
||||
const rangeBottomLeft = { x: viewMinX, y: viewMaxY };
|
||||
|
||||
const rangeTop = getCenterFromTwoPoints(rangeTopLeft, rangeTopRight);
|
||||
const rangeBottom = getCenterFromTwoPoints(rangeBottomLeft, rangeBottomRight);
|
||||
const rangeLeft = getCenterFromTwoPoints(rangeTopLeft, rangeBottomLeft);
|
||||
const rangeRight = getCenterFromTwoPoints(rangeTopRight, rangeBottomRight);
|
||||
|
||||
const rangeRectInfo: ViewRectInfo = {
|
||||
center: rangeCenter,
|
||||
topLeft: rangeTopLeft,
|
||||
topRight: rangeTopRight,
|
||||
bottomLeft: rangeBottomLeft,
|
||||
bottomRight: rangeBottomRight,
|
||||
top: rangeTop,
|
||||
right: rangeRight,
|
||||
left: rangeLeft,
|
||||
bottom: rangeBottom
|
||||
};
|
||||
return rangeRectInfo;
|
||||
}
|
||||
|
||||
return viewRectInfo;
|
||||
}
|
||||
|
||||
export function calcElementViewRectInfoMap(
|
||||
elemSize: ElementSize,
|
||||
opts: {
|
||||
groupQueue: Element<'group'>[];
|
||||
viewScaleInfo: ViewScaleInfo;
|
||||
}
|
||||
): ViewRectInfoMap {
|
||||
const { groupQueue, viewScaleInfo } = opts;
|
||||
|
||||
// Original RectInfo
|
||||
const originRectInfo = calcElementOriginRectInfo(elemSize, { groupQueue });
|
||||
const { center, top, bottom, left, right, topLeft, topRight, bottomLeft, bottomRight } = originRectInfo;
|
||||
|
||||
// View RectInfo
|
||||
const viewRectInfo: ViewRectInfo = {
|
||||
center: calcViewPointSize(center, { viewScaleInfo }),
|
||||
topLeft: calcViewPointSize(topLeft, { viewScaleInfo }),
|
||||
topRight: calcViewPointSize(topRight, { viewScaleInfo }),
|
||||
bottomLeft: calcViewPointSize(bottomLeft, { viewScaleInfo }),
|
||||
bottomRight: calcViewPointSize(bottomRight, { viewScaleInfo }),
|
||||
top: calcViewPointSize(top, { viewScaleInfo }),
|
||||
right: calcViewPointSize(right, { viewScaleInfo }),
|
||||
left: calcViewPointSize(left, { viewScaleInfo }),
|
||||
bottom: calcViewPointSize(bottom, { viewScaleInfo })
|
||||
};
|
||||
|
||||
// Range RectInfo
|
||||
const viewMaxX = Math.max(viewRectInfo.topLeft.x, viewRectInfo.topRight.x, viewRectInfo.bottomRight.x, viewRectInfo.bottomLeft.x);
|
||||
const viewMaxY = Math.max(viewRectInfo.topLeft.y, viewRectInfo.topRight.y, viewRectInfo.bottomRight.y, viewRectInfo.bottomLeft.y);
|
||||
const viewMinX = Math.min(viewRectInfo.topLeft.x, viewRectInfo.topRight.x, viewRectInfo.bottomRight.x, viewRectInfo.bottomLeft.x);
|
||||
const viewMinY = Math.min(viewRectInfo.topLeft.y, viewRectInfo.topRight.y, viewRectInfo.bottomRight.y, viewRectInfo.bottomLeft.y);
|
||||
|
||||
const rangeCenter = { x: viewRectInfo.center.x, y: viewRectInfo.center.y };
|
||||
const rangeTopLeft = { x: viewMinX, y: viewMinY };
|
||||
const rangeTopRight = { x: viewMaxX, y: viewMinY };
|
||||
const rangeBottomRight = { x: viewMaxX, y: viewMaxY };
|
||||
const rangeBottomLeft = { x: viewMinX, y: viewMaxY };
|
||||
|
||||
const rangeTop = getCenterFromTwoPoints(rangeTopLeft, rangeTopRight);
|
||||
const rangeBottom = getCenterFromTwoPoints(rangeBottomLeft, rangeBottomRight);
|
||||
const rangeLeft = getCenterFromTwoPoints(rangeTopLeft, rangeBottomLeft);
|
||||
const rangeRight = getCenterFromTwoPoints(rangeTopRight, rangeBottomRight);
|
||||
|
||||
const rangeRectInfo: ViewRectInfo = {
|
||||
center: rangeCenter,
|
||||
topLeft: rangeTopLeft,
|
||||
topRight: rangeTopRight,
|
||||
bottomLeft: rangeBottomLeft,
|
||||
bottomRight: rangeBottomRight,
|
||||
top: rangeTop,
|
||||
right: rangeRight,
|
||||
left: rangeLeft,
|
||||
bottom: rangeBottom
|
||||
};
|
||||
|
||||
return {
|
||||
originRectInfo,
|
||||
viewRectInfo,
|
||||
rangeRectInfo
|
||||
};
|
||||
}
|
||||
|
|
|
|||
49
packages/util/src/lib/view-visible.ts
Normal file
49
packages/util/src/lib/view-visible.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import { Element, ElementPosition, Elements, ViewRectInfo, ViewVisibleInfoMap, ViewVisibleInfo } from '@idraw/types';
|
||||
import { calcElementOriginRectInfo } from './view-calc';
|
||||
import { getGroupQueueByElementPosition } from './element';
|
||||
|
||||
export function sortElementsViewVisiableInfoMap(elements: Elements): ViewVisibleInfoMap {
|
||||
const visibleInfoMap: ViewVisibleInfoMap = {};
|
||||
const currentPosition: ElementPosition = [];
|
||||
|
||||
const _walk = (elem: Element) => {
|
||||
const baseInfo: Omit<ViewVisibleInfo, 'originRectInfo'> = {
|
||||
viewRectInfo: null,
|
||||
rangeRectInfo: null,
|
||||
isVisibleInView: true,
|
||||
isGroup: elem.type === 'group',
|
||||
position: [...currentPosition]
|
||||
};
|
||||
let originRectInfo: ViewRectInfo | null = null;
|
||||
const groupQueue = getGroupQueueByElementPosition(elements, currentPosition);
|
||||
originRectInfo = calcElementOriginRectInfo(elem, {
|
||||
groupQueue: groupQueue || []
|
||||
});
|
||||
visibleInfoMap[elem.uuid] = {
|
||||
...baseInfo,
|
||||
...{
|
||||
originRectInfo: originRectInfo as ViewRectInfo
|
||||
}
|
||||
};
|
||||
|
||||
if (elem.type === 'group') {
|
||||
(elem as Element<'group'>).detail.children.forEach((ele, i) => {
|
||||
if (ele.type === 'group') {
|
||||
currentPosition.push(i);
|
||||
}
|
||||
_walk(ele);
|
||||
if (ele.type === 'group') {
|
||||
currentPosition.pop();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
elements.forEach((elem, index) => {
|
||||
currentPosition.push(index);
|
||||
_walk(elem);
|
||||
currentPosition.pop();
|
||||
});
|
||||
|
||||
return visibleInfoMap;
|
||||
}
|
||||
Loading…
Reference in a new issue