Skip to content

Commit

Permalink
Merge pull request #325 from axonivy/row-virtualization-12
Browse files Browse the repository at this point in the history
XIVY-14462 add row virtualization
  • Loading branch information
ivy-lgi authored Feb 7, 2025
2 parents a98d038 + c285974 commit 52f7b2c
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 22 deletions.
28 changes: 28 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/variable-editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@axonivy/variable-editor-protocol": "~12.0.4-next",
"@tanstack/react-query": "5.32.1",
"@tanstack/react-query-devtools": "5.32.1",
"@tanstack/react-virtual": "^3.12.0",
"yaml": "^2.7.0"
},
"peerDependencies": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
.ui-table-row.row-error {
border: 1px solid var(--error-color);
width: calc(100% - 2px);
}
.ui-table-row.row-warning {
border: 1px solid var(--warning-color);
}
.ui-table-row:has(+ .ui-table-row.row-error) {
border-bottom: 1px solid var(--error-color);
}
.ui-table-row:has(+ .ui-table-row.row-warning) {
border-bottom: 1px solid var(--warning-color);
width: calc(100% - 2px);
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,36 @@
import { MessageRow, SelectRow, TableCell } from '@axonivy/ui-components';
import type { Severity, ValidationMessages } from '@axonivy/variable-editor-protocol';
import { flexRender, type Row } from '@tanstack/react-table';
import type { VirtualItem, Virtualizer } from '@tanstack/react-virtual';
import { useValidations } from '../../../context/useValidation';
import { toTreePath } from '../../../utils/tree/tree';
import type { Variable } from '../data/variable';
import './ValidationRow.css';
import { ROW_HEIGHT } from './VariablesMasterContent';

type ValidationRowProps = {
row: Row<Variable>;
virtualRow: VirtualItem;
virtualizer: Virtualizer<HTMLDivElement, HTMLTableRowElement>;
};

export const ValidationRow = ({ row }: ValidationRowProps) => {
export const ValidationRow = ({ row, virtualRow, virtualizer }: ValidationRowProps) => {
const validations = useValidations(toTreePath(row.id));
return (
<>
<SelectRow row={row} className={rowClass(validations)}>
<SelectRow
row={row}
className={rowClass(validations)}
data-index={virtualRow.index}
ref={virtualizer.measureElement}
style={{
transform: `translateY(${virtualRow.start}px)`
}}
>
{row.getVisibleCells().map(cell => (
<TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
<TableCell key={cell.id} style={{ width: cell.column.getSize() }}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</SelectRow>
{validations
Expand All @@ -26,6 +40,9 @@ export const ValidationRow = ({ row }: ValidationRowProps) => {
key={index}
columnCount={2}
message={{ message: val.message, variant: val.severity.toLocaleLowerCase() as Lowercase<Severity> }}
style={{
transform: `translateY(${virtualRow.start + ROW_HEIGHT * (index + 1)}px)`
}}
/>
))}
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,42 @@
.master-content-container {
height: 100%;
overflow: auto;
}

.master-content {
margin: var(--size-3);
min-height: 0;
height: fit-content;
}

.virtual-table-container {
overflow-x: hidden;
position: relative;
}

.virtual-table-body tr {
display: flex;
position: absolute;
width: 100%;
height: 36px;
box-sizing: border-box;
}

.virtual-table-body td {
align-content: center;
}

.virtual-table-body span,
.virtual-table-body p {
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

/* Because 'display' is set to 'block' instead of 'flex' on the parent 'p' to
make 'text-overflow: ellipsis' work, we need to manually style the icon in
the message rows */
.virtual-table-body p i {
padding-right: var(--size-1);
vertical-align: middle;
line-height: inherit;
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
} from '@axonivy/ui-components';
import { IvyIcons } from '@axonivy/ui-icons';
import { getCoreRowModel, useReactTable, type ColumnDef } from '@tanstack/react-table';
import { useVirtualizer } from '@tanstack/react-virtual';
import { useRef } from 'react';
import { useAppContext } from '../../../context/AppContext';
import { useKnownHotkeys } from '../../../utils/hotkeys';
Expand All @@ -32,6 +33,8 @@ import { OverwriteDialog } from '../dialog/OverwriteDialog';
import { ValidationRow } from './ValidationRow';
import './VariablesMasterContent.css';

export const ROW_HEIGHT = 36 as const;

export const VariablesMasterContent = () => {
const { variables, setVariables, setSelectedVariable, detail, setDetail } = useAppContext();

Expand All @@ -48,13 +51,22 @@ export const VariablesMasterContent = () => {
{
accessorKey: 'name',
header: header => <ExpandableHeader name='Name' header={header} />,
cell: cell => <ExpandableCell cell={cell} icon={variableIcon(cell.row.original)} />,
minSize: 50
cell: cell => (
<ExpandableCell cell={cell} icon={variableIcon(cell.row.original)}>
<span>{cell.getValue()}</span>
</ExpandableCell>
),
minSize: 200,
size: 500,
maxSize: 1000
},
{
accessorFn: (variable: Variable) => (variable.metadata.type === 'password' ? '***' : variable.value),
header: 'Value',
cell: cell => <div>{cell.getValue()}</div>
cell: cell => <span>{cell.getValue()}</span>,
minSize: 200,
size: 500,
maxSize: 1000
}
];
const table = useReactTable({
Expand All @@ -71,6 +83,25 @@ export const VariablesMasterContent = () => {
}
});

const rows = table.getRowModel().rows;
const tableContainer = useRef<HTMLDivElement>(null);
const measureElement = (element: HTMLTableRowElement) => {
let height = ROW_HEIGHT;
let nextElement = element.nextElementSibling;
while (nextElement?.classList.contains('ui-message-row')) {
height += ROW_HEIGHT;
nextElement = nextElement.nextElementSibling;
}
return height;
};
const virtualizer = useVirtualizer<HTMLDivElement, HTMLTableRowElement>({
count: rows.length,
estimateSize: () => ROW_HEIGHT,
measureElement,
getScrollElement: () => tableContainer.current,
overscan: 20
});

const { handleKeyDown } = useTableKeyHandler({
table,
data: variables
Expand Down Expand Up @@ -124,14 +155,17 @@ export const VariablesMasterContent = () => {
onClick={event => event.stopPropagation()}
>
{globalFilter.filter}
<Table onKeyDown={e => handleKeyDown(e, () => setDetail(!detail))} style={{ overflowX: 'unset' }}>
<TableResizableHeader headerGroups={table.getHeaderGroups()} onClick={resetSelection} />
<TableBody>
{table.getRowModel().rows.map(row => (
<ValidationRow key={row.id} row={row} />
))}
</TableBody>
</Table>
<div ref={tableContainer} className='virtual-table-container'>
<Table onKeyDown={e => handleKeyDown(e, () => setDetail(!detail))} style={{ display: 'grid' }}>
<TableResizableHeader headerGroups={table.getHeaderGroups()} onClick={resetSelection} />
<TableBody style={{ height: `${virtualizer.getTotalSize()}px` }} className='virtual-table-body'>
{virtualizer.getVirtualItems().map(virtualRow => {
const row = rows[virtualRow.index];
return <ValidationRow key={row.id} row={row} virtualRow={virtualRow} virtualizer={virtualizer} />;
})}
</TableBody>
</Table>
</div>
</BasicField>
</Flex>
);
Expand Down

0 comments on commit 52f7b2c

Please sign in to comment.