mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-22 16:48:23 +01:00
csvviwer focus support (#46)
* more fixes * add delay and slight opacity animation * cleanup * focus support * use savedHeight as initial tbody height * cleanup * set container to savedHeight initially * fix calc issue * more calc fixes * more calc fixes
This commit is contained in:
parent
65db030126
commit
736fb7be6a
@ -6,6 +6,8 @@
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@monaco-editor/react": "^4.5.1",
|
||||
"@table-nav/core": "^0.0.7",
|
||||
"@table-nav/react": "^0.0.7",
|
||||
"@tanstack/match-sorter-utils": "^8.8.4",
|
||||
"@tanstack/react-table": "^8.10.3",
|
||||
"autobind-decorator": "^2.4.0",
|
||||
|
@ -21,10 +21,6 @@
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.global-search-render {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
table.probe {
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
@ -98,6 +94,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.csv-renderer.loaded {
|
||||
.csv-renderer.show {
|
||||
opacity: 1; /* When loaded class is added, set the opacity to 1, making it visible */
|
||||
}
|
||||
|
@ -10,11 +10,10 @@ import {
|
||||
flexRender,
|
||||
useReactTable,
|
||||
getCoreRowModel,
|
||||
getFilteredRowModel,
|
||||
getSortedRowModel,
|
||||
FilterFn,
|
||||
} from "@tanstack/react-table";
|
||||
import { rankItem } from "@tanstack/match-sorter-utils";
|
||||
import { useTableNav } from "@table-nav/react";
|
||||
import SortUpIcon from "./img/sort-up-solid.svg";
|
||||
import SortDownIcon from "./img/sort-down-solid.svg";
|
||||
import cn from "classnames";
|
||||
@ -27,27 +26,16 @@ type CSVRow = {
|
||||
[key: string]: string | number;
|
||||
};
|
||||
|
||||
const fuzzyFilter: FilterFn<any> = (row, columnId, value, addMeta) => {
|
||||
// Rank the item
|
||||
const itemRank = rankItem(row.getValue(columnId), value);
|
||||
|
||||
// Store the itemRank info
|
||||
addMeta({
|
||||
itemRank,
|
||||
});
|
||||
|
||||
// Return if the item should be filtered in/out
|
||||
return itemRank.passed;
|
||||
};
|
||||
|
||||
interface Props {
|
||||
data: Blob;
|
||||
readOnly: boolean;
|
||||
context: RendererContext;
|
||||
opts: RendererOpts;
|
||||
savedHeight: number;
|
||||
scrollToBringIntoViewport: () => void;
|
||||
lineState: LineStateType;
|
||||
shouldFocus: boolean;
|
||||
rendererApi: RendererModelContainerApi;
|
||||
scrollToBringIntoViewport: () => void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
@ -59,7 +47,7 @@ interface State {
|
||||
const columnHelper = createColumnHelper<any>();
|
||||
|
||||
const CSVRenderer: FC<Props> = (props: Props) => {
|
||||
const { data, opts, lineState, context, savedHeight } = props;
|
||||
const { data, opts, lineState, context, shouldFocus, rendererApi, savedHeight } = props;
|
||||
const { height: maxHeight } = opts.maxSize;
|
||||
|
||||
const csvCacheRef = useRef(new Map<string, string>());
|
||||
@ -70,11 +58,11 @@ const CSVRenderer: FC<Props> = (props: Props) => {
|
||||
const [state, setState] = useState<State>({
|
||||
content: null,
|
||||
showReadonly: true,
|
||||
tbodyHeight: maxHeight,
|
||||
tbodyHeight: 0,
|
||||
});
|
||||
const [globalFilter, setGlobalFilter] = useState("");
|
||||
const [isFileTooLarge, setIsFileTooLarge] = useState<boolean>(false);
|
||||
const [isRendererLoaded, setRendererLoaded] = useState(false);
|
||||
const [tableLoaded, setTableLoaded] = useState(false);
|
||||
const { listeners } = useTableNav();
|
||||
|
||||
const filePath = lineState["prompt:file"];
|
||||
const { screenId, lineId } = context;
|
||||
@ -157,12 +145,12 @@ const CSVRenderer: FC<Props> = (props: Props) => {
|
||||
useEffect(() => {
|
||||
if (probeRef.current && headerRef.current && parsedData.length) {
|
||||
const rowHeight = probeRef.current.offsetHeight;
|
||||
const tbodyHeight = rowHeight * parsedData.length - rowHeight;
|
||||
const headerHeight = headerRef.current.offsetHeight; // For some reason, if we subtract this from maxHeight, the table is too short
|
||||
const tbodyHeightLessHeader = tbodyHeight - headerHeight;
|
||||
const maxTbodyHeight = Math.min(maxHeight, tbodyHeightLessHeader);
|
||||
const fullTBodyHeight = rowHeight * parsedData.length;
|
||||
const headerHeight = headerRef.current.offsetHeight;
|
||||
const maxHeightLessHeader = maxHeight - headerHeight;
|
||||
const tbodyHeight = Math.min(maxHeightLessHeader, fullTBodyHeight);
|
||||
|
||||
setState((prevState) => ({ ...prevState, tbodyHeight: maxTbodyHeight }));
|
||||
setState((prevState) => ({ ...prevState, tbodyHeight }));
|
||||
}
|
||||
}, [probeRef, headerRef, maxHeight, parsedData]);
|
||||
|
||||
@ -172,27 +160,24 @@ const CSVRenderer: FC<Props> = (props: Props) => {
|
||||
|
||||
if (rowRef.current.length === parsedData.length) {
|
||||
timer = setTimeout(() => {
|
||||
setRendererLoaded(true);
|
||||
}, 100); // Delay a bit to make sure the rows are rendered
|
||||
setTableLoaded(true);
|
||||
}, 50); // Delay a bit to make sure the rows are rendered
|
||||
}
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [rowRef, parsedData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldFocus) {
|
||||
rendererApi.onFocusChanged(true);
|
||||
}
|
||||
}, [shouldFocus]);
|
||||
|
||||
const table = useReactTable({
|
||||
manualPagination: true,
|
||||
data: parsedData,
|
||||
columns,
|
||||
filterFns: {
|
||||
fuzzy: fuzzyFilter,
|
||||
},
|
||||
state: {
|
||||
globalFilter,
|
||||
},
|
||||
globalFilterFn: fuzzyFilter,
|
||||
onGlobalFilterChange: setGlobalFilter,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
});
|
||||
|
||||
@ -205,7 +190,10 @@ const CSVRenderer: FC<Props> = (props: Props) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn("csv-renderer", { loaded: isRendererLoaded })}>
|
||||
<div
|
||||
className={cn("csv-renderer", { show: tableLoaded })}
|
||||
style={{ height: tableLoaded ? "auto" : savedHeight }}
|
||||
>
|
||||
<table className="probe">
|
||||
<tbody>
|
||||
<tr ref={probeRef}>
|
||||
@ -213,12 +201,18 @@ const CSVRenderer: FC<Props> = (props: Props) => {
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table>
|
||||
<table {...listeners}>
|
||||
<thead>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<tr key={headerGroup.id} ref={headerRef}>
|
||||
{headerGroup.headers.map((header) => (
|
||||
<th key={header.id} colSpan={header.colSpan} style={{ width: header.getSize() }}>
|
||||
{table.getHeaderGroups().map((headerGroup, index) => (
|
||||
<tr key={headerGroup.id} ref={headerRef} id={headerGroup.id} tabIndex={index}>
|
||||
{headerGroup.headers.map((header, index) => (
|
||||
<th
|
||||
key={header.id}
|
||||
colSpan={header.colSpan}
|
||||
id={header.id}
|
||||
tabIndex={index}
|
||||
style={{ width: header.getSize() }}
|
||||
>
|
||||
{header.isPlaceholder ? null : (
|
||||
<div
|
||||
{...{
|
||||
@ -251,9 +245,11 @@ const CSVRenderer: FC<Props> = (props: Props) => {
|
||||
</thead>
|
||||
<tbody style={{ height: `${state.tbodyHeight}px` }} ref={tbodyRef}>
|
||||
{table.getRowModel().rows.map((row, index) => (
|
||||
<tr key={row.id} ref={(el) => (rowRef.current[index] = el)}>
|
||||
<tr key={row.id} ref={(el) => (rowRef.current[index] = el)} id={row.id} tabIndex={index}>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<td key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</td>
|
||||
<td key={cell.id} id={cell.id} tabIndex={index}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
|
@ -1,35 +0,0 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React, { FC, useEffect, useState } from "react";
|
||||
|
||||
const DebouncedInput: FC<{
|
||||
value: string | number;
|
||||
onChange: (value: string | number) => void;
|
||||
debounce?: number;
|
||||
} & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'>> = ({
|
||||
value: initialValue,
|
||||
onChange,
|
||||
debounce = 500,
|
||||
...props
|
||||
}) => {
|
||||
const [value, setValue] = useState(initialValue);
|
||||
|
||||
useEffect(() => {
|
||||
setValue(initialValue);
|
||||
}, [initialValue]);
|
||||
|
||||
useEffect(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
onChange(value);
|
||||
}, debounce);
|
||||
|
||||
return () => clearTimeout(timeout);
|
||||
}, [value]);
|
||||
|
||||
return <div className="search-renderer">
|
||||
<input {...props} value={value} onChange={e => setValue(e.target.value)} />
|
||||
</div>;
|
||||
};
|
||||
|
||||
export default DebouncedInput
|
10
yarn.lock
10
yarn.lock
@ -1641,6 +1641,16 @@
|
||||
dependencies:
|
||||
defer-to-connect "^2.0.0"
|
||||
|
||||
"@table-nav/core@^0.0.7":
|
||||
version "0.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@table-nav/core/-/core-0.0.7.tgz#2d7f58eb81aacf5bdd1b40a823dbef5a88792995"
|
||||
integrity sha512-pCh18jHDRe3tw9sJZXfKi4cSD6VjHbn40CYdqhp5X91SIX7rakDEQAsTx6F7Fv9TUv265l+5rUDcYNaJ0N0cqQ==
|
||||
|
||||
"@table-nav/react@^0.0.7":
|
||||
version "0.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@table-nav/react/-/react-0.0.7.tgz#a97699a490e31713fccd5f624ddb62e9fdc06a82"
|
||||
integrity sha512-S+DsD/qDqp50Z4dqt5tZFMWA3sRu0OOT/grMQuq/z/52jPEKJB+b9t+YSH8Ms55vCJOJ0DxuYldJpYrJLMG5ew==
|
||||
|
||||
"@tanstack/match-sorter-utils@^8.8.4":
|
||||
version "8.8.4"
|
||||
resolved "https://registry.yarnpkg.com/@tanstack/match-sorter-utils/-/match-sorter-utils-8.8.4.tgz#0b2864d8b7bac06a9f84cb903d405852cc40a457"
|
||||
|
Loading…
Reference in New Issue
Block a user