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:
Red J Adaya 2023-10-26 12:41:52 +08:00 committed by GitHub
parent 65db030126
commit 736fb7be6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 53 additions and 84 deletions

View File

@ -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",

View File

@ -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 */
}

View File

@ -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>
))}

View File

@ -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

View File

@ -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"