mirror of
https://github.com/idrawjs/idraw
synced 2026-05-23 09:38:22 +00:00
commit
76bbd0349c
25 changed files with 409 additions and 141 deletions
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
|
|
@ -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 }}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"private": false,
|
||||
"version": "0.4.0-beta.44",
|
||||
"version": "0.4.0-beta.45",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
|
|
|
|||
|
|
@ -94,43 +94,45 @@ export class Board<T extends BoardExtendEventMap = BoardExtendEventMap> {
|
|||
}
|
||||
|
||||
#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<T extends BoardExtendEventMap = BoardExtendEventMap> {
|
|||
}
|
||||
|
||||
onWatcherEvents() {
|
||||
if (this.#opts.disableWatcher === true) {
|
||||
return;
|
||||
}
|
||||
this.#watcher.onEvents();
|
||||
}
|
||||
|
||||
offWatcherEvents() {
|
||||
if (this.#opts.disableWatcher === true) {
|
||||
return;
|
||||
}
|
||||
this.#watcher.offEvents();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ export class Core<E extends CoreEventMap = CoreEventMap> {
|
|||
#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<E extends CoreEventMap = CoreEventMap> {
|
|||
container.appendChild(canvas);
|
||||
|
||||
const boardContent = createBoardContent(canvas, { width, height, devicePixelRatio });
|
||||
const board = new Board<E>({ boardContent, container });
|
||||
const board = new Board<E>({ boardContent, container, disableWatcher });
|
||||
const sharer = board.getSharer();
|
||||
sharer.setActiveViewSizeInfo({
|
||||
width,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ export function getPointTarget(
|
|||
if (selectedElements && selectedElements?.length > 0) {
|
||||
target.groupQueue = groupQueue || [];
|
||||
target.elements = [selectedElements[0]];
|
||||
return target;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -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<IDrawEvent>;
|
||||
|
|
@ -35,6 +37,7 @@ export class iDraw {
|
|||
#store: Store<IDrawStorage> = new Store<IDrawStorage>({
|
||||
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();
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,7 +115,6 @@ export {
|
|||
insertElementToListByPosition,
|
||||
deleteElementInListByPosition,
|
||||
deleteElementInList,
|
||||
deepResizeGroupElement,
|
||||
deepCloneElement,
|
||||
calcViewCenterContent,
|
||||
calcViewCenter,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -88,11 +88,11 @@ export class Loader extends EventEmitter<LoaderEventMap> implements RendererLoad
|
|||
if (supportElementTypes.includes((element as Element<LoadElementType>).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') {
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ export type BoardMiddleware<
|
|||
export interface BoardOptions {
|
||||
boardContent: BoardContent;
|
||||
container?: HTMLDivElement;
|
||||
disableWatcher?: boolean;
|
||||
}
|
||||
|
||||
export interface BoardViewerFrameSnapshot<S extends Record<any | symbol, any> = any> {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export interface CoreOptions {
|
|||
width: number;
|
||||
height: number;
|
||||
devicePixelRatio?: number;
|
||||
disableWatcher?: boolean;
|
||||
}
|
||||
|
||||
export type CursorType =
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -22,11 +22,12 @@ export interface IDrawSettings {
|
|||
layoutSelector?: Partial<MiddlewareLayoutSelectorStyle>;
|
||||
};
|
||||
history?: boolean;
|
||||
historyLimit?: number;
|
||||
}
|
||||
|
||||
export type IDrawOptions = CoreOptions & IDrawSettings;
|
||||
|
||||
export type IDrawHistory = {
|
||||
export type HistoryHandler = {
|
||||
undo: () => void;
|
||||
redo: () => void;
|
||||
canUndo: () => void;
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Element, 'detail'> = { detail: {} };
|
||||
const afterElem: Pick<Element, 'detail'> = { 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<Partial<ElementSize>, '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<ElementSize>,
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue