feat(print/table): basic implementation using export module

This commit is contained in:
Elian Doran 2025-11-21 21:28:31 +02:00
parent 4552b2b158
commit 6f83b932b0
No known key found for this signature in database
5 changed files with 130 additions and 77 deletions

View File

@ -14,6 +14,7 @@ import { WebSocketMessage } from "@triliumnext/commons";
import froca from "../../services/froca";
import PresentationView from "./presentation";
import { ListPrintView } from "./legacy/ListPrintView";
import TablePrintView from "./table/TablePrintView";
interface NoteListProps {
note: FNote | null | undefined;
@ -117,9 +118,13 @@ function getComponentByViewType(viewType: ViewTypeOptions, props: ViewModeProps<
case "geoMap":
return <GeoView {...props} />;
case "calendar":
return <CalendarView {...props} />
return <CalendarView {...props} />;
case "table":
return <TableView {...props} />
if (props.media !== "print") {
return <TableView {...props} />;
} else {
return <TablePrintView {...props} />;
}
case "board":
return <BoardView {...props} />
case "presentation":

View File

@ -0,0 +1,38 @@
import { useRef, useState } from "preact/hooks";
import { ViewModeProps } from "../interface";
import useData, { TableConfig } from "./data";
import { ExportModule, PrintModule, Tabulator as VanillaTabulator} from 'tabulator-tables';
import Tabulator from "./tabulator";
import { RawHtmlBlock } from "../../react/RawHtml";
export default function TablePrintView({ note, noteIds, viewConfig }: ViewModeProps<TableConfig>) {
const tabulatorRef = useRef<VanillaTabulator>(null);
const { columnDefs, rowData, movableRows, hasChildren } = useData(note, noteIds, viewConfig, undefined, () => {});
const [ html, setHtml ] = useState<string>();
return rowData && (
<div className="table-print-view">
{!html ? (
<Tabulator
tabulatorRef={tabulatorRef}
className="table-print-view-container"
modules={[ PrintModule, ExportModule ]}
columns={columnDefs ?? []}
data={rowData}
index="branchId"
dataTree={hasChildren}
printAsHtml={true}
printStyled={true}
onReady={() => {
const tabulator = tabulatorRef.current;
if (!tabulator) return;
setHtml(tabulator.getHtml());
}}
/>
) : (
<RawHtmlBlock html={html} />
)}
</div>
)
}

View File

@ -0,0 +1,77 @@
import type { ColumnDefinition } from "tabulator-tables";
import FNote from "../../../entities/fnote";
import { useNoteLabelBoolean, useNoteLabelInt, useTriliumEvent } from "../../react/hooks";
import { useEffect, useState } from "preact/hooks";
import getAttributeDefinitionInformation, { buildRowDefinitions, TableData } from "./rows";
import froca from "../../../services/froca";
import { buildColumnDefinitions } from "./columns";
import attributes from "../../../services/attributes";
import { RefObject } from "preact";
export interface TableConfig {
tableData: {
columns?: ColumnDefinition[];
};
}
export default function useData(note: FNote, noteIds: string[], viewConfig: TableConfig | undefined, newAttributePosition: RefObject<number | undefined> | undefined, resetNewAttributePosition: () => void) {
const [ maxDepth ] = useNoteLabelInt(note, "maxNestingDepth") ?? -1;
const [ includeArchived ] = useNoteLabelBoolean(note, "includeArchived");
const [ columnDefs, setColumnDefs ] = useState<ColumnDefinition[]>();
const [ rowData, setRowData ] = useState<TableData[]>();
const [ hasChildren, setHasChildren ] = useState<boolean>();
const [ isSorted ] = useNoteLabelBoolean(note, "sorted");
const [ movableRows, setMovableRows ] = useState(false);
async function refresh() {
const info = getAttributeDefinitionInformation(note);
// Ensure all note IDs are loaded.
await froca.getNotes(noteIds);
const { definitions: rowData, hasSubtree: hasChildren, rowNumber } = await buildRowDefinitions(note, info, includeArchived, maxDepth);
const columnDefs = buildColumnDefinitions({
info,
movableRows,
existingColumnData: viewConfig?.tableData?.columns,
rowNumberHint: rowNumber,
position: newAttributePosition?.current ?? undefined
});
setColumnDefs(columnDefs);
setRowData(rowData);
setHasChildren(hasChildren);
resetNewAttributePosition();
}
useEffect(() => { refresh() }, [ note, noteIds, maxDepth, movableRows ]);
useTriliumEvent("entitiesReloaded", ({ loadResults}) => {
if (glob.device === "print") return;
// 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();
return;
}
// React to external row updates.
if (loadResults.getBranchRows().some(branch => branch.parentNoteId === note.noteId || noteIds.includes(branch.parentNoteId ?? ""))
|| 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();
return;
}
});
// Identify if movable rows.
useEffect(() => {
setMovableRows(!isSorted && note.type !== "search" && !hasChildren);
}, [ isSorted, note, hasChildren ]);
return { columnDefs, rowData, movableRows, hasChildren };
}

View File

@ -1,10 +1,9 @@
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
import { ViewModeProps } from "../interface";
import { buildColumnDefinitions } from "./columns";
import getAttributeDefinitionInformation, { buildRowDefinitions, TableData } from "./rows";
import { useLegacyWidget, useNoteLabelBoolean, useNoteLabelInt, useTriliumEvent } from "../../react/hooks";
import { TableData } from "./rows";
import { useLegacyWidget } from "../../react/hooks";
import Tabulator from "./tabulator";
import { Tabulator as VanillaTabulator, SortModule, FormatModule, InteractionModule, EditModule, ResizeColumnsModule, FrozenColumnsModule, PersistenceModule, MoveColumnsModule, MoveRowsModule, ColumnDefinition, DataTreeModule, Options, RowComponent} from 'tabulator-tables';
import { Tabulator as VanillaTabulator, SortModule, FormatModule, InteractionModule, EditModule, ResizeColumnsModule, FrozenColumnsModule, PersistenceModule, MoveColumnsModule, MoveRowsModule, DataTreeModule, Options, RowComponent} from 'tabulator-tables';
import { useContextMenu } from "./context_menu";
import { ParentComponent } from "../../react/react_utils";
import FNote from "../../../entities/fnote";
@ -14,16 +13,8 @@ import "./index.css";
import useRowTableEditing from "./row_editing";
import useColTableEditing from "./col_editing";
import AttributeDetailWidget from "../../attribute_widgets/attribute_detail";
import attributes from "../../../services/attributes";
import { RefObject } from "preact";
import SpacedUpdate from "../../../services/spaced_update";
import froca from "../../../services/froca";
interface TableConfig {
tableData: {
columns?: ColumnDefinition[];
};
}
import useData, { TableConfig } from "./data";
export default function TableView({ note, noteIds, notePath, viewConfig, saveConfig }: ViewModeProps<TableConfig>) {
const tabulatorRef = useRef<VanillaTabulator>(null);
@ -118,67 +109,7 @@ function usePersistence(viewConfig: TableConfig | null | undefined, saveConfig:
return () => {
spacedUpdate.updateNowIfNecessary();
};
}, [ viewConfig, saveConfig ])
}, [ viewConfig, saveConfig ]);
return persistenceProps;
}
function useData(note: FNote, noteIds: string[], viewConfig: TableConfig | undefined, newAttributePosition: RefObject<number | undefined>, resetNewAttributePosition: () => void) {
const [ maxDepth ] = useNoteLabelInt(note, "maxNestingDepth") ?? -1;
const [ includeArchived ] = useNoteLabelBoolean(note, "includeArchived");
const [ columnDefs, setColumnDefs ] = useState<ColumnDefinition[]>();
const [ rowData, setRowData ] = useState<TableData[]>();
const [ hasChildren, setHasChildren ] = useState<boolean>();
const [ isSorted ] = useNoteLabelBoolean(note, "sorted");
const [ movableRows, setMovableRows ] = useState(false);
async function refresh() {
const info = getAttributeDefinitionInformation(note);
// Ensure all note IDs are loaded.
await froca.getNotes(noteIds);
const { definitions: rowData, hasSubtree: hasChildren, rowNumber } = await buildRowDefinitions(note, info, includeArchived, maxDepth);
const columnDefs = buildColumnDefinitions({
info,
movableRows,
existingColumnData: viewConfig?.tableData?.columns,
rowNumberHint: rowNumber,
position: newAttributePosition.current ?? undefined
});
setColumnDefs(columnDefs);
setRowData(rowData);
setHasChildren(hasChildren);
resetNewAttributePosition();
}
useEffect(() => { refresh() }, [ note, noteIds, maxDepth, movableRows ]);
useTriliumEvent("entitiesReloaded", ({ loadResults}) => {
// 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();
return;
}
// React to external row updates.
if (loadResults.getBranchRows().some(branch => branch.parentNoteId === note.noteId || noteIds.includes(branch.parentNoteId ?? ""))
|| 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();
return;
}
});
// Identify if movable rows.
useEffect(() => {
setMovableRows(!isSorted && note.type !== "search" && !hasChildren);
}, [ isSorted, note, hasChildren ]);
return { columnDefs, rowData, movableRows, hasChildren };
}

View File

@ -14,9 +14,10 @@ interface TableProps<T> extends Omit<Options, "data" | "footerElement" | "index"
events?: Partial<EventCallBackMethods>;
index: keyof T;
footerElement?: string | HTMLElement | JSX.Element;
onReady?: () => void;
}
export default function Tabulator<T>({ className, columns, data, modules, tabulatorRef: externalTabulatorRef, footerElement, events, index, dataTree, ...restProps }: TableProps<T>) {
export default function Tabulator<T>({ className, columns, data, modules, tabulatorRef: externalTabulatorRef, footerElement, events, index, dataTree, onReady, ...restProps }: TableProps<T>) {
const parentComponent = useContext(ParentComponent);
const containerRef = useRef<HTMLDivElement>(null);
const tabulatorRef = useRef<VanillaTabulator>(null);
@ -43,6 +44,7 @@ export default function Tabulator<T>({ className, columns, data, modules, tabula
tabulator.on("tableBuilt", () => {
tabulatorRef.current = tabulator;
externalTabulatorRef.current = tabulator;
onReady?.();
});
return () => tabulator.destroy();