diff --git a/packages/core/src/middleware/selector/index.ts b/packages/core/src/middleware/selector/index.ts index e4872d0..6685c11 100644 --- a/packages/core/src/middleware/selector/index.ts +++ b/packages/core/src/middleware/selector/index.ts @@ -10,6 +10,7 @@ import { findElementsFromList, findElementsFromListByPositions, getElementPositionFromList, + getElementPositionMapFromList, deepResizeGroupElement, getElementSize } from '@idraw/util'; @@ -99,6 +100,7 @@ export const MiddlewareSelector: BoardMiddleware elem.uuid), positions: [] }); + const uuids = list.map((elem) => elem.uuid); + const data = sharer.getActiveStorage('data'); + const positionMap = getElementPositionMapFromList(uuids, data?.elements || []); + eventHub.trigger(coreEventKeys.SELECT, { uuids, positions: list.map((elem) => [...positionMap[elem.uuid]]) }); } }; @@ -471,6 +476,7 @@ export const MiddlewareSelector: BoardMiddleware 1) { const moveX = (end.x - start.x) / scale; @@ -546,6 +553,7 @@ export const MiddlewareSelector: BoardMiddleware[] = []; groupQueue.forEach((group) => { @@ -700,7 +708,10 @@ export const MiddlewareSelector: BoardMiddleware[] +): { + [uuid: string]: ElementPosition; +} { + const currentPosition: ElementPosition = []; + const positionMap: { + [uuid: string]: ElementPosition; + } = {}; + + let over = false; + const _loop = (list: Element[]) => { + for (let i = 0; i < list.length; i++) { + if (over === true) { + break; + } + currentPosition.push(i); + const elem = list[i]; + if (uuids.includes(elem.uuid)) { + positionMap[elem.uuid] = [...currentPosition]; + + if (Object.keys(positionMap).length === uuids.length) { + over = true; + break; + } + } else if (elem.type === 'group') { + _loop((elem as Element<'group'>)?.detail?.children || []); + } + if (over) { + break; + } + currentPosition.pop(); + } + }; + _loop(elements); + return positionMap; +} diff --git a/packages/util/src/lib/group.ts b/packages/util/src/lib/group.ts new file mode 100644 index 0000000..c45adfe --- /dev/null +++ b/packages/util/src/lib/group.ts @@ -0,0 +1,97 @@ +import type { Elements, Element, ElementPosition } from '@idraw/types'; +import { findElementFromListByPosition, calcElementListSize } from './element'; +import { deleteElementInListByPosition, insertElementToListByPosition } from './handle-element'; +import { createUUID } from './uuid'; + +export function groupElementsByPosition(list: Elements, positions: ElementPosition[]): Elements { + if (positions.length > 1) { + let isValidPositions: boolean = true; + let lastIndexs: number[] = []; + for (let i = 1; i < positions.length; i++) { + const prevPosition = positions[i - 1]; + const position = positions[i]; + if (!(prevPosition.length > 0 && position.length > 0)) { + isValidPositions = false; + break; + } + + if (prevPosition.length !== position.length) { + isValidPositions = false; + break; + } + + const temp1 = [...prevPosition]; + const temp2 = [...position]; + const lastIndex1 = temp1.pop(); + const lastIndex2 = temp2.pop(); + if (i === 1 && typeof lastIndex1 === 'number' && lastIndex1 >= 0) { + lastIndexs.push(lastIndex1 as number); + } + if (typeof lastIndex2 === 'number' && lastIndex2 >= 0) { + lastIndexs.push(lastIndex2 as number); + } + } + if (isValidPositions !== true) { + console.error('[idraw]: The grouped elements are not siblings!'); + return list; + } + lastIndexs.sort((a, b) => a - b); + const groupParentPosition = [...positions[0]].splice(0, positions[0].length - 1); + const groupChildren: Elements = []; + + const groupPosition = [...groupParentPosition, lastIndexs[0]]; + for (let i = 0; i < lastIndexs.length; i++) { + const position = [...groupParentPosition, lastIndexs[i]]; + const elem = findElementFromListByPosition(position, list); + if (elem) { + groupChildren.push(elem); + } + } + + const groupSize = calcElementListSize(groupChildren); + for (let i = 0; i < groupChildren.length; i++) { + const elem = groupChildren[i]; + if (elem) { + elem.x -= groupSize.x; + elem.y -= groupSize.y; + } + } + + for (let i = lastIndexs.length - 1; i >= 0; i--) { + const position = [...groupParentPosition, lastIndexs[i]]; + deleteElementInListByPosition(position, list); + } + + const group: Element<'group'> = { + name: 'Group', + uuid: createUUID(), + type: 'group', + ...groupSize, + detail: { + children: groupChildren + } + }; + insertElementToListByPosition(group, groupPosition, list); + } + return list; +} + +export function ungroupElementsByPosition(list: Elements, position: ElementPosition): Elements { + const elem = findElementFromListByPosition(position, list) as Element<'group'>; + if (!(elem && elem?.type === 'group' && Array.isArray(elem?.detail?.children))) { + console.error('[idraw]: The ungrouped element is not a group element!'); + } + const groupParentPosition = [...position].splice(0, position.length - 1); + const groupLastIndex = position[position.length - 1]; + + const { x, y } = elem; + deleteElementInListByPosition(position, list); + elem.detail.children.forEach((child, i) => { + child.x += x; + child.y += y; + const elemPosition = [...groupParentPosition, groupLastIndex + i]; + insertElementToListByPosition(child, elemPosition, list); + }); + + return list; +} diff --git a/packages/util/src/lib/store.ts b/packages/util/src/lib/store.ts index 7a66a1c..5637aee 100644 --- a/packages/util/src/lib/store.ts +++ b/packages/util/src/lib/store.ts @@ -1,12 +1,18 @@ +import type { RecursivePartial } from '@idraw/types'; import { deepClone } from './data'; -export class Store = Record> { +export class Store< + T extends Record = Record, + S extends Record = Record +> { #temp: T; #backUpDefaultStorage: T; + #static: RecursivePartial; - constructor(opts: { defaultStorage: T }) { + constructor(opts: { defaultStorage: T; defaultStatic?: S }) { this.#backUpDefaultStorage = deepClone(opts.defaultStorage); this.#temp = this.#createTempStorage(); + this.#static = opts.defaultStatic || {}; } set(name: K, value: T[K]) { @@ -17,6 +23,14 @@ export class Store = Record(name: K, value: S[K]) { + this.#static[name] = value; + } + + getStatic(name: K): S[K] | undefined { + return this.#static[name] as S[K] | undefined; + } + getSnapshot(opts?: { deepClone?: boolean }): T { if (opts?.deepClone === true) { return deepClone(this.#temp); @@ -30,6 +44,7 @@ export class Store = Record