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

feat(common): support zIndex #817

Merged
merged 12 commits into from
Apr 12, 2024
6 changes: 6 additions & 0 deletions .changeset/four-dodos-sell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@plait/common': minor
'@plait/core': minor
---

support zIndex
huanhuanwa marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions packages/common/src/transforms/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './property';
export * from './align';
export * from './text';
export * from './z-index';
182 changes: 182 additions & 0 deletions packages/common/src/transforms/z-index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import {
Path,
PlaitBoard,
PlaitElement,
PlaitGroup,
PlaitGroupElement,
Transforms,
getEditingGroup,
getElementsInGroup,
getGroupByElement,
getSelectedElements,
getSelectedGroups
} from '@plait/core';
import { findIndex, findLastIndex } from '../utils';

interface ZIndexMoveOptions {
huanhuanwa marked this conversation as resolved.
Show resolved Hide resolved
element: PlaitElement;
newPath: Path;
}

export const moveToTop = () => {};

export const moveToBottom = () => {};

export const moveUp = (board: PlaitBoard) => {
const zIndexMoveOptions = getZIndexMoveOptionsByOne(board, 'up');
moveElementsToNewPath(board, zIndexMoveOptions);
};

export const moveDown = (board: PlaitBoard) => {
const zIndexMoveOptions = getZIndexMoveOptionsByOne(board, 'down');
moveElementsToNewPath(board, zIndexMoveOptions);
};

const moveElementsToNewPath = (board: PlaitBoard, zIndexMoveOptions: ZIndexMoveOptions[]) => {
zIndexMoveOptions
.map(item => {
const path = PlaitBoard.findPath(board, item.element);
const ref = board.pathRef(path);
return () => {
ref.current && Transforms.moveNode(board, ref.current, item.newPath);
ref.unref();
};
})
.forEach(action => {
action();
});
};

const getZIndexMoveOptionsByOne = (board: PlaitBoard, direction: 'down' | 'up'): ZIndexMoveOptions[] => {
huanhuanwa marked this conversation as resolved.
Show resolved Hide resolved
const indicesToMove = getIndicesToMove(board);
let groupedIndices = toContiguousGroups(board, indicesToMove);
if (direction === 'up') {
groupedIndices = groupedIndices.reverse();
}
let moveContents: ZIndexMoveOptions[] = [];
groupedIndices.forEach((indices, i) => {
const leadingIndex = indices[0];
const trailingIndex = indices[indices.length - 1];
const boundaryIndex = direction === 'down' ? leadingIndex : trailingIndex;
const targetIndex = getTargetIndex(board, boundaryIndex, direction);
if (targetIndex === -1 || boundaryIndex === targetIndex) {
return;
}
if (direction === 'down') {
moveContents.push(
...indices.reverse().map(path => {
return {
element: board.children[path],
newPath: [targetIndex]
};
})
);
} else {
moveContents.push(
...indices.map(path => {
return {
element: board.children[path],
newPath: [targetIndex]
};
})
);
}
});

return moveContents;
};

const getIndicesToMove = (board: PlaitBoard) => {
const selectedElements = [...getSelectedElements(board), ...getSelectedGroups(board)];
return selectedElements
.map(item => {
return board.children.indexOf(item);
})
.sort((a, b) => {
return a - b;
});
};

const toContiguousGroups = (board: PlaitBoard, array: number[]) => {
let cursor = 0;
return array.reduce((acc, value, index) => {
if (index > 0) {
const currentElement = board.children[value];
const previousElement = board.children[array[index - 1]];
const isInSameGroup = currentElement.groupId === previousElement.groupId;
const isContain = currentElement.id === previousElement.groupId || currentElement.groupId === previousElement.id;
if (array[index - 1] !== value - 1 || (!isInSameGroup && !isContain)) {
cursor = ++cursor;
}
}
(acc[cursor] || (acc[cursor] = [])).push(value);
return acc;
}, [] as number[][]);
};

