mirror of
https://github.com/idrawjs/idraw
synced 2026-05-24 10:08:34 +00:00
feat: add features of undo and redo
This commit is contained in:
parent
61e01719d2
commit
d8289d4d83
45 changed files with 93 additions and 62 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"private": false,
|
||||
"version": "0.4.0-beta.45",
|
||||
"version": "0.4.0-rc.0",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import {
|
|||
} from '@idraw/util';
|
||||
import { Board, Sharer, Calculator } from './board';
|
||||
import { createBoardContent, validateElements } from '@idraw/util';
|
||||
import { Cursor } from './lib/cursor';
|
||||
import { Cursor } from './cursor/cursor';
|
||||
import { getModifyElementRecord } from './record';
|
||||
|
||||
export { coreEventKeys } from './config';
|
||||
|
|
@ -43,15 +43,15 @@ export type { CoreEventKeys } from './config';
|
|||
export { Board, Sharer, Calculator };
|
||||
|
||||
// export { MiddlewareSelector } from './middleware/selector';
|
||||
export { MiddlewareSelector } from './middleware/selector';
|
||||
export { MiddlewareScroller } from './middleware/scroller';
|
||||
export { MiddlewareScaler } from './middleware/scaler';
|
||||
export { MiddlewareRuler } from './middleware/ruler';
|
||||
export { MiddlewareTextEditor } from './middleware/text-editor';
|
||||
export { MiddlewareDragger } from './middleware/dragger';
|
||||
export { MiddlewareInfo } from './middleware/info';
|
||||
export { MiddlewareLayoutSelector } from './middleware/layout-selector';
|
||||
export { MiddlewarePointer } from './middleware/pointer';
|
||||
export { MiddlewareSelector } from './middlewares/selector';
|
||||
export { MiddlewareScroller } from './middlewares/scroller';
|
||||
export { MiddlewareScaler } from './middlewares/scaler';
|
||||
export { MiddlewareRuler } from './middlewares/ruler';
|
||||
export { MiddlewareTextEditor } from './middlewares/text-editor';
|
||||
export { MiddlewareDragger } from './middlewares/dragger';
|
||||
export { MiddlewareInfo } from './middlewares/info';
|
||||
export { MiddlewareLayoutSelector } from './middlewares/layout-selector';
|
||||
export { MiddlewarePointer } from './middlewares/pointer';
|
||||
|
||||
export class Core<E extends CoreEventMap = CoreEventMap> {
|
||||
#board: Board<E>;
|
||||
|
|
|
|||
|
|
@ -391,7 +391,7 @@ export const MiddlewareLayoutSelector: Middleware<
|
|||
};
|
||||
}
|
||||
eventHub.trigger(coreEventKeys.CHANGE, {
|
||||
type: 'dragLayout',
|
||||
type: 'resizeLayout',
|
||||
data,
|
||||
modifyRecord
|
||||
});
|
||||
|
|
@ -120,6 +120,9 @@ export const MiddlewareSelector: Middleware<
|
|||
devicePixelRatio: sharer.getActiveViewSizeInfo().devicePixelRatio
|
||||
});
|
||||
|
||||
let startResizeGroupRecord: ModifyRecord<'resizeElements'> | null = null;
|
||||
let endResizeGroupRecord: ModifyRecord<'resizeElements'> | null = null;
|
||||
|
||||
sharer.setSharedStorage(keyActionType, null);
|
||||
sharer.setSharedStorage(keyEnableSnapToGrid, true);
|
||||
|
||||
|
|
@ -213,6 +216,8 @@ export const MiddlewareSelector: Middleware<
|
|||
};
|
||||
|
||||
const clear = () => {
|
||||
startResizeGroupRecord = null;
|
||||
endResizeGroupRecord = null;
|
||||
sharer.setSharedStorage(keyActionType, null);
|
||||
sharer.setSharedStorage(keyResizeType, null);
|
||||
sharer.setSharedStorage(keyAreaStart, null);
|
||||
|
|
@ -419,6 +424,8 @@ export const MiddlewareSelector: Middleware<
|
|||
prevPoint = e.point;
|
||||
moveOriginalStartPoint = e.point;
|
||||
|
||||
startResizeGroupRecord = null;
|
||||
endResizeGroupRecord = null;
|
||||
sharer.setSharedStorage(keyActionType, null);
|
||||
sharer.setSharedStorage(keyResizeType, null);
|
||||
sharer.setSharedStorage(keyAreaStart, null);
|
||||
|
|
@ -674,7 +681,7 @@ export const MiddlewareSelector: Middleware<
|
|||
const gridW = calculator.toGridNum(resizedElemSize.w, calcOpts);
|
||||
const gridH = calculator.toGridNum(resizedElemSize.h, calcOpts);
|
||||
if (elems[0].type === 'group') {
|
||||
resizeEffectGroupElement(
|
||||
endResizeGroupRecord = resizeEffectGroupElement(
|
||||
elems[0] as Element<'group'>,
|
||||
{
|
||||
x: gridX,
|
||||
|
|
@ -684,6 +691,9 @@ export const MiddlewareSelector: Middleware<
|
|||
},
|
||||
{ resizeEffect: elems[0].operations?.resizeEffect }
|
||||
);
|
||||
if (!startResizeGroupRecord) {
|
||||
startResizeGroupRecord = endResizeGroupRecord;
|
||||
}
|
||||
elems[0].x = gridX;
|
||||
elems[0].y = gridY;
|
||||
} else {
|
||||
|
|
@ -791,16 +801,16 @@ export const MiddlewareSelector: Middleware<
|
|||
}
|
||||
|
||||
if (data && (['drag', 'drag-list', 'drag-list-end', 'resize'] as ActionType[]).includes(actionType)) {
|
||||
let type: any = 'dragElement';
|
||||
let type: any = 'resizeElement';
|
||||
if (type === 'resize') {
|
||||
type = 'resizeElement';
|
||||
}
|
||||
if (hasChangedData) {
|
||||
const startSize = pointStartElementSizeList[0] as ElementSize & { uuid: string };
|
||||
let modifyRecord: ModifyRecord | undefined = undefined;
|
||||
let modifyRecord: ModifyRecord | null | undefined = null;
|
||||
if (selectedElements.length === 1) {
|
||||
modifyRecord = {
|
||||
type: 'dragElement',
|
||||
type: 'resizeElement',
|
||||
time: 0,
|
||||
content: {
|
||||
method: 'modifyElement',
|
||||
|
|
@ -809,9 +819,18 @@ export const MiddlewareSelector: Middleware<
|
|||
after: toFlattenElement(getElementSize(selectedElements[0]))
|
||||
}
|
||||
};
|
||||
if (selectedElements[0].type === 'group' && startResizeGroupRecord && endResizeGroupRecord) {
|
||||
modifyRecord = {
|
||||
...endResizeGroupRecord,
|
||||
content: {
|
||||
...endResizeGroupRecord.content,
|
||||
before: startResizeGroupRecord.content.before
|
||||
}
|
||||
};
|
||||
}
|
||||
} else if (selectedElements.length > 1) {
|
||||
modifyRecord = {
|
||||
type: 'dragElements',
|
||||
type: 'resizeElements',
|
||||
time: 0,
|
||||
content: {
|
||||
method: 'modifyElements',
|
||||
|
|
@ -238,15 +238,15 @@ export class iDraw {
|
|||
return this.#core.forceRender();
|
||||
}
|
||||
|
||||
// getHistoryHandler() {
|
||||
// return this.#historyHandler;
|
||||
// }
|
||||
getHistoryHandler() {
|
||||
return this.#historyHandler;
|
||||
}
|
||||
|
||||
// redo() {
|
||||
// this.#historyHandler?.redo();
|
||||
// }
|
||||
redo() {
|
||||
this.#historyHandler?.redo();
|
||||
}
|
||||
|
||||
// undo() {
|
||||
// this.#historyHandler?.undo();
|
||||
// }
|
||||
undo() {
|
||||
this.#historyHandler?.undo();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ const supportRecordTypes = [
|
|||
'deleteElement',
|
||||
'moveElement',
|
||||
'addElement',
|
||||
'dragElement',
|
||||
'resizeElement',
|
||||
'dragLayout',
|
||||
'resizeElements',
|
||||
'resizeLayout',
|
||||
'modifyLayout',
|
||||
'modifyGlobal'
|
||||
];
|
||||
|
|
@ -90,7 +90,7 @@ export const useHistory = (opts: { core: Core; limit?: number }) => {
|
|||
undoRecord = core.modifyGlobal(info) as ModifyRecord;
|
||||
} else if (record.content.method === 'modifyElements') {
|
||||
undoRecord = core.modifyElements(
|
||||
record.content.before.forEach((item) => unflatObject(item)) as unknown as Array<
|
||||
record.content.before.map((item) => unflatObject(item)) as unknown as Array<
|
||||
RecursivePartial<Omit<Element, 'uuid'>> & Pick<Element, 'uuid'>
|
||||
>
|
||||
) as ModifyRecord;
|
||||
|
|
@ -153,7 +153,7 @@ export const useHistory = (opts: { core: Core; limit?: number }) => {
|
|||
redoRecord = core.modifyGlobal(info) as ModifyRecord;
|
||||
} else if (record.content.method === 'modifyElements') {
|
||||
redoRecord = core.modifyElements(
|
||||
record.content.before.forEach((item) => unflatObject(item)) as unknown as Array<
|
||||
record.content.before.map((item) => unflatObject(item)) as unknown as Array<
|
||||
RecursivePartial<Omit<Element, 'uuid'>> & Pick<Element, 'uuid'>
|
||||
>
|
||||
) as ModifyRecord;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { Element, RendererDrawElementOptions, ViewContext2D } from '@idraw/types';
|
||||
import { rotateElement, calcViewElementSize, enhanceFontFamliy } from '@idraw/util';
|
||||
import { is, isColorStr, getDefaultElementDetailConfig } from '@idraw/util';
|
||||
import { drawBox, drawBoxShadow } from './box';
|
||||
import { drawBox, drawBoxShadow, getOpacity } from './box';
|
||||
|
||||
const detailConfig = getDefaultElementDetailConfig();
|
||||
|
||||
|
|
@ -35,6 +35,9 @@ export function drawText(ctx: ViewContext2D, elem: Element<'text'>, opts: Render
|
|||
return;
|
||||
}
|
||||
|
||||
const { parentOpacity } = opts;
|
||||
const opacity = getOpacity(elem) * parentOpacity;
|
||||
ctx.globalAlpha = opacity;
|
||||
ctx.fillStyle = elem.detail.color || detailConfig.color;
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.$setFont({
|
||||
|
|
@ -64,6 +67,8 @@ export function drawText(ctx: ViewContext2D, elem: Element<'text'>, opts: Render
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
ctx.globalAlpha = parentOpacity;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
import type { ElementBaseDetail, ElementTextDetail, ElementGroupDetail } from './element';
|
||||
|
||||
export type DefaultElementDetailConfig = Required<Omit<ElementBaseDetail, 'clipPath' | 'background'>> &
|
||||
Required<Pick<ElementTextDetail, 'color' | 'textAlign' | 'verticalAlign' | 'fontSize' | 'fontFamily' | 'fontWeight' | 'minInlineSize' | 'wordBreak'>> &
|
||||
export type DefaultElementDetailConfig = Required<
|
||||
Omit<ElementBaseDetail, 'clipPath' | 'clipPathStrokeWidth' | 'clipPathStrokeColor' | 'background'>
|
||||
> &
|
||||
Required<
|
||||
Pick<
|
||||
ElementTextDetail,
|
||||
'color' | 'textAlign' | 'verticalAlign' | 'fontSize' | 'fontFamily' | 'fontWeight' | 'minInlineSize' | 'wordBreak'
|
||||
>
|
||||
> &
|
||||
Required<Pick<ElementGroupDetail, 'overflow'>>;
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export interface CoreEventChange<T extends ModifyType = ModifyType> {
|
|||
type: T | 'setData' | 'other' | string;
|
||||
selectedElements?: Element[] | null;
|
||||
hoverElement?: Element | null;
|
||||
modifyRecord?: ModifyRecord<T>;
|
||||
modifyRecord?: ModifyRecord<T> | null;
|
||||
}
|
||||
export interface CoreEventScale {
|
||||
scale: number;
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ export type ModifyMethod =
|
|||
| 'deleteElement'
|
||||
| 'moveElement'
|
||||
| 'addElement'
|
||||
| 'dragElement'
|
||||
| 'dragElements'
|
||||
| 'resizeElement'
|
||||
| 'resizeElements'
|
||||
| 'modifyElements'
|
||||
| 'dragLayout'
|
||||
| 'resizeLayout'
|
||||
| 'modifyLayout'
|
||||
| 'modifyGlobal';
|
||||
|
||||
|
|
@ -92,19 +92,19 @@ export interface ModifyContentMap {
|
|||
from: ElementPosition;
|
||||
to: ElementPosition;
|
||||
};
|
||||
dragElement: {
|
||||
resizeElement: {
|
||||
method: 'modifyElement';
|
||||
uuid: string;
|
||||
before: FlattenElement | null;
|
||||
after: FlattenElement | null;
|
||||
};
|
||||
dragElements: {
|
||||
resizeElements: {
|
||||
method: 'modifyElements';
|
||||
before: (FlattenLayout & { uuid: string })[];
|
||||
after: (FlattenLayout & { uuid: string })[];
|
||||
};
|
||||
dragLayout: {
|
||||
method: 'modifyElement';
|
||||
resizeLayout: {
|
||||
method: 'modifyLayout';
|
||||
before: FlattenLayout;
|
||||
after: FlattenLayout;
|
||||
};
|
||||
|
|
@ -151,13 +151,13 @@ export interface ModifyRecordMap {
|
|||
time: number;
|
||||
content: ModifyContentMap['moveElement'];
|
||||
};
|
||||
dragElement: {
|
||||
type: 'dragElement';
|
||||
resizeElement: {
|
||||
type: 'resizeElement';
|
||||
time: number;
|
||||
content: ModifyContentMap['modifyElement'];
|
||||
};
|
||||
dragElements: {
|
||||
type: 'dragElements';
|
||||
resizeElements: {
|
||||
type: 'resizeElements';
|
||||
time: number;
|
||||
content: ModifyContentMap['modifyElements'];
|
||||
};
|
||||
|
|
@ -166,10 +166,10 @@ export interface ModifyRecordMap {
|
|||
time: number;
|
||||
content: ModifyContentMap['modifyElements'];
|
||||
};
|
||||
dragLayout: {
|
||||
type: 'dragLayout';
|
||||
resizeLayout: {
|
||||
type: 'resizeLayout';
|
||||
time: number;
|
||||
content: ModifyContentMap['dragLayout'];
|
||||
content: ModifyContentMap['resizeLayout'];
|
||||
};
|
||||
modifyLayout: {
|
||||
type: 'modifyLayout';
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ export function getDefaultElementTextDetail(elementSize: ElementSize): ElementTe
|
|||
color: detailConfig.color,
|
||||
fontFamily: detailConfig.fontFamily,
|
||||
fontWeight: detailConfig.fontWeight,
|
||||
lineHeight: elementSize.w / defaultText.length,
|
||||
// lineHeight: elementSize.w / defaultText.length,
|
||||
fontSize: elementSize.w / defaultText.length,
|
||||
textAlign: 'center',
|
||||
verticalAlign: 'middle'
|
||||
|
|
|
|||
|
|
@ -105,6 +105,10 @@ function resizeElementBaseDetailByRatio(elem: Element, opts: DeepResizeRatioOpti
|
|||
function resizeElementBaseByRatio(elem: Element, opts: DeepResizeRatioOptions): ModifyRecord<'modifyElement'> {
|
||||
const { xRatio, yRatio } = opts;
|
||||
const { uuid, x, y, w, h } = elem;
|
||||
elem.x = doNum(x * xRatio);
|
||||
elem.y = doNum(y * yRatio);
|
||||
elem.w = doNum(w * xRatio);
|
||||
elem.h = doNum(h * yRatio);
|
||||
const record: ModifyRecord<'modifyElement'> = {
|
||||
type: 'modifyElement',
|
||||
time: Date.now(),
|
||||
|
|
@ -112,13 +116,10 @@ function resizeElementBaseByRatio(elem: Element, opts: DeepResizeRatioOptions):
|
|||
method: 'modifyElement',
|
||||
uuid: uuid,
|
||||
before: { x, y, w, h },
|
||||
after: { x, y, w, h }
|
||||
after: { x: elem.x, y: elem.y, w: elem.w, h: elem.h }
|
||||
}
|
||||
};
|
||||
elem.x = doNum(x * xRatio);
|
||||
elem.y = doNum(y * yRatio);
|
||||
elem.w = doNum(w * xRatio);
|
||||
elem.h = doNum(h * yRatio);
|
||||
|
||||
const detailRecord = resizeElementBaseDetailByRatio(elem, opts);
|
||||
record.content.before = {
|
||||
...record.content.before,
|
||||
|
|
@ -170,7 +171,7 @@ function resizeTextElementDetailByRatio(
|
|||
function deepResizeElementByRatio(
|
||||
elem: Element,
|
||||
opts: DeepResizeRatioOptions,
|
||||
record?: ModifyRecord<'modifyElements'>
|
||||
record?: ModifyRecord<'resizeElements'>
|
||||
) {
|
||||
const { type, uuid } = elem;
|
||||
|
||||
|
|
@ -210,7 +211,7 @@ function deepResizeElementByRatio(
|
|||
function fixedResizeGroupElementChildren(
|
||||
elem: Element<'group'>,
|
||||
opts: FixedResizeOptions,
|
||||
record?: ModifyRecord<'modifyElements'>
|
||||
record?: ModifyRecord<'resizeElements'>
|
||||
) {
|
||||
if (!(elem.type === 'group' && Array.isArray(elem.detail.children))) {
|
||||
return;
|
||||
|
|
@ -250,12 +251,9 @@ export function resizeEffectGroupElement(
|
|||
opts?: {
|
||||
resizeEffect?: ElementOperations['resizeEffect'];
|
||||
}
|
||||
): ModifyRecord<'modifyElements'> | null {
|
||||
if (!istype.number(size.x) && !istype.number(size.y)) {
|
||||
return null;
|
||||
}
|
||||
const record: ModifyRecord<'modifyElements'> = {
|
||||
type: 'modifyElements',
|
||||
): ModifyRecord<'resizeElements'> | null {
|
||||
const record: ModifyRecord<'resizeElements'> = {
|
||||
type: 'resizeElements',
|
||||
time: Date.now(),
|
||||
content: {
|
||||
method: 'modifyElements',
|
||||
|
|
@ -279,6 +277,8 @@ export function resizeEffectGroupElement(
|
|||
const afterGroupElem: FlattenLayout & { uuid: string } = { uuid, x: resizeX, y: resizeY, w: resizeW, h: resizeH };
|
||||
|
||||
if (opts?.resizeEffect === 'deepResize') {
|
||||
record.content.before.push(beforeGroupElem);
|
||||
record.content.after.push(afterGroupElem);
|
||||
const xRatio = resizeW / elem.w;
|
||||
const yRatio = resizeH / elem.h;
|
||||
if (xRatio === yRatio && xRatio === 1) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue