fix: Optimize the re-render node when the selection changes

This commit is contained in:
qinluhe 2023-03-23 15:09:23 +08:00
parent 3039f0427f
commit a38c213744
7 changed files with 89 additions and 32 deletions

View File

@ -2,10 +2,11 @@ import { BaseEditor, BaseSelection, Descendant } from "slate";
import { TreeNode } from '$app/block_editor/view/tree_node';
import { Operation } from "$app/block_editor/core/operation";
import { TextBlockSelectionManager } from './text_selection';
import { BlockType } from "@/appflowy_app/interfaces";
export class TextBlockManager {
public selectionManager: TextBlockSelectionManager;
constructor(private operation: Operation) {
constructor(private rootId: string, private operation: Operation) {
this.selectionManager = new TextBlockSelectionManager();
}
@ -18,6 +19,24 @@ export class TextBlockManager {
this.operation.updateNode(node.id, path, data);
}
deleteNode(node: TreeNode) {
if (node.type !== BlockType.TextBlock) {
this.operation.updateNode(node.id, ['type'], BlockType.TextBlock);
return;
}
if (node.parent!.id !== this.rootId) {
const newParent = node.parent!.parent!;
const newPrev = node.parent;
this.operation.moveNode(node.id, newParent.id, newPrev?.id || '');
}
if (!node.prevLine) return;
this.operation.updateNode(node.prevLine.id, ['data', 'content'], [
...node.prevLine.data.content,
...node.data.content,
]);
this.operation.deleteNode(node.id);
}
splitNode(node: TreeNode, editor: BaseEditor) {
const focus = editor.selection?.focus;
const path = focus?.path || [0, editor.children.length - 1];

View File

@ -5,6 +5,7 @@ import { Block } from './block';
export class Operation {
private sync: BlockEditorSync;
constructor(private blockChain: BlockChain) {
this.sync = new BlockEditorSync();
}
@ -66,6 +67,19 @@ export class Operation {
});
this.sync.sendOps([op]);
}
moveNode(blockId: string, newParentId: string, newPrevId: string) {
const op = this.getMoveOp(blockId, newParentId, newPrevId);
this.blockChain.move(blockId, newParentId, newPrevId);
this.sync.sendOps([op]);
}
deleteNode(blockId: string) {
const op = this.getRemoveOp(blockId);
this.blockChain.remove(blockId);
this.sync.sendOps([op]);
}
private getUpdateNodeOp<T>(blockId: string, path: string[], value: T): {
type: 'update',
data: UpdateOpData

View File

@ -3,30 +3,31 @@ export class BlockPositionManager {
private regionGrid: RegionGrid;
private viewportBlocks: Set<string> = new Set();
private blockPositions: Map<string, BlockPosition> = new Map();
private observer: IntersectionObserver;
private container: HTMLDivElement | null = null;
constructor(container: HTMLDivElement) {
this.container = container;
this.regionGrid = new RegionGrid(container.offsetHeight);
this.observer = new IntersectionObserver((entries) => {
for (const entry of entries) {
const blockId = entry.target.getAttribute('data-block-id');
if (!blockId) return;
if (entry.isIntersecting) {
this.updateBlockPosition(blockId);
this.viewportBlocks.add(blockId);
} else {
this.viewportBlocks.delete(blockId);
}
}
}, { root: container });
}
isInViewport(nodeId: string) {
return this.viewportBlocks.has(nodeId);
}
observeBlock(node: HTMLDivElement) {
this.observer.observe(node);
const blockId = node.getAttribute('data-block-id');
if (blockId) {
this.updateBlockPosition(blockId);
this.viewportBlocks.add(blockId);
}
return {
unobserve: () => this.observer.unobserve(node),
unobserve: () => {
if (blockId) {
this.viewportBlocks.delete(blockId);
}
},
}
}
@ -67,7 +68,6 @@ export class BlockPositionManager {
destroy() {
this.container = null;
this.observer.disconnect();
}
}

View File

@ -133,21 +133,25 @@ export class RenderTree {
updateSelections(selections: string[]) {
const newSelections = filterSelections<TreeNode>(selections, this.map);
let isDiff = false;
if (newSelections.length !== this.selections.size) {
isDiff = true;
}
const selectedBlocksSet = new Set(newSelections);
if (Array.from(this.selections).some((id) => !selectedBlocksSet.has(id))) {
isDiff = true;
}
if (isDiff) {
const shouldUpdateIds = new Set([...this.selections, ...newSelections]);
this.selections = selectedBlocksSet;
shouldUpdateIds.forEach((id) => this.forceUpdate(id));
}
const updateNotSelected: string[] = [];
const updateSelected: string[] = [];
Array.from(this.selections).forEach((id) => {
if (!selectedBlocksSet.has(id)) {
updateNotSelected.push(id);
}
});
newSelections.forEach(id => {
if (!this.selections.has(id)) {
updateSelected.push(id);
}
});
this.selections = selectedBlocksSet;
[...updateNotSelected, ...updateSelected].forEach((id) => {
this.forceUpdate(id);
});
}
isSelected(nodeId: string) {

View File

@ -52,6 +52,24 @@ export class TreeNode {
this.children.push(node);
}
get lastChild() {
return this.children[this.children.length - 1];
}
get prevLine(): TreeNode | null {
if (!this.parent) return null;
const index = this.parent?.children.findIndex(item => item.id === this.id);
if (index === 0) {
return this.parent;
}
const prev = this.parent.children[index - 1];
let line = prev;
while(line.lastChild) {
line = line.lastChild;
}
return line;
}
get block() {
return this._block;
}

View File

@ -21,7 +21,6 @@ export function useBlockList({ blockId, blockEditor }: BlockListProps) {
const rowVirtualizer = useVirtualizer({
count: root?.children.length || 0,
getScrollElement: () => parentRef.current,
overscan: 5,
estimateSize: () => {
return defaultSize;
},
@ -71,7 +70,7 @@ export function ErrorBoundaryFallbackComponent({ error, resetErrorBoundary }: Fa
export function withTextBlockManager(Component: (props: BlockListProps) => React.ReactElement) {
return (props: BlockListProps) => {
const textBlockManager = new TextBlockManager(props.blockEditor.operation);
const textBlockManager = new TextBlockManager(props.blockId, props.blockEditor.operation);
useEffect(() => {
return () => {

View File

@ -49,6 +49,9 @@ export function useTextBlock({
return;
}
case 'Backspace': {
console.log(editor.selection)
}
}
triggerHotkey(event, editor);