feat: init deep selector middleware

This commit is contained in:
chenshenhai 2023-06-10 18:09:11 +08:00
parent 3b63dce68b
commit 06dc95707b
36 changed files with 1362 additions and 747 deletions

View file

@ -1,28 +1,33 @@
module.exports = {
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"extends": ["plugin:@typescript-eslint/recommended"],
"parserOptions": {
"sourceType": "module"
},
"rules": {
"semi": "error",
"indent": ["error", 2, {
"SwitchCase": 1,
"VariableDeclarator": 1,
"outerIIFEBody": 1,
"MemberExpression": 1,
"FunctionDeclaration": { "parameters": 1, "body": 1 },
"FunctionExpression": { "parameters": 1, "body": 1 },
"CallExpression": { "arguments": 1 },
"ArrayExpression": 1,
"ObjectExpression": 1,
"ImportDeclaration": 1,
"flatTernaryExpressions": false,
"ignoreComments": false
}],
"@typescript-eslint/rule-name": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/explicit-module-boundary-types": "off"
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
extends: ['plugin:@typescript-eslint/recommended'],
parserOptions: {
sourceType: 'module'
},
rules: {
semi: 'error',
indent: [
'error',
2,
{
SwitchCase: 1,
VariableDeclarator: 1,
outerIIFEBody: 1,
MemberExpression: 1,
FunctionDeclaration: { parameters: 1, body: 1 },
FunctionExpression: { parameters: 1, body: 1 },
CallExpression: { arguments: 1 },
ArrayExpression: 1,
ObjectExpression: 1,
ImportDeclaration: 1,
flatTernaryExpressions: false,
ignoreComments: false
}
],
'no-console': 1,
'@typescript-eslint/rule-name': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off'
}
};

View file

@ -71,6 +71,7 @@ export class Board {
this._handleHover(e);
}, frameTime)
);
this._watcher.on(
'wheelX',
throttle((e) => {
@ -87,6 +88,7 @@ export class Board {
this._watcher.on('scrollX', this._handleScrollX.bind(this));
this._watcher.on('scrollY', this._handleScrollY.bind(this));
this._watcher.on('resize', this._handleResize.bind(this));
this._watcher.on('doubleClick', this._handleDoubleClick.bind(this));
}
private _handlePointStart(e: BoardWatcherEventMap['pointStart']) {
@ -129,6 +131,16 @@ export class Board {
}
}
private _handleDoubleClick(e: BoardWatcherEventMap['doubleClick']) {
for (let i = 0; i < this._activeMiddlewareObjs.length; i++) {
const obj = this._activeMiddlewareObjs[i];
const result = obj?.doubleClick?.(e);
if (result === false) {
return;
}
}
}
private _handleWheelX(e: BoardWatcherEventMap['wheelX']) {
for (let i = 0; i < this._activeMiddlewareObjs.length; i++) {
const obj = this._activeMiddlewareObjs[i];

View file

@ -11,7 +11,7 @@ export class Calculator implements ViewCalculator {
viewScale(num: number, prevScaleInfo: ViewScaleInfo, viewSizeInfo: ViewSizeInfo): ViewScaleInfo {
const scale = num;
const { width, height, contextX, contextY, contextWidth, contextHeight } = viewSizeInfo;
const { width, height, contextWidth, contextHeight } = viewSizeInfo;
let offsetLeft = 0;
let offsetRight = 0;
let offsetTop = 0;
@ -47,7 +47,7 @@ export class Calculator implements ViewCalculator {
viewScroll(opts: { moveX?: number; moveY?: number }, scaleInfo: ViewScaleInfo, viewSizeInfo: ViewSizeInfo): ViewScaleInfo {
const scale = scaleInfo.scale;
const { moveX, moveY } = opts;
const { width, height, contextWidth, contextHeight, contextX, contextY } = viewSizeInfo;
const { width, height, contextWidth, contextHeight } = viewSizeInfo;
let offsetLeft = scaleInfo.offsetLeft;
let offsetRight = scaleInfo.offsetRight;
let offsetTop = scaleInfo.offsetTop;
@ -103,7 +103,8 @@ export class Calculator implements ViewCalculator {
const { x, y, w, h, angle } = size;
const { contextX = 0, contextY = 0 } = viewSizeInfo;
const { scale, offsetTop, offsetLeft } = scaleInfo;
return {
const newSize = {
x: x * scale + offsetLeft - contextX,
y: y * scale + offsetTop - contextY,
w: w * scale,
@ -111,6 +112,8 @@ export class Calculator implements ViewCalculator {
angle
};
return newSize;
// const { x, y, w, h, angle } = size;
// const { scale, offsetTop, offsetLeft } = scaleInfo;
// return {

View file

@ -10,7 +10,7 @@ export class BoardWatcher extends EventEmitter<BoardWatcherEventMap> {
private _store: Store<BoardWatcherStore>;
constructor(opts: BoardWatcherOptions) {
super();
const store = new Store<BoardWatcherStore>({ defaultStorage: { hasPointDown: true } });
const store = new Store<BoardWatcherStore>({ defaultStorage: { hasPointDown: true, prevClickPoint: null } });
this._store = store;
this._opts = opts;
this._init();
@ -97,6 +97,25 @@ export class BoardWatcher extends EventEmitter<BoardWatcherEventMap> {
},
{ passive: false }
);
container.addEventListener('click', (e: MouseEvent) => {
if (!this._isInTarget(e)) {
return;
}
e.preventDefault();
this._store.set('hasPointDown', true);
const point = this._getPoint(e);
if (!this._isVaildPoint(point)) {
return;
}
const maxLimitTime = 500;
const t = Date.now();
const preClickPoint = this._store.get('prevClickPoint');
if (preClickPoint && t - preClickPoint.t <= maxLimitTime && Math.abs(preClickPoint.x - point.x) <= 5 && Math.abs(preClickPoint.y - point.y) <= 5) {
this.trigger('doubleClick', { point });
} else {
this._store.set('prevClickPoint', point);
}
});
}
private _isInTarget(e: MouseEvent | WheelEvent) {

View file

@ -2,7 +2,8 @@ import type { Data, CoreOptions, BoardMiddleware, ViewSizeInfo } from '@idraw/ty
import { Board } from '@idraw/board';
import { createBoardContexts, validateElements, calcElementsContextSize } from '@idraw/util';
export { MiddlewareSelector } from './middleware/selector';
// export { MiddlewareSelector } from './middleware/selector';
export { MiddlewareSelector } from './middleware/deep-selector';
export { MiddlewareScroller } from './middleware/scroller';
export { MiddlewareRuler } from './middleware/rule';
@ -13,6 +14,7 @@ export class Core {
private _canvas: HTMLCanvasElement;
constructor(mount: HTMLDivElement, opts: CoreOptions) {
const { devicePixelRatio = 1, width, height } = opts;
this._opts = opts;
this._mount = mount;
const canvas = document.createElement('canvas');
@ -45,13 +47,12 @@ export class Core {
this._board.setData(data);
const sharer = this._board.getSharer();
const currentViewSize = sharer.getActiveViewSizeInfo();
const currentScaleInfo = sharer.getActiveScaleInfo();
// const currentScaleInfo = sharer.getActiveScaleInfo();
const newViewContextSize = calcElementsContextSize(data.elements, {
viewWidth: currentViewSize.width,
viewHeight: currentViewSize.height
viewHeight: currentViewSize.height,
extend: true
});
this.resize({
...currentViewSize,
...newViewContextSize

View file

@ -0,0 +1,14 @@
export const key = 'SELECT';
export const keyHoverElementSize = Symbol(`${key}_hoverElementSize`);
export const keyActionType = Symbol(`${key}_actionType`); // 'select' | 'drag-list' | 'drag-list-end' | 'drag' | 'hover' | 'resize' | 'area' | null = null;
export const keyResizeType = Symbol(`${key}_resizeType`); // ResizeType | null;
export const keyAreaStart = Symbol(`${key}_areaStart`); // Point
export const keyAreaEnd = Symbol(`${key}_areaEnd`); // Point
export const keyInGroupQueue = Symbol(`${key}_targetQueue`); // Element<'group'>[]
// export const keyHoverElementSize = `${key}_hoverElementSize`;
// export const keyActionType = `${key}_actionType`; // 'select' | 'drag-list' | 'drag-list-end' | 'drag' | 'hover' | 'resize' | 'area' | null = null;
// export const keyResizeType = `${key}_resizeType`; // ResizeType | null;
// export const keyAreaStart = `${key}_areaStart`; // Point
// export const keyAreaEnd = `${key}_areaEnd`; // Point
// export const keyInGroupQueue = `${key}_targetQueue`; // Element<'group'>[]

View file

@ -0,0 +1,88 @@
import type { ElementSize } from '@idraw/types';
import type { ElementSizeController } from './types';
const wrapperColor = '#1973ba';
export function calcElementControllerStyle(elemSize: ElementSize): ElementSizeController {
const bw = 0; // TODO
const ctrlSize = 8;
const ctrlBgColor = '#FFFFFF';
const ctrlBorderWidth = 2;
const ctrlBorderColor = wrapperColor;
const { x, y, w, h } = elemSize;
const sizeControllers: ElementSizeController = {
// topLeft: {
// x: x - bw - ctrlSize / 2,
// y: y - bw - ctrlSize / 2,
// w: ctrlSize,
// h: ctrlSize,
// borderWidth: ctrlBorderWidth,
// borderColor: ctrlBorderColor,
// bgColor: ctrlBgColor
// },
top: {
x: x - bw + w / 2 - ctrlSize / 2,
y: y - bw - ctrlSize / 2,
w: ctrlSize,
h: ctrlSize,
borderWidth: ctrlBorderWidth,
borderColor: ctrlBorderColor,
bgColor: ctrlBgColor
},
// topRight: {
// x: x + w - bw - ctrlSize / 2,
// y: y - bw - ctrlSize / 2,
// w: ctrlSize,
// h: ctrlSize,
// borderWidth: ctrlBorderWidth,
// borderColor: ctrlBorderColor,
// bgColor: ctrlBgColor
// },
right: {
x: x + w - bw - ctrlSize / 2,
y: y + h / 2 - bw - ctrlSize / 2,
w: ctrlSize,
h: ctrlSize,
borderWidth: ctrlBorderWidth,
borderColor: ctrlBorderColor,
bgColor: ctrlBgColor
},
// bottomRight: {
// x: x + w - bw - ctrlSize / 2,
// y: y + h - bw - ctrlSize / 2,
// w: ctrlSize,
// h: ctrlSize,
// borderWidth: ctrlBorderWidth,
// borderColor: ctrlBorderColor,
// bgColor: ctrlBgColor
// },
bottom: {
x: x + w / 2 - bw - ctrlSize / 2,
y: y + h - bw - ctrlSize / 2,
w: ctrlSize,
h: ctrlSize,
borderWidth: ctrlBorderWidth,
borderColor: ctrlBorderColor,
bgColor: ctrlBgColor
},
// bottomLeft: {
// x: x - bw - ctrlSize / 2,
// y: y + h - bw - ctrlSize / 2,
// w: ctrlSize,
// h: ctrlSize,
// borderWidth: ctrlBorderWidth,
// borderColor: ctrlBorderColor,
// bgColor: ctrlBgColor
// },
left: {
x: x - bw - ctrlSize / 2,
y: y + h / 2 - bw - ctrlSize / 2,
w: ctrlSize,
h: ctrlSize,
borderWidth: ctrlBorderWidth,
borderColor: ctrlBorderColor,
bgColor: ctrlBgColor
}
};
return sizeControllers;
}

View file

@ -0,0 +1,189 @@
import type { Element, ElementSize, ElementType, PointSize, RendererDrawElementOptions, ViewContext2D } from '@idraw/types';
import { rotateElement, rotateElementVertexes } from '@idraw/util';
// import { calcElementControllerStyle } from './controller';
import type { AreaSize, ControllerStyle, ElementSizeController } from './types';
const wrapperColor = '#1973ba';
export function drawPointWrapper(ctx: ViewContext2D, elem: ElementSize) {
const bw = 0;
const { x, y, w, h } = elem;
const { angle = 0 } = elem;
rotateElement(ctx, { x, y, w, h, angle }, () => {
ctx.setLineDash([]);
ctx.lineWidth = 1;
ctx.strokeStyle = wrapperColor;
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: ViewContext2D, elem: ElementSize) {
const bw = 0;
const { x, y, w, h } = elem;
const { angle = 0 } = elem;
rotateElement(ctx, { x, y, w, h, angle }, () => {
// ctx.setLineDash([4, 4]);
ctx.setLineDash([]);
ctx.lineWidth = 1;
ctx.strokeStyle = wrapperColor;
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();
});
}
function drawController(ctx: ViewContext2D, style: ControllerStyle) {
const { x, y, w, h, borderColor, borderWidth, bgColor } = style;
ctx.setLineDash([]);
ctx.lineWidth = borderWidth;
ctx.strokeStyle = borderColor;
ctx.fillStyle = bgColor;
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.closePath();
ctx.stroke();
ctx.fill();
}
export function drawElementControllers(
ctx: ViewContext2D,
elem: ElementSize,
opts: Omit<RendererDrawElementOptions, 'loader' | 'parentElementSize'> & { sizeControllers: ElementSizeController }
) {
const bw = 0;
const { x, y, w, h } = elem;
const { angle = 0 } = elem;
const { sizeControllers } = opts;
rotateElement(ctx, { x, y, w, h, angle }, () => {
ctx.setLineDash([]);
ctx.lineWidth = 2;
ctx.strokeStyle = wrapperColor;
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();
Object.keys(sizeControllers).forEach((name: string) => {
const ctrl = sizeControllers[name];
drawController(ctx, { ...ctrl, ...{} });
});
});
}
export function drawElementListShadows(ctx: ViewContext2D, elements: Element<ElementType>[], opts?: Omit<RendererDrawElementOptions, 'loader'>) {
elements.forEach((elem) => {
let { x, y, w, h } = elem;
const { angle = 0 } = elem;
if (opts?.calculator) {
const { calculator } = opts;
const size = calculator.elementSize({ x, y, w, h }, opts.scaleInfo, opts.viewSize);
x = size.x;
y = size.y;
w = size.w;
h = size.h;
}
const vertexes = rotateElementVertexes({ x, y, w, h, angle });
if (vertexes.length >= 2) {
ctx.setLineDash([]);
ctx.lineWidth = 1;
ctx.strokeStyle = '#aaaaaa';
ctx.fillStyle = '#0000001A';
ctx.beginPath();
ctx.moveTo(vertexes[0].x, vertexes[0].y);
for (let i = 0; i < vertexes.length; i++) {
const p = vertexes[i];
ctx.lineTo(p.x, p.y);
}
ctx.closePath();
ctx.stroke();
ctx.fill();
}
});
}
export function drawArea(ctx: ViewContext2D, opts: { start: PointSize; end: PointSize }) {
const { start, end } = opts;
ctx.setLineDash([]);
ctx.lineWidth = 1;
ctx.strokeStyle = '#1976d2';
ctx.fillStyle = '#1976d24f';
ctx.beginPath();
ctx.moveTo(start.x, start.y);
ctx.lineTo(end.x, start.y);
ctx.lineTo(end.x, end.y);
ctx.lineTo(start.x, end.y);
ctx.closePath();
ctx.stroke();
ctx.fill();
}
export function drawListArea(ctx: ViewContext2D, opts: { areaSize: AreaSize }) {
const { areaSize } = opts;
const { x, y, w, h } = areaSize;
ctx.setLineDash([]);
ctx.lineWidth = 1;
ctx.strokeStyle = '#1976d2';
ctx.fillStyle = '#1976d21c';
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + w, y);
ctx.lineTo(x + w, y + h);
ctx.lineTo(x, y + h);
ctx.closePath();
ctx.stroke();
ctx.fill();
}
export function drawGroupsWrapper(ctx: ViewContext2D, elemList: ElementSize[]) {
let totalX = 0;
let totalY = 0;
let totalAngle = 0;
for (let i = 0; i < elemList.length; i++) {
const elem = elemList[i];
const bw = 0;
const { x, y, w, h, angle = 0 } = elem;
totalX += x;
totalY += y;
totalAngle += angle;
rotateElement(ctx, { x: totalX, y: totalY, w, h, angle: totalAngle }, () => {
ctx.setLineDash([4, 4]);
ctx.lineWidth = 2;
ctx.strokeStyle = wrapperColor;
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();
});
}
}

View file

@ -0,0 +1,379 @@
import { getSelectedElementIndexes, getSelectedElements, calcElementsViewInfo } from '@idraw/util';
import type { Point, PointWatcherEvent, BoardMiddleware, Element, ElementSize, ActionType, ResizeType, DeepSelectorSharedStorage } from './types';
import { drawPointWrapper, drawHoverWrapper, drawElementControllers, drawArea, drawListArea, drawGroupsWrapper } from './draw-wrapper';
import { calcElementControllerStyle } from './controller';
import { getPointTarget, resizeElement, getSelectedListArea, calcSelectedElementsArea, isElementInGroup } from './util';
import { key, keyHoverElementSize, keyActionType, keyResizeType, keyAreaStart, keyAreaEnd, keyInGroupQueue } from './config';
export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage> = (opts) => {
const { viewer, sharer, viewContent, calculator } = opts;
const { helperContext } = viewContent;
let prevPoint: Point | null = null;
sharer.setSharedStorage(keyActionType, null);
const getIndexes = () => {
const data = sharer.getActiveStorage('data');
if (data) {
const uuids = sharer.getActiveStorage('selectedUUIDs');
const idxes: Array<number | string> = getSelectedElementIndexes(data, uuids);
return idxes;
} else {
return [];
}
};
const getActiveElements = () => {
const data = sharer.getActiveStorage('data');
if (data) {
const uuids = sharer.getActiveStorage('selectedUUIDs');
const elems = getSelectedElements(data, uuids);
return elems;
} else {
return [];
}
};
const pushGroupQueue = (elem: Element<'group'>) => {
let groupQueue = sharer.getSharedStorage(keyInGroupQueue);
if (!Array.isArray(groupQueue)) {
groupQueue = [];
}
if (groupQueue.length > 0) {
if (isElementInGroup(elem, groupQueue[groupQueue.length - 1])) {
groupQueue.push(elem);
} else {
groupQueue = [];
}
} else if (groupQueue.length === 0) {
groupQueue.push(elem);
}
sharer.setSharedStorage(keyInGroupQueue, groupQueue);
return groupQueue.length > 0;
};
const clear = () => {
sharer.setSharedStorage(keyActionType, null);
sharer.setSharedStorage(keyHoverElementSize, null);
sharer.setSharedStorage(keyResizeType, null);
sharer.setSharedStorage(keyAreaStart, null);
sharer.setSharedStorage(keyAreaEnd, null);
sharer.setSharedStorage(keyInGroupQueue, null);
};
clear();
return {
mode: key,
hover: (e: PointWatcherEvent) => {
const data = sharer.getActiveStorage('data');
const resizeType = sharer.getSharedStorage(keyResizeType);
const actionType = sharer.getSharedStorage(keyActionType);
if (resizeType || (['area', 'drag', 'drag-list'] as ActionType[]).includes(actionType)) {
sharer.setSharedStorage(keyHoverElementSize, null);
return;
}
if (actionType === 'drag') {
sharer.setSharedStorage(keyHoverElementSize, null);
} else if (data) {
const selectedElements = getActiveElements();
const scaleInfo = sharer.getActiveScaleInfo();
const viewSize = sharer.getActiveViewSizeInfo();
const target = getPointTarget(e.point, {
ctx: helperContext,
data,
selectedIndexes: getIndexes(),
selectedUUIDs: sharer.getActiveStorage('selectedUUIDs') || [],
selectedElements: selectedElements,
scaleInfo,
viewSize,
calculator,
areaSize: calcSelectedElementsArea(selectedElements, {
scaleInfo,
viewSize,
calculator
}),
groupQueue: [] // TODO
});
if (target.type === 'over-element' && target?.elements?.length === 1) {
const { x, y, w, h, angle } = target.elements[0];
sharer.setSharedStorage(keyHoverElementSize, { x, y, w, h, angle });
viewer.drawFrame();
return;
}
if (sharer.getSharedStorage(keyHoverElementSize)) {
sharer.setSharedStorage(keyHoverElementSize, null);
viewer.drawFrame();
return;
}
}
},
pointStart: (e: PointWatcherEvent) => {
// reset all shared storage
// clear();
sharer.setSharedStorage(keyHoverElementSize, null);
const data = sharer.getActiveStorage('data');
const listAreaSize = calcSelectedElementsArea(getActiveElements(), {
scaleInfo: sharer.getActiveScaleInfo(),
viewSize: sharer.getActiveViewSizeInfo(),
calculator
});
const target = getPointTarget(e.point, {
ctx: helperContext,
data,
selectedIndexes: getIndexes(),
selectedUUIDs: sharer.getActiveStorage('selectedUUIDs') || [],
selectedElements: getActiveElements(),
scaleInfo: sharer.getActiveScaleInfo(),
viewSize: sharer.getActiveViewSizeInfo(),
calculator,
areaSize: listAreaSize,
groupQueue: [] // TODO
});
if (target.type === 'list-area') {
sharer.setSharedStorage(keyActionType, 'drag-list');
} else if (target.type === 'over-element' && target?.uuids?.length === 1 && target?.elements?.length === 1) {
sharer.setActiveStorage('selectedUUIDs', target?.uuids[0] ? [target?.uuids[0]] : []);
sharer.setSharedStorage(keyActionType, 'drag');
} else if (target.type?.startsWith('resize-')) {
sharer.setSharedStorage(keyResizeType, target.type as ResizeType);
sharer.setSharedStorage(keyActionType, 'resize');
} else {
clear();
sharer.setSharedStorage(keyActionType, 'area');
sharer.setSharedStorage(keyAreaStart, e.point);
sharer.setActiveStorage('selectedUUIDs', []);
}
if (target.type) {
prevPoint = e.point;
} else {
prevPoint = null;
}
viewer.drawFrame();
},
pointMove: (e: PointWatcherEvent) => {
const data = sharer.getActiveStorage('data');
const indexes = getIndexes();
const elems = getActiveElements();
const scale = sharer.getActiveStorage('scale') || 1;
const start = prevPoint;
const end = e.point;
const resizeType = sharer.getSharedStorage(keyResizeType);
const actionType = sharer.getSharedStorage(keyActionType);
if (actionType === 'drag') {
if (data && elems?.length === 1 && indexes?.length === 1 && typeof indexes[0] === 'number' && indexes[0] >= 0 && start && end) {
data.elements[indexes[0]].x += (end.x - start.x) / scale;
data.elements[indexes[0]].y += (end.y - start.y) / scale;
sharer.setActiveStorage('data', data);
prevPoint = e.point;
} else {
prevPoint = null;
}
viewer.drawFrame();
} else if (actionType === 'drag-list') {
if (data && start && end && indexes?.length > 1) {
const moveX = (end.x - start.x) / scale;
const moveY = (end.y - start.y) / scale;
indexes.forEach((idx: number | string) => {
if (typeof idx === 'number' && data.elements[idx]) {
data.elements[idx].x += moveX;
data.elements[idx].y += moveY;
}
});
sharer.setActiveStorage('data', data);
prevPoint = e.point;
} else {
prevPoint = null;
}
viewer.drawFrame();
} else if (actionType === 'resize') {
if (
data &&
elems?.length === 1 &&
indexes?.length === 1 &&
typeof indexes[0] === 'number' &&
indexes[0] >= 0 &&
start &&
resizeType?.startsWith('resize-')
) {
const resizedElemSize = resizeElement(elems[0], { scale, start, end, resizeType });
data.elements[indexes[0]].x = resizedElemSize.x;
data.elements[indexes[0]].y = resizedElemSize.y;
data.elements[indexes[0]].w = resizedElemSize.w;
data.elements[indexes[0]].h = resizedElemSize.h;
prevPoint = e.point;
viewer.drawFrame();
}
} else if (actionType === 'area') {
sharer.setSharedStorage(keyAreaEnd, e.point);
viewer.drawFrame();
}
},
pointEnd(e: PointWatcherEvent) {
const data = sharer.getActiveStorage('data');
const resizeType = sharer.getSharedStorage(keyResizeType);
const actionType = sharer.getSharedStorage(keyActionType);
const scaleInfo = sharer.getActiveScaleInfo();
const viewSize = sharer.getActiveViewSizeInfo();
const { offsetLeft, offsetTop } = scaleInfo;
let needDrawFrame = false;
if (actionType === 'resize' && resizeType) {
sharer.setSharedStorage(keyResizeType, null);
} else if (actionType === 'area') {
sharer.setSharedStorage(keyActionType, null);
if (data) {
const start = sharer.getSharedStorage(keyAreaStart);
const end = sharer.getSharedStorage(keyAreaEnd);
if (start && end) {
const { uuids } = getSelectedListArea(data, {
start,
end,
calculator,
scaleInfo: sharer.getActiveScaleInfo(),
viewSize: sharer.getActiveViewSizeInfo()
});
if (uuids.length > 0) {
sharer.setActiveStorage('selectedUUIDs', uuids);
sharer.setSharedStorage(keyActionType, 'drag-list');
needDrawFrame = true;
}
}
}
} else if (actionType === 'drag-list') {
sharer.setSharedStorage(keyActionType, 'drag-list-end');
needDrawFrame = true;
} else if (data) {
const result = calculator.getPointElement(e.point, data, sharer.getActiveScaleInfo(), sharer.getActiveViewSizeInfo());
if (result.element) {
sharer.setSharedStorage(keyActionType, 'select');
needDrawFrame = true;
} else {
sharer.setSharedStorage(keyActionType, null);
}
}
if (sharer.getSharedStorage(keyActionType) === null) {
clear();
needDrawFrame = true;
}
const finalDrawFrame = () => {
if (!needDrawFrame) {
return;
}
if (data && Array.isArray(data?.elements) && (['drag', 'drag-list'] as ActionType[]).includes(actionType)) {
const viewInfo = calcElementsViewInfo(data.elements, viewSize, { extend: true });
sharer.setActiveStorage('contextX', viewInfo.contextSize.contextX);
sharer.setActiveStorage('contextY', viewInfo.contextSize.contextY);
sharer.setActiveStorage('contextHeight', viewInfo.contextSize.contextHeight);
sharer.setActiveStorage('contextWidth', viewInfo.contextSize.contextWidth);
viewer.scrollX(offsetLeft + viewInfo.changeContextLeft);
viewer.scrollY(offsetTop + viewInfo.changeContextTop);
}
viewer.drawFrame();
};
finalDrawFrame();
},
pointLeave() {
clear();
viewer.drawFrame();
},
doubleClick(e: PointWatcherEvent) {
// console.log('doubleClick =====', e);
const groupQueue = sharer.getSharedStorage(keyInGroupQueue);
const data = sharer.getActiveStorage('data');
const target = getPointTarget(e.point, {
ctx: helperContext,
data,
selectedIndexes: getIndexes(),
selectedUUIDs: sharer.getActiveStorage('selectedUUIDs') || [],
selectedElements: getActiveElements(),
scaleInfo: sharer.getActiveScaleInfo(),
viewSize: sharer.getActiveViewSizeInfo(),
calculator,
areaSize: null,
groupQueue: [] // TODO
});
if (target.elements.length === 1 && target.elements[0]?.type === 'group') {
const pushResult = pushGroupQueue(target.elements[0] as Element<'group'>);
if (pushResult === true) {
viewer.drawFrame();
return;
}
}
sharer.setSharedStorage(keyActionType, null);
console.log('doubleClick target ======= ', target);
},
beforeDrawFrame({ snapshot }) {
const { activeStore, sharedStore } = snapshot;
const {
data,
selectedUUIDs,
scale,
offsetLeft,
offsetTop,
offsetRight,
offsetBottom,
width,
height,
contextX,
contextY,
contextHeight,
contextWidth,
devicePixelRatio
} = activeStore;
const scaleInfo = { scale, offsetLeft, offsetTop, offsetRight, offsetBottom };
const viewSize = { width, height, contextX, contextY, contextHeight, contextWidth, devicePixelRatio };
// const elem = data?.elements?.[selectedIndexes?.[0] as number];
const selectedElements = getSelectedElements(data, selectedUUIDs);
const elem = selectedElements[0];
const hoverElement: ElementSize = sharedStore[keyHoverElementSize] as ElementSize;
const actionType: ActionType = sharedStore[keyActionType] as ActionType;
const areaStart: Point | null = sharedStore[keyAreaStart];
const areaEnd: Point | null = sharedStore[keyAreaEnd];
const groupQueue: Element<'group'>[] | null = sharedStore[keyInGroupQueue];
if (groupQueue && groupQueue?.length > 0) {
// in group
drawGroupsWrapper(helperContext, groupQueue);
} else {
// in root
const drawOpts = { calculator, scaleInfo, viewSize };
if (hoverElement && actionType !== 'drag') {
const hoverElemSize = calculator.elementSize(hoverElement, scaleInfo, viewSize);
drawHoverWrapper(helperContext, hoverElemSize);
}
if (elem && (['select', 'drag', 'resize'] as ActionType[]).includes(actionType)) {
const selectedElemSize = calculator.elementSize(elem, scaleInfo, viewSize);
const sizeControllers = calcElementControllerStyle(selectedElemSize);
drawPointWrapper(helperContext, selectedElemSize);
drawElementControllers(helperContext, selectedElemSize, { ...drawOpts, sizeControllers });
} else if (actionType === 'area' && areaStart && areaEnd) {
drawArea(helperContext, { start: areaStart, end: areaEnd });
} else if ((['drag-list', 'drag-list-end'] as ActionType[]).includes(actionType)) {
const listAreaSize = calcSelectedElementsArea(getActiveElements(), {
scaleInfo: sharer.getActiveScaleInfo(),
viewSize: sharer.getActiveViewSizeInfo(),
calculator
});
if (listAreaSize) {
drawListArea(helperContext, { areaSize: listAreaSize });
}
}
}
}
};
};

View file

@ -0,0 +1,63 @@
import { keyHoverElementSize, keyActionType, keyResizeType, keyAreaStart, keyAreaEnd, keyInGroupQueue } from './config';
import {
Data,
ElementSize,
ElementType,
Element,
ViewContext2D,
Point,
PointSize,
ViewScaleInfo,
ViewSizeInfo,
ViewCalculator,
PointWatcherEvent,
BoardMiddleware
} from '@idraw/types';
export {
Data,
ElementType,
Element,
ElementSize,
ViewContext2D,
Point,
PointSize,
ViewScaleInfo,
ViewSizeInfo,
ViewCalculator,
PointWatcherEvent,
BoardMiddleware
};
export type ControllerStyle = ElementSize & {
borderWidth: number;
borderColor: string;
bgColor: string;
};
export type ElementSizeController = Record<string, ControllerStyle>;
export type ResizeType = 'resize-left' | 'resize-right' | 'resize-top' | 'resize-bottom';
export type PointTargetType = null | 'list-area' | 'over-element' | ResizeType;
export interface PointTarget {
type: PointTargetType;
elements: Element<ElementType>[];
indexes: Array<number | string>;
uuids: string[];
}
export type AreaSize = ElementSize;
export type ActionType = 'select' | 'drag-list' | 'drag-list-end' | 'drag' | 'hover' | 'resize' | 'area' | null;
export type DeepSelectorSharedStorage = {
[keyHoverElementSize]: ElementSize | null;
[keyActionType]: ActionType;
[keyResizeType]: ResizeType | null;
[keyAreaStart]: Point | null;
[keyAreaEnd]: Point | null;
[keyInGroupQueue]: Element<'group'>[] | null;
};

View file

@ -0,0 +1,488 @@
import type {
Data,
Element,
ViewContext2D,
Point,
PointSize,
PointTarget,
PointTargetType,
ViewScaleInfo,
ViewCalculator,
ElementType,
ElementSize,
ResizeType,
AreaSize,
ViewSizeInfo
} from './types';
import { rotateElement, calcElementCenter, rotateElementVertexes } from '@idraw/util';
import { calcElementControllerStyle } from './controller';
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);
}
export function getPointTarget(
p: PointSize,
opts: {
ctx: ViewContext2D;
data?: Data | null;
selectedIndexes?: Array<number | string>;
selectedUUIDs: Array<string>;
selectedElements?: Element<ElementType>[];
areaSize?: AreaSize | null;
scaleInfo: ViewScaleInfo;
viewSize: ViewSizeInfo;
calculator: ViewCalculator;
groupQueue: Element<'group'>[];
}
): PointTarget {
const target: PointTarget = {
type: null,
elements: [],
indexes: [],
uuids: []
};
const { ctx, data, calculator, selectedElements, selectedIndexes, selectedUUIDs, scaleInfo, viewSize, areaSize } = opts;
// list area
if (areaSize && Array.isArray(selectedElements) && selectedElements?.length > 1 && Array.isArray(selectedIndexes) && selectedIndexes?.length > 1) {
const { x, y, w, h } = areaSize;
if (p.x >= x && p.x <= x + w && p.y >= y && p.y <= y + h) {
target.type = 'list-area';
target.elements = selectedElements;
target.indexes = selectedIndexes;
target.uuids = selectedUUIDs;
return target;
}
}
// resize
if (selectedElements?.length === 1) {
const elemSize = calculator.elementSize(selectedElements[0], scaleInfo, viewSize);
const ctrls = calcElementControllerStyle(elemSize);
rotateElement(ctx, elemSize, () => {
const ctrlKeys = Object.keys(ctrls);
for (let i = 0; i < ctrlKeys.length; i++) {
const key = ctrlKeys[i];
const ctrl = ctrls[key];
const { x, y, w, h } = ctrl;
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + w, y);
ctx.lineTo(x + w, y + h);
ctx.lineTo(x, y + h);
ctx.closePath();
if (ctx.isPointInPath(p.x, p.y)) {
target.type = `resize-${key}` as PointTargetType;
break;
}
}
});
if (target.type !== null) {
return target;
}
}
// over-element
if (data) {
const { index, element } = calculator.getPointElement(p as Point, data, scaleInfo, viewSize);
if (index >= 0 && element) {
target.indexes = [index];
target.elements = [element];
target.uuids = [element.uuid];
target.type = 'over-element';
return target;
}
}
return target;
}
export function resizeElement(
elem: Element<ElementType>,
opts: {
start: Point;
end: Point;
resizeType: ResizeType;
scale: number;
}
): ElementSize {
let { x, y, w, h, angle = 0 } = elem;
if (angle < 0) {
angle = Math.max(0, 360 + angle);
}
angle = angle > 0 ? angle : Math.max(0, angle + 360);
const { start, end, resizeType, scale } = opts;
let moveX = (end.x - start.x) / scale;
let moveY = (end.y - start.y) / scale;
if (elem.operation?.limitRatio === true) {
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 (resizeType) {
case 'resize-top': {
if (elem.angle === 0) {
if (h - moveY > 0) {
y += moveY;
h -= moveY;
if (elem.operation?.limitRatio === true) {
x += ((moveY / elem.h) * elem.w) / 2;
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 = x + elem.w / 2;
let centerY = 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 (h + moveDist > 0) {
if (elem.operation?.limitRatio === true) {
w = w + (moveDist / elem.h) * elem.w;
}
h = h + moveDist;
x = centerX - w / 2;
y = centerY - h / 2;
}
} else {
if (h - moveY > 0) {
y += moveY;
h -= moveY;
if (elem.operation?.limitRatio === true) {
x -= moveX / 2;
w += moveX;
}
}
}
break;
}
case 'resize-bottom': {
if (elem.angle === 0) {
if (elem.h + moveY > 0) {
h += moveY;
if (elem.operation?.limitRatio === true) {
x -= ((moveY / elem.h) * elem.w) / 2;
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 = x + elem.w / 2;
let centerY = 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 (h + moveDist > 0) {
if (elem.operation?.limitRatio === true) {
w = w + (moveDist / elem.h) * elem.w;
}
h = h + moveDist;
x = centerX - w / 2;
y = centerY - h / 2;
}
} else {
if (elem.h + moveY > 0) {
h += moveY;
if (elem.operation?.limitRatio === true) {
x -= ((moveY / elem.h) * elem.w) / 2;
w += (moveY / elem.h) * elem.w;
}
}
}
break;
}
case 'resize-left': {
if (angle === 0 || !angle) {
if (elem.w - moveX > 0) {
x += moveX;
w -= moveX;
if (elem.operation?.limitRatio === true) {
h -= (moveX / elem.w) * elem.h;
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 = x + elem.w / 2;
let centerY = 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 (w + moveDist > 0) {
if (elem.operation?.limitRatio === true) {
h = h + (moveDist / elem.w) * elem.h;
}
w = w + moveDist;
x = centerX - w / 2;
y = centerY - h / 2;
}
} else {
if (elem.w - moveX > 0) {
x += moveX;
w -= moveX;
if (elem.operation?.limitRatio === true) {
h -= (moveX / elem.w) * elem.h;
y += ((moveX / elem.w) * elem.h) / 2;
}
}
}
break;
}
case 'resize-right': {
if (angle === 0 || !angle) {
if (elem.w + moveX > 0) {
w += moveX;
if (elem.operation?.limitRatio === true) {
y -= (moveX * elem.h) / elem.w / 2;
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 = x + elem.w / 2;
let centerY = 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 (w + moveDist > 0) {
if (elem.operation?.limitRatio === true) {
h = h + (moveDist / elem.w) * elem.h;
}
w = w + moveDist;
x = centerX - w / 2;
y = centerY - h / 2;
}
} else {
if (elem.w + moveX > 0) {
w += moveX;
if (elem.operation?.limitRatio === true) {
h += (moveX * elem.h) / elem.w;
y -= (moveX * elem.h) / elem.w / 2;
}
}
}
break;
}
default: {
break;
}
}
return { x, y, w, h, angle: elem.angle };
}
export function getSelectedListArea(
data: Data,
opts: {
start: Point;
end: Point;
scaleInfo: ViewScaleInfo;
viewSize: ViewSizeInfo;
calculator: ViewCalculator;
}
): { indexes: number[]; uuids: string[] } {
const indexes: number[] = [];
const uuids: string[] = [];
const { calculator, scaleInfo, viewSize, start, end } = opts;
if (!(Array.isArray(data.elements) && start && end)) {
return { indexes, uuids };
}
const startX = Math.min(start.x, end.x);
const endX = Math.max(start.x, end.x);
const startY = Math.min(start.y, end.y);
const endY = Math.max(start.y, end.y);
data.elements.forEach((elem, idx) => {
const elemSize = calculator.elementSize(elem, scaleInfo, viewSize);
const center = calcElementCenter(elemSize);
if (center.x >= startX && center.x <= endX && center.y >= startY && center.y <= endY) {
indexes.push(idx);
uuids.push(elem.uuid);
if (elemSize.angle && (elemSize.angle > 0 || elemSize.angle < 0)) {
const ves = rotateElementVertexes(elemSize);
if (ves.length === 4) {
const xList = [ves[0].x, ves[1].x, ves[2].x, ves[3].x];
const yList = [ves[0].y, ves[1].y, ves[2].y, ves[3].y];
elemSize.x = Math.min(...xList);
elemSize.y = Math.min(...yList);
elemSize.w = Math.abs(Math.max(...xList) - Math.min(...xList));
elemSize.h = Math.abs(Math.max(...yList) - Math.min(...yList));
}
}
}
});
return { indexes, uuids };
}
export function calcSelectedElementsArea(
elements: Element<ElementType>[],
opts: {
scaleInfo: ViewScaleInfo;
viewSize: ViewSizeInfo;
calculator: ViewCalculator;
}
): AreaSize | null {
if (!Array.isArray(elements)) {
return null;
}
const area: AreaSize = { x: 0, y: 0, w: 0, h: 0 };
const { calculator, scaleInfo, viewSize } = opts;
let prevElemSize: ElementSize | null = null;
elements.forEach((elem) => {
const elemSize = calculator.elementSize(elem, scaleInfo, viewSize);
if (elemSize.angle && (elemSize.angle > 0 || elemSize.angle < 0)) {
const ves = rotateElementVertexes(elemSize);
if (ves.length === 4) {
const xList = [ves[0].x, ves[1].x, ves[2].x, ves[3].x];
const yList = [ves[0].y, ves[1].y, ves[2].y, ves[3].y];
elemSize.x = Math.min(...xList);
elemSize.y = Math.min(...yList);
elemSize.w = Math.abs(Math.max(...xList) - Math.min(...xList));
elemSize.h = Math.abs(Math.max(...yList) - Math.min(...yList));
}
}
if (prevElemSize) {
const areaStartX = Math.min(elemSize.x, area.x);
const areaStartY = Math.min(elemSize.y, area.y);
const areaEndX = Math.max(elemSize.x + elemSize.w, area.x + area.w);
const areaEndY = Math.max(elemSize.y + elemSize.h, area.y + area.h);
area.x = areaStartX;
area.y = areaStartY;
area.w = Math.abs(areaEndX - areaStartX);
area.h = Math.abs(areaEndY - areaStartY);
} else {
area.x = elemSize.x;
area.y = elemSize.y;
area.w = elemSize.w;
area.h = elemSize.h;
}
prevElemSize = elemSize;
});
return area;
}
export function isElementInGroup(elem: Element<ElementType>, group: Element<'group'>): boolean {
if (group?.type === 'group' && Array.isArray(group?.desc?.children)) {
for (let i = 0; i < group.desc.children.length; i++) {
const child = group.desc.children[i];
if (elem.uuid === child.uuid) {
return true;
}
}
}
return false;
}

View file

@ -1,4 +1,5 @@
import { createUUID } from '@idraw/util';
import type { ElementSize } from '@idraw/types';
import type { DesignComponent, DesignComponentItem } from '../../../src';
function createButtonItem(variantName: string) {
@ -73,20 +74,21 @@ function createButtonItem(variantName: string) {
return componentItem;
}
export function createButton(name: string) {
export function createButton(name: string, size?: Partial<ElementSize>) {
const button: DesignComponent = {
uuid: createUUID(),
type: 'component',
name: `Button ${name}`,
x: 50,
y: 50,
w: 800,
h: 400,
w: 360,
h: 200,
desc: {
bgColor: '#aaaaaa54',
default: createButtonItem('default'),
variants: [createButtonItem('primary'), createButtonItem('secondary')]
}
},
...(size || {})
};
return button;
}

View file

@ -1,4 +1,5 @@
import { createUUID } from '@idraw/util';
import type { ElementSize } from '@idraw/types';
import type { DesignComponent, DesignComponentItem } from '../../../src';
function createCheckboxItem(variantName: string) {
@ -73,20 +74,21 @@ function createCheckboxItem(variantName: string) {
return componentItem;
}
export function createCheckbox(name: string) {
export function createCheckbox(name: string, size?: Partial<ElementSize>) {
const checkbox: DesignComponent = {
uuid: createUUID(),
type: 'component',
name: `Checkbox ${name}`,
x: 50,
y: 50,
w: 800,
h: 400,
w: 360,
h: 200,
desc: {
bgColor: '#aaaaaa54',
default: createCheckboxItem('default'),
variants: [createCheckboxItem('primary'), createCheckboxItem('secondary')]
}
},
...(size || {})
};
return checkbox;
}

View file

@ -3,7 +3,7 @@ import { createButton } from './components/button';
import { createCheckbox } from './components/checkbox';
const data: DesignData = {
components: [createButton('001'), createButton('002'), createCheckbox('001'), createCheckbox('002')],
components: [createButton('001'), createButton('002', { x: 450 }), createCheckbox('001', { x: 50, y: 300 }), createCheckbox('002', { x: 450, y: 300 })],
modules: [],
pages: []
};

View file

@ -47,7 +47,7 @@ export const Sketch = (props: DashboardProps) => {
return;
}
const core = refCore.current;
const contextSize = calcElementsContextSize(state.viewDrawData.elements, { viewWidth: width, viewHeight: height });
const contextSize = calcElementsContextSize(state.viewDrawData.elements, { viewWidth: width, viewHeight: height, extend: true });
core.resize({
width,
height,

View file

@ -1 +0,0 @@
# @idraw/studio

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

View file

@ -1,27 +0,0 @@
<html>
<head>
<style></style>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<style>
html, body {
margin: 0; padding: 0;
background: #f0f0f088;
}
canvas {
/* border-right: 1px solid #aaaaaa40;
border-bottom: 1px solid #aaaaaa40; */
background-image:
linear-gradient(#aaaaaa40 1px, transparent 0),
linear-gradient(90deg, #aaaaaa40 1px, transparent 0),
linear-gradient(#aaa 1px, transparent 0),
linear-gradient(90deg, #aaa 1px, transparent 0);
background-size: 10px 10px, 10px 10px, 50px 50px, 50px 50px;
background-color: #ffffff;
}
</style>
</head>
<body>
<div id="lab"></div>
</body>
<script type="module" src="./main.tsx"></script>
</html>

View file

@ -1,8 +0,0 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import { Lab } from '../src/index';
const dom = document.querySelector('#lab') as HTMLDivElement;
const root = createRoot(dom);
root.render(<Lab />);

View file

@ -1,16 +0,0 @@
{
"name": "@idraw/lab",
"version": "0.4.0-alpha.0",
"dependencies": {
"@idraw/core": "^0.4.0-alpha.0",
"@idraw/util": "^0.4.0-alpha.0",
"antd": "^5.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@idraw/types": "^0.4.0-alpha.0",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.1"
}
}

View file

@ -1,521 +0,0 @@
import type { Data } from '@idraw/types';
import { deepClone } from '@idraw/util';
const data: Data = {
elements: [
{
uuid: 'xxx-0003',
type: 'image',
x: 100,
y: 100,
w: 100,
h: 100,
angle: 30,
desc: {
src: './images/lena.png'
}
},
{
uuid: 'xxxx-0001',
x: -50,
y: -40,
w: 100,
h: 100,
type: 'circle',
desc: {
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'
}
},
{
uuid: 'xxxx-0009',
x: 0,
y: 150,
w: 100,
h: 100,
type: 'circle',
desc: {
bgColor: '#ff9800'
}
},
{
uuid: 'xxxx-0010',
x: 150,
y: 50,
w: 100,
h: 100,
type: 'circle',
desc: {
bgColor: '#cddc39'
}
},
{
uuid: 'text-0010',
name: 'text-002',
x: 300,
y: 100,
w: 100,
h: 60,
type: 'text',
desc: {
fontSize: 16,
text: [0, 1, 2, 3, 4].map((i) => `Hello Text ${i}`).join('\r\n'),
// text: [0, 1, 2, 3, 4].map(i => `Hello Text ${i}`).join(''),
fontWeight: 'bold',
color: '#666666',
borderRadius: 30,
borderWidth: 2,
borderColor: '#ff5722'
}
},
{
uuid: 'xxx-0011',
type: 'svg',
x: 400,
y: 100,
w: 100,
h: 100,
desc: {
svg: `<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M336 421m-48 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0Z" ></path><path d="M688 421m-48 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0Z" ></path><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64z m263 711c-34.2 34.2-74 61-118.3 79.8C611 874.2 562.3 884 512 884c-50.3 0-99-9.8-144.8-29.2-44.3-18.7-84.1-45.6-118.3-79.8-34.2-34.2-61-74-79.8-118.3C149.8 611 140 562.3 140 512s9.8-99 29.2-144.8c18.7-44.3 45.6-84.1 79.8-118.3 34.2-34.2 74-61 118.3-79.8C413 149.8 461.7 140 512 140c50.3 0 99 9.8 144.8 29.2 44.3 18.7 84.1 45.6 118.3 79.8 34.2 34.2 61 74 79.8 118.3C874.2 413 884 461.7 884 512s-9.8 99-29.2 144.8c-18.7 44.3-45.6 84.1-79.8 118.2z"></path><path d="M664 533h-48.1c-4.2 0-7.8 3.2-8.1 7.4C604 589.9 562.5 629 512 629s-92.1-39.1-95.8-88.6c-0.3-4.2-3.9-7.4-8.1-7.4H360c-4.6 0-8.2 3.8-8 8.4 4.4 84.3 74.5 151.6 160 151.6s155.6-67.3 160-151.6c0.2-4.6-3.4-8.4-8-8.4z" ></path></svg>`
}
},
{
uuid: 'xxx-0012',
x: 400,
y: 200,
w: 150,
h: 100,
type: 'html',
angle: 0,
desc: {
html: `
<style>
.btn-box {
margin: 10px 0;
}
.btn {
line-height: 1.5715;
position: relative;
display: inline-block;
font-weight: 400;
white-space: nowrap;
text-align: center;
background-image: none;
border: 1px solid transparent;
box-shadow: 0 2px #00000004;
cursor: pointer;
user-select: none;
height: 32px;
padding: 4px 15px;
font-size: 14px;
border-radius: 2px;
color: #000000d9;
background: #fff;
border-color: #d9d9d9;
}
.btn-primary {
color: #fff;
background: #1890ff;
border-color: #1890ff;
text-shadow: 0 -1px 0 rgb(0 0 0 / 12%);
box-shadow: 0 2px #0000000b;
}
</style>
<div>
<div class="btn-box">
<button class="btn">
<span> Hello &nbsp; Button</span>
</button>
</div>
<div class="btn-box">
<button class="btn btn-primary">
<span>Button Primary</span>
</button>
</div>
</div>
`
}
},
{
uuid: 'group-001',
x: 400,
y: 400,
w: 100,
h: 100,
type: 'group',
desc: {
bgColor: '#1f1f1f',
children: [
{
uuid: 'group-001-0014',
type: 'circle',
x: -40,
y: 0,
w: 100,
h: 100,
desc: {
bgColor: '#f44336'
}
},
{
uuid: 'group-001-0015',
type: 'circle',
x: -20,
y: 0,
w: 100,
h: 100,
desc: {
bgColor: '#ff9800'
}
},
{
uuid: 'group-001-0016',
type: 'circle',
x: 0,
y: 0,
w: 100,
h: 100,
desc: {
bgColor: '#ffc106'
}
},
{
uuid: 'group-001-0017',
type: 'circle',
x: 20,
y: 0,
w: 100,
h: 100,
desc: {
bgColor: '#cddc39'
}
},
{
uuid: 'group-001-0018',
type: 'circle',
x: 40,
y: 0,
w: 100,
h: 100,
desc: {
bgColor: '#4caf50'
}
}
]
}
},
{
uuid: 'group-003',
x: 550,
y: 50,
w: 173.20508075688775,
// w: 100,
h: 100,
angle: 30,
type: 'group',
desc: {
children: [
{
uuid: 'group-003-014',
type: 'circle',
x: -40,
y: 0,
w: 100,
h: 100,
desc: {
bgColor: '#f44336'
}
},
{
uuid: 'group-003-0015',
type: 'circle',
x: -20,
y: 0,
w: 100,
h: 100,
desc: {
bgColor: '#ff9800'
}
},
{
uuid: 'group-003-0016',
type: 'circle',
x: 0,
y: 0,
w: 100,
h: 100,
desc: {
bgColor: '#ffc106'
}
},
{
uuid: 'group-003-0017',
type: 'circle',
x: 20,
y: 0,
w: 100,
h: 100,
desc: {
bgColor: '#cddc39'
}
},
{
uuid: 'group-003-0018',
type: 'circle',
x: 40,
y: 0,
w: 100,
h: 100,
desc: {
bgColor: '#4caf50'
}
}
]
}
},
{
uuid: 'xxxx-0017',
type: 'image',
x: 100,
y: 300,
w: 100,
h: 100,
angle: 30,
desc: {
src: './images/lena.png?v=0017'
}
},
{
uuid: 'group-004',
x: 550,
y: 250,
w: 375,
h: 400,
type: 'group',
desc: {
bgColor: '#FFFFFF',
children: [
{
uuid: 'groud-004-001',
type: 'image',
x: 200,
y: 200,
w: 100,
h: 100,
angle: 30,
desc: {
src: './images/lena.png'
}
},
{
uuid: 'groud-004-002',
type: 'group',
x: 50,
y: 50,
w: 200,
h: 200,
angle: 30,
desc: {
bgColor: '#f0f0f0',
children: [
{
uuid: 'group-004-002-014',
type: 'circle',
x: -40,
y: 0,
w: 100,
h: 100,
desc: {
bgColor: '#f44336'
}
},
{
uuid: 'group-004-001-0015',
type: 'circle',
x: -20,
y: 0,
w: 100,
h: 100,
desc: {
bgColor: '#ff9800'
}
},
{
uuid: 'group-004-002-0016',
type: 'circle',
x: 0,
y: 0,
w: 100,
h: 100,
desc: {
bgColor: '#ffc106'
}
},
{
uuid: 'group-004-002-0017',
type: 'circle',
x: 20,
y: 0,
w: 100,
h: 100,
desc: {
bgColor: '#cddc39'
}
},
{
uuid: 'group-004-002-0018',
type: 'circle',
x: 40,
y: 0,
w: 100,
h: 100,
desc: {
bgColor: '#4caf50'
}
},
{
uuid: 'groud-004-002-xxxx',
type: 'group',
x: 50,
y: 100,
w: 200,
h: 100,
angle: 30,
desc: {
bgColor: '#666666',
children: [
{
uuid: 'group-004-002-xxx-014',
type: 'circle',
x: -40,
y: 0,
w: 100,
h: 100,
desc: {
bgColor: '#f44336'
}
},
{
uuid: 'group-004-002-xxx-0015',
type: 'circle',
x: -20,
y: 0,
w: 100,
h: 100,
desc: {
bgColor: '#ff9800'
}
},
{
uuid: 'group-004-002-xxx-0016',
type: 'circle',
x: 0,
y: 0,
w: 100,
h: 100,
desc: {
bgColor: '#ffc106'
}
},
{
uuid: 'group-004-002-xxx-0017',
type: 'circle',
x: 20,
y: 0,
w: 100,
h: 100,
desc: {
bgColor: '#cddc39'
}
},
{
uuid: 'group-004-002-xxx-0018',
type: 'circle',
x: 40,
y: 0,
w: 100,
h: 100,
desc: {
bgColor: '#4caf50'
}
}
]
}
}
]
}
}
]
}
}
]
};
export function getData() {
return deepClone(data);
}

View file

@ -1,46 +0,0 @@
import React, { useEffect, useRef } from 'react';
import { Core, MiddlewareScroller, MiddlewareSelector } from '@idraw/core';
import { calcElementsContextSize } from '@idraw/util';
import { getData } from './data';
export const Lab = () => {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (ref?.current) {
const data = getData();
const width = window.innerWidth;
const height = window.innerHeight;
const devicePixelRatio = window.devicePixelRatio;
const options = {
width,
height,
devicePixelRatio
};
const core = new Core(ref.current, options);
core.use(MiddlewareScroller);
core.use(MiddlewareSelector);
core.setData(data);
// core.scrollX(0);
// core.scrollY(0);
window.addEventListener('resize', () => {
const width = window.innerWidth;
const height = window.innerHeight;
const devicePixelRatio = window.devicePixelRatio;
const contextSize = calcElementsContextSize(data.elements, { viewWidth: width, viewHeight: height });
core.resize({
width,
height,
devicePixelRatio,
...contextSize
});
});
}
}, []);
return (
<div style={{ position: 'fixed', left: 0, right: 0, width: '100%', height: '100%' }}>
<div ref={ref}></div>
</div>
);
};

View file

@ -1,46 +0,0 @@
import React, { useEffect, useRef } from 'react';
import { Core, MiddlewareScroller, MiddlewareSelector } from '@idraw/core';
import { calcElementsContextSize } from '@idraw/util';
import { getData } from './data';
export const Lab = () => {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (ref?.current) {
const data = getData();
const width = window.innerWidth;
const height = window.innerHeight;
const devicePixelRatio = window.devicePixelRatio;
const options = {
width,
height,
devicePixelRatio
};
const core = new Core(ref.current, options);
core.use(MiddlewareScroller);
core.use(MiddlewareSelector);
core.setData(data);
// core.scrollX(0);
// core.scrollY(0);
window.addEventListener('resize', () => {
const width = window.innerWidth;
const height = window.innerHeight;
const devicePixelRatio = window.devicePixelRatio;
const contextSize = calcElementsContextSize(data.elements, { viewWidth: width, viewHeight: height });
core.resize({
width,
height,
devicePixelRatio,
...contextSize
});
});
}
}, []);
return (
<div style={{ position: 'fixed', left: 0, right: 0, width: '100%', height: '100%' }}>
<div ref={ref}></div>
</div>
);
};

View file

@ -18,8 +18,8 @@ export interface BoardWatherWheelYEvent {
point: Point;
}
export interface BoardWatherDrawFrameEvent {
snapshot: BoardViewerFrameSnapshot;
export interface BoardWatherDrawFrameEvent<S extends Record<any | symbol, any>> {
snapshot: BoardViewerFrameSnapshot<S>;
}
export type BoardWatherScaleEvent = ViewScaleInfo;
@ -30,7 +30,7 @@ export type BoardWatherScrollYEvent = ViewScaleInfo;
export type BoardWatherResizeEvent = ViewSizeInfo;
export interface BoardWatcherEventMap {
export interface BoardWatcherEventMap<S extends Record<any | symbol, any>> {
hover: BoardWatcherPointEvent;
pointStart: BoardWatcherPointEvent;
pointMove: BoardWatcherPointEvent;
@ -43,52 +43,52 @@ export interface BoardWatcherEventMap {
scrollX: BoardWatherScrollXEvent;
scrollY: BoardWatherScrollYEvent;
resize: BoardWatherResizeEvent;
beforeDrawFrame: BoardWatherDrawFrameEvent;
afterDrawFrame: BoardWatherDrawFrameEvent;
beforeDrawFrame: BoardWatherDrawFrameEvent<S>;
afterDrawFrame: BoardWatherDrawFrameEvent<S>;
}
export type BoardMode = 'SELECT' | 'SCROLL' | 'RULE' | 'CONNECT' | 'PENCIL' | 'PEN' | string;
export interface BoardMiddlewareObject {
export interface BoardMiddlewareObject<S extends Record<any | symbol, any>> {
mode: BoardMode;
isDefault?: boolean;
created?: () => void;
// action
hover?: (e: BoardWatcherEventMap['hover']) => void | boolean;
pointStart?: (e: BoardWatcherEventMap['pointStart']) => void | boolean;
pointMove?: (e: BoardWatcherEventMap['pointMove']) => void | boolean;
pointEnd?: (e: BoardWatcherEventMap['pointEnd']) => void | boolean;
pointLeave?: (e: BoardWatcherEventMap['pointLeave']) => void | boolean;
doubleClick?: (e: BoardWatcherEventMap['doubleClick']) => void | boolean;
wheelX?: (e: BoardWatcherEventMap['wheelX']) => void | boolean;
wheelY?: (e: BoardWatcherEventMap['wheelY']) => void | boolean;
hover?: (e: BoardWatcherEventMap<S>['hover']) => void | boolean;
pointStart?: (e: BoardWatcherEventMap<S>['pointStart']) => void | boolean;
pointMove?: (e: BoardWatcherEventMap<S>['pointMove']) => void | boolean;
pointEnd?: (e: BoardWatcherEventMap<S>['pointEnd']) => void | boolean;
pointLeave?: (e: BoardWatcherEventMap<S>['pointLeave']) => void | boolean;
doubleClick?: (e: BoardWatcherEventMap<S>['doubleClick']) => void | boolean;
wheelX?: (e: BoardWatcherEventMap<S>['wheelX']) => void | boolean;
wheelY?: (e: BoardWatcherEventMap<S>['wheelY']) => void | boolean;
scale?: (e: BoardWatcherEventMap['scale']) => void | boolean;
scrollX?: (e: BoardWatcherEventMap['scrollX']) => void | boolean;
scrollY?: (e: BoardWatcherEventMap['scrollY']) => void | boolean;
resize?: (e: BoardWatcherEventMap['resize']) => void | boolean;
scale?: (e: BoardWatcherEventMap<S>['scale']) => void | boolean;
scrollX?: (e: BoardWatcherEventMap<S>['scrollX']) => void | boolean;
scrollY?: (e: BoardWatcherEventMap<S>['scrollY']) => void | boolean;
resize?: (e: BoardWatcherEventMap<S>['resize']) => void | boolean;
// draw
beforeDrawFrame?(e: BoardWatcherEventMap['beforeDrawFrame']): void | boolean;
afterDrawFrame?(e: BoardWatcherEventMap['afterDrawFrame']): void | boolean;
beforeDrawFrame?(e: BoardWatcherEventMap<S>['beforeDrawFrame']): void | boolean;
afterDrawFrame?(e: BoardWatcherEventMap<S>['afterDrawFrame']): void | boolean;
}
export interface BoardMiddlewareOptions {
export interface BoardMiddlewareOptions<S extends Record<any | symbol, any>> {
viewContent: ViewContent;
sharer: StoreSharer;
sharer: StoreSharer<S>;
viewer: BoardViewer;
calculator: ViewCalculator;
}
export type BoardMiddleware = (opts: BoardMiddlewareOptions) => BoardMiddlewareObject;
export type BoardMiddleware<S extends Record<any | symbol, any> = any> = (opts: BoardMiddlewareOptions<S>) => BoardMiddlewareObject<S>;
export interface BoardOptions {
viewContent: ViewContent;
}
export interface BoardViewerFrameSnapshot {
export interface BoardViewerFrameSnapshot<S extends Record<any | symbol, any>> {
activeStore: ActiveStore;
sharedStore: Record<string, any>;
sharedStore: S;
}
export interface BoardViewerEventMap {
@ -97,12 +97,12 @@ export interface BoardViewerEventMap {
}
export interface BoardViewerOptions {
sharer: StoreSharer;
sharer: StoreSharer<Record<any | symbol, any>>;
renderer: BoardRenderer;
calculator: ViewCalculator;
viewContent: ViewContent;
beforeDrawFrame: (e: { snapshot: BoardViewerFrameSnapshot }) => void;
afterDrawFrame: (e: { snapshot: BoardViewerFrameSnapshot }) => void;
beforeDrawFrame: (e: { snapshot: BoardViewerFrameSnapshot<Record<any | symbol, any>> }) => void;
afterDrawFrame: (e: { snapshot: BoardViewerFrameSnapshot<Record<any | symbol, any>> }) => void;
}
export interface BoardViewer extends UtilEventEmitter<BoardViewerEventMap> {
@ -121,9 +121,10 @@ export interface BoardRenderer extends UtilEventEmitter<RendererEventMap> {
export interface BoardWatcherOptions {
viewContent: ViewContent;
sharer: StoreSharer;
sharer: StoreSharer<Record<any | symbol, any>>;
}
export interface BoardWatcherStore {
hasPointDown: boolean;
prevClickPoint: Point | null;
}

View file

@ -2,6 +2,7 @@ export interface CoreOptions {
width: number;
height: number;
devicePixelRatio?: number;
padding?: number;
// contextWidth?: number;
// contextHeight?: number;
// onlyRender?: boolean;

View file

@ -8,13 +8,14 @@ export type ActiveStore = ViewSizeInfo &
selectedUUIDs: string[];
};
export interface StoreSharer {
export interface StoreSharer<S extends Record<any, any>> {
getActiveStorage<T extends keyof ActiveStore>(key: T): ActiveStore[T];
setActiveStorage<T extends keyof ActiveStore>(key: T, storage: ActiveStore[T]): void;
getActiveStoreSnapshot(): ActiveStore;
getSharedStorage(key: string): any;
setSharedStorage(key: string, storage: any): void;
getSharedStorage<K extends keyof S>(key: K): S[K];
setSharedStorage<K extends keyof S>(key: K, storage: S[K]): void;
getSharedStoreSnapshot(): Record<string, any>;
getActiveScaleInfo(): ViewScaleInfo;
setActiveScaleInfo(scaleInfo: ViewScaleInfo): void;
setActiveViewSizeInfo(size: ViewSizeInfo): void;

View file

@ -10,11 +10,15 @@ export function deepClone(target: any): any {
});
return arr;
} else if (type === 'Object') {
const obj: { [key: string]: any } = {};
const obj: { [key: string | symbol]: any } = {};
const keys = Object.keys(t);
keys.forEach((key) => {
obj[key] = _clone(t[key]);
});
const symbolKeys = Object.getOwnPropertySymbols(t);
symbolKeys.forEach((key) => {
obj[key] = _clone(t[key]);
});
return obj;
}
}

View file

@ -124,7 +124,10 @@ export function validateElements(elements: Array<Element<ElementType>>): boolean
type AreaSize = ElementSize;
export function calcElementsContextSize(elements: Array<Element<ElementType>>, opts?: { viewWidth: number; viewHeight: number }): ViewContextSize {
export function calcElementsContextSize(
elements: Array<Element<ElementType>>,
opts?: { viewWidth: number; viewHeight: number; extend?: boolean }
): ViewContextSize {
const area: AreaSize = { x: 0, y: 0, w: 0, h: 0 };
let prevElemSize: ElementSize | null = null;
elements.forEach((elem: Element<ElementType>) => {
@ -167,6 +170,11 @@ export function calcElementsContextSize(elements: Array<Element<ElementType>>, o
prevElemSize = elemSize;
});
if (opts?.extend) {
area.x = Math.min(area.x, 0);
area.y = Math.min(area.y, 0);
}
const ctxSize: ViewContextSize = {
contextX: area.x,
contextY: area.y,
@ -198,11 +206,11 @@ export function calcElementsViewInfo(
changeContextTop: number;
changeContextBottom: number;
} {
const contextSize = calcElementsContextSize(elements, { viewWidth: prevViewSize.width, viewHeight: prevViewSize.height });
const contextSize = calcElementsContextSize(elements, { viewWidth: prevViewSize.width, viewHeight: prevViewSize.height, extend: options?.extend });
if (options?.extend === true) {
contextSize.contextX = Math.min(contextSize.contextX, prevViewSize.contextX);
contextSize.contextY = Math.min(contextSize.contextY, prevViewSize.contextY);
contextSize.contextX = Math.min(0, contextSize.contextX, prevViewSize.contextX);
contextSize.contextY = Math.min(0, contextSize.contextY, prevViewSize.contextY);
contextSize.contextWidth = Math.max(contextSize.contextWidth, prevViewSize.contextWidth);
contextSize.contextHeight = Math.max(contextSize.contextHeight, prevViewSize.contextHeight);
}

View file

@ -1,6 +1,6 @@
import { deepClone } from './data';
export class Store<T extends Record<string, any>> {
export class Store<T extends Record<string | symbol, any>> {
private _temp: T;
private _backUpDefaultStorage: T;