Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: copy paste #163

Merged
merged 1 commit into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion packages/common/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,19 @@ export const noop = () => {
/**
* 生成唯一 ID
*/
export const genId = () => {
export const genUuid = () => {
return uuidv4();
};

export const increaseIdGenerator = () => {
let count = 0;
return () => {
const id = String(count);
count++;
return id;
};
};

export const objectNameGenerator = {
maxIdxMap: new Map<string, number>(),
gen(type: string) {
Expand Down
161 changes: 142 additions & 19 deletions packages/core/src/clipboard.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import { arrMap, noop, omit } from '@suika/common';
import { genUuid, increaseIdGenerator, noop } from '@suika/common';
import {
boxToRect,
invertMatrix,
mergeBoxes,
multiplyMatrix,
} from '@suika/geo';
import { generateNKeysBetween } from 'fractional-indexing';

import { AddGraphCmd } from './commands/add_graphs';
import { type Editor } from './editor';
import { SuikaGraphics } from './graphs';
import { type GraphicsAttrs, isFrameGraphics, SuikaGraphics } from './graphs';
import { isCanvasGraphics } from './graphs/canvas';
import { toSVG } from './to_svg';
import { Transaction } from './transaction';
import { type IEditorPaperData } from './type';
import { getChildNodeSet } from './utils';

/**
* Clipboard Manager
Expand Down Expand Up @@ -88,23 +97,125 @@ export class ClipboardManager {
}

private getSelectedItemsSnapshot() {
const selectedItems = this.editor.selectedElements.getItems();
const selectedItems = SuikaGraphics.sortGraphics(
this.editor.selectedElements.getItems(),
);
if (selectedItems.length === 0) {
return null;
}

// remove id attr
const copiedData = arrMap(selectedItems, (item) =>
omit(item.getAttrs(), 'id'),
);
const idGenerator = increaseIdGenerator();
const replacedIdMap = new Map<string, string>();
const copiedData: GraphicsAttrs[] = [];

for (const item of selectedItems) {
const attrs = item.getAttrs();
attrs.transform = item.getWorldTransform();
attrs.parentIndex = undefined;
const tmpId = idGenerator();
replacedIdMap.set(attrs.id, tmpId);
attrs.id = tmpId;

copiedData.push(attrs);
}

const childNodes = getChildNodeSet(selectedItems);
for (const item of childNodes) {
const attrs = item.getAttrs();
const tmpId = idGenerator();
replacedIdMap.set(attrs.id, tmpId);
attrs.id = tmpId;

if (attrs.parentIndex) {
attrs.parentIndex.guid = replacedIdMap.get(attrs.parentIndex.guid)!;
}

copiedData.push(attrs);
}

return JSON.stringify({
appVersion: this.editor.appVersion,
paperId: this.editor.paperId,
data: JSON.stringify(copiedData),
data: copiedData,
});
}

/**
* update parentIndex.guid and transform for attributes array
* @param attrsArr attribute array
* @returns top parent count
*/
private updateAttrsParentIndex(attrsArr: GraphicsAttrs[]): number {
/**
* TODO: to finish
* (逻辑待梳理)
* 如果选中一个 group 节点。按顺序粘贴子节点中的到最上方
* 如果选中一个非 group 节点,按顺序粘贴到它的上方
* 如果选中多个节点。等价于选中最靠上的那一个节点,应用上面两种情况之一的效果
*
* 选中单个 group 后,然后粘贴的位置依旧是这个 group,在 group 后粘贴。
*/
let left: string | null = null;
let right: string | null = null;
const firstGraphics =
SuikaGraphics.sortGraphics(this.editor.selectedElements.getItems()).at(
-1,
) ?? this.editor.doc.getCurrCanvas();
let parent = firstGraphics;

if (isCanvasGraphics(firstGraphics) || isFrameGraphics(firstGraphics)) {
left = firstGraphics.getMaxChildIndex();
} else {
parent = firstGraphics.getParent()!;
left = firstGraphics.getSortIndex();
const nextSibling = firstGraphics.getNextSibling();
right = nextSibling ? nextSibling.getSortIndex() : null;
}

const parentId = parent.attrs.id;
const parentInvertWorldTf = invertMatrix(parent.getWorldTransform());

let i = 0;
while (i < attrsArr.length) {
const attrs = attrsArr[i];
if (attrs.parentIndex) {
break;
}
i++;
}

const topGraphicsCount = i;
const sortIndies = generateNKeysBetween(left, right, topGraphicsCount);

// top parent node
const oldNewIdMap = new Map<string, string>();
for (let j = 0; j < topGraphicsCount; j++) {
const attrs = attrsArr[j];
attrs.parentIndex = {
guid: parentId,
position: sortIndies[j],
};

const newId = genUuid();
oldNewIdMap.set(attrs.id, newId);
attrs.id = newId;

attrs.transform = multiplyMatrix(parentInvertWorldTf, attrs.transform);
}

// child node
while (i < attrsArr.length) {
const attrs = attrsArr[i];
const newId = genUuid();
oldNewIdMap.set(attrs.id, newId);
attrs.id = newId;
attrs.parentIndex!.guid = oldNewIdMap.get(attrs.parentIndex!.guid)!;
i++;
}

return topGraphicsCount;
}

private addGraphsFromClipboard(dataStr: string): void;
private addGraphsFromClipboard(dataStr: string, x: number, y: number): void;
private addGraphsFromClipboard(dataStr: string, x?: number, y?: number) {
Expand All @@ -130,20 +241,23 @@ export class ClipboardManager {
}

const editor = this.editor;
const pastedGraphs = editor.sceneGraph.parseStrAndAddGraphics(
pastedData.data,
);
if (pastedGraphs.length === 0) {
if (pastedData.data.length === 0) {
return;
}

// TODO: duplicated objectName should be renamed
editor.commandManager.pushCommand(
new AddGraphCmd('pasted graphs', editor, pastedGraphs),
const topGraphicsCount = this.updateAttrsParentIndex(pastedData.data);
const pastedGraphicsArr = editor.sceneGraph.createGraphicsArr(
pastedData.data,
);
editor.selectedElements.setItems(pastedGraphs);
editor.sceneGraph.addItems(pastedGraphicsArr);
editor.sceneGraph.initGraphicsTree(pastedGraphicsArr);

const boundingRect = editor.selectedElements.getBoundingRect()!;
const selectedItems = pastedGraphicsArr.slice(0, topGraphicsCount);
editor.selectedElements.setItems(selectedItems);

const boundingRect = boxToRect(
mergeBoxes(selectedItems.map((item) => item.getBbox())),
);
if (
(x === undefined || y === undefined) &&
pastedData.paperId !== editor.paperId
Expand All @@ -157,9 +271,18 @@ export class ClipboardManager {
const dx = x - boundingRect.x;
const dy = y - boundingRect.y;
if (dx || dy) {
SuikaGraphics.dMove(pastedGraphs, dx, dy);
SuikaGraphics.dMove(selectedItems, dx, dy);
}
}

// TODO: duplicated objectName should be renamed

const transaction = new Transaction(editor);
transaction
.addNewIds(pastedGraphicsArr.map((item) => item.attrs.id))
.updateParentSize(selectedItems)
.commit('pasted graphs');

editor.render();
}

Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/editor.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
genId,
genUuid,
sceneCoordsToViewportUtil,
viewportCoordsToSceneUtil,
} from '@suika/common';
Expand Down Expand Up @@ -155,7 +155,7 @@ export class Editor {
);
this.sceneGraph.addItems([canvas]);
}
this.paperId ??= genId();
this.paperId ??= genUuid();
this.autoSaveGraphs.autoSave();

// 设置初始视口
Expand Down Expand Up @@ -186,7 +186,7 @@ export class Editor {
this.sceneGraph.load(data.data);
this.commandManager.clearRecords();
this.paperId = data.paperId;
this.paperId ??= genId();
this.paperId ??= genUuid();
}
destroy() {
this.containerElement.removeChild(this.canvasElement);
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/graphs/canvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,9 @@ export class SuikaCanvas extends SuikaGraphics<SuikaCanvasAttrs> {
return identityMatrix();
}
}

export const isCanvasGraphics = (
graphics: SuikaGraphics,
): graphics is SuikaCanvas => {
return graphics instanceof SuikaCanvas;
};
17 changes: 15 additions & 2 deletions packages/core/src/graphs/graphics/graphics.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
calcCoverScale,
cloneDeep,
genId,
genUuid,
objectNameGenerator,
omit,
parseRGBToHex,
Expand Down Expand Up @@ -81,7 +81,7 @@
}

this.attrs = { ...attrs } as ATTRS;
this.attrs.id ??= genId();
this.attrs.id ??= genUuid();
this.attrs.transform = transform;

if (this.attrs.objectName) {
Expand Down Expand Up @@ -726,7 +726,7 @@
return content;
}

protected getSVGTagHead(_offset?: IPoint) {

Check warning on line 729 in packages/core/src/graphs/graphics/graphics.ts

View workflow job for this annotation

GitHub Actions / eslint

'_offset' is defined but never used
return '';
}

Expand Down Expand Up @@ -906,6 +906,19 @@
return this.attrs.parentIndex?.position ?? '';
}

getNextSibling() {
const parent = this.getParent();
if (!parent) {
return null;
}
const children = parent.getChildren();
const index = children.findIndex((item) => item === this);
if (index == -1) {
console.warn('index should not be -1!');
}
return children[index + 1] ?? null;
}

getSortIndexPath() {
const path: string[] = [];
// eslint-disable-next-line @typescript-eslint/no-this-alias
Expand Down
7 changes: 5 additions & 2 deletions packages/core/src/graphs/rect.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { parseHexToRGBA, parseRGBAStr } from '@suika/common';
import { cloneDeep, parseHexToRGBA, parseRGBAStr } from '@suika/common';
import {
type IMatrixArr,
type IPoint,
Expand Down Expand Up @@ -38,7 +38,10 @@ export class SuikaRect extends SuikaGraphics<RectAttrs> {
}

override getAttrs(): RectAttrs {
return { ...this.attrs, cornerRadius: this.attrs.cornerRadius ?? 0 };
return cloneDeep({
...this.attrs,
cornerRadius: this.attrs.cornerRadius ?? 0,
});
}

override toJSON() {
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/graphs/regular_polygon.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { parseHexToRGBA, parseRGBAStr } from '@suika/common';
import { cloneDeep, parseHexToRGBA, parseRGBAStr } from '@suika/common';
import {
getPointsBbox,
getRegularPolygon,
Expand Down Expand Up @@ -40,7 +40,7 @@
}

override getAttrs(): RegularPolygonAttrs {
return { ...this.attrs, count: this.attrs.count };
return cloneDeep({ ...this.attrs, count: this.attrs.count });
}

override toJSON() {
Expand Down Expand Up @@ -170,7 +170,7 @@
super.updateAttrs(partialAttrs, options);
}

override hitTest(x: number, y: number, _padding?: number) {

Check warning on line 173 in packages/core/src/graphs/regular_polygon.ts

View workflow job for this annotation

GitHub Actions / eslint

'_padding' is defined but never used
// TODO: solve padding
const tf = new Matrix(...this.getWorldTransform());
const point = tf.applyInverse({ x, y });
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/graphs/star.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { parseHexToRGBA, parseRGBAStr } from '@suika/common';
import { cloneDeep, parseHexToRGBA, parseRGBAStr } from '@suika/common';
import {
getPointsBbox,
getStar,
Expand Down Expand Up @@ -41,7 +41,7 @@
}

override getAttrs(): StarAttrs {
return { ...this.attrs, count: this.attrs.count };
return cloneDeep({ ...this.attrs, count: this.attrs.count });
}

override toJSON() {
Expand Down Expand Up @@ -179,7 +179,7 @@
super.updateAttrs(partialAttrs, options);
}

override hitTest(x: number, y: number, _padding?: number) {

Check warning on line 182 in packages/core/src/graphs/star.ts

View workflow job for this annotation

GitHub Actions / eslint

'_padding' is defined but never used
// TODO: solve padding
const tf = new Matrix(...this.getWorldTransform());
const point = tf.applyInverse({ x, y });
Expand Down
Loading
Loading