mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2024-07-07 04:53:25 -05: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 { usePgAdmin } from '../BrowserComponent';
|
||||||
import { requestAnimationAndFocus } from '../utils';
|
import { requestAnimationAndFocus } from '../utils';
|
||||||
import { PgReactTable, PgReactTableBody, PgReactTableCell, PgReactTableHeader, PgReactTableRow, PgReactTableRowContent, PgReactTableRowExpandContent } from '../components/PgReactTableStyled';
|
import { PgReactTable, PgReactTableBody, PgReactTableCell, PgReactTableHeader, PgReactTableRow, PgReactTableRowContent, PgReactTableRowExpandContent } from '../components/PgReactTableStyled';
|
||||||
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||||
|
|
||||||
const StyledBox = styled(Box)(({theme}) => ({
|
const StyledBox = styled(Box)(({theme}) => ({
|
||||||
'& .DataGridView-grid': {
|
'& .DataGridView-grid': {
|
||||||
...theme.mixins.panelBorder,
|
...theme.mixins.panelBorder,
|
||||||
backgroundColor: theme.palette.background.default,
|
backgroundColor: theme.palette.background.default,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
minHeight: 0,
|
||||||
|
height: '100%',
|
||||||
'& .DataGridView-gridHeader': {
|
'& .DataGridView-gridHeader': {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
...theme.mixins.panelBorder.bottom,
|
...theme.mixins.panelBorder.bottom,
|
||||||
|
@ -69,7 +74,6 @@ const StyledBox = styled(Box)(({theme}) => ({
|
||||||
'&.pgrt-table': {
|
'&.pgrt-table': {
|
||||||
'& .pgrt-body':{
|
'& .pgrt-body':{
|
||||||
'& .pgrt-row': {
|
'& .pgrt-row': {
|
||||||
position: 'unset',
|
|
||||||
backgroundColor: theme.otherVars.emptySpaceBg,
|
backgroundColor: theme.otherVars.emptySpaceBg,
|
||||||
'& .pgrt-row-content':{
|
'& .pgrt-row-content':{
|
||||||
'& .pgrd-row-cell': {
|
'& .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 ()=>{
|
return ()=>{
|
||||||
/* Cleanup the listeners when unmounting */
|
/* Cleanup the listeners when unmounting */
|
||||||
depListener?.removeDepListener(accessPath);
|
depListener?.removeDepListener(accessPath);
|
||||||
|
@ -494,28 +495,6 @@ export default function DataGridView({
|
||||||
},[props.canEdit, props.canDelete, props.canReorder]
|
},[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 columnVisibility = useMemo(()=>{
|
||||||
const ret = {};
|
const ret = {};
|
||||||
|
|
||||||
|
@ -544,6 +523,27 @@ export default function DataGridView({
|
||||||
|
|
||||||
const rows = table.getRowModel().rows;
|
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(()=>{
|
useEffect(()=>{
|
||||||
let rowsPromise = fixedRows;
|
let rowsPromise = fixedRows;
|
||||||
|
|
||||||
|
@ -564,8 +564,17 @@ export default function DataGridView({
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
if(newRowIndex.current >= 0) {
|
if(newRowIndex.current >= 0) {
|
||||||
rows[newRowIndex.current]?.toggleExpanded(true);
|
virtualizer.scrollToIndex(newRowIndex.current);
|
||||||
newRowIndex.current = null;
|
|
||||||
|
// 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]);
|
}, [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 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) {
|
if(!props.visible) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
@ -598,12 +619,19 @@ export default function DataGridView({
|
||||||
<DndProvider backend={HTML5Backend}>
|
<DndProvider backend={HTML5Backend}>
|
||||||
<PgReactTable ref={tableRef} table={table} data-test="data-grid-view" tableClassName='DataGridView-table'>
|
<PgReactTable ref={tableRef} table={table} data-test="data-grid-view" tableClassName='DataGridView-table'>
|
||||||
<PgReactTableHeader table={table} />
|
<PgReactTableHeader table={table} />
|
||||||
<PgReactTableBody>
|
<PgReactTableBody style={{ height: virtualizer.getTotalSize() + 'px'}}>
|
||||||
{rows.map((row, i) => {
|
{virtualizer.getVirtualItems().map((virtualRow) => {
|
||||||
return <PgReactTableRow key={row.index}>
|
const row = rows[virtualRow.index];
|
||||||
<DataTableRow index={i} row={row} totalRows={rows.length} isResizing={isResizing}
|
|
||||||
|
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])}
|
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 &&
|
{props.canEdit &&
|
||||||
<PgReactTableRowExpandContent row={row}>
|
<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 { styled } from '@mui/material/styles';
|
||||||
import { Box, Tab, Tabs, Grid } from '@mui/material';
|
import { Box, Tab, Tabs, Grid } from '@mui/material';
|
||||||
import _ from 'lodash';
|
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 */
|
/* Optional SQL tab */
|
||||||
|
@ -423,6 +432,17 @@ export default function FormView({
|
||||||
onTabChange?.(tabValue, Object.keys(tabs)[tabValue], sqlTabActive);
|
onTabChange?.(tabValue, Object.keys(tabs)[tabValue], sqlTabActive);
|
||||||
}, [tabValue]);
|
}, [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 */
|
/* check whether form is kept hidden by visible prop */
|
||||||
if(!_.isUndefined(visible) && !visible) {
|
if(!_.isUndefined(visible) && !visible) {
|
||||||
return <></>;
|
return <></>;
|
||||||
|
@ -463,10 +483,10 @@ export default function FormView({
|
||||||
</StyledBox>
|
</StyledBox>
|
||||||
);
|
);
|
||||||
} else {
|
} 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 (
|
return (
|
||||||
<StyledBox height="100%" display="flex" flexDirection="column" className={className} ref={formRef} data-test="form-view">
|
<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(' ')}>
|
className={contentClassName.join(' ')}>
|
||||||
{Object.keys(finalTabs).map((tabName)=>{
|
{Object.keys(finalTabs).map((tabName)=>{
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -261,7 +261,7 @@ class UserManagementSchema extends BaseUISchema {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
id: 'userManagement', label: '', type: 'collection', schema: obj.userManagementCollObj,
|
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,
|
addOnTop: true,
|
||||||
canDeleteRow: (row)=>{
|
canDeleteRow: (row)=>{
|
||||||
return row['id'] != current_user['id'];
|
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="field2"]'), '2');
|
||||||
await user.type(ctrl.container.querySelector('[name="field5"]'), 'val5');
|
await user.type(ctrl.container.querySelector('[name="field5"]'), 'val5');
|
||||||
/* Add a row */
|
/* Add a 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('[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"]')[0], 'rval51');
|
||||||
await user.type(ctrl.container.querySelectorAll('[name="field5"]')[1], 'rval52');
|
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,
|
id: 'field5', label: 'Field5', type: 'multiline', group: null,
|
||||||
cell: 'text', mode: ['properties', 'edit', 'create'], disabled: false,
|
cell: 'text', mode: ['properties', 'edit', 'create'], disabled: false,
|
||||||
noEmpty: true, minWidth: '50%',
|
noEmpty: true, minWidth: 50,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'fieldskip', label: 'FieldSkip', type: 'text', group: null,
|
id: 'fieldskip', label: 'FieldSkip', type: 'text', group: null,
|
||||||
|
|
|
@ -65,5 +65,33 @@ document.createRange = () => {
|
||||||
return range;
|
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
|
jest.setTimeout(18000); // 1 second
|
||||||
|
|
Loading…
Reference in New Issue
Block a user