Merge pull request #358 from idrawjs/dev-v0.4

Dev v0.4
This commit is contained in:
Deepsea 2025-05-24 11:01:17 +08:00 committed by GitHub
commit 76bbd0349c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 409 additions and 141 deletions

View file

@ -37,6 +37,6 @@ jobs:
- run: npm publish --provenance --access public -w ./packages/idraw
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- run: npm publish --provenance --access public -w ./packages/figma
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
# - run: npm publish --provenance --access public -w ./packages/figma
# env:
# NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View file

@ -1,6 +1,6 @@
{
"private": false,
"version": "0.4.0-beta.44",
"version": "0.4.0-beta.45",
"workspaces": [
"packages/*"
],

View file

@ -94,43 +94,45 @@ 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', this.#handlePointMove.bind(this));
this.#watcher.on('pointLeave', this.#handlePointLeave.bind(this));
if (this.#opts.disableWatcher !== true) {
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', this.#handlePointMove.bind(this));
this.#watcher.on('pointLeave', this.#handlePointLeave.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));
this.#watcher.on('doubleClick', this.#handleDoubleClick.bind(this));
this.#watcher.on('contextMenu', this.#handleContextMenu.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));
this.#watcher.on('doubleClick', this.#handleDoubleClick.bind(this));
this.#watcher.on('contextMenu', this.#handleContextMenu.bind(this));
}
this.#renderer.on('load', () => {
this.#eventHub.trigger('loadResource');
@ -445,10 +447,16 @@ export class Board<T extends BoardExtendEventMap = BoardExtendEventMap> {
}
onWatcherEvents() {
if (this.#opts.disableWatcher === true) {
return;
}
this.#watcher.onEvents();
}
offWatcherEvents() {
if (this.#opts.disableWatcher === true) {
return;
}
this.#watcher.offEvents();
}
}

View file

