update -> 用 oldProps 还原
add -> 删除这个 uuid
remove -> 按 parentId/index 放回去this.debouncedSave = debounce(() => {
const state = this.getCurrentState();
if (state !== false) {
this.pushDiff(state);
}
}, 200);private isIMEComposing = false;
private onCompositionStart = () => {
this.isIMEComposing = true;
};
private onCompositionEnd = () => {
this.isIMEComposing = false;
const active = this.canvas?.getActiveObject?.();
if ((active?.tag === "Text" || active?.tag === "HTMLText") && this.app.editor.innerEditing) {
setTimeout(() => this.snapshotState(), 0);
}
};
private onEditableInput = (event: InputEvent): void => {
const target = event.target as HTMLElement | null;
if (!target) return;
const isTextEditor = target.id === "textInnerEditor" || target.isContentEditable;
if (!isTextEditor) return;
if (!this.app.editor.innerEditing) return;
if (this.isIMEComposing) return;
this.snapshotState();
};private onKeyEvent = (event: KeyboardEvent): void => {
const isMod = event.metaKey || event.ctrlKey;
const key = (event.key || "").toLowerCase();
const isUndo = isMod && key === "z" && !event.shiftKey;
const isRedo = isMod && (key === "y" || (key === "z" && event.shiftKey));
const target = event.target as HTMLElement | null;
const isEditable =
!!target &&
(target.tagName === "INPUT" ||
target.tagName === "TEXTAREA" ||
target.isContentEditable);
if (isEditable && (isUndo || isRedo)) {
event.preventDefault();
event.stopPropagation();
if (isUndo) this.undo();
else this.redo();
}
};private async copy() {
const activeObject = this.canvas.getActiveObject();
if (!activeObject) return;
const cloneObj = activeObject.clone();
const group = MEditorHelper.group([cloneObj], activeObject);
const json = JSON.stringify(group.toJSON());
await this.clipboard.writeText(json);
}private paste() {
this.clipboard.readBlob().then(async (blobs) => {
if (!blobs) return;
for (const blob of blobs) {
if (blob.type.startsWith("image/")) {
await this.handleImageBlob(blob);
} else if (blob.type === "text/plain") {
await this.handleTextPlainBlob(blob);
} else {
await this.handleJsonBlob(blob);
}
}
});
}const file = new File([blob], "pasted-image.png", { type: blob.type });canvas.app.tree.on(PointerEvent.MOVE, (event) => {
this.pointer = new Point(event.x, event.y);
});const { x, y } = this.pointer;
const pastePoint = this.canvas.contentFrame.getInnerPoint({ x, y });