mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: Optimize the re-render node when the selection changes
This commit is contained in:
parent
3039f0427f
commit
a38c213744
@ -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];
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 () => {
|
||||
|
@ -49,6 +49,9 @@ export function useTextBlock({
|
||||
|
||||
return;
|
||||
}
|
||||
case 'Backspace': {
|
||||
console.log(editor.selection)
|
||||
}
|
||||
}
|
||||
|
||||
triggerHotkey(event, editor);
|
||||
|
Loading…
Reference in New Issue
Block a user