mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2024-11-22 00:37:36 -06:00
Automatically apply virtualization in the DataGridView of SchemaView if the schema contains only one collection. #7607
This commit is contained in:
parent
6f96f67655
commit
feb3093c6d
@ -43,11 +43,16 @@ import { InputText } from '../components/FormComponents';
|
||||
import { usePgAdmin } from '../BrowserComponent';
|
||||
import { requestAnimationAndFocus } from '../utils';
|
||||
import { PgReactTable, PgReactTableBody, PgReactTableCell, PgReactTableHeader, PgReactTableRow, PgReactTableRowContent, PgReactTableRowExpandContent } from '../components/PgReactTableStyled';
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
|
||||
const StyledBox = styled(Box)(({theme}) => ({
|
||||
'& .DataGridView-grid': {
|
||||
...theme.mixins.panelBorder,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
minHeight: 0,
|
||||
height: '100%',
|
||||
'& .DataGridView-gridHeader': {
|
||||
display: 'flex',
|
||||
...theme.mixins.panelBorder.bottom,
|
||||
@ -69,7 +74,6 @@ const StyledBox = styled(Box)(({theme}) => ({
|
||||
'&.pgrt-table': {
|
||||
'& .pgrt-body':{
|
||||
'& .pgrt-row': {
|
||||
position: 'unset',
|
||||
backgroundColor: theme.otherVars.emptySpaceBg,
|
||||
'& .pgrt-row-content':{
|
||||
'& .pgrd-row-cell': {
|
||||
@ -174,9 +178,6 @@ function DataTableRow({index, row, totalRows, isResizing, isHovered, schema, sch
|
||||
});
|
||||
});
|
||||
|
||||
// Try autofocus on newly added row.
|
||||
requestAnimationAndFocus(rowRef.current?.querySelector('input'));
|
||||
|
||||
return ()=>{
|
||||
/* Cleanup the listeners when unmounting */
|
||||
depListener?.removeDepListener(accessPath);
|
||||
@ -494,28 +495,6 @@ export default function DataGridView({
|
||||
},[props.canEdit, props.canDelete, props.canReorder]
|
||||
);
|
||||
|
||||
const onAddClick = useCallback(()=>{
|
||||
if(!props.canAddRow) {
|
||||
return;
|
||||
}
|
||||
let newRow = schemaRef.current.getNewData();
|
||||
|
||||
const current_macros = schemaRef.current?._top?._sessData?.macro || null;
|
||||
if (current_macros){
|
||||
newRow = schemaRef.current.getNewData(current_macros);
|
||||
}
|
||||
|
||||
if(props.expandEditOnAdd && props.canEdit) {
|
||||
newRowIndex.current = rows.length;
|
||||
}
|
||||
dataDispatch({
|
||||
type: SCHEMA_STATE_ACTIONS.ADD_ROW,
|
||||
path: accessPath,
|
||||
value: newRow,
|
||||
addOnTop: props.addOnTop
|
||||
});
|
||||
}, [props.canAddRow, rows?.length]);
|
||||
|
||||
const columnVisibility = useMemo(()=>{
|
||||
const ret = {};
|
||||
|
||||
@ -544,6 +523,27 @@ export default function DataGridView({
|
||||
|
||||
const rows = table.getRowModel().rows;
|
||||
|
||||
const onAddClick = useCallback(()=>{
|
||||
if(!props.canAddRow) {
|
||||
return;
|
||||
}
|
||||
let newRow = schemaRef.current.getNewData();
|
||||
|
||||
const current_macros = schemaRef.current?._top?._sessData?.macro || null;
|
||||
if (current_macros){
|
||||
newRow = schemaRef.current.getNewData(current_macros);
|
||||
}
|
||||
|
||||
newRowIndex.current = props.addOnTop ? 0 : rows.length;
|
||||
|
||||
dataDispatch({
|
||||
type: SCHEMA_STATE_ACTIONS.ADD_ROW,
|
||||
path: accessPath,
|
||||
value: newRow,
|
||||
addOnTop: props.addOnTop
|
||||
});
|
||||
}, [props.canAddRow, rows?.length]);
|
||||
|
||||
useEffect(()=>{
|
||||
let rowsPromise = fixedRows;
|
||||
|
||||
@ -564,8 +564,17 @@ export default function DataGridView({
|
||||
|
||||
useEffect(()=>{
|
||||
if(newRowIndex.current >= 0) {
|
||||
rows[newRowIndex.current]?.toggleExpanded(true);
|
||||
newRowIndex.current = null;
|
||||
virtualizer.scrollToIndex(newRowIndex.current);
|
||||
|
||||
// Try autofocus on newly added row.
|
||||
setTimeout(()=>{
|
||||
const rowInput = tableRef.current.querySelector(`.pgrt-row[data-index="${newRowIndex.current}"] input`);
|
||||
if(!rowInput) return;
|
||||
|
||||
requestAnimationAndFocus(tableRef.current.querySelector(`.pgrt-row[data-index="${newRowIndex.current}"] input`));
|
||||
props.expandEditOnAdd && props.canEdit && rows[newRowIndex.current]?.toggleExpanded(true);
|
||||
newRowIndex.current = undefined;
|
||||
}, 50);
|
||||
}
|
||||
}, [rows?.length]);
|
||||
|
||||
@ -582,6 +591,18 @@ export default function DataGridView({
|
||||
|
||||
const isResizing = _.flatMap(table.getHeaderGroups(), headerGroup => headerGroup.headers.map(header=>header.column.getIsResizing())).includes(true);
|
||||
|
||||
const virtualizer = useVirtualizer({
|
||||
count: rows.length,
|
||||
getScrollElement: () => tableRef.current,
|
||||
estimateSize: () => 42,
|
||||
measureElement:
|
||||
typeof window !== 'undefined' &&
|
||||
navigator.userAgent.indexOf('Firefox') === -1
|
||||
? element => element?.getBoundingClientRect().height
|
||||
: undefined,
|
||||
overscan: viewHelperProps.virtualiseOverscan ?? 10,
|
||||
});
|
||||
|
||||
if(!props.visible) {
|
||||
return <></>;
|
||||
}
|
||||
@ -598,12 +619,19 @@ export default function DataGridView({
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<PgReactTable ref={tableRef} table={table} data-test="data-grid-view" tableClassName='DataGridView-table'>
|
||||
<PgReactTableHeader table={table} />
|
||||
<PgReactTableBody>
|
||||
{rows.map((row, i) => {
|
||||
return <PgReactTableRow key={row.index}>
|
||||
<DataTableRow index={i} row={row} totalRows={rows.length} isResizing={isResizing}
|
||||
<PgReactTableBody style={{ height: virtualizer.getTotalSize() + 'px'}}>
|
||||
{virtualizer.getVirtualItems().map((virtualRow) => {
|
||||
const row = rows[virtualRow.index];
|
||||
|
||||
return <PgReactTableRow key={row.id} data-index={virtualRow.index}
|
||||
ref={node => virtualizer.measureElement(node)}
|
||||
style={{
|
||||
transform: `translateY(${virtualRow.start}px)`, // this should always be a `style` as it changes on scroll
|
||||
}}>
|
||||
<DataTableRow index={virtualRow.index} row={row} totalRows={rows.length} isResizing={isResizing}
|
||||
schema={schemaRef.current} schemaRef={schemaRef} accessPath={accessPath.concat([row.index])}
|
||||
moveRow={moveRow} isHovered={i == hoverIndex} setHoverIndex={setHoverIndex} viewHelperProps={viewHelperProps}
|
||||
moveRow={moveRow} isHovered={virtualRow.index == hoverIndex} setHoverIndex={setHoverIndex} viewHelperProps={viewHelperProps}
|
||||
measureElement={virtualizer.measureElement}
|
||||
/>
|
||||
{props.canEdit &&
|
||||
<PgReactTableRowExpandContent row={row}>
|
||||
|
@ -7,7 +7,7 @@
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import { Box, Tab, Tabs, Grid } from '@mui/material';
|
||||
import _ from 'lodash';
|
||||
@ -60,6 +60,15 @@ const StyledBox = styled(Box)(({theme}) => ({
|
||||
},
|
||||
}
|
||||
},
|
||||
'& .FormView-singleCollectionPanel': {
|
||||
...theme.mixins.tabPanel,
|
||||
'& .FormView-singleCollectionPanelContent': {
|
||||
'& .FormView-controlRow': {
|
||||
marginBottom: theme.spacing(1),
|
||||
height: '100%',
|
||||
},
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
/* Optional SQL tab */
|
||||
@ -423,6 +432,17 @@ export default function FormView({
|
||||
onTabChange?.(tabValue, Object.keys(tabs)[tabValue], sqlTabActive);
|
||||
}, [tabValue]);
|
||||
|
||||
const isSingleCollection = useMemo(()=>{
|
||||
// we can check if it is a single-collection.
|
||||
// in that case, we could force virtualization of the collection.
|
||||
if(isTabView) return false;
|
||||
|
||||
const visibleEle = Object.values(finalTabs)[0].filter((c)=>c.props.visible);
|
||||
return visibleEle.length == 1
|
||||
&& visibleEle[0]?.type == DataGridView;
|
||||
|
||||
}, [isTabView, finalTabs]);
|
||||
|
||||
/* check whether form is kept hidden by visible prop */
|
||||
if(!_.isUndefined(visible) && !visible) {
|
||||
return <></>;
|
||||
@ -463,10 +483,10 @@ export default function FormView({
|
||||
</StyledBox>
|
||||
);
|
||||
} else {
|
||||
let contentClassName = ['FormView-nonTabPanelContent', (stateUtils.formErr.message ? 'FormView-errorMargin' : null)];
|
||||
let contentClassName = [isSingleCollection ? 'FormView-singleCollectionPanelContent' : 'FormView-nonTabPanelContent', (stateUtils.formErr.message ? 'FormView-errorMargin' : null)];
|
||||
return (
|
||||
<StyledBox height="100%" display="flex" flexDirection="column" className={className} ref={formRef} data-test="form-view">
|
||||
<TabPanel value={tabValue} index={0} classNameRoot={['FormView-nonTabPanel',className].join(' ')}
|
||||
<TabPanel value={tabValue} index={0} classNameRoot={[isSingleCollection ? 'FormView-singleCollectionPanel' : 'FormView-nonTabPanel',className].join(' ')}
|
||||
className={contentClassName.join(' ')}>
|
||||
{Object.keys(finalTabs).map((tabName)=>{
|
||||
return (
|
||||
|
@ -261,7 +261,7 @@ class UserManagementSchema extends BaseUISchema {
|
||||
return [
|
||||
{
|
||||
id: 'userManagement', label: '', type: 'collection', schema: obj.userManagementCollObj,
|
||||
canAdd: true, canDelete: true, isFullTab: true, group: 'temp_user',
|
||||
canAdd: true, canDelete: true, isFullTab: true,
|
||||
addOnTop: true,
|
||||
canDeleteRow: (row)=>{
|
||||
return row['id'] != current_user['id'];
|
||||
|
@ -70,8 +70,8 @@ describe('SchemaView', ()=>{
|
||||
await user.type(ctrl.container.querySelector('[name="field2"]'), '2');
|
||||
await user.type(ctrl.container.querySelector('[name="field5"]'), 'val5');
|
||||
/* Add a row */
|
||||
await user.click(ctrl.container.querySelector('[data-test="add-row"]'));
|
||||
await user.click(ctrl.container.querySelector('[data-test="add-row"]'));
|
||||
await user.click(ctrl.container.querySelector('button[data-test="add-row"]'));
|
||||
await user.click(ctrl.container.querySelector('button[data-test="add-row"]'));
|
||||
await user.type(ctrl.container.querySelectorAll('[name="field5"]')[0], 'rval51');
|
||||
await user.type(ctrl.container.querySelectorAll('[name="field5"]')[1], 'rval52');
|
||||
};
|
||||
|
@ -35,7 +35,7 @@ class TestSubSchema extends BaseUISchema {
|
||||
{
|
||||
id: 'field5', label: 'Field5', type: 'multiline', group: null,
|
||||
cell: 'text', mode: ['properties', 'edit', 'create'], disabled: false,
|
||||
noEmpty: true, minWidth: '50%',
|
||||
noEmpty: true, minWidth: 50,
|
||||
},
|
||||
{
|
||||
id: 'fieldskip', label: 'FieldSkip', type: 'text', group: null,
|
||||
|
@ -65,5 +65,33 @@ document.createRange = () => {
|
||||
return range;
|
||||
};
|
||||
|
||||
// for virtual tables, height should exist.
|
||||
Element.prototype.getBoundingClientRect = jest.fn(function () {
|
||||
if (this.classList?.contains('pgrt')) {
|
||||
return {
|
||||
width: 400,
|
||||
height: 400,
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
toJSON: () => {},
|
||||
};
|
||||
}
|
||||
return {
|
||||
width: 0,
|
||||
height: 0,
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
toJSON: () => {},
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
jest.setTimeout(18000); // 1 second
|
||||
|
Loading…
Reference in New Issue
Block a user