@ -60,7 +60,7 @@ export class Core<E extends CoreEventMap = CoreEventMap> {
#container: HTMLDivElement;
constructor(container: HTMLDivElement, opts: CoreOptions) {
const { devicePixelRatio = 1, width, height } = opts;
const { devicePixelRatio = 1, width, height, disableWatcher = false } = opts;
// this.#opts = opts;
this.#container = container;
@ -71,7 +71,7 @@ export class Core<E extends CoreEventMap = CoreEventMap> {
container.appendChild(canvas);
const boardContent = createBoardContent(canvas, { width, height, devicePixelRatio });
const board = new Board<E>({ boardContent, container });
const board = new Board<E>({ boardContent, container, disableWatcher });
const sharer = board.getSharer();
sharer.setActiveViewSizeInfo({
width,

View file

@ -11,7 +11,7 @@ import {
findElementsFromListByPositions,
getElementPositionFromList,
getElementPositionMapFromList,
deepResizeGroupElement,
resizeEffectGroupElement,
getElementSize,
calcPointMoveElementInGroup,
isSameElementSize,
@ -669,16 +669,28 @@ export const MiddlewareSelector: Middleware<
{ scale, start: resizeStart, end: resizeEnd, resizeType, sharer }
);
const calcOpts = { ignore: !!moveOriginalStartElementSize.angle };
elems[0].x = calculator.toGridNum(resizedElemSize.x, calcOpts);
elems[0].y = calculator.toGridNum(resizedElemSize.y, calcOpts);
if (elems[0].type === 'group' && elems[0].operations?.deepResize === true) {
deepResizeGroupElement(elems[0] as Element<'group'>, {
w: calculator.toGridNum(resizedElemSize.w, calcOpts),
h: calculator.toGridNum(resizedElemSize.h, calcOpts)
});
const gridX = calculator.toGridNum(resizedElemSize.x, calcOpts);
const gridY = calculator.toGridNum(resizedElemSize.y, calcOpts);
const gridW = calculator.toGridNum(resizedElemSize.w, calcOpts);
const gridH = calculator.toGridNum(resizedElemSize.h, calcOpts);
if (elems[0].type === 'group') {
resizeEffectGroupElement(
elems[0] as Element<'group'>,
{
x: gridX,
y: gridY,
w: gridW,
h: gridH
},
{ resizeEffect: elems[0].operations?.resizeEffect }
);
elems[0].x = gridX;
elems[0].y = gridY;
} else {
elems[0].w = calculator.toGridNum(resizedElemSize.w, calcOpts);
elems[0].h = calculator.toGridNum(resizedElemSize.h, calcOpts);
elems[0].x = gridX;
elems[0].y = gridY;
elems[0].w = gridW;
elems[0].h = gridH;
}
}

View file

@ -138,6 +138,7 @@ export function getPointTarget(
if (selectedElements && selectedElements?.length > 0) {
target.groupQueue = groupQueue || [];
target.elements = [selectedElements[0]];
return target;
}
break;
}

View file

@ -44,8 +44,8 @@ describe('idraw: useHistory ', () => {
height: 200,
width: 200
});
const { MiddlewareHistory, history } = useHistory({ instance: idraw });
const { undo, redo, __getDoRecords, __getUndoRecords } = history;
const { MiddlewareHistory, historyHandler } = useHistory({ core: idraw.getCore() });
const { undo, redo, __getDoRecords, __getUndoRecords } = historyHandler;
idraw.use(MiddlewareHistory);
idraw.setData(data);

View file

@ -47,8 +47,8 @@ describe('idraw: useHistory ', () => {
height: 200,
width: 200
});
const { MiddlewareHistory, history } = useHistory({ instance: idraw });
const { undo, redo, __getDoRecords, __getUndoRecords } = history;
const { MiddlewareHistory, historyHandler } = useHistory({ core: idraw.getCore() });
const { undo, redo, __getDoRecords, __getUndoRecords } = historyHandler;
idraw.use(MiddlewareHistory);
idraw.setData(data);

View file

@ -53,8 +53,8 @@ describe('idraw: useHistory ', () => {
height: 200,
width: 200
});
const { MiddlewareHistory, history } = useHistory({ instance: idraw });
const { undo, redo, __getDoRecords, __getUndoRecords } = history;
const { MiddlewareHistory, historyHandler } = useHistory({ core: idraw.getCore() });
const { undo, redo, __getDoRecords, __getUndoRecords } = historyHandler;
idraw.use(MiddlewareHistory);
idraw.setData(data);
const targetElement = deepClone(data.elements[0]);

View file

@ -54,8 +54,8 @@ describe('idraw: useHistory ', () => {
height: 200,
width: 200
});
const { MiddlewareHistory, history } = useHistory({ instance: idraw });
const { undo, redo, __getDoRecords, __getUndoRecords } = history;
const { MiddlewareHistory, historyHandler } = useHistory({ core: idraw.getCore() });
const { undo, redo, __getDoRecords, __getUndoRecords } = historyHandler;
idraw.use(MiddlewareHistory);
idraw.setData(data);

View file

@ -54,8 +54,8 @@ describe('idraw: useHistory ', () => {
height: 200,
width: 200
});
const { MiddlewareHistory, history } = useHistory({ instance: idraw });
const { undo, redo, __getDoRecords, __getUndoRecords } = history;
const { MiddlewareHistory, historyHandler } = useHistory({ core: idraw.getCore() });
const { undo, redo, __getDoRecords, __getUndoRecords } = historyHandler;
idraw.use(MiddlewareHistory);
idraw.setData(data);

View file

@ -53,8 +53,8 @@ describe('idraw: useHistory ', () => {
height: 200,
width: 200
});
const { MiddlewareHistory, history } = useHistory({ instance: idraw });
const { undo, redo, __getDoRecords, __getUndoRecords } = history;
const { MiddlewareHistory, historyHandler } = useHistory({ core: idraw.getCore() });
const { undo, redo, __getDoRecords, __getUndoRecords } = historyHandler;
idraw.use(MiddlewareHistory);
idraw.setData(data);

View file

@ -52,8 +52,8 @@ describe('idraw: useHistory ', () => {
height: 200,
width: 200
});
const { MiddlewareHistory, history } = useHistory({ instance: idraw });
const { undo, redo, __getDoRecords, __getUndoRecords } = history;
const { MiddlewareHistory, historyHandler } = useHistory({ core: idraw.getCore() });
const { undo, redo, __getDoRecords, __getUndoRecords } = historyHandler;
idraw.use(MiddlewareHistory);
idraw.setData(data);
const targetElement = deepClone(data.elements[0]);

View file

@ -53,8 +53,8 @@ describe('idraw: useHistory ', () => {
height: 200,
width: 200
});
const { MiddlewareHistory, history } = useHistory({ instance: idraw });
const { undo, redo, __getDoRecords, __getUndoRecords } = history;
const { MiddlewareHistory, historyHandler } = useHistory({ core: idraw.getCore() });
const { undo, redo, __getDoRecords, __getUndoRecords } = historyHandler;
idraw.use(MiddlewareHistory);
idraw.setData(data);
const targetElement = deepClone(data.elements[0]);

View file

@ -15,7 +15,8 @@ import type {
IDrawStorage,
DataLayout,
DataGlobal,
Middleware
Middleware,
HistoryHandler
} from '@idraw/types';
import { filterCompactData, calcViewCenterContent, calcViewCenter, Store } from '@idraw/util';
import { defaultSettings, defaultOptions, getDefaultStorage, defaultMode, parseStyles } from './setting/config';
@ -28,6 +29,7 @@ import { modifyGlobal } from './methods/global';
import { reset } from './methods/reset';
import { setFeature } from './methods/feature';
import { getImageBlobURL } from './methods/image';
import { useHistory } from './middlewares/use-history';
export class iDraw {
#core: Core<IDrawEvent>;
@ -35,6 +37,7 @@ export class iDraw {
#store: Store<IDrawStorage> = new Store<IDrawStorage>({
defaultStorage: getDefaultStorage()
});
#historyHandler: HistoryHandler | null = null;
constructor(mount: HTMLDivElement, options: IDrawOptions) {
const opts = { ...defaultSettings, ...defaultOptions, ...options };
@ -49,6 +52,13 @@ export class iDraw {
#init() {
const core = this.#core;
const store = this.#store;
if (this.#opts.history === true) {
const { historyLimit } = this.#opts;
const { historyHandler, MiddlewareHistory } = useHistory({ core, limit: historyLimit });
this.#historyHandler = historyHandler;
core.use(MiddlewareHistory);
}
changeMode('select', core, store);
}
@ -208,6 +218,10 @@ export class iDraw {
destroy() {
this.#core.destroy();
this.#store.destroy();
this.#historyHandler?.destroy();
this.#core = null as any;
this.#store = null as any;
this.#historyHandler = null as any;
}
getViewCenter(): PointSize {
@ -216,14 +230,6 @@ export class iDraw {
return pointSize;
}
// $onBoardWatcherEvents() {
// this.#core.onBoardWatcherEvents();
// }
// $offBoardWatcherEvents() {
// this.#core.offBoardWatcherEvents();
// }
getCore() {
return this.#core;
}
@ -231,4 +237,16 @@ export class iDraw {
forceRender() {
return this.#core.forceRender();
}
// getHistoryHandler() {
// return this.#historyHandler;
// }
// redo() {
// this.#historyHandler?.redo();
// }
// undo() {
// this.#historyHandler?.undo();
// }
}

View file

@ -115,7 +115,6 @@ export {
insertElementToListByPosition,
deleteElementInListByPosition,
deleteElementInList,
deepResizeGroupElement,
deepCloneElement,
calcViewCenterContent,
calcViewCenter,

View file

@ -5,12 +5,12 @@ import type {
DataLayout,
RecursivePartial,
DataGlobal,
IDrawHistory
HistoryHandler
} from '@idraw/types';
import { unflatObject, calcResultMovePosition } from '@idraw/util';
import { Core } from '@idraw/core';
import type { IDrawEvent } from '../event';
import { eventKeys } from '../event';
import type { iDraw } from '../idraw';
const supportRecordTypes = [
'updateElement',
@ -25,9 +25,12 @@ const supportRecordTypes = [
'modifyGlobal'
];
export const useHistory = (opts: { instance: iDraw }) => {
const { instance } = opts;
const core = instance.getCore();
const LIMIT = 100;
export const useHistory = (opts: { core: Core; limit?: number }) => {
const { core, limit } = opts;
const historyLimit = limit && limit > 0 ? limit : LIMIT;
let doRecords: ModifyRecord[] = [];
let undoRecords: ModifyRecord[] = [];
@ -95,6 +98,9 @@ export const useHistory = (opts: { instance: iDraw }) => {
undoRecord = { ...undoRecord, type: 'undo' } as ModifyRecord<'undo'>;
undoRecords.push(undoRecord);
if (undoRecords.length > historyLimit) {
undoRecords.splice(historyLimit - undoRecords.length, undoRecords.length);
}
}
};
@ -154,6 +160,9 @@ export const useHistory = (opts: { instance: iDraw }) => {
}
redoRecord = { ...redoRecord, type: 'redo' } as ModifyRecord<'redo'>;
doRecords.push(redoRecord);
if (doRecords.length > historyLimit) {
doRecords.splice(historyLimit - doRecords.length, doRecords.length);
}
}
};
@ -203,7 +212,7 @@ export const useHistory = (opts: { instance: iDraw }) => {
const getDoRecords = () => doRecords;
const getUndoRecords = () => undoRecords;
const history: IDrawHistory = {
const historyHandler: HistoryHandler = {
undo: undoAction,
redo: redoAction,
destroy,
@ -216,6 +225,6 @@ export const useHistory = (opts: { instance: iDraw }) => {
return {
MiddlewareHistory,
history
historyHandler
} as const;
};

View file

@ -88,11 +88,11 @@ export class Loader extends EventEmitter<LoaderEventMap> implements RendererLoad
if (supportElementTypes.includes((element as Element<LoadElementType>).type)) {
let assetId: string | null = null;
let resource: string | null = null;
if (element.type === 'image' && typeof (element as Element<'image'>).detail.src === 'string') {
if (element.type === 'image' && typeof (element as Element<'image'>)?.detail?.src === 'string') {
resource = (element as Element<'image'>).detail.src;
} else if (element.type === 'svg' && typeof (element as Element<'svg'>).detail.svg === 'string') {
} else if (element.type === 'svg' && typeof (element as Element<'svg'>)?.detail?.svg === 'string') {
resource = (element as Element<'svg'>).detail.svg;
} else if (element.type === 'html' && typeof (element as Element<'html'>).detail.html === 'string') {
} else if (element.type === 'html' && typeof (element as Element<'html'>)?.detail?.html === 'string') {
resource = (element as Element<'html'>).detail.html;
}
if (typeof resource === 'string') {

View file

@ -110,6 +110,7 @@ export type BoardMiddleware<
export interface BoardOptions {
boardContent: BoardContent;
container?: HTMLDivElement;
disableWatcher?: boolean;
}
export interface BoardViewerFrameSnapshot<S extends Record<any | symbol, any> = any> {

View file

@ -8,6 +8,7 @@ export interface CoreOptions {
width: number;
height: number;
devicePixelRatio?: number;
disableWatcher?: boolean;
}
export type CursorType =

View file

@ -174,8 +174,8 @@ export interface ElementOperations {
invisible?: boolean;
rotatable?: boolean;
limitRatio?: boolean;
deepResize?: boolean;
lastModified?: number;
resizeEffect?: 'absolute' | 'deepResize' | 'fixed'; // for Group default "absolute"
lastModified?: number; // TODO
}
export interface ElementGlobal {

View file

@ -22,11 +22,12 @@ export interface IDrawSettings {
layoutSelector?: Partial<MiddlewareLayoutSelectorStyle>;
};
history?: boolean;
historyLimit?: number;
}
export type IDrawOptions = CoreOptions & IDrawSettings;
export type IDrawHistory = {
export type HistoryHandler = {
undo: () => void;
redo: () => void;
canUndo: () => void;

View file

@ -111,7 +111,7 @@ export {
updateElementInList,
updateElementInListByPosition
} from './view/handle-element';
export { deepResizeGroupElement } from './view/resize-element';
export { resizeEffectGroupElement } from './view/resize-element';
export { calcViewCenterContent, calcViewCenter } from './view/view-content';
export { toFlattenElement, toFlattenLayout, toFlattenGlobal } from './view/modify-record';
export { enhanceFontFamliy } from './view/text';

View file

@ -21,7 +21,7 @@ import {
import { toFlattenElement } from './modify-record';
import { set, del } from '../tool/get-set-del';
import { findElementFromListByPosition, getElementPositionFromList } from './element';
import { deepResizeGroupElement } from './resize-element';
import { resizeEffectGroupElement } from './resize-element';
const defaultViewWidth = 200;
const defaultViewHeight = 200;
@ -330,13 +330,16 @@ export function updateElementInList(
for (let i = 0; i < elements.length; i++) {
const elem = elements[i];
if (elem.uuid === uuid) {
if (elem.type === 'group' && elem.operations?.deepResize === true) {
if ((updateContent.w && updateContent.w > 0) || (updateContent.h && updateContent.h > 0)) {
deepResizeGroupElement(elem as Element<'group'>, {
w: updateContent.w,
h: updateContent.h
});
}
if (elem.type === 'group' && elem.operations?.resizeEffect) {
resizeEffectGroupElement(
elem as Element<'group'>,
{
...updateContent
},
{
resizeEffect: elem.operations?.resizeEffect
}
);
}
mergeElement(elem, updateContent);
@ -357,13 +360,16 @@ export function updateElementInListByPosition(
): Element | null {
const elem: Element | null = findElementFromListByPosition(position, elements);
if (elem) {
if (elem.type === 'group' && elem.operations?.deepResize === true) {
if ((updateContent.w && updateContent.w > 0) || (updateContent.h && updateContent.h > 0)) {
deepResizeGroupElement(elem as Element<'group'>, {
w: updateContent.w,
h: updateContent.h
});
}
if (elem.type === 'group' && elem.operations?.resizeEffect) {
resizeEffectGroupElement(
elem as Element<'group'>,
{
...updateContent
},
{
resizeEffect: elem.operations?.resizeEffect
}
);
}
mergeElement(elem, updateContent, opts);
}

View file

@ -1,87 +1,197 @@
import type { Element, ElementSize } from '@idraw/types';
import type {
Element,
ElementSize,
ElementOperations,
ModifyRecord,
FlattenLayout,
FlattenElement
} from '@idraw/types';
import { istype } from '../tool/istype';
import { formatNumber } from '../tool/number';
import { toFlattenElement } from '../view/modify-record';
const doNum = (n: number) => {
return formatNumber(n, { decimalPlaces: 4 });
};
interface ResizeOptions {
type DeepResizeRatioOptions = {
xRatio: number;
yRatio: number;
minRatio: number;
maxRatio: number;
}
};
type FixedResizeOptions = {
moveX: number;
moveY: number;
moveW: number;
moveH: number;
};
function resizeElementBaseDetailByRatio(elem: Element, opts: DeepResizeRatioOptions): ModifyRecord<'modifyElement'> {
const beforeElem: Pick<Element, 'detail'> = { detail: {} };
const afterElem: Pick<Element, 'detail'> = { detail: {} };
const record: ModifyRecord<'modifyElement'> = {
type: 'modifyElement',
time: Date.now(),
content: {
method: 'modifyElement',
uuid: elem.uuid,
before: null,
after: null
}
};
function resizeElementBaseDetail(elem: Element, opts: ResizeOptions) {
const { detail } = elem;
const { xRatio, yRatio, maxRatio } = opts;
const middleRatio = (xRatio + yRatio) / 2;
const { borderWidth, borderRadius, borderDash, shadowOffsetX, shadowOffsetY, shadowBlur } = detail;
if (typeof borderWidth === 'number') {
detail.borderWidth = doNum(borderWidth * middleRatio);
beforeElem.detail.borderWidth = borderWidth;
afterElem.detail.borderWidth = detail.borderWidth;
} else if (Array.isArray(detail.borderWidth)) {
const bw = borderWidth as [number, number, number, number];
// [top, right, bottom, left]
detail.borderWidth = [doNum(bw[0] * yRatio), doNum(bw[1] * xRatio), doNum(bw[2] * yRatio), doNum(bw[3] * xRatio)];
beforeElem.detail.borderWidth = [...bw];
afterElem.detail.borderWidth = [...detail.borderWidth];
}
if (typeof borderRadius === 'number') {
detail.borderRadius = doNum(borderRadius * middleRatio);
beforeElem.detail.borderRadius = borderRadius;
afterElem.detail.borderRadius = detail.borderRadius;
} else if (Array.isArray(detail.borderRadius)) {
const br = borderRadius as [number, number, number, number];
// [top-left, top-right, bottom-left, bottom-right]
detail.borderRadius = [br[0] * xRatio, br[1] * xRatio, br[2] * yRatio, br[3] * yRatio];
beforeElem.detail.borderRadius = [...br];
afterElem.detail.borderRadius = [...detail.borderRadius];
}
if (Array.isArray(borderDash)) {
borderDash.forEach((dash: number, i) => {
(detail.borderDash as number[])[i] = doNum(dash * maxRatio);
});
beforeElem.detail.borderDash = [...borderDash];
afterElem.detail.borderDash = [...(detail.borderDash as number[])];
}
if (typeof shadowOffsetX === 'number') {
detail.shadowOffsetX = doNum(shadowOffsetX * maxRatio);
beforeElem.detail.shadowOffsetX = shadowOffsetX;
afterElem.detail.shadowOffsetX = detail.shadowOffsetX;
}
if (typeof shadowOffsetY === 'number') {
detail.shadowOffsetX = doNum(shadowOffsetY * maxRatio);
detail.shadowOffsetY = doNum(shadowOffsetY * maxRatio);
beforeElem.detail.shadowOffsetY = shadowOffsetY;
afterElem.detail.shadowOffsetY = detail.shadowOffsetY;
}
if (typeof shadowBlur === 'number') {
detail.shadowOffsetX = doNum(shadowBlur * maxRatio);
detail.shadowBlur = doNum(shadowBlur * maxRatio);
beforeElem.detail.shadowBlur = shadowBlur;
afterElem.detail.shadowBlur = detail.shadowBlur;
}
record.content.before = toFlattenElement(beforeElem);
record.content.after = toFlattenElement(afterElem);
return record;
}
function resizeElementBase(elem: Element, opts: ResizeOptions) {
function resizeElementBaseByRatio(elem: Element, opts: DeepResizeRatioOptions): ModifyRecord<'modifyElement'> {
const { xRatio, yRatio } = opts;
const { x, y, w, h } = elem;
const { uuid, x, y, w, h } = elem;
const record: ModifyRecord<'modifyElement'> = {
type: 'modifyElement',
time: Date.now(),
content: {
method: 'modifyElement',
uuid: uuid,
before: { x, y, w, h },
after: { x, y, w, h }
}
};
elem.x = doNum(x * xRatio);
elem.y = doNum(y * yRatio);
elem.w = doNum(w * xRatio);
elem.h = doNum(h * yRatio);
resizeElementBaseDetail(elem, opts);
const detailRecord = resizeElementBaseDetailByRatio(elem, opts);
record.content.before = {
...record.content.before,
...detailRecord.content.before
};
record.content.after = {
...record.content.after,
...detailRecord.content.after
};
return record;
}
function resizeTextElementDetail(elem: Element<'text'>, opts: ResizeOptions) {
function resizeTextElementDetailByRatio(
elem: Element<'text'>,
opts: DeepResizeRatioOptions
): ModifyRecord<'modifyElement'> {
const { minRatio, maxRatio } = opts;
const { fontSize, lineHeight } = elem.detail;
const ratio = (minRatio + maxRatio) / 2;
const beforeFlattenElem: FlattenElement = {};
const afterFlattenElem: FlattenElement = {};
if (fontSize && fontSize > 0) {
elem.detail.fontSize = doNum(fontSize * ratio);
beforeFlattenElem['detail.fontSize'] = fontSize;
afterFlattenElem['detail.fontSize'] = elem.detail.fontSize;
}
if (lineHeight && lineHeight > 0) {
elem.detail.lineHeight = doNum(lineHeight * ratio);
beforeFlattenElem['detail.lineHeight'] = lineHeight;
afterFlattenElem['detail.lineHeight'] = elem.detail.lineHeight;
}
const record: ModifyRecord<'modifyElement'> = {
type: 'modifyElement',
time: Date.now(),
content: {
method: 'modifyElement',
uuid: elem.uuid,
before: beforeFlattenElem,
after: afterFlattenElem
}
};
return record;
}
function resizeElement(elem: Element, opts: ResizeOptions) {
const { type } = elem;
function deepResizeElementByRatio(
elem: Element,
opts: DeepResizeRatioOptions,
record?: ModifyRecord<'modifyElements'>
) {
const { type, uuid } = elem;
// base and rect
resizeElementBase(elem, opts);
const rootRecord = resizeElementBaseByRatio(elem, opts);
const rootRecordBefore: FlattenLayout & { uuid: string } = { ...rootRecord.content.before, uuid };
const rootRecordAfter: FlattenLayout & { uuid: string } = { ...rootRecord.content.after, uuid };
record?.content.before.push(rootRecordBefore);
record?.content.after.push(rootRecordAfter);
if (type === 'circle') {
// TODO
} else if (type === 'text') {
resizeTextElementDetail(elem as Element<'text'>, opts);
const textRecord = resizeTextElementDetailByRatio(elem as Element<'text'>, opts);
Object.keys(textRecord.content.before || {}).forEach((key) => {
rootRecordBefore[key] = textRecord.content.before?.[key];
});
Object.keys(textRecord.content.after || {}).forEach((key) => {
rootRecordAfter[key] = textRecord.content.after?.[key];
});
} else if (type === 'image') {
// TODO
} else if (type === 'svg') {
@ -92,34 +202,136 @@ function resizeElement(elem: Element, opts: ResizeOptions) {
// TODO
} else if (type === 'group' && Array.isArray((elem as Element<'group'>).detail.children)) {
(elem as Element<'group'>).detail.children.forEach((child) => {
resizeElement(child, opts);
deepResizeElementByRatio(child, opts, record);
});
}
}
export function deepResizeGroupElement(
function fixedResizeGroupElementChildren(
elem: Element<'group'>,
size: Pick<Partial<ElementSize>, 'w' | 'h'>
): Element<'group'> {
const resizeW: number = size.w && size.w > 0 ? size.w : elem.w;
const resizeH: number = size.h && size.h > 0 ? size.h : elem.h;
const xRatio = resizeW / elem.w;
const yRatio = resizeH / elem.h;
if (xRatio === yRatio && xRatio === 1) {
return elem;
opts: FixedResizeOptions,
record?: ModifyRecord<'modifyElements'>
) {
if (!(elem.type === 'group' && Array.isArray(elem.detail.children))) {
return;
}
const { moveX, moveY, moveH, moveW } = opts;
let childChangedX = 0;
let childChangedY = 0;
let needReszieChildren = false;
if ((moveX !== 0 || moveY !== 0) && (moveH !== 0 || moveW !== 0)) {
needReszieChildren = true;
childChangedX = -moveX;
childChangedY = -moveY;
}
const minRatio = Math.min(xRatio, yRatio);
const maxRatio = Math.max(xRatio, yRatio);
if (needReszieChildren !== true) {
return;
}
elem.detail.children.forEach((child) => {
const { uuid, x, y } = child;
const afterX = x + childChangedX;
const afterY = y + childChangedY;
const before: FlattenLayout & { uuid: string } = { uuid, x, y };
const after: FlattenLayout & { uuid: string } = { uuid, x: afterX, y: afterY };
child.x = afterX;
child.y = afterY;
record?.content.before.push(before);
record?.content.after.push(after);
});
}
export function resizeEffectGroupElement(
elem: Element<'group'>,
size: Partial<ElementSize>,
opts?: {
resizeEffect?: ElementOperations['resizeEffect'];
}
): ModifyRecord<'modifyElements'> | null {
if (!istype.number(size.x) && !istype.number(size.y)) {
return null;
}
const record: ModifyRecord<'modifyElements'> = {
type: 'modifyElements',
time: Date.now(),
content: {
method: 'modifyElements',
before: [],
after: []
}
};
const uuid = elem.uuid;
const originX: number = elem.x;
const originY: number = elem.y;
const originW: number = elem.w;
const originH: number = elem.h;
const resizeX: number = (istype.number(size.x) ? size.x : elem.x) as number;
const resizeY: number = (istype.number(size.y) ? size.y : elem.y) as number;
const resizeW: number = (size.w && size.w > 0 ? size.w : elem.w) || 0;
const resizeH: number = (size.h && size.h > 0 ? size.h : elem.h) || 0;
const beforeGroupElem: FlattenLayout & { uuid: string } = { uuid, x: originX, y: originY, w: originW, h: originH };
const afterGroupElem: FlattenLayout & { uuid: string } = { uuid, x: resizeX, y: resizeY, w: resizeW, h: resizeH };
if (opts?.resizeEffect === 'deepResize') {
const xRatio = resizeW / elem.w;
const yRatio = resizeH / elem.h;
if (xRatio === yRatio && xRatio === 1) {
return record;
}
const minRatio = Math.min(xRatio, yRatio);
const maxRatio = Math.max(xRatio, yRatio);
elem.w = resizeW;
elem.h = resizeH;
const resizeRadioOpts = { xRatio, yRatio, minRatio, maxRatio };
if (elem.type === 'group' && Array.isArray(elem.detail.children)) {
elem.detail.children.forEach((child) => {
deepResizeElementByRatio(child, resizeRadioOpts, record);
});
}
const groupDetailRecord = resizeElementBaseDetailByRatio(elem, resizeRadioOpts);
Object.keys(groupDetailRecord.content.before || {}).forEach((key) => {
beforeGroupElem[key] = groupDetailRecord.content.before?.[key];
});
Object.keys(groupDetailRecord.content.after || {}).forEach((key) => {
afterGroupElem[key] = groupDetailRecord.content.after?.[key];
});
return record;
}
// fixed
if (opts?.resizeEffect === 'fixed') {
record.content.before.push(beforeGroupElem);
record.content.after.push(afterGroupElem);
const moveX = resizeX - elem.x;
const moveY = resizeY - elem.y;
const moveW = resizeW - elem.w;
const moveH = resizeH - elem.h;
fixedResizeGroupElementChildren(elem, { moveX, moveY, moveH, moveW }, record);
elem.w = resizeW;
elem.h = resizeH;
elem.x = resizeX;
elem.y = resizeY;
return record;
}
// default 'absolute'
elem.w = resizeW;
elem.h = resizeH;
const opts = { xRatio, yRatio, minRatio, maxRatio };
if (elem.type === 'group' && Array.isArray(elem.detail.children)) {
elem.detail.children.forEach((child) => {
resizeElement(child, opts);
});
}
resizeElementBaseDetail(elem, opts);
return elem;
elem.x = resizeX;
elem.y = resizeY;
record.content.before.push(beforeGroupElem);
record.content.after.push(afterGroupElem);
return record;
}