From 64f89efdbdb91842df3099b85483425541cc5007 Mon Sep 17 00:00:00 2001 From: chenshenhai Date: Sat, 24 May 2025 10:45:55 +0800 Subject: [PATCH 1/3] refactor: refactor operations of group element --- packages/core/src/board/index.ts | 80 ++--- packages/core/src/index.ts | 4 +- .../core/src/middleware/selector/index.ts | 32 +- packages/core/src/middleware/selector/util.ts | 1 + packages/idraw/src/idraw.ts | 36 ++- packages/idraw/src/index.ts | 1 - packages/idraw/src/middlewares/use-history.ts | 23 +- packages/renderer/src/loader.ts | 6 +- packages/types/src/lib/board.ts | 1 + packages/types/src/lib/core.ts | 1 + packages/types/src/lib/element.ts | 4 +- packages/types/src/lib/idraw.ts | 3 +- packages/util/src/index.ts | 2 +- packages/util/src/view/handle-element.ts | 36 ++- packages/util/src/view/resize-element.ts | 280 +++++++++++++++--- 15 files changed, 389 insertions(+), 121 deletions(-) diff --git a/packages/core/src/board/index.ts b/packages/core/src/board/index.ts index ed8fec5..d51806f 100644 --- a/packages/core/src/board/index.ts +++ b/packages/core/src/board/index.ts @@ -94,43 +94,45 @@ export class Board { } #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 { } onWatcherEvents() { + if (this.#opts.disableWatcher === true) { + return; + } this.#watcher.onEvents(); } offWatcherEvents() { + if (this.#opts.disableWatcher === true) { + return; + } this.#watcher.offEvents(); } } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 80ec0ea..8e99249 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -60,7 +60,7 @@ export class Core { #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 { container.appendChild(canvas); const boardContent = createBoardContent(canvas, { width, height, devicePixelRatio }); - const board = new Board({ boardContent, container }); + const board = new Board({ boardContent, container, disableWatcher }); const sharer = board.getSharer(); sharer.setActiveViewSizeInfo({ width, diff --git a/packages/core/src/middleware/selector/index.ts b/packages/core/src/middleware/selector/index.ts index 7c30f9d..a0229f5 100644 --- a/packages/core/src/middleware/selector/index.ts +++ b/packages/core/src/middleware/selector/index.ts @@ -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; } } diff --git a/packages/core/src/middleware/selector/util.ts b/packages/core/src/middleware/selector/util.ts index fd480a8..43ed18c 100644 --- a/packages/core/src/middleware/selector/util.ts +++ b/packages/core/src/middleware/selector/util.ts @@ -138,6 +138,7 @@ export function getPointTarget( if (selectedElements && selectedElements?.length > 0) { target.groupQueue = groupQueue || []; target.elements = [selectedElements[0]]; + return target; } break; } diff --git a/packages/idraw/src/idraw.ts b/packages/idraw/src/idraw.ts index 8a899a5..831097f 100644 --- a/packages/idraw/src/idraw.ts +++ b/packages/idraw/src/idraw.ts @@ -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; @@ -35,6 +37,7 @@ export class iDraw { #store: Store = new Store({ 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(); + // } } diff --git a/packages/idraw/src/index.ts b/packages/idraw/src/index.ts index cdef852..f52d97a 100644 --- a/packages/idraw/src/index.ts +++ b/packages/idraw/src/index.ts @@ -115,7 +115,6 @@ export { insertElementToListByPosition, deleteElementInListByPosition, deleteElementInList, - deepResizeGroupElement, deepCloneElement, calcViewCenterContent, calcViewCenter, diff --git a/packages/idraw/src/middlewares/use-history.ts b/packages/idraw/src/middlewares/use-history.ts index 2d193cc..b0d6379 100644 --- a/packages/idraw/src/middlewares/use-history.ts +++ b/packages/idraw/src/middlewares/use-history.ts @@ -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; }; diff --git a/packages/renderer/src/loader.ts b/packages/renderer/src/loader.ts index fae8469..620f594 100644 --- a/packages/renderer/src/loader.ts +++ b/packages/renderer/src/loader.ts @@ -88,11 +88,11 @@ export class Loader extends EventEmitter implements RendererLoad if (supportElementTypes.includes((element as Element).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') { diff --git a/packages/types/src/lib/board.ts b/packages/types/src/lib/board.ts index 2523821..965f359 100644 --- a/packages/types/src/lib/board.ts +++ b/packages/types/src/lib/board.ts @@ -110,6 +110,7 @@ export type BoardMiddleware< export interface BoardOptions { boardContent: BoardContent; container?: HTMLDivElement; + disableWatcher?: boolean; } export interface BoardViewerFrameSnapshot = any> { diff --git a/packages/types/src/lib/core.ts b/packages/types/src/lib/core.ts index 62fde69..9ca9cd4 100644 --- a/packages/types/src/lib/core.ts +++ b/packages/types/src/lib/core.ts @@ -8,6 +8,7 @@ export interface CoreOptions { width: number; height: number; devicePixelRatio?: number; + disableWatcher?: boolean; } export type CursorType = diff --git a/packages/types/src/lib/element.ts b/packages/types/src/lib/element.ts index 721b221..94a8a84 100644 --- a/packages/types/src/lib/element.ts +++ b/packages/types/src/lib/element.ts @@ -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 { diff --git a/packages/types/src/lib/idraw.ts b/packages/types/src/lib/idraw.ts index e12331d..3ed0e6f 100644 --- a/packages/types/src/lib/idraw.ts +++ b/packages/types/src/lib/idraw.ts @@ -22,11 +22,12 @@ export interface IDrawSettings { layoutSelector?: Partial; }; history?: boolean; + historyLimit?: number; } export type IDrawOptions = CoreOptions & IDrawSettings; -export type IDrawHistory = { +export type HistoryHandler = { undo: () => void; redo: () => void; canUndo: () => void; diff --git a/packages/util/src/index.ts b/packages/util/src/index.ts index ddc2a31..5e5782b 100644 --- a/packages/util/src/index.ts +++ b/packages/util/src/index.ts @@ -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'; diff --git a/packages/util/src/view/handle-element.ts b/packages/util/src/view/handle-element.ts index 1534402..bc6c976 100644 --- a/packages/util/src/view/handle-element.ts +++ b/packages/util/src/view/handle-element.ts @@ -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); } diff --git a/packages/util/src/view/resize-element.ts b/packages/util/src/view/resize-element.ts index 310c074..53ac686 100644 --- a/packages/util/src/view/resize-element.ts +++ b/packages/util/src/view/resize-element.ts @@ -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 = { detail: {} }; + const afterElem: Pick = { 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, '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, + 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; } From 5fe7d27c00f0389e97c82951a44f0c06dcce5f33 Mon Sep 17 00:00:00 2001 From: chenshenhai Date: Sat, 24 May 2025 10:47:41 +0800 Subject: [PATCH 2/3] chore: bump version 0.4.0-beta.45 --- .github/workflows/release.yml | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4e3f979..a5f0588 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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 }} diff --git a/package.json b/package.json index 1fc0c94..7a56256 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": false, - "version": "0.4.0-beta.44", + "version": "0.4.0-beta.45", "workspaces": [ "packages/*" ], From 61e01719d29869d7bb3e1ed56871764a7021c88f Mon Sep 17 00:00:00 2001 From: chenshenhai Date: Sat, 24 May 2025 10:54:00 +0800 Subject: [PATCH 3/3] test: fix unit test of history middleware --- packages/idraw/__tests__/history-addElement.test.ts | 4 ++-- packages/idraw/__tests__/history-deleteElement.test.ts | 4 ++-- packages/idraw/__tests__/history-modifyElement.test.ts | 4 ++-- packages/idraw/__tests__/history-modifyGlobal.test.ts | 4 ++-- packages/idraw/__tests__/history-modifyLayout.test.ts | 4 ++-- packages/idraw/__tests__/history-moveElement.test.ts | 4 ++-- packages/idraw/__tests__/history-updateElement.test.ts | 4 ++-- packages/idraw/__tests__/history.test.ts | 4 ++-- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/idraw/__tests__/history-addElement.test.ts b/packages/idraw/__tests__/history-addElement.test.ts index 213b8c8..33085e6 100644 --- a/packages/idraw/__tests__/history-addElement.test.ts +++ b/packages/idraw/__tests__/history-addElement.test.ts @@ -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); diff --git a/packages/idraw/__tests__/history-deleteElement.test.ts b/packages/idraw/__tests__/history-deleteElement.test.ts index 498fcd0..60d5b55 100644 --- a/packages/idraw/__tests__/history-deleteElement.test.ts +++ b/packages/idraw/__tests__/history-deleteElement.test.ts @@ -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); diff --git a/packages/idraw/__tests__/history-modifyElement.test.ts b/packages/idraw/__tests__/history-modifyElement.test.ts index 0188da2..23ce658 100644 --- a/packages/idraw/__tests__/history-modifyElement.test.ts +++ b/packages/idraw/__tests__/history-modifyElement.test.ts @@ -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]); diff --git a/packages/idraw/__tests__/history-modifyGlobal.test.ts b/packages/idraw/__tests__/history-modifyGlobal.test.ts index e7dfedc..300ea40 100644 --- a/packages/idraw/__tests__/history-modifyGlobal.test.ts +++ b/packages/idraw/__tests__/history-modifyGlobal.test.ts @@ -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); diff --git a/packages/idraw/__tests__/history-modifyLayout.test.ts b/packages/idraw/__tests__/history-modifyLayout.test.ts index 4cd88ad..eed9735 100644 --- a/packages/idraw/__tests__/history-modifyLayout.test.ts +++ b/packages/idraw/__tests__/history-modifyLayout.test.ts @@ -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); diff --git a/packages/idraw/__tests__/history-moveElement.test.ts b/packages/idraw/__tests__/history-moveElement.test.ts index 5f9a848..d61222e 100644 --- a/packages/idraw/__tests__/history-moveElement.test.ts +++ b/packages/idraw/__tests__/history-moveElement.test.ts @@ -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); diff --git a/packages/idraw/__tests__/history-updateElement.test.ts b/packages/idraw/__tests__/history-updateElement.test.ts index dd3e4de..7d07de1 100644 --- a/packages/idraw/__tests__/history-updateElement.test.ts +++ b/packages/idraw/__tests__/history-updateElement.test.ts @@ -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]); diff --git a/packages/idraw/__tests__/history.test.ts b/packages/idraw/__tests__/history.test.ts index 0188da2..23ce658 100644 --- a/packages/idraw/__tests__/history.test.ts +++ b/packages/idraw/__tests__/history.test.ts @@ -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]);