From bf3629a9779f529c0fc02316e5224ae3f5d03676 Mon Sep 17 00:00:00 2001 From: chenshenhai Date: Sat, 3 Feb 2024 13:18:18 +0800 Subject: [PATCH] feat: improve text-editor, fix getData and fix type error --- packages/board/src/index.ts | 15 +- packages/board/src/lib/watcher.ts | 16 +- packages/core/src/index.ts | 12 ++ packages/core/src/lib/cursor.ts | 68 ++++--- .../core/src/middleware/text-editor/index.ts | 12 +- packages/idraw/src/idraw.ts | 22 +- packages/idraw/src/index.ts | 3 +- packages/renderer/src/draw/html.ts | 2 +- packages/renderer/src/draw/image.ts | 2 +- packages/renderer/src/draw/svg.ts | 2 +- packages/renderer/src/index.ts | 7 + packages/renderer/src/loader.ts | 47 +++-- packages/types/src/lib/board.ts | 1 + packages/types/src/lib/core.ts | 3 +- packages/types/src/lib/{html.tsx => html.ts} | 0 packages/types/src/lib/renderer.ts | 1 + packages/util/__tests__/_assets/base.ts | 50 +++++ packages/util/__tests__/lib/data.test.ts | 192 +++++++++++++++++- packages/util/src/index.ts | 2 +- packages/util/src/lib/data.ts | 36 ++-- packages/util/src/lib/event.ts | 4 + packages/util/src/lib/view-content.ts | 35 +++- 22 files changed, 438 insertions(+), 94 deletions(-) rename packages/types/src/lib/{html.tsx => html.ts} (100%) create mode 100644 packages/util/__tests__/_assets/base.ts diff --git a/packages/board/src/index.ts b/packages/board/src/index.ts index 75f1ed8fe..1dc2c7138 100644 --- a/packages/board/src/index.ts +++ b/packages/board/src/index.ts @@ -34,7 +34,7 @@ export class Board { #viewer: Viewer; #calculator: Calculator; #eventHub: EventEmitter = new EventEmitter(); - + #hasDestroyed: boolean = false; constructor(opts: BoardOptions) { const { boardContent } = opts; const sharer = new Sharer(); @@ -70,6 +70,10 @@ export class Board { this.#resetActiveMiddlewareObjs(); } + isDestroyed() { + return this.#hasDestroyed; + } + destroy() { // #opts // #middlewareMap @@ -81,6 +85,7 @@ export class Board { // #viewer: Viewer; this.#calculator.destroy(); this.#eventHub.destroy(); + this.#hasDestroyed = true; } #init() { @@ -376,6 +381,14 @@ export class Board { getEventHub(): EventEmitter { return this.#eventHub; } + + onWatcherEvents() { + this.#watcher.onEvents(); + } + + offWatcherEvents() { + this.#watcher.offEvents(); + } } export { Sharer, Calculator }; diff --git a/packages/board/src/lib/watcher.ts b/packages/board/src/lib/watcher.ts index 504049e07..1f8d1d9a9 100644 --- a/packages/board/src/lib/watcher.ts +++ b/packages/board/src/lib/watcher.ts @@ -8,6 +8,7 @@ function isBoardAvailableNum(num: any): boolean { export class BoardWatcher extends EventEmitter { #opts: BoardWatcherOptions; #store: Store; + #hasDestroyed: boolean = false; constructor(opts: BoardWatcherOptions) { super(); const store = new Store({ defaultStorage: { hasPointDown: false, prevClickPoint: null } }); @@ -17,6 +18,13 @@ export class BoardWatcher extends EventEmitter { } #init() { + this.onEvents(); + } + + onEvents() { + if (this.#hasDestroyed) { + return; + } const container = window; container.addEventListener('mousemove', this.#onHover); container.addEventListener('mousedown', this.#onPointStart); @@ -28,7 +36,7 @@ export class BoardWatcher extends EventEmitter { container.addEventListener('contextmenu', this.#onContextMenu); } - destroy() { + offEvents() { const container = window; container.removeEventListener('mousemove', this.#onHover); container.removeEventListener('mousedown', this.#onPointStart); @@ -40,6 +48,12 @@ export class BoardWatcher extends EventEmitter { container.removeEventListener('contextmenu', this.#onContextMenu); } + destroy() { + this.offEvents(); + this.#store.destroy(); + this.#hasDestroyed = true; + } + #onWheel = (e: WheelEvent) => { if (!this.#isInTarget(e)) { return; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index e9711a556..63a0a6d99 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -45,6 +45,10 @@ export class Core { }); } + isDestroyed() { + return this.#board.isDestroyed(); + } + destroy() { this.#board.destroy(); this.#canvas.remove(); @@ -129,4 +133,12 @@ export class Core { getLoadItemMap(): LoadItemMap { return this.#board.getRenderer().getLoadItemMap(); } + + onBoardWatcherEvents() { + this.#board.onWatcherEvents(); + } + + offBoardWatcherEvents() { + this.#board.offWatcherEvents(); + } } diff --git a/packages/core/src/lib/cursor.ts b/packages/core/src/lib/cursor.ts index db336e410..73beb915e 100644 --- a/packages/core/src/lib/cursor.ts +++ b/packages/core/src/lib/cursor.ts @@ -3,11 +3,11 @@ import { limitAngle, loadImage, parseAngleToRadian } from '@idraw/util'; import { CURSOR, CURSOR_RESIZE, CURSOR_DRAG_DEFAULT, CURSOR_DRAG_ACTIVE } from './cursor-image'; export class Cursor { - private _eventHub: UtilEventEmitter; - private _container: HTMLDivElement; - private _cursorType: 'auto' | string | null = null; - private _resizeCursorBaseImage: HTMLImageElement | null = null; - private _cursorImageMap: Record = { + #eventHub: UtilEventEmitter; + #container: HTMLDivElement; + #cursorType: 'default' | string | null = null; + #resizeCursorBaseImage: HTMLImageElement | null = null; + #cursorImageMap: Record = { auto: CURSOR, 'drag-default': CURSOR_DRAG_DEFAULT, 'drag-active': CURSOR_DRAG_ACTIVE, @@ -19,56 +19,60 @@ export class Cursor { eventHub: UtilEventEmitter; } ) { - this._container = container; - this._eventHub = opts.eventHub; - this._init(); - this._loadResizeCursorBaseImage(); + this.#container = container; + this.#eventHub = opts.eventHub; + this.#init(); + this.#loadResizeCursorBaseImage(); } - private _init() { - const { _eventHub: eventHub } = this; - this._resetCursor('auto'); + #init() { + const eventHub = this.#eventHub; + this.#resetCursor('default'); eventHub.on('cursor', (e) => { if (e.type === 'over-element' || !e.type) { - this._resetCursor('auto'); + this.#resetCursor('auto'); } else if (typeof e.type === 'string' && e.type?.startsWith('resize-')) { - this._setCursorResize(e); + this.#setCursorResize(e); } else if (e.type === 'drag-default') { - this._resetCursor('drag-default'); + this.#resetCursor('drag-default'); } else if (e.type === 'drag-active') { - this._resetCursor('drag-active'); + this.#resetCursor('drag-active'); } else { - this._resetCursor('auto'); + this.#resetCursor('auto'); } }); } - private _loadResizeCursorBaseImage() { + #loadResizeCursorBaseImage() { loadImage(CURSOR_RESIZE) .then((img) => { - this._resizeCursorBaseImage = img; + this.#resizeCursorBaseImage = img; }) .catch((err) => { console.error(err); }); } - private _resetCursor(cursorKey: string) { - if (this._cursorType === cursorKey) { + #resetCursor(cursorKey: string) { + if (this.#cursorType === cursorKey) { return; } - this._cursorType = cursorKey; - const image = this._cursorImageMap[this._cursorType] || this._cursorImageMap['auto']; + this.#cursorType = cursorKey; + const image = this.#cursorImageMap[this.#cursorType] || this.#cursorImageMap['auto']; let offsetX = 0; let offsetY = 0; - if (cursorKey.startsWith('rotate-') && this._cursorImageMap[this._cursorType]) { + if (cursorKey.startsWith('rotate-') && this.#cursorImageMap[this.#cursorType]) { offsetX = 10; offsetY = 10; } - this._container.style.cursor = `image-set(url(${image})2x) ${offsetX} ${offsetY}, auto`; + if (cursorKey === 'default') { + this.#container.style.cursor = 'default'; + } else { + this.#container.style.cursor = `image-set(url(${image})2x) ${offsetX} ${offsetY}, auto`; + } } - private _setCursorResize(e: CoreEvent['cursor']) { + #setCursorResize(e: CoreEvent['cursor']) { let totalAngle = 0; if (e.type === 'resize-top') { totalAngle += 0; @@ -94,14 +98,14 @@ export class Cursor { }); } totalAngle = limitAngle(totalAngle); - const cursorKey = this._appendRotateResizeImage(totalAngle); - this._resetCursor(cursorKey); + const cursorKey = this.#appendRotateResizeImage(totalAngle); + this.#resetCursor(cursorKey); } - private _appendRotateResizeImage(angle: number): string { + #appendRotateResizeImage(angle: number): string { const key = `rotate-${angle}`; - if (!this._cursorImageMap[key]) { - const baseImage = this._resizeCursorBaseImage; + if (!this.#cursorImageMap[key]) { + const baseImage = this.#resizeCursorBaseImage; if (baseImage) { const canvas = document.createElement('canvas'); const w = baseImage.width; @@ -126,7 +130,7 @@ export class Cursor { ctx.translate(-center.x, -center.y); const base = canvas.toDataURL('image/png'); - this._cursorImageMap[key] = base; + this.#cursorImageMap[key] = base; } } return key; diff --git a/packages/core/src/middleware/text-editor/index.ts b/packages/core/src/middleware/text-editor/index.ts index c362248b9..1eea746c9 100644 --- a/packages/core/src/middleware/text-editor/index.ts +++ b/packages/core/src/middleware/text-editor/index.ts @@ -110,10 +110,10 @@ export const MiddlewareTextEditor: BoardMiddleware, CoreEven } textarea.style.position = 'absolute'; - textarea.style.left = `${elemX}px`; - textarea.style.top = `${elemY}px`; - textarea.style.width = `${elemW}px`; - textarea.style.height = `${elemH}px`; + textarea.style.left = `${elemX - 1}px`; + textarea.style.top = `${elemY - 1}px`; + textarea.style.width = `${elemW + 2}px`; + textarea.style.height = `${elemH + 2}px`; textarea.style.transform = `rotate(${limitAngle(element.angle || 0)}deg)`; // textarea.style.border = 'none'; textarea.style.boxSizing = 'border-box'; @@ -127,6 +127,10 @@ export const MiddlewareTextEditor: BoardMiddleware, CoreEven textarea.style.lineHeight = `${detail.lineHeight * scale}px`; textarea.style.fontFamily = detail.fontFamily; textarea.style.fontWeight = `${detail.fontWeight}`; + textarea.style.padding = '0'; + textarea.style.margin = '0'; + textarea.style.outline = 'none'; + textarea.value = detail.text || ''; parent.appendChild(textarea); }; diff --git a/packages/idraw/src/idraw.ts b/packages/idraw/src/idraw.ts index ff9df7235..efb56b18d 100644 --- a/packages/idraw/src/idraw.ts +++ b/packages/idraw/src/idraw.ts @@ -30,7 +30,8 @@ import { getElementPositionFromList, calcElementListSize, filterCompactData, - calcViewCenterContent + calcViewCenterContent, + calcViewCenter } from '@idraw/util'; import { defaultSettings } from './config'; import { exportImageFileBlobURL } from './file'; @@ -143,7 +144,7 @@ export class iDraw { centerContent(opts?: { data?: Data }) { const data = opts?.data || this.#core.getData(); const { viewSizeInfo } = this.getViewInfo(); - if (data) { + if (Array.isArray(data?.elements) && data?.elements.length > 0) { const result = calcViewCenterContent(data, { viewSizeInfo }); this.setViewScale(result); } @@ -274,8 +275,25 @@ export class iDraw { }); } + isDestroyed() { + return this.#core.isDestroyed(); + } + destroy() { const core = this.#core; core.destroy(); } + + getViewCenter() { + const { viewScaleInfo, viewSizeInfo } = this.getViewInfo(); + return calcViewCenter({ viewScaleInfo, viewSizeInfo }); + } + + $onBoardWatcherEvents() { + this.#core.onBoardWatcherEvents(); + } + + $offBoardWatcherEvents() { + this.#core.offBoardWatcherEvents(); + } } diff --git a/packages/idraw/src/index.ts b/packages/idraw/src/index.ts index 8fc33b62f..848efa2b3 100644 --- a/packages/idraw/src/index.ts +++ b/packages/idraw/src/index.ts @@ -116,7 +116,8 @@ export { deleteElementInList, deepResizeGroupElement, deepCloneElement, - calcViewCenterContent + calcViewCenterContent, + calcViewCenter } from '@idraw/util'; export { iDraw } from './idraw'; export type { IDrawEvent, IDrawEventKeys } from './event'; diff --git a/packages/renderer/src/draw/html.ts b/packages/renderer/src/draw/html.ts index 40ee874db..3b8a1a65d 100644 --- a/packages/renderer/src/draw/html.ts +++ b/packages/renderer/src/draw/html.ts @@ -7,7 +7,7 @@ export function drawHTML(ctx: ViewContext2D, elem: Element<'html'>, opts: Render const { calculator, viewScaleInfo, viewSizeInfo, parentOpacity } = opts; const { x, y, w, h, angle } = calculator?.elementSize(elem, viewScaleInfo, viewSizeInfo) || elem; rotateElement(ctx, { x, y, w, h, angle }, () => { - if (!content) { + if (!content && !opts.loader.isDestroyed()) { opts.loader.load(elem as Element<'html'>, opts.elementAssets || {}); } if (elem.type === 'html' && content) { diff --git a/packages/renderer/src/draw/image.ts b/packages/renderer/src/draw/image.ts index 78dc2d200..12184bbb1 100644 --- a/packages/renderer/src/draw/image.ts +++ b/packages/renderer/src/draw/image.ts @@ -20,7 +20,7 @@ export function drawImage(ctx: ViewContext2D, elem: Element<'image'>, opts: Rend viewSizeInfo, parentOpacity, renderContent: () => { - if (!content) { + if (!content && !opts.loader.isDestroyed()) { opts.loader.load(elem as Element<'image'>, opts.elementAssets || {}); } if (elem.type === 'image' && content) { diff --git a/packages/renderer/src/draw/svg.ts b/packages/renderer/src/draw/svg.ts index e84ecb20b..dfcec8ab8 100644 --- a/packages/renderer/src/draw/svg.ts +++ b/packages/renderer/src/draw/svg.ts @@ -7,7 +7,7 @@ export function drawSVG(ctx: ViewContext2D, elem: Element<'svg'>, opts: Renderer const { calculator, viewScaleInfo, viewSizeInfo, parentOpacity } = opts; const { x, y, w, h, angle } = calculator?.elementSize(elem, viewScaleInfo, viewSizeInfo) || elem; rotateElement(ctx, { x, y, w, h, angle }, () => { - if (!content) { + if (!content && !opts.loader.isDestroyed()) { opts.loader.load(elem as Element<'svg'>, opts.elementAssets || {}); } if (elem.type === 'svg' && content) { diff --git a/packages/renderer/src/index.ts b/packages/renderer/src/index.ts index 067154ffe..54e65fcef 100644 --- a/packages/renderer/src/index.ts +++ b/packages/renderer/src/index.ts @@ -7,6 +7,7 @@ import type { Data, BoardRenderer, RendererOptions, RendererEventMap, RendererDr export class Renderer extends EventEmitter implements BoardRenderer { #opts: RendererOptions; #loader: Loader = new Loader(); + #hasDestroyed: boolean = false; constructor(opts: RendererOptions) { super(); @@ -14,10 +15,16 @@ export class Renderer extends EventEmitter implements BoardRen this.#init(); } + isDestroyed() { + return this.#hasDestroyed; + } + destroy() { + this.clear(); this.#opts = null as any; this.#loader.destroy(); this.#loader = null as any; + this.#hasDestroyed = true; } #init() { diff --git a/packages/renderer/src/loader.ts b/packages/renderer/src/loader.ts index efb1707bc..4920caa23 100644 --- a/packages/renderer/src/loader.ts +++ b/packages/renderer/src/loader.ts @@ -25,6 +25,7 @@ export class Loader extends EventEmitter implements RendererLoad #loadFuncMap: Record> = {}; #currentLoadItemMap: LoadItemMap = {}; #storageLoadItemMap: LoadItemMap = {}; + #hasDestroyed: boolean = false; constructor() { super(); @@ -60,7 +61,13 @@ export class Loader extends EventEmitter implements RendererLoad }); } + isDestroyed() { + return this.#hasDestroyed; + } + destroy() { + this.#hasDestroyed = true; + this.clear(); this.#loadFuncMap = null as any; this.#currentLoadItemMap = null as any; this.#storageLoadItemMap = null as any; @@ -99,28 +106,32 @@ export class Loader extends EventEmitter implements RendererLoad #emitLoad(item: LoadItem) { const assetId = getAssetIdFromElement(item.element); const storageItem = this.#storageLoadItemMap[assetId]; - if (storageItem) { - if (storageItem.startTime < item.startTime) { + if (!this.#hasDestroyed) { + if (storageItem) { + if (storageItem.startTime < item.startTime) { + this.#storageLoadItemMap[assetId] = item; + this.trigger('load', { ...item, countTime: item.endTime - item.startTime }); + } + } else { this.#storageLoadItemMap[assetId] = item; this.trigger('load', { ...item, countTime: item.endTime - item.startTime }); } - } else { - this.#storageLoadItemMap[assetId] = item; - this.trigger('load', { ...item, countTime: item.endTime - item.startTime }); } } #emitError(item: LoadItem) { const assetId = getAssetIdFromElement(item.element); const storageItem = this.#storageLoadItemMap?.[assetId]; - if (storageItem) { - if (storageItem.startTime < item.startTime) { + if (!this.#hasDestroyed) { + if (storageItem) { + if (storageItem.startTime < item.startTime) { + this.#storageLoadItemMap[assetId] = item; + this.trigger('error', { ...item, countTime: item.endTime - item.startTime }); + } + } else { this.#storageLoadItemMap[assetId] = item; this.trigger('error', { ...item, countTime: item.endTime - item.startTime }); } - } else { - this.#storageLoadItemMap[assetId] = item; - this.trigger('error', { ...item, countTime: item.endTime - item.startTime }); } } @@ -130,16 +141,19 @@ export class Loader extends EventEmitter implements RendererLoad this.#currentLoadItemMap[assetId] = item; const loadFunc = this.#loadFuncMap[element.type]; - if (typeof loadFunc === 'function') { + if (typeof loadFunc === 'function' && !this.#hasDestroyed) { item.startTime = Date.now(); loadFunc(element, assets) .then((result) => { - item.content = result.content; - item.endTime = Date.now(); - item.status = 'load'; - this.#emitLoad(item); + if (!this.#hasDestroyed) { + item.content = result.content; + item.endTime = Date.now(); + item.status = 'load'; + this.#emitLoad(item); + } }) .catch((err: Error) => { + // eslint-disable-next-line no-console console.warn(`Load element source "${item.source}" fail`, err, element); item.endTime = Date.now(); item.status = 'error'; @@ -159,6 +173,9 @@ export class Loader extends EventEmitter implements RendererLoad } load(element: Element, assets: ElementAssets) { + if (this.#hasDestroyed === true) { + return; + } if (this.#isExistingErrorStorage(element)) { return; } diff --git a/packages/types/src/lib/board.ts b/packages/types/src/lib/board.ts index 929810d95..dcb43a6a7 100644 --- a/packages/types/src/lib/board.ts +++ b/packages/types/src/lib/board.ts @@ -134,6 +134,7 @@ export interface BoardRenderer extends UtilEventEmitter { drawData(data: Data, opts: RendererDrawOptions): void; scale(num: number): void; destroy(): void; + isDestroyed(): boolean; getLoader(): RendererLoader; } diff --git a/packages/types/src/lib/core.ts b/packages/types/src/lib/core.ts index 2a8e67c2f..2e1236835 100644 --- a/packages/types/src/lib/core.ts +++ b/packages/types/src/lib/core.ts @@ -19,7 +19,8 @@ export type CursorType = | 'resize-bottom-left' | 'resize-bottom-right' | 'drag-default' - | 'drag-active'; + | 'drag-active' + | 'default'; export interface CoreEventCursor { type: CursorType | string | null; diff --git a/packages/types/src/lib/html.tsx b/packages/types/src/lib/html.ts similarity index 100% rename from packages/types/src/lib/html.tsx rename to packages/types/src/lib/html.ts diff --git a/packages/types/src/lib/renderer.ts b/packages/types/src/lib/renderer.ts index 0527c2bde..eea1cd575 100644 --- a/packages/types/src/lib/renderer.ts +++ b/packages/types/src/lib/renderer.ts @@ -26,6 +26,7 @@ export interface RendererLoader extends UtilEventEmitter { getLoadItemMap(): LoadItemMap; setLoadItemMap(itemMap: LoadItemMap): void; destroy(): void; + isDestroyed(): boolean; } export interface RendererDrawOptions { diff --git a/packages/util/__tests__/_assets/base.ts b/packages/util/__tests__/_assets/base.ts new file mode 100644 index 000000000..e427c657c --- /dev/null +++ b/packages/util/__tests__/_assets/base.ts @@ -0,0 +1,50 @@ +export const imageBase64 = `data:image/png;base64,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`; + +export const svg = ``; + +export const html = ` + +
+
+ +
+
+ +
+
+`; diff --git a/packages/util/__tests__/lib/data.test.ts b/packages/util/__tests__/lib/data.test.ts index f3851012c..238694e1d 100644 --- a/packages/util/__tests__/lib/data.test.ts +++ b/packages/util/__tests__/lib/data.test.ts @@ -1,7 +1,6 @@ -import { - deepClone -} from '../../src/lib/data'; - +import { deepClone, filterCompactData } from '../../src/lib/data'; +import { imageBase64, html, svg } from '../_assets/base'; +import type { Data } from '@idraw/types'; describe('@idraw/util: lib/data', () => { const json = { @@ -12,12 +11,12 @@ describe('@idraw/util: lib/data', () => { { num: 1, str: 'a', - bool: false, + bool: false }, { num: 2, str: 'b', - bool: false, + bool: false } ], json: { @@ -27,10 +26,10 @@ describe('@idraw/util: lib/data', () => { json: { num: 11, str: 'bbbb', - bool: false, + bool: false } } - } + }; const json2 = deepClone(json); json2.json.json.num *= 2; @@ -41,7 +40,180 @@ describe('@idraw/util: lib/data', () => { expect(result).toStrictEqual(json2); }); - + test('filterCompactData', () => { + const originData: Data = { + elements: [ + { + uuid: 'b37213ce-d711-cbb3-51ac-d8081c19f127', + type: 'image', + x: 0, + y: 0, + w: 100, + h: 100, + detail: { + src: imageBase64 + } + }, + { + uuid: '39308517-e10f-76df-43a9-50ed7295e61e', + type: 'svg', + x: 0, + y: 0, + w: 100, + h: 100, + detail: { + svg: svg + } + }, + { + uuid: 'ef934ab7-a32e-040c-9ac0-ed193405e6e4', + type: 'html', + x: 0, + y: 0, + w: 100, + h: 100, + detail: { + html: html + } + }, + { + uuid: '063e3a80-1ede-7912-f919-975e34a9bd01', + type: 'group', + x: 0, + y: 0, + w: 100, + h: 100, + detail: { + children: [ + { + uuid: 'e0889472-1f16-d6cd-3c7a-4b827d52279d', + type: 'image', + x: 0, + y: 0, + w: 100, + h: 100, + detail: { + src: imageBase64 + } + }, + { + uuid: 'b60e64e8-833e-e112-d7eb-1ab6e7d6870c', + type: 'svg', + x: 0, + y: 0, + w: 100, + h: 100, + detail: { + svg: svg + } + }, + { + uuid: '61f2a61e-cdd5-ae36-983f-686ba8e35973', + type: 'html', + x: 0, + y: 0, + w: 100, + h: 100, + detail: { + html: html + } + } + ] + } + } + ] + }; + const data = deepClone(originData); + const compactData = filterCompactData(data); -}); + const expectData: Data = { + elements: [ + { + uuid: 'b37213ce-d711-cbb3-51ac-d8081c19f127', + type: 'image', + x: 0, + y: 0, + w: 100, + h: 100, + detail: { src: '@assets/1919ff71-124e-2766-23bb-9a251bf3241c' } + }, + { + uuid: '39308517-e10f-76df-43a9-50ed7295e61e', + type: 'svg', + x: 0, + y: 0, + w: 100, + h: 100, + detail: { svg: '@assets/b9b92016-5290-54e8-9668-807574952823' } + }, + { + uuid: 'ef934ab7-a32e-040c-9ac0-ed193405e6e4', + type: 'html', + x: 0, + y: 0, + w: 100, + h: 100, + detail: { html: '@assets/34017fa0-2d48-2506-3464-238f34642b5c' } + }, + { + uuid: '063e3a80-1ede-7912-f919-975e34a9bd01', + type: 'group', + x: 0, + y: 0, + w: 100, + h: 100, + detail: { + children: [ + { + uuid: 'e0889472-1f16-d6cd-3c7a-4b827d52279d', + type: 'image', + x: 0, + y: 0, + w: 100, + h: 100, + detail: { src: '@assets/1919ff71-124e-2766-23bb-9a251bf3241c' } + }, + { + uuid: 'b60e64e8-833e-e112-d7eb-1ab6e7d6870c', + type: 'svg', + x: 0, + y: 0, + w: 100, + h: 100, + detail: { svg: '@assets/b9b92016-5290-54e8-9668-807574952823' } + }, + { + uuid: '61f2a61e-cdd5-ae36-983f-686ba8e35973', + type: 'html', + x: 0, + y: 0, + w: 100, + h: 100, + detail: { html: '@assets/34017fa0-2d48-2506-3464-238f34642b5c' } + } + ] + } + } + ], + assets: { + '@assets/1919ff71-124e-2766-23bb-9a251bf3241c': { + type: 'image', + value: imageBase64 + }, + '@assets/b9b92016-5290-54e8-9668-807574952823': { + type: 'svg', + value: svg + }, + '@assets/34017fa0-2d48-2506-3464-238f34642b5c': { + type: 'html', + value: html + } + } + }; + expect(compactData).toStrictEqual(expectData); + const data2: Data = deepClone(expectData); + const compactData2 = filterCompactData(data2); + expect(compactData2).toStrictEqual(expectData); + }); +}); diff --git a/packages/util/src/index.ts b/packages/util/src/index.ts index 568f63a30..9a73752b9 100644 --- a/packages/util/src/index.ts +++ b/packages/util/src/index.ts @@ -74,4 +74,4 @@ export { updateElementInList } from './lib/handle-element'; export { deepResizeGroupElement } from './lib/resize-element'; -export { calcViewCenterContent } from './lib/view-content'; +export { calcViewCenterContent, calcViewCenter } from './lib/view-content'; diff --git a/packages/util/src/lib/data.ts b/packages/util/src/lib/data.ts index 46ac9afcf..e899ac639 100644 --- a/packages/util/src/lib/data.ts +++ b/packages/util/src/lib/data.ts @@ -119,7 +119,7 @@ export function filterCompactData(data: Data, opts?: { loadItemMap?: LoadItemMap type: 'image', value: loadItemMap[src].source as string }; - } else { + } else if (!assets[src]) { const assetUUID = createAssetId(src); if (!assets[assetUUID]) { assets[assetUUID] = { @@ -131,34 +131,40 @@ export function filterCompactData(data: Data, opts?: { loadItemMap?: LoadItemMap } } else if (elem.type === 'svg') { const svg = (elem as Element<'svg'>).detail.svg; - const assetUUID = createAssetId(svg); + if (isAssetId(svg) && !assets[svg] && loadItemMap[svg] && typeof loadItemMap[svg]?.source === 'string') { assets[svg] = { type: 'svg', value: loadItemMap[svg].source as string }; - } else if (!assets[assetUUID]) { - assets[assetUUID] = { - type: 'svg', - value: svg - }; + } else if (!assets[svg]) { + const assetUUID = createAssetId(svg); + if (!assets[assetUUID]) { + assets[assetUUID] = { + type: 'svg', + value: svg + }; + } + (elem as Element<'svg'>).detail.svg = assetUUID; } - (elem as Element<'svg'>).detail.svg = assetUUID; } else if (elem.type === 'html') { const html = (elem as Element<'html'>).detail.html; - const assetUUID = createAssetId(html); + if (isAssetId(html) && !assets[html] && loadItemMap[html] && typeof loadItemMap[html]?.source === 'string') { assets[html] = { type: 'html', value: loadItemMap[html].source as string }; - } else if (!assets[assetUUID]) { - assets[assetUUID] = { - type: 'html', - value: html - }; + } else if (!assets[html]) { + const assetUUID = createAssetId(html); + if (!assets[assetUUID]) { + assets[assetUUID] = { + type: 'html', + value: html + }; + } + (elem as Element<'html'>).detail.html = assetUUID; } - (elem as Element<'html'>).detail.html = assetUUID; } else if (elem.type === 'group' && Array.isArray((elem as Element<'group'>).detail.children)) { const groupAssets = (elem as Element<'group'>).detail.assets || {}; Object.keys(groupAssets).forEach((assetId) => { diff --git a/packages/util/src/lib/event.ts b/packages/util/src/lib/event.ts index 316ca5c8d..cd4ab7b57 100644 --- a/packages/util/src/lib/event.ts +++ b/packages/util/src/lib/event.ts @@ -55,6 +55,10 @@ export class EventEmitter> implements UtilEventEmi } destroy() { + this.clear(); + } + + clear() { this.#listeners.clear(); } } diff --git a/packages/util/src/lib/view-content.ts b/packages/util/src/lib/view-content.ts index 0ab4ae975..bdbad5060 100644 --- a/packages/util/src/lib/view-content.ts +++ b/packages/util/src/lib/view-content.ts @@ -1,4 +1,4 @@ -import type { Data, ViewSizeInfo, Element, ElementSize } from 'idraw'; +import type { Data, ViewSizeInfo, Element, ElementSize, ViewScaleInfo, PointSize } from '@idraw/types'; import { rotateElementVertexes } from './rotate'; import {} from './view-calc'; import { formatNumber } from './number'; @@ -12,12 +12,12 @@ interface ViewCenterContentResult { export function calcViewCenterContent(data: Data, opts: { viewSizeInfo: ViewSizeInfo }): ViewCenterContentResult { let offsetX: number = 0; let offsetY: number = 0; - let scale: number = 0; + let scale: number = 1; - let contentX: number = 0; - let contentY: number = 0; - let contentW: number = 0; - let contentH: number = 0; + let contentX: number = data?.elements?.[0]?.x || 0; + let contentY: number = data?.elements?.[0]?.y || 0; + let contentW: number = data?.elements?.[0]?.w || 0; + let contentH: number = data?.elements?.[0]?.h || 0; const { width, height } = opts.viewSizeInfo; @@ -56,8 +56,8 @@ export function calcViewCenterContent(data: Data, opts: { viewSizeInfo: ViewSize const scaleW = formatNumber(width / contentW, { decimalPlaces: 4 }); const scaleH = formatNumber(height / contentH, { decimalPlaces: 4 }); scale = Math.min(scaleW, scaleH, 1); - offsetX = (contentW * scale - width) / 2 / scale; - offsetY = (contentH * scale - height) / 2 / scale; + offsetX = (contentW * scale - width) / 2 / scale + contentX; + offsetY = (contentH * scale - height) / 2 / scale + contentY; } const result: ViewCenterContentResult = { @@ -65,5 +65,24 @@ export function calcViewCenterContent(data: Data, opts: { viewSizeInfo: ViewSize offsetY: formatNumber(offsetY, { decimalPlaces: 0 }), scale }; + return result; } + +export function calcViewCenter(opts?: { viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo }): PointSize { + let x = 0; + let y = 0; + + if (opts) { + const { viewScaleInfo, viewSizeInfo } = opts; + const { offsetLeft, offsetTop, scale } = viewScaleInfo; + const { width, height } = viewSizeInfo; + x = 0 - offsetLeft + width / scale / 2; + y = 0 - offsetTop + height / scale / 2; + } + const p: PointSize = { + x, + y + }; + return p; +}