Line data Source code
1 : import * as React from 'react';
2 :
3 : import type { QueryExecutionState } from '../../query/query_execution_state.js';
4 : import { useComputationRegistry } from '../../compute/computation_registry.js';
5 : import type { ComputationAction, TableComputationState } from '../../compute/computation_state.js';
6 : import type { Dispatch } from '../../utils/variant.js';
7 : import { QueryResultSearchControls } from './query_result_search_controls.js';
8 : import { useQueryResultSearch } from './use_query_result_search.js';
9 : import { forEachIntegerValue } from './visible_rows.js';
10 :
11 : interface Props {
12 : query: QueryExecutionState;
13 : children?: React.ReactNode;
14 : }
15 :
16 1 : export function QueryResultToolbar(props: Props): React.ReactNode {
17 1 : const [computationState, dispatch] = useComputationRegistry();
18 1 : const table = computationState.tableComputations[props.query.queryId] ?? null;
19 1 : return <TableSearchToolbar table={table} dispatch={dispatch}>{props.children}</TableSearchToolbar>;
20 1 : }
21 :
22 : interface TableSearchToolbarProps {
23 : table: TableComputationState | null;
24 : dispatch: Dispatch<ComputationAction>;
25 : children?: React.ReactNode;
26 : }
27 :
28 : /// The column and data search of any analyzed table, not only of query results
29 1 : export function TableSearchToolbar(props: TableSearchToolbarProps) {
30 1 : const { table, dispatch } = props;
31 1 : const search = useQueryResultSearch(table, dispatch);
32 1 : if (search.columnSearch == null || search.dataSearch == null) return props.children;
33 1 : const dataMatchCount = getMatchingRowCount(table);
34 1 : return (
35 1 : <>
36 1 : <QueryResultSearchControls
37 1 : columnSearch={search.columnSearch}
38 1 : dataSearch={search.dataSearch}
39 1 : onColumnPatternChange={search.setColumnPattern}
40 1 : onDataPatternChange={search.setDataPattern}
41 1 : columnMatchCount={search.columnSearch.matchingColumnGroups?.length ?? null}
42 1 : dataMatchCount={dataMatchCount}
43 1 : />
44 1 : {props.children}
45 1 : </>
46 : );
47 1 : }
48 :
49 1 : export function useQueryResultRowCounts(query: QueryExecutionState | null) {
50 1 : const [computationState] = useComputationRegistry();
51 1 : const table = query == null ? null : computationState.tableComputations[query.queryId] ?? null;
52 1 : const totalRows = table?.dataTable.numRows ?? null;
53 1 : const matchingRows = getMatchingRowCount(table);
54 1 : const currentRows = matchingRows ?? table?.filterTable?.dataTable.numRows ?? totalRows;
55 1 : return { totalRows, currentRows, matchingRows };
56 1 : }
57 :
58 : /// Matching row counts per filter table and data search matches.
59 : /// Weakly keyed by both, so a count never outlives the tables it was computed from.
60 1 : const matchingRowCounts = new WeakMap<object, WeakMap<object, number>>();
61 :
62 1 : export function getMatchingRowCount(table: ReturnType<typeof useComputationRegistry>[0]['tableComputations'][number] | null): number | null {
63 1 : const dataMatches = table?.dataSearch?.matchingRows;
64 1 : if (table == null || dataMatches == null) return null;
65 0 : if (table.filterTable == null) return dataMatches.size;
66 0 : const filterDataTable = table.filterTable.dataTable;
67 0 : let countsByMatches = matchingRowCounts.get(filterDataTable);
68 1 : const cached = countsByMatches?.get(dataMatches);
69 1 : if (cached !== undefined) return cached;
70 0 : const rowIds = filterDataTable.getChildAt(0);
71 0 : if (rowIds == null) return 0;
72 0 : let count = 0;
73 0 : forEachIntegerValue(rowIds, rowId => {
74 0 : if (dataMatches.has(rowId)) ++count;
75 0 : });
76 0 : if (countsByMatches == null) {
77 0 : countsByMatches = new WeakMap();
78 0 : matchingRowCounts.set(filterDataTable, countsByMatches);
79 0 : }
80 0 : countsByMatches.set(dataMatches, count);
81 0 : return count;
82 1 : }
|