mirror of
https://github.com/makeplane/plane.git
synced 2026-02-05 13:39:37 -06:00
[WIKI-740] refactor: editor table performance (#8411)
This commit is contained in:
committed by
GitHub
parent
21df1028f8
commit
373e640a25
@@ -12,7 +12,7 @@ import {
|
||||
} from "@floating-ui/react";
|
||||
import type { Editor } from "@tiptap/core";
|
||||
import { Ellipsis } from "lucide-react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
// plane imports
|
||||
import { cn } from "@plane/utils";
|
||||
// constants
|
||||
@@ -49,6 +49,25 @@ export function ColumnDragHandle(props: ColumnDragHandleProps) {
|
||||
const { col, editor } = props;
|
||||
// states
|
||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||
// Track active event listeners for cleanup
|
||||
const activeListenersRef = useRef<{
|
||||
mouseup?: (e: MouseEvent) => void;
|
||||
mousemove?: (e: MouseEvent) => void;
|
||||
}>({});
|
||||
|
||||
// Cleanup window event listeners on unmount
|
||||
useEffect(() => {
|
||||
const listenersRef = activeListenersRef.current;
|
||||
return () => {
|
||||
// Remove any lingering window event listeners when component unmounts
|
||||
if (listenersRef.mouseup) {
|
||||
window.removeEventListener("mouseup", listenersRef.mouseup);
|
||||
}
|
||||
if (listenersRef.mousemove) {
|
||||
window.removeEventListener("mousemove", listenersRef.mousemove);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
// floating ui
|
||||
const { refs, floatingStyles, context } = useFloating({
|
||||
placement: "bottom-start",
|
||||
@@ -94,6 +113,17 @@ export function ColumnDragHandle(props: ColumnDragHandleProps) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
// Prevent multiple simultaneous drag operations
|
||||
// If there are already listeners attached, remove them first
|
||||
if (activeListenersRef.current.mouseup) {
|
||||
window.removeEventListener("mouseup", activeListenersRef.current.mouseup);
|
||||
}
|
||||
if (activeListenersRef.current.mousemove) {
|
||||
window.removeEventListener("mousemove", activeListenersRef.current.mousemove);
|
||||
}
|
||||
activeListenersRef.current.mouseup = undefined;
|
||||
activeListenersRef.current.mousemove = undefined;
|
||||
|
||||
const table = findTable(editor.state.selection);
|
||||
if (!table) return;
|
||||
|
||||
@@ -133,6 +163,9 @@ export function ColumnDragHandle(props: ColumnDragHandleProps) {
|
||||
}
|
||||
window.removeEventListener("mouseup", handleFinish);
|
||||
window.removeEventListener("mousemove", handleMove);
|
||||
// Clear the ref
|
||||
activeListenersRef.current.mouseup = undefined;
|
||||
activeListenersRef.current.mousemove = undefined;
|
||||
};
|
||||
|
||||
let pseudoColumn: HTMLElement | undefined;
|
||||
@@ -169,6 +202,9 @@ export function ColumnDragHandle(props: ColumnDragHandleProps) {
|
||||
};
|
||||
|
||||
try {
|
||||
// Store references for cleanup
|
||||
activeListenersRef.current.mouseup = handleFinish;
|
||||
activeListenersRef.current.mousemove = handleMove;
|
||||
window.addEventListener("mouseup", handleFinish);
|
||||
window.addEventListener("mousemove", handleMove);
|
||||
} catch (error) {
|
||||
|
||||
@@ -18,6 +18,8 @@ type TableColumnDragHandlePluginState = {
|
||||
// track table structure to detect changes
|
||||
tableWidth?: number;
|
||||
tableNodePos?: number;
|
||||
// track renderers for cleanup
|
||||
renderers?: ReactRenderer[];
|
||||
};
|
||||
|
||||
const TABLE_COLUMN_DRAG_HANDLE_PLUGIN_KEY = new PluginKey("tableColumnHandlerDecorationPlugin");
|
||||
@@ -58,11 +60,22 @@ export const TableColumnDragHandlePlugin = (editor: Editor): Plugin<TableColumnD
|
||||
decorations: mapped,
|
||||
tableWidth: tableMap.width,
|
||||
tableNodePos: table.pos,
|
||||
renderers: prev.renderers,
|
||||
};
|
||||
}
|
||||
|
||||
// Clean up old renderers before creating new ones
|
||||
prev.renderers?.forEach((renderer) => {
|
||||
try {
|
||||
renderer.destroy();
|
||||
} catch (error) {
|
||||
console.error("Error destroying renderer:", error);
|
||||
}
|
||||
});
|
||||
|
||||
// recreate all decorations
|
||||
const decorations: Decoration[] = [];
|
||||
const renderers: ReactRenderer[] = [];
|
||||
|
||||
for (let col = 0; col < tableMap.width; col++) {
|
||||
const pos = getTableCellWidgetDecorationPos(table, tableMap, col);
|
||||
@@ -75,6 +88,7 @@ export const TableColumnDragHandlePlugin = (editor: Editor): Plugin<TableColumnD
|
||||
editor,
|
||||
});
|
||||
|
||||
renderers.push(dragHandleComponent);
|
||||
decorations.push(Decoration.widget(pos, () => dragHandleComponent.element));
|
||||
}
|
||||
|
||||
@@ -82,12 +96,27 @@ export const TableColumnDragHandlePlugin = (editor: Editor): Plugin<TableColumnD
|
||||
decorations: DecorationSet.create(newState.doc, decorations),
|
||||
tableWidth: tableMap.width,
|
||||
tableNodePos: table.pos,
|
||||
renderers,
|
||||
};
|
||||
},
|
||||
},
|
||||
props: {
|
||||
decorations(state) {
|
||||
return TABLE_COLUMN_DRAG_HANDLE_PLUGIN_KEY.getState(state).decorations;
|
||||
return (TABLE_COLUMN_DRAG_HANDLE_PLUGIN_KEY.getState(state) as TableColumnDragHandlePluginState | undefined)
|
||||
?.decorations;
|
||||
},
|
||||
},
|
||||
destroy() {
|
||||
// Clean up all renderers when plugin is destroyed
|
||||
const state =
|
||||
editor.state &&
|
||||
(TABLE_COLUMN_DRAG_HANDLE_PLUGIN_KEY.getState(editor.state) as TableColumnDragHandlePluginState | undefined);
|
||||
state?.renderers?.forEach((renderer: ReactRenderer) => {
|
||||
try {
|
||||
renderer.destroy();
|
||||
} catch (error) {
|
||||
console.error("Error destroying renderer:", error);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from "@floating-ui/react";
|
||||
import type { Editor } from "@tiptap/core";
|
||||
import { Ellipsis } from "lucide-react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
// plane imports
|
||||
import { cn } from "@plane/utils";
|
||||
// constants
|
||||
@@ -49,6 +49,25 @@ export function RowDragHandle(props: RowDragHandleProps) {
|
||||
const { editor, row } = props;
|
||||
// states
|
||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||
// Track active event listeners for cleanup
|
||||
const activeListenersRef = useRef<{
|
||||
mouseup?: (e: MouseEvent) => void;
|
||||
mousemove?: (e: MouseEvent) => void;
|
||||
}>({});
|
||||
|
||||
// Cleanup window event listeners on unmount
|
||||
useEffect(() => {
|
||||
const listenersRef = activeListenersRef.current;
|
||||
return () => {
|
||||
// Remove any lingering window event listeners when component unmounts
|
||||
if (listenersRef.mouseup) {
|
||||
window.removeEventListener("mouseup", listenersRef.mouseup);
|
||||
}
|
||||
if (listenersRef.mousemove) {
|
||||
window.removeEventListener("mousemove", listenersRef.mousemove);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
// floating ui
|
||||
const { refs, floatingStyles, context } = useFloating({
|
||||
placement: "bottom-start",
|
||||
@@ -94,6 +113,17 @@ export function RowDragHandle(props: RowDragHandleProps) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
// Prevent multiple simultaneous drag operations
|
||||
// If there are already listeners attached, remove them first
|
||||
if (activeListenersRef.current.mouseup) {
|
||||
window.removeEventListener("mouseup", activeListenersRef.current.mouseup);
|
||||
}
|
||||
if (activeListenersRef.current.mousemove) {
|
||||
window.removeEventListener("mousemove", activeListenersRef.current.mousemove);
|
||||
}
|
||||
activeListenersRef.current.mouseup = undefined;
|
||||
activeListenersRef.current.mousemove = undefined;
|
||||
|
||||
const table = findTable(editor.state.selection);
|
||||
if (!table) return;
|
||||
|
||||
@@ -133,6 +163,9 @@ export function RowDragHandle(props: RowDragHandleProps) {
|
||||
}
|
||||
window.removeEventListener("mouseup", handleFinish);
|
||||
window.removeEventListener("mousemove", handleMove);
|
||||
// Clear the ref
|
||||
activeListenersRef.current.mouseup = undefined;
|
||||
activeListenersRef.current.mousemove = undefined;
|
||||
};
|
||||
|
||||
let pseudoRow: HTMLElement | undefined;
|
||||
@@ -168,6 +201,9 @@ export function RowDragHandle(props: RowDragHandleProps) {
|
||||
};
|
||||
|
||||
try {
|
||||
// Store references for cleanup
|
||||
activeListenersRef.current.mouseup = handleFinish;
|
||||
activeListenersRef.current.mousemove = handleMove;
|
||||
window.addEventListener("mouseup", handleFinish);
|
||||
window.addEventListener("mousemove", handleMove);
|
||||
} catch (error) {
|
||||
|
||||
@@ -18,6 +18,8 @@ type TableRowDragHandlePluginState = {
|
||||
// track table structure to detect changes
|
||||
tableHeight?: number;
|
||||
tableNodePos?: number;
|
||||
// track renderers for cleanup
|
||||
renderers?: ReactRenderer[];
|
||||
};
|
||||
|
||||
const TABLE_ROW_DRAG_HANDLE_PLUGIN_KEY = new PluginKey("tableRowDragHandlePlugin");
|
||||
@@ -58,11 +60,22 @@ export const TableRowDragHandlePlugin = (editor: Editor): Plugin<TableRowDragHan
|
||||
decorations: mapped,
|
||||
tableHeight: tableMap.height,
|
||||
tableNodePos: table.pos,
|
||||
renderers: prev.renderers,
|
||||
};
|
||||
}
|
||||
|
||||
// Clean up old renderers before creating new ones
|
||||
prev.renderers?.forEach((renderer) => {
|
||||
try {
|
||||
renderer.destroy();
|
||||
} catch (error) {
|
||||
console.error("Error destroying renderer:", error);
|
||||
}
|
||||
});
|
||||
|
||||
// recreate all decorations
|
||||
const decorations: Decoration[] = [];
|
||||
const renderers: ReactRenderer[] = [];
|
||||
|
||||
for (let row = 0; row < tableMap.height; row++) {
|
||||
const pos = getTableCellWidgetDecorationPos(table, tableMap, row * tableMap.width);
|
||||
@@ -75,6 +88,7 @@ export const TableRowDragHandlePlugin = (editor: Editor): Plugin<TableRowDragHan
|
||||
editor,
|
||||
});
|
||||
|
||||
renderers.push(dragHandleComponent);
|
||||
decorations.push(Decoration.widget(pos, () => dragHandleComponent.element));
|
||||
}
|
||||
|
||||
@@ -82,12 +96,27 @@ export const TableRowDragHandlePlugin = (editor: Editor): Plugin<TableRowDragHan
|
||||
decorations: DecorationSet.create(newState.doc, decorations),
|
||||
tableHeight: tableMap.height,
|
||||
tableNodePos: table.pos,
|
||||
renderers,
|
||||
};
|
||||
},
|
||||
},
|
||||
props: {
|
||||
decorations(state) {
|
||||
return TABLE_ROW_DRAG_HANDLE_PLUGIN_KEY.getState(state).decorations;
|
||||
return (TABLE_ROW_DRAG_HANDLE_PLUGIN_KEY.getState(state) as TableRowDragHandlePluginState | undefined)
|
||||
?.decorations;
|
||||
},
|
||||
},
|
||||
destroy() {
|
||||
// Clean up all renderers when plugin is destroyed
|
||||
const state =
|
||||
editor.state &&
|
||||
(TABLE_ROW_DRAG_HANDLE_PLUGIN_KEY.getState(editor.state) as TableRowDragHandlePluginState | undefined);
|
||||
state?.renderers?.forEach((renderer: ReactRenderer) => {
|
||||
try {
|
||||
renderer.destroy();
|
||||
} catch (error) {
|
||||
console.error("Error destroying renderer:", error);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user