mirror of
https://github.com/zadam/trilium.git
synced 2025-11-09 21:30:53 +08:00
Merge 1f050255a8 into 4d4cd7d130
This commit is contained in:
commit
1a285af8a1
2 changed files with 192 additions and 9 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue