From e09749850433677901593d5b310853cb4cc66cce Mon Sep 17 00:00:00 2001 From: chenshenhai Date: Sun, 10 Dec 2023 14:14:42 +0800 Subject: [PATCH] feat: implement element handle methods --- packages/core/src/index.ts | 14 +-- .../core/src/middleware/text-editor/index.ts | 24 +++- packages/idraw/src/event.ts | 7 +- packages/idraw/src/idraw.ts | 80 +++++++----- packages/idraw/src/index.ts | 5 +- packages/util/__tests__/lib/element.test.ts | 59 +++++++++ packages/util/src/index.ts | 13 +- packages/util/src/lib/element.ts | 117 +++--------------- packages/util/src/lib/handle-element.ts | 113 ++++++++++++++++- 9 files changed, 283 insertions(+), 149 deletions(-) create mode 100644 packages/util/__tests__/lib/element.test.ts diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index ce79d00..24f8960 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -10,8 +10,8 @@ export { MiddlewareScaler, middlewareEventScale } from './middleware/scaler'; export { MiddlewareRuler, middlewareEventRuler } from './middleware/ruler'; export { MiddlewareTextEditor, middlewareEventTextEdit } from './middleware/text-editor'; -export class Core { - #board: Board; +export class Core { + #board: Board; // #opts: CoreOptions; // #canvas: HTMLCanvasElement; #container: HTMLDivElement; @@ -26,7 +26,7 @@ export class Core { container.appendChild(canvas); const viewContent = createViewContent(canvas, { width, height, devicePixelRatio, offscreen: true }); - const board = new Board({ viewContent, container }); + const board = new Board({ viewContent, container }); const sharer = board.getSharer(); sharer.setActiveViewSizeInfo({ width, @@ -81,17 +81,17 @@ export class Core { this.#board.clear(); } - on(name: T, callback: (e: CoreEvent[T]) => void) { + on(name: T, callback: (e: E[T]) => void) { const eventHub = this.#board.getEventHub(); eventHub.on(name, callback); } - off(name: T, callback: (e: CoreEvent[T]) => void) { + off(name: T, callback: (e: E[T]) => void) { const eventHub = this.#board.getEventHub(); eventHub.off(name, callback); } - trigger(name: T, e: CoreEvent[T]) { + trigger(name: T, e: E[T]) { const eventHub = this.#board.getEventHub(); eventHub.trigger(name, e); } @@ -111,7 +111,7 @@ export class Core { this.#board.getViewer().drawFrame(); } - updateViewScale(opts: { scale: number; offsetX: number; offsetY: number }) { + setViewScale(opts: { scale: number; offsetX: number; offsetY: number }) { this.#board.updateViewScaleInfo(opts); } } diff --git a/packages/core/src/middleware/text-editor/index.ts b/packages/core/src/middleware/text-editor/index.ts index 7f237b3..a6fab19 100644 --- a/packages/core/src/middleware/text-editor/index.ts +++ b/packages/core/src/middleware/text-editor/index.ts @@ -99,13 +99,27 @@ export const MiddlewareTextEditor: BoardMiddleware, CoreEven ...element.detail }; + let elemX = element.x * scale + offsetLeft; + let elemY = element.y * scale + offsetTop; + let elemW = element.w * scale; + let elemH = element.h * scale; + + if (groupQueue.length > 0) { + elemX = element.x * scale; + elemY = element.y * scale; + elemW = element.w * scale; + elemH = element.h * scale; + } + textarea.style.position = 'absolute'; - textarea.style.left = `${element.x * scale}px`; - textarea.style.top = `${element.y * scale}px`; - textarea.style.width = `${element.w * scale}px`; - textarea.style.height = `${element.h * scale}px`; + textarea.style.left = `${elemX}px`; + textarea.style.top = `${elemY}px`; + textarea.style.width = `${elemW}px`; + textarea.style.height = `${elemH}px`; textarea.style.transform = `rotate(${limitAngle(element.angle || 0)}deg)`; - textarea.style.border = 'none'; + // textarea.style.border = 'none'; + textarea.style.boxSizing = 'border-box'; + textarea.style.border = '1px solid #1973ba'; textarea.style.resize = 'none'; textarea.style.overflow = 'hidden'; textarea.style.wordBreak = 'break-all'; diff --git a/packages/idraw/src/event.ts b/packages/idraw/src/event.ts index c1d4178..bd940f3 100644 --- a/packages/idraw/src/event.ts +++ b/packages/idraw/src/event.ts @@ -1,5 +1,5 @@ import { middlewareEventScale, middlewareEventSelect } from '@idraw/core'; -import type { CoreEvent } from '@idraw/types'; +import type { CoreEvent, Data } from '@idraw/types'; export interface IDrawEventKeys { select: typeof middlewareEventSelect; @@ -8,7 +8,10 @@ export interface IDrawEventKeys { } export type IDrawEvent = CoreEvent & { - [key: string]: any; + change: { + data: Data; + type: 'update-element' | 'delete-element' | 'move-element' | 'add-element' | 'set-data' | 'other'; + }; }; // TODO diff --git a/packages/idraw/src/idraw.ts b/packages/idraw/src/idraw.ts index 56f7e1f..1ea42ae 100644 --- a/packages/idraw/src/idraw.ts +++ b/packages/idraw/src/idraw.ts @@ -1,14 +1,21 @@ import { Core, MiddlewareSelector, MiddlewareScroller, MiddlewareScaler, MiddlewareRuler, MiddlewareTextEditor, middlewareEventSelect } from '@idraw/core'; import type { PointSize, IDrawOptions, Data, ViewSizeInfo, ElementType, Element, RecursivePartial, ElementPosition } from '@idraw/types'; import type { IDrawEvent } from './event'; -import { createElement } from '@idraw/util'; +import { + createElement, + insertElementToListByPosition, + updateElementInList, + deleteElementInList, + moveElementPosition, + getElementPositionFromList +} from '@idraw/util'; export class iDraw { - #core: Core; + #core: Core; // private #opts: IDrawOptions; constructor(mount: HTMLDivElement, opts: IDrawOptions) { - const core = new Core(mount, opts); + const core = new Core(mount, opts); this.#core = core; // this.#opts = opts; core.use(MiddlewareScroller); @@ -19,7 +26,9 @@ export class iDraw { } setData(data: Data) { - this.#core.setData(data); + const core = this.#core; + core.setData(data); + core.trigger('change', { data, type: 'set-data' }); } getData(): Data | null { @@ -30,9 +39,9 @@ export class iDraw { this.#core.scale(opts); } - updateViewScale(opts: { scale: number; offsetX: number; offsetY: number }) { + setViewScale(opts: { scale: number; offsetX: number; offsetY: number }) { const core = this.#core; - core.updateViewScale(opts); + core.setViewScale(opts); core.refresh(); } @@ -52,10 +61,18 @@ export class iDraw { this.#core.trigger(name, e); } + selectElement(uuid: string) { + this.selectElements([uuid]); + } + selectElements(uuids: string[]) { this.trigger(middlewareEventSelect, { uuids }); } + selectElementByPosition(position: ElementPosition) { + this.selectElementsByPositions([position]); + } + selectElementsByPositions(positions: ElementPosition[]) { this.trigger(middlewareEventSelect, { positions }); } @@ -84,50 +101,51 @@ export class iDraw { ); } - updateElement() { - // TODO + updateElement(element: Element) { + const core = this.#core; + const data: Data = core.getData() || { elements: [] }; + updateElementInList(element.uuid, element, data.elements); + core.setData(data); + core.refresh(); + core.trigger('change', { data, type: 'update-element' }); } addElement( element: Element, opts?: { - uuid: string; - referenceType: 'group' | 'front' | 'back'; + position: ElementPosition; } ): Data { const core = this.#core; const data: Data = core.getData() || { elements: [] }; if (!opts) { data.elements.push(element); - } else { - // TODO + } else if (opts?.position) { + insertElementToListByPosition(element, opts?.position, data.elements); } core.setData(data); core.refresh(); + core.trigger('change', { data, type: 'add-element' }); return data; } deleteElement(uuid: string) { - // TODO + const core = this.#core; + const data: Data = core.getData() || { elements: [] }; + deleteElementInList(uuid, data.elements); + core.setData(data); + core.refresh(); + core.trigger('change', { data, type: 'delete-element' }); } - moveElementToFront(uuid: string, referenceUUID?: string) { - // TODO + moveElement(uuid: string, to: ElementPosition) { + const core = this.#core; + const data: Data = core.getData() || { elements: [] }; + const from = getElementPositionFromList(uuid, data.elements); + const list = moveElementPosition(data.elements, { from, to }); + data.elements = list; + core.setData(data); + core.refresh(); + core.trigger('change', { data, type: 'move-element' }); } - - moveElementToBack(uuid: string, referenceUUID?: string) { - // TODO - } - - // scrollLeft() { - // // TODO - // } - - // scrollTop() { - // // TODO - // } - - // exportDataURL() { - // // TODO - // } } diff --git a/packages/idraw/src/index.ts b/packages/idraw/src/index.ts index 8951e86..ec7cca3 100644 --- a/packages/idraw/src/index.ts +++ b/packages/idraw/src/index.ts @@ -105,5 +105,8 @@ export { getDefaultElementDetailConfig, calcViewBoxSize, createElement, - moveElementPosition + moveElementPosition, + insertElementToListByPosition, + deleteElementInListByPosition, + deleteElementInList } from '@idraw/util'; diff --git a/packages/util/__tests__/lib/element.test.ts b/packages/util/__tests__/lib/element.test.ts new file mode 100644 index 0000000..00de7bc --- /dev/null +++ b/packages/util/__tests__/lib/element.test.ts @@ -0,0 +1,59 @@ +import { createUUID, deepClone, getElementPositionFromList } from '@idraw/util'; +import type { Elements } from '@idraw/types'; +const getElemBase = () => { + return { + x: 0, + y: 0, + w: 1, + h: 1 + }; +}; + +function generateElements(list: any[]): Elements { + const elements: Elements = list.map((item, i) => { + if (Array.isArray(item)) { + return { + ...getElemBase(), + uuid: `${i}-${createUUID()}`, + type: 'group', + detail: { + children: generateElements(item) + } + }; + } else { + return { + ...getElemBase(), + uuid: `${i}-${createUUID()}`, + type: 'rect', + detail: {} + }; + } + }) as Elements; + return elements; +} + +describe('@idraw/util: element ', () => { + // [4] + test('getElementPositionFromList [4]', () => { + const list: Elements = generateElements([0, [0, 1, [0, 1, 2, [0, 1, 2, 3, [0, 1, 2, 3, 4, 5], 5], 4, 5], 3, 4, 5], 2, 3, 4, 5]); + const uuid = (list as any)[4].uuid; + const position = getElementPositionFromList(uuid, list); + expect(position).toStrictEqual([4]); + }); + + // [1, 2, 3, 4, 5] + test('getElementPositionFromList [1, 2, 3, 4, 5]', () => { + const list: Elements = generateElements([0, [0, 1, [0, 1, 2, [0, 1, 2, 3, [0, 1, 2, 3, 4, 5], 5], 4, 5], 3, 4, 5], 2, 3, 4, 5]); + const uuid = (list as any)[1].detail.children[2].detail.children[3].detail.children[4].detail.children[5].uuid; + const position = getElementPositionFromList(uuid, list); + expect(position).toStrictEqual([1, 2, 3, 4, 5]); + }); + + // [1, 2, 3, 4] + test('getElementPositionFromList [1, 2, 3, 4, 5]', () => { + const list: Elements = generateElements([0, [0, 1, [0, 1, 2, [0, 1, 2, 3, [0, 1, 2, 3, 4, 5], 5], 4, 5], 3, 4, 5], 2, 3, 4, 5]); + const uuid = (list as any)[1].detail.children[2].detail.children[3].detail.children[4].uuid; + const position = getElementPositionFromList(uuid, list); + expect(position).toStrictEqual([1, 2, 3, 4]); + }); +}); diff --git a/packages/util/src/index.ts b/packages/util/src/index.ts index a2fb143..3dbef12 100644 --- a/packages/util/src/index.ts +++ b/packages/util/src/index.ts @@ -34,12 +34,12 @@ export { findElementsFromList, findElementFromListByPosition, findElementsFromListByPositions, - updateElementInList, getGroupQueueFromList, getElementSize, mergeElementAsset, filterElementAsset, - isResourceElement + isResourceElement, + getElementPositionFromList } from './lib/element'; export { checkRectIntersect } from './lib/rect'; export { @@ -63,4 +63,11 @@ export { formatNumber } from './lib/number'; export { matrixToAngle, matrixToRadian } from './lib/matrix'; export { getDefaultElementDetailConfig, getDefaultElementRectDetail } from './lib/config'; export { calcViewBoxSize } from './lib/view-box'; -export { createElement, moveElementPosition } from './lib/handle-element'; +export { + createElement, + insertElementToListByPosition, + deleteElementInListByPosition, + deleteElementInList, + moveElementPosition, + updateElementInList +} from './lib/handle-element'; diff --git a/packages/util/src/lib/element.ts b/packages/util/src/lib/element.ts index 35dfd57..71bbcf0 100644 --- a/packages/util/src/lib/element.ts +++ b/packages/util/src/lib/element.ts @@ -6,7 +6,6 @@ import type { ElementSize, ViewContextSize, ViewSizeInfo, - RecursivePartial, ElementAssets, ElementAssetsItem, LoadElementType, @@ -14,7 +13,6 @@ import type { } from '@idraw/types'; import { rotateElementVertexes } from './rotate'; import { isAssetId, createAssetId } from './uuid'; -import { istype } from './istype'; function getGroupUUIDs(elements: Array>, index: string): string[] { const uuids: string[] = []; @@ -237,61 +235,6 @@ export function getGroupQueueFromList(uuid: string, elements: Element = Element>(originElem: T, updateContent: RecursivePartial): T { - const commonKeys = Object.keys(updateContent); - for (let i = 0; i < commonKeys.length; i++) { - const commonKey = commonKeys[i]; - if (['x', 'y', 'w', 'h', 'angle', 'name'].includes(commonKey)) { - // @ts-ignore - originElem[commonKey] = updateContent[commonKey]; - } else if (['detail', 'operations'].includes(commonKey)) { - // @ts-ignore - if (istype.json(updateContent[commonKey] as any)) { - if (!(originElem as Object)?.hasOwnProperty(commonKey)) { - // @ts-ignore - originElem[commonKey] = {}; - } - // @ts-ignore - if (istype.json(originElem[commonKey])) { - // @ts-ignore - originElem[commonKey] = { ...originElem[commonKey], ...updateContent[commonKey] }; - } - // @ts-ignore - } else if (istype.array(updateContent[commonKey] as any)) { - if (!(originElem as Object)?.hasOwnProperty(commonKey)) { - // @ts-ignore - originElem[commonKey] = []; - } - // @ts-ignore - if (istype.array(originElem[commonKey])) { - ((updateContent as any)?.[commonKey] as Array)?.forEach((item, i) => { - // @ts-ignore - originElem[commonKey][i] = item; - }); - // @ts-ignore - originElem[commonKey] = [...originElem[commonKey], ...updateContent[commonKey]]; - } - } - } - } - return originElem; -} - -export function updateElementInList(uuid: string, updateContent: RecursivePartial>, elements: Element[]): Element | null { - let targetElement: Element | null = null; - for (let i = 0; i < elements.length; i++) { - const elem = elements[i]; - if (elem.uuid === uuid) { - mergeElement(elem, updateContent); - targetElement = elem; - break; - } else if (elem.type === 'group') { - targetElement = updateElementInList(uuid, updateContent, (elem as Element<'group'>)?.detail?.children || []); - } - } - return targetElement; -} - export function getElementSize(elem: Element): ElementSize { const { x, y, w, h, angle } = elem; const size: ElementSize = { x, y, w, h, angle }; @@ -400,52 +343,28 @@ export function findElementFromListByPosition(position: ElementPosition, list: E return result; } -export function insertElementToListByPosition(element: Element, position: ElementPosition, list: Element[]): boolean { - let result = false; - if (position.length === 1) { - const pos = position[0]; - list.splice(pos, 0, element); - result = true; - } else if (position.length > 1) { - let tempList: Element[] = list; - for (let i = 0; i < position.length; i++) { - const pos = position[i]; - const item = tempList[pos]; - if (i === position.length - 1) { - const pos = position[i]; - tempList.splice(pos, 0, element); - result = true; - } else if (i < position.length - 1 && item.type === 'group') { - tempList = (item as Element<'group'>).detail.children; - } else { +export function getElementPositionFromList(uuid: string, elements: Element[]): ElementPosition { + let result: ElementPosition = []; + let over = false; + const _loop = (list: Element[]) => { + for (let i = 0; i < list.length; i++) { + if (over === true) { break; } - } - } - return result; -} - -export function deleteElementInListByPosition(position: ElementPosition, list: Element[]): boolean { - let result = false; - if (position.length === 1) { - const pos = position[0]; - list.splice(pos, 1); - result = true; - } else if (position.length > 1) { - let tempList: Element[] = list; - for (let i = 0; i < position.length; i++) { - const pos = position[i]; - const item = tempList[pos]; - if (i === position.length - 1) { - const pos = position[i]; - tempList.splice(pos, 1); - result = true; - } else if (i < position.length - 1 && item.type === 'group') { - tempList = (item as Element<'group'>).detail.children; - } else { + result.push(i); + const elem = list[i]; + if (elem.uuid === uuid) { + over = true; + break; + } else if (elem.type === 'group') { + _loop((elem as Element<'group'>)?.detail?.children || []); + } + if (over) { break; } + result.pop(); } - } + }; + _loop(elements); return result; } diff --git a/packages/util/src/lib/handle-element.ts b/packages/util/src/lib/handle-element.ts index 175864c..f532504 100644 --- a/packages/util/src/lib/handle-element.ts +++ b/packages/util/src/lib/handle-element.ts @@ -9,7 +9,8 @@ import { getDefaultElementImageDetail, getDefaultElementGroupDetail } from './config'; -import { findElementFromListByPosition, insertElementToListByPosition, deleteElementInListByPosition } from './element'; +import { istype } from './istype'; +import { findElementFromListByPosition, getElementPositionFromList } from './element'; const defaultViewWidth = 200; const defaultViewHeight = 200; @@ -101,6 +102,61 @@ export function createElement( return elem; } +export function insertElementToListByPosition(element: Element, position: ElementPosition, list: Element[]): boolean { + let result = false; + if (position.length === 1) { + const pos = position[0]; + list.splice(pos, 0, element); + result = true; + } else if (position.length > 1) { + let tempList: Element[] = list; + for (let i = 0; i < position.length; i++) { + const pos = position[i]; + const item = tempList[pos]; + if (i === position.length - 1) { + const pos = position[i]; + tempList.splice(pos, 0, element); + result = true; + } else if (i < position.length - 1 && item.type === 'group') { + tempList = (item as Element<'group'>).detail.children; + } else { + break; + } + } + } + return result; +} + +export function deleteElementInListByPosition(position: ElementPosition, list: Element[]): boolean { + let result = false; + if (position.length === 1) { + const pos = position[0]; + list.splice(pos, 1); + result = true; + } else if (position.length > 1) { + let tempList: Element[] = list; + for (let i = 0; i < position.length; i++) { + const pos = position[i]; + const item = tempList[pos]; + if (i === position.length - 1) { + const pos = position[i]; + tempList.splice(pos, 1); + result = true; + } else if (i < position.length - 1 && item.type === 'group') { + tempList = (item as Element<'group'>).detail.children; + } else { + break; + } + } + } + return result; +} + +export function deleteElementInList(uuid: string, list: Element[]): boolean { + const position = getElementPositionFromList(uuid, list); + return deleteElementInListByPosition(position, list); +} + export function moveElementPosition( elements: Elements, opts: { @@ -159,3 +215,58 @@ export function moveElementPosition( } return elements; } + +function mergeElement = Element>(originElem: T, updateContent: RecursivePartial): T { + const commonKeys = Object.keys(updateContent); + for (let i = 0; i < commonKeys.length; i++) { + const commonKey = commonKeys[i]; + if (['x', 'y', 'w', 'h', 'angle', 'name'].includes(commonKey)) { + // @ts-ignore + originElem[commonKey] = updateContent[commonKey]; + } else if (['detail', 'operations'].includes(commonKey)) { + // @ts-ignore + if (istype.json(updateContent[commonKey] as any)) { + if (!(originElem as Object)?.hasOwnProperty(commonKey)) { + // @ts-ignore + originElem[commonKey] = {}; + } + // @ts-ignore + if (istype.json(originElem[commonKey])) { + // @ts-ignore + originElem[commonKey] = { ...originElem[commonKey], ...updateContent[commonKey] }; + } + // @ts-ignore + } else if (istype.array(updateContent[commonKey] as any)) { + if (!(originElem as Object)?.hasOwnProperty(commonKey)) { + // @ts-ignore + originElem[commonKey] = []; + } + // @ts-ignore + if (istype.array(originElem[commonKey])) { + ((updateContent as any)?.[commonKey] as Array)?.forEach((item, i) => { + // @ts-ignore + originElem[commonKey][i] = item; + }); + // @ts-ignore + originElem[commonKey] = [...originElem[commonKey], ...updateContent[commonKey]]; + } + } + } + } + return originElem; +} + +export function updateElementInList(uuid: string, updateContent: RecursivePartial>, elements: Element[]): Element | null { + let targetElement: Element | null = null; + for (let i = 0; i < elements.length; i++) { + const elem = elements[i]; + if (elem.uuid === uuid) { + mergeElement(elem, updateContent); + targetElement = elem; + break; + } else if (elem.type === 'group') { + targetElement = updateElementInList(uuid, updateContent, (elem as Element<'group'>)?.detail?.children || []); + } + } + return targetElement; +}