feat: implement element handle methods

This commit is contained in:
chenshenhai 2023-12-10 14:14:42 +08:00
parent 92909d201c
commit e097498504
9 changed files with 283 additions and 149 deletions

View file

@ -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<CoreEvent>;
export class Core<E extends CoreEvent = CoreEvent> {
#board: Board<E>;
// #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<CoreEvent>({ viewContent, container });
const board = new Board<E>({ viewContent, container });
const sharer = board.getSharer();
sharer.setActiveViewSizeInfo({
width,
@ -81,17 +81,17 @@ export class Core {
this.#board.clear();
}
on<T extends keyof CoreEvent>(name: T, callback: (e: CoreEvent[T]) => void) {
on<T extends keyof E>(name: T, callback: (e: E[T]) => void) {
const eventHub = this.#board.getEventHub();
eventHub.on(name, callback);
}
off<T extends keyof CoreEvent>(name: T, callback: (e: CoreEvent[T]) => void) {
off<T extends keyof E>(name: T, callback: (e: E[T]) => void) {
const eventHub = this.#board.getEventHub();
eventHub.off(name, callback);
}
trigger<T extends keyof CoreEvent>(name: T, e: CoreEvent[T]) {
trigger<T extends keyof E>(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);
}
}

View file

@ -99,13 +99,27 @@ export const MiddlewareTextEditor: BoardMiddleware<Record<string, any>, 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';

View file

@ -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

View file

@ -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<IDrawEvent>;
// private #opts: IDrawOptions;
constructor(mount: HTMLDivElement, opts: IDrawOptions) {
const core = new Core(mount, opts);
const core = new Core<IDrawEvent>(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
// }
}

View file

@ -105,5 +105,8 @@ export {
getDefaultElementDetailConfig,
calcViewBoxSize,
createElement,
moveElementPosition
moveElementPosition,
insertElementToListByPosition,
deleteElementInListByPosition,
deleteElementInList
} from '@idraw/util';

View file

@ -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]);
});
});

View file

@ -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';

View file

@ -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<Element<ElementType>>, index: string): string[] {
const uuids: string[] = [];
@ -237,61 +235,6 @@ export function getGroupQueueFromList(uuid: string, elements: Element<ElementTyp
return groupQueue;
}
function mergeElement<T extends Element<ElementType> = Element<ElementType>>(originElem: T, updateContent: RecursivePartial<T>): 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<any>)?.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<Element<ElementType>>, 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<ElementType>[]): ElementPosition {
let result: ElementPosition = [];
let over = false;
const _loop = (list: Element<ElementType>[]) => {
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;
}

View file

@ -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<T extends ElementType>(
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<T extends Element<ElementType> = Element<ElementType>>(originElem: T, updateContent: RecursivePartial<T>): 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<any>)?.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<Element<ElementType>>, 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;
}