This commit is contained in:
bangbangbanggit 2025-10-05 21:51:16 +03:00 committed by GitHub
commit 1a285af8a1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 192 additions and 9 deletions

View file

@ -27,6 +27,34 @@ interface TableConfig {
export default function TableView({ note, noteIds, notePath, viewConfig, saveConfig }: ViewModeProps<TableConfig>) {
const tabulatorRef = useRef<VanillaTabulator>(null);
const parentComponent = useContext(ParentComponent);
const expandedRowsRef = useRef<Set<string>>(new Set());
const isDataRefreshingRef = useRef<boolean>(false);
// Load persisted expansion state on mount
useEffect(() => {
const storageKey = `trilium-tree-expanded-${note.noteId}`;
try {
const stored = localStorage.getItem(storageKey);
if (stored) {
const expandedIds = JSON.parse(stored);
expandedRowsRef.current = new Set(expandedIds);
console.log('Loaded expansion state from storage:', expandedIds);
}
} catch (e) {
console.warn('Failed to load tree expansion state:', e);
}
}, [note.noteId]);
// Save expansion state changes to localStorage
const persistExpandedState = useCallback(() => {
const storageKey = `trilium-tree-expanded-${note.noteId}`;
try {
const expandedIds = Array.from(expandedRowsRef.current);
localStorage.setItem(storageKey, JSON.stringify(expandedIds));
} catch (e) {
console.warn('Failed to save tree expansion state:', e);
}
}, [note.noteId]);
const [ attributeDetailWidgetEl, attributeDetailWidget ] = useLegacyWidget(() => new AttributeDetailWidget().contentSized());
const contextMenuEvents = useContextMenu(note, parentComponent, tabulatorRef);
@ -43,7 +71,11 @@ export default function TableView({ note, noteIds, notePath, viewConfig, saveCon
dataTreeElementColumn: "title",
dataTreeChildIndent: 20,
dataTreeExpandElement: `<button class="tree-expand"><span class="bx bx-chevron-right"></span></button>`,
dataTreeCollapseElement: `<button class="tree-collapse"><span class="bx bx-chevron-down"></span></button>`
dataTreeCollapseElement: `<button class="tree-collapse"><span class="bx bx-chevron-down"></span></button>`,
persistenceMode: "local",
persistence: {
tree: true
}
}
}, [ hasChildren ]);
@ -65,7 +97,74 @@ export default function TableView({ note, noteIds, notePath, viewConfig, saveCon
footerElement={<TableFooter note={note} />}
events={{
...contextMenuEvents,
...rowEditingEvents
...rowEditingEvents,
tableBuilt: () => {
console.log('Table built - setting up tree event tracking');
},
// Try all possible expand event names
rowTreeExpanded: (row) => {
const data = row.getData() as TableData;
console.log('Row expanded (rowTreeExpanded):', data.branchId, data.title, 'refreshing:', isDataRefreshingRef.current);
if (data.branchId && !isDataRefreshingRef.current) {
expandedRowsRef.current.add(data.branchId);
console.log('Updated expanded set:', Array.from(expandedRowsRef.current));
persistExpandedState();
}
},
dataTreeRowExpanded: (row) => {
const data = row.getData() as TableData;
console.log('Row expanded (dataTreeRowExpanded):', data.branchId, data.title, 'refreshing:', isDataRefreshingRef.current);
if (data.branchId && !isDataRefreshingRef.current) {
expandedRowsRef.current.add(data.branchId);
console.log('Updated expanded set:', Array.from(expandedRowsRef.current));
persistExpandedState();
}
// Call the original context menu handler if it exists
if (contextMenuEvents.dataTreeRowExpanded) {
contextMenuEvents.dataTreeRowExpanded(row);
}
},
treeExpanded: (row) => {
const data = row.getData() as TableData;
console.log('Row expanded (treeExpanded):', data.branchId, data.title, 'refreshing:', isDataRefreshingRef.current);
if (data.branchId && !isDataRefreshingRef.current) {
expandedRowsRef.current.add(data.branchId);
console.log('Updated expanded set:', Array.from(expandedRowsRef.current));
persistExpandedState();
}
},
// Try all possible collapse event names
rowTreeCollapsed: (row) => {
const data = row.getData() as TableData;
console.log('Row collapsed (rowTreeCollapsed):', data.branchId, data.title, 'refreshing:', isDataRefreshingRef.current);
if (data.branchId && !isDataRefreshingRef.current) {
expandedRowsRef.current.delete(data.branchId);
console.log('Updated expanded set:', Array.from(expandedRowsRef.current));
persistExpandedState();
}
},
dataTreeRowCollapsed: (row) => {
const data = row.getData() as TableData;
console.log('Row collapsed (dataTreeRowCollapsed):', data.branchId, data.title, 'refreshing:', isDataRefreshingRef.current);
if (data.branchId && !isDataRefreshingRef.current) {
expandedRowsRef.current.delete(data.branchId);
console.log('Updated expanded set:', Array.from(expandedRowsRef.current));
persistExpandedState();
}
// Call the original context menu handler if it exists
if (contextMenuEvents.dataTreeRowCollapsed) {
contextMenuEvents.dataTreeRowCollapsed(row);
}
},
treeCollapsed: (row) => {
const data = row.getData() as TableData;
console.log('Row collapsed (treeCollapsed):', data.branchId, data.title, 'refreshing:', isDataRefreshingRef.current);
if (data.branchId && !isDataRefreshingRef.current) {
expandedRowsRef.current.delete(data.branchId);
console.log('Updated expanded set:', Array.from(expandedRowsRef.current));
persistExpandedState();
}
}
}}
persistence {...persistenceProps}
layout="fitDataFill"
@ -73,7 +172,19 @@ export default function TableView({ note, noteIds, notePath, viewConfig, saveCon
movableColumns
movableRows={movableRows}
rowFormatter={rowFormatter}
preserveTreeState={hasChildren}
expandedRowsRef={expandedRowsRef}
isDataRefreshingRef={isDataRefreshingRef}
{...dataTreeProps}
dataTreeStartExpanded={(row, level) => {
if (expandedRowsRef.current && expandedRowsRef.current.size > 0) {
const rowData = row.getData() as TableData;
const isExpanded = expandedRowsRef.current.has(rowData.branchId);
console.log(`dataTreeStartExpanded called for ${rowData.branchId}: ${isExpanded}`);
return isExpanded;
}
return false; // Default collapsed state
}}
/>
<TableFooter note={note} />
</>
@ -133,6 +244,9 @@ function useData(note: FNote, noteIds: string[], viewConfig: TableConfig | undef
const [ movableRows, setMovableRows ] = useState(false);
function refresh() {
console.log('🔄 TABLE REFRESH TRIGGERED');
console.trace('Refresh call stack'); // This will show us what triggered it
const info = getAttributeDefinitionInformation(note);
buildRowDefinitions(note, info, includeArchived, maxDepth).then(({ definitions: rowData, hasSubtree: hasChildren, rowNumber }) => {
@ -150,15 +264,43 @@ function useData(note: FNote, noteIds: string[], viewConfig: TableConfig | undef
});
}
useEffect(refresh, [ note, noteIds, maxDepth, movableRows ]);
useEffect(() => {
console.log('⚡ useEffect refresh triggered by:', { note: note.noteId, noteIds: noteIds.length, maxDepth, movableRows });
// Debounce rapid changes to movableRows
const timeoutId = setTimeout(() => {
refresh();
}, 50);
return () => clearTimeout(timeoutId);
}, [ note, noteIds.length, maxDepth ]); // Remove movableRows from dependencies
const refreshTimeoutRef = useRef<number>();
// Cleanup timeout on unmount
useEffect(() => {
return () => {
if (refreshTimeoutRef.current) {
clearTimeout(refreshTimeoutRef.current);
}
};
}, []);
useTriliumEvent("entitiesReloaded", ({ loadResults}) => {
console.log('🔄 entitiesReloaded event triggered');
console.log('Attributes changed:', loadResults.getAttributeRows().length);
console.log('Branches changed:', loadResults.getBranchRows().length);
console.log('Notes changed:', loadResults.getNoteIds().length);
// React to column changes.
if (loadResults.getAttributeRows().find(attr =>
attr.type === "label" &&
(attr.name?.startsWith("label:") || attr.name?.startsWith("relation:")) &&
attributes.isAffecting(attr, note))) {
refresh();
console.log('✅ Refreshing due to column changes');
// Clear any pending refresh
if (refreshTimeoutRef.current) {
clearTimeout(refreshTimeoutRef.current);
}
refreshTimeoutRef.current = setTimeout(() => refresh(), 100);
return;
}
@ -167,9 +309,16 @@ function useData(note: FNote, noteIds: string[], viewConfig: TableConfig | undef
|| loadResults.getNoteIds().some(noteId => noteIds.includes(noteId))
|| loadResults.getAttributeRows().some(attr => noteIds.includes(attr.noteId!))
|| loadResults.getAttributeRows().some(attr => attr.name === "archived" && attr.noteId && noteIds.includes(attr.noteId))) {
refresh();
console.log('✅ Refreshing due to row updates');
// Clear any pending refresh and debounce
if (refreshTimeoutRef.current) {
clearTimeout(refreshTimeoutRef.current);
}
refreshTimeoutRef.current = setTimeout(() => refresh(), 100);
return;
}
console.log('❌ No refresh needed for this entitiesReloaded event');
});
// Identify if movable rows.

View file

@ -14,9 +14,12 @@ interface TableProps<T> extends Omit<Options, "data" | "footerElement" | "index"
events?: Partial<EventCallBackMethods>;
index: keyof T;
footerElement?: string | HTMLElement | JSX.Element;
preserveTreeState?: boolean;
expandedRowsRef?: RefObject<Set<string>>;
isDataRefreshingRef?: RefObject<boolean>;
}
export default function Tabulator<T>({ className, columns, data, modules, tabulatorRef: externalTabulatorRef, footerElement, events, index, ...restProps }: TableProps<T>) {
export default function Tabulator<T>({ className, columns, data, modules, tabulatorRef: externalTabulatorRef, footerElement, events, index, preserveTreeState, expandedRowsRef, isDataRefreshingRef, ...restProps }: TableProps<T>) {
const parentComponent = useContext(ParentComponent);
const containerRef = useRef<HTMLDivElement>(null);
const tabulatorRef = useRef<VanillaTabulator>(null);
@ -62,8 +65,39 @@ export default function Tabulator<T>({ className, columns, data, modules, tabula
}
}, Object.values(events ?? {}));
// Change in data.
useEffect(() => { tabulatorRef.current?.setData(data) }, [ data ]);
const treeStateTimeoutRef = useRef<number>();
// Cleanup timeout on unmount
useEffect(() => {
return () => {
if (treeStateTimeoutRef.current) {
clearTimeout(treeStateTimeoutRef.current);
}
};
}, []);
// Change in data - with tree state preservation
useEffect(() => {
const tabulator = tabulatorRef.current;
if (!tabulator || !data) return;
console.log('Data update triggered, preserveTreeState:', preserveTreeState, 'dataTree option:', tabulator.options?.dataTree);
if (preserveTreeState && tabulator.options && "dataTree" in tabulator.options && tabulator.options.dataTree && expandedRowsRef) {
console.log('Tree state preservation using dataTreeStartExpanded approach');
// Clear any existing timeout to prevent overlapping updates
if (treeStateTimeoutRef.current) {
clearTimeout(treeStateTimeoutRef.current);
}
// Simply update data - expansion state will be handled by dataTreeStartExpanded function
tabulator.setData(data);
} else {
tabulator.setData(data);
}
}, [ data, preserveTreeState, index ]);
useEffect(() => { columns && tabulatorRef.current?.setColumns(columns)}, [ data]);
return (