/**
* Returns next candidate index that's available to be moved to. Currently that
* is a non-deleted element, and not inside a group (unless we're editing it).
*/
const getTargetIndex = (board: PlaitBoard, boundaryIndex: number, direction: 'down' | 'up') => {
if ((boundaryIndex === 0 && direction === 'down') || (boundaryIndex === board.children.length - 1 && direction === 'up')) {
return -1;
}
const indexFilter = (element: PlaitElement) => {
if (element.isDeleted || PlaitGroupElement.isGroup(element)) {
return false;
}
return true;
};
const candidateIndex =
direction === 'down'
? findLastIndex(board.children, el => indexFilter(el), Math.max(0, boundaryIndex - 1))
: findIndex(board.children, el => indexFilter(el), boundaryIndex + 1);

const nextElement = board.children[candidateIndex];
if (!nextElement) {
return -1;
}

if (!nextElement.groupId) {
return candidateIndex;
}

const elements = [...board.children];
const sourceElement = elements[boundaryIndex];
const editingGroup = getEditingGroup(board, sourceElement);
const nextElementGroups = (getGroupByElement(board, nextElement, true) || []) as PlaitGroup[];
// candidate element is a sibling in current editing group → return
if (editingGroup && sourceElement?.groupId !== nextElement?.groupId) {
// candidate element is outside current editing group → prevent
if (!(nextElementGroups as PlaitGroup[]).find(item => item.id === editingGroup.id)) {
return -1;
}
}
let siblingGroup: PlaitGroup;
if (editingGroup) {
siblingGroup = nextElementGroups[nextElementGroups.indexOf(editingGroup) - 1];
} else {
siblingGroup = nextElementGroups[nextElementGroups.length - 1];
}
if (siblingGroup) {
let elementsInSiblingGroup = getElementsInGroup(board, siblingGroup, true, true);
if (elementsInSiblingGroup.length) {
elementsInSiblingGroup = [...elementsInSiblingGroup, siblingGroup];
elementsInSiblingGroup.sort((a, b) => {
const indexA = board.children.findIndex(child => child.id === a.id);
const indexB = board.children.findIndex(child => child.id === b.id);
return indexA - indexB;
});
// assumes getElementsInGroup() returned elements are sorted
// by zIndex (ascending)
return direction === 'down'
? elements.indexOf(elementsInSiblingGroup[0])
: elements.indexOf(elementsInSiblingGroup[elementsInSiblingGroup.length - 1]);
}
}

return candidateIndex;
};

export const ZIndexTransforms = { moveUp, moveDown, moveToTop, moveToBottom };
huanhuanwa marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions packages/common/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export * from './memorize';
export * from './vector';
export * from './math';
export * from './drawing';
export * from './utils';
36 changes: 36 additions & 0 deletions packages/common/src/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export const findLastIndex = <T>(
huanhuanwa marked this conversation as resolved.
Show resolved Hide resolved
array: readonly T[],
cb: (element: T, index: number, array: readonly T[]) => boolean,
fromIndex: number = array.length - 1
) => {
if (fromIndex < 0) {
fromIndex = array.length + fromIndex;
}
fromIndex = Math.min(array.length - 1, Math.max(fromIndex, 0));
let index = fromIndex + 1;
while (--index > -1) {
if (cb(array[index], index, array)) {
return index;
}
}
return -1;
};

export const findIndex = <T>(
array: readonly T[],
cb: (element: T, index: number, array: readonly T[]) => boolean,
fromIndex: number = 0
) => {
// fromIndex = 2
if (fromIndex < 0) {
fromIndex = array.length + fromIndex;
}
fromIndex = Math.min(array.length, Math.max(fromIndex, 0));
let index = fromIndex - 1;
while (++index < array.length) {
if (cb(array[index], index, array)) {
return index;
}
}
return -1;
};
15 changes: 15 additions & 0 deletions packages/core/src/utils/group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,3 +233,18 @@ export const canRemoveGroup = (board: PlaitBoard, elements?: PlaitElement[]) =>
const selectedElements = elements || getSelectedElements(board);
return selectedElements.length > 0 && selectedGroups.length > 0;
};


export const getEditingGroup = (board: PlaitBoard, element: PlaitElement) => {
const groups = getGroupByElement(board, element, true) as PlaitGroup[];
let editingGroup = null;
if (groups?.length) {
for (let i = 0; i < groups?.length; i++) {
if (!isSelectedAllElementsInGroup(board, groups[i])) {
editingGroup = groups[i];
break;
}
}
}
return editingGroup;
};
2 changes: 2 additions & 0 deletions src/app/editor/editor.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
</plait-board>

<div #contextMenu class="context-menu">
<div class="context-menu-item" (pointerup)="moveUp($event)">上移一层</div>
<div class="context-menu-item" (pointerup)="moveDown($event)">下移一层</div>
<div class="context-menu-item" *ngIf="showAddGroup" (pointerup)="addGroup($event)">成组</div>
<div class="context-menu-item" *ngIf="showRemoveGroup" (pointerup)="removeGroup($event)">取消成组</div>
<div class="context-menu-item" (pointerup)="copy($event)">复制</div>
Expand Down
14 changes: 13 additions & 1 deletion src/app/editor/editor.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import { withCommonPlugin } from '../plugins/with-common';
import { AppMenuComponent } from '../components/menu/menu.component';
import { NgIf } from '@angular/common';
import { mockTurningPointData } from './mock-turning-point-data';
import { withGroup } from '@plait/common';
import { withGroup, ZIndexTransforms } from '@plait/common';

const LOCAL_STORAGE_KEY = 'plait-board-data';

Expand Down Expand Up @@ -169,6 +169,18 @@ export class BasicEditorComponent implements OnInit {
BoardTransforms.updateThemeColor(this.board, value as ThemeColorMode);
}

moveUp(event: MouseEvent) {
event.stopPropagation();
event.preventDefault();
ZIndexTransforms.moveUp(this.board);
}

moveDown(event: MouseEvent) {
event.stopPropagation();
event.preventDefault();
ZIndexTransforms.moveDown(this.board);
}

copy(event: MouseEvent) {
event.stopPropagation();
event.preventDefault();
Expand Down