Fixed following issues for Query Tool (after React Porting):

1) Find/Replace both opens the same dialogue box.(by clicking menu option)
2) Add New Server Connection > Server options keep loading(For multiple Server groups & should have some server)
3) Fixed CSS issues of slickgrid at various places.
4) C should be captial in ’<New connection…>'
5) In pop title for New Connection, all words should be capital.(Add new connection)
6) Explain > Analaysis tab > Column heading missing ROWS PLAN with cost & In explain only.
7) Explain > Analaysis tab > with cost enabled > Upward arrow size does not match with font of number. Arrow is little bigger than number.
8) Boolean default is not considered while ading new row.(try table from feature test defaults)
9) In query history , when not query history present, warning icon size big. Match it to warning message - No history found
10) Select table/db object > Open query tool from Tools menu > NOT FOUND error is shown. Existing issue, fixed.
11) Any cell just open by clicking it > Do NOT change any thing > Click Ok > Cell is shown as edited.

refs #6131
This commit is contained in:
Aditya Toshniwal 2022-04-20 18:59:28 +05:30 committed by Akshay Joshi
parent 9470c68c18
commit e5ef6a7b21
25 changed files with 336 additions and 106 deletions

View File

@ -151,7 +151,7 @@
"react": "^17.0.1",
"react-aspen": "^1.1.0",
"react-checkbox-tree": "^1.7.2",
"react-data-grid": "^7.0.0-beta.11",
"react-data-grid": "^7.0.0-beta.12",
"react-dom": "^17.0.1",
"react-draggable": "^4.4.4",
"react-leaflet": "^3.2.2",

View File

@ -15,6 +15,7 @@ import FiberManualRecordIcon from '@material-ui/icons/FiberManualRecord';
import HTMLReactParse from 'html-react-parser';
import { commonTableStyles } from '../Theme';
import PropTypes from 'prop-types';
import gettext from 'sources/gettext';
const useStyles = makeStyles((theme)=>({
collapsible: {
@ -172,26 +173,26 @@ export default function Analysis({explainTable}) {
<th rowSpan="2"><button disabled="">#</button></th>
<th rowSpan="2"><button disabled="">Node</button></th>
<th colSpan="2" style={explainTable.show_timings ? {} : {display: 'none'}}>
<button disabled="">Timings</button>
<button disabled="">{gettext('Timings')}</button>
</th>
<th style={(explainTable.show_rowsx || explainTable.show_rows) ? {} : {display: 'none'}}
<th style={(explainTable.show_rowsx || explainTable.show_rows || explainTable.show_plan_rows) ? {} : {display: 'none'}}
colSpan={(explainTable.show_rowsx) ? '3' : '1'}>
<button disabled="">Rows</button>
<button disabled="">{gettext('Rows')}</button>
</th>
<th style={(explainTable.show_rowsx || explainTable.show_rows) ? {} : {display: 'none'}} rowSpan="2">
<button disabled="">Loops</button>
<button disabled="">{gettext('Loops')}</button>
</th>
</tr>
<tr>
<th style={explainTable.show_timings ? {} : {display: 'none'}}>
<button disabled="">Exclusive</button>
<button disabled="">{gettext('Exclusive')}</button>
</th>
<th style={explainTable.show_timings ? {} : {display: 'none'}}>
<button disabled="">Inclusive</button>
<button disabled="">{gettext('Inclusive')}</button>
</th>
<th style={explainTable.show_rowsx ? {} : {display: 'none'}}><button disabled="">Rows X</button></th>
<th style={(explainTable.show_rowsx || explainTable.show_rows) ? {} : {display: 'none'}}><button disabled="">Actual</button></th>
<th style={(explainTable.show_rowsx || explainTable.plan_rows) ? {} : {display: 'none'}}><button disabled="">Plan</button></th>
<th style={explainTable.show_rowsx ? {} : {display: 'none'}}><button disabled="">{gettext('Rows X')}</button></th>
<th style={(explainTable.show_rowsx || explainTable.show_rows) ? {} : {display: 'none'}}><button disabled="">{gettext('Actual')}</button></th>
<th style={(explainTable.show_rowsx || explainTable.show_plan_rows) ? {} : {display: 'none'}}><button disabled="">{gettext('Plan')}</button></th>
</tr>
</thead>
<tbody>

View File

@ -92,7 +92,7 @@ export default function ExplainStatistics({explainTable}) {
<tbody>
{_.sortBy(Object.keys(explainTable.statistics.tables)).map((key, i)=>{
let table = explainTable.statistics.tables[key];
table.sum_of_times = _.sumBy(table.nodes, 'sum_of_times');
table.sum_of_times = _.sumBy(Object.values(table.nodes), 'sum_of_times');
return <React.Fragment key={i}>
<tr className={classes.tableRow}>
<td className={classes.tableName}>{table.name}</td>

View File

@ -419,7 +419,7 @@ export default function Graphical({planData, ctx}) {
</Box>
</Box>} />
<CardContent className={classes.explainContent}>
<table className={clsx(tableStyles.table, tableStyles.borderBottom)}>
<table className={clsx(tableStyles.table, tableStyles.borderBottom, tableStyles.wrapTd)}>
<tbody>
<NodeDetails download={false} plan={explainPlanDetails} />
</tbody>

View File

@ -237,6 +237,14 @@ function nodeExplainTableData(_planData, _ctx) {
info.statistics.nodes[node_info] = node;
}
function parseExplainTableData(plan, ctx) {
nodeExplainTableData(plan, ctx);
plan['Plans']?.map((p)=>{
parseExplainTableData(p, ctx);
});
}
function parsePlan(data, ctx) {
var idx = 1,
lvl = data.level = data.level || [idx],
@ -408,7 +416,6 @@ function parsePlan(data, ctx) {
// Final Width and Height of current node
data['width'] += maxChildWidth;
data['Plans'] = plans;
nodeExplainTableData(data, ctx);
return data;
}
@ -456,6 +463,8 @@ function parsePlanData(data, ctx) {
if (data && 'Settings' in data) {
retPlan['Statistics']['Settings'] = data['Settings'];
}
parseExplainTableData(retPlan['Plan'], ctx);
}
return retPlan;
}

View File

@ -27,6 +27,18 @@ basicSettings = createMuiTheme(basicSettings, {
typography: {
fontSize: 14,
htmlFontSize: 14,
fontFamily: [
'Roboto',
'"Helvetica Neue"',
'-apple-system',
'BlinkMacSystemFont',
'"Segoe UI"',
'Arial',
'sans-serif',
'"Apple Color Emoji"',
'"Segoe UI Emoji"',
'"Segoe UI Symbol"',
].join(','),
},
shape: {
borderRadius: 4,
@ -281,6 +293,9 @@ function getFinalTheme(baseTheme) {
overrides: {
MuiCssBaseline: {
'@global': {
body: {
fontFamily: baseTheme.typography.fontFamily,
},
ul: {
margin: 0,
padding: 0,
@ -618,6 +633,11 @@ export const commonTableStyles = makeStyles((theme)=>({
borderBottom: '1px solid '+theme.otherVars.borderColor,
},
},
wrapTd: {
'& tbody td': {
whiteSpace: 'pre-wrap',
}
},
noHover: {
'& tbody > tr': {
'&:hover': {

View File

@ -462,16 +462,12 @@ export default function CodeMirror({currEditor, name, value, options, events, re
useEffect(()=>{
if(editor.current) {
if(disabled) {
editor.current.setOption('readOnly', true);
cmWrapper.current.classList.add(classes.hideCursor);
} else if(readonly) {
if(readonly || disabled) {
editor.current.setOption('readOnly', true);
editor.current.addKeyMap({'Tab': false});
editor.current.addKeyMap({'Shift-Tab': false});
cmWrapper.current.classList.add(classes.hideCursor);
} else {
cmWrapper.current.classList.remove('cm_disabled');
editor.current.setOption('readOnly', false);
editor.current.removeKeyMap('Tab');
editor.current.removeKeyMap('Shift-Tab');

View File

@ -9,7 +9,9 @@ const useStyles = makeStyles((theme)=>({
color: theme.palette.text.primary,
margin: 'auto',
marginTop: '24px',
fontSize: '0.9em',
fontSize: '0.8rem',
display: 'flex',
alignItems: 'center',
},
}));
@ -17,8 +19,8 @@ export default function EmptyPanelMessage({text}) {
const classes = useStyles();
return (
<Box className={classes.root}>
<InfoRoundedIcon />
<span marginLeft='4px'>{text}</span>
<InfoRoundedIcon style={{height: '1.2rem'}}/>
<span style={{marginLeft: '4px'}}>{text}</span>
</Box>
);
}

View File

@ -81,6 +81,9 @@ const useStyles = makeStyles((theme)=>({
},
'& > div': {
padding: '4px 10px',
'&:focus': {
outline: '2px solid '+theme.otherVars.activeBorder,
}
},
'& .drag-initiator': {
display: 'flex',
@ -190,6 +193,11 @@ export class LayoutHelper {
return Boolean(docker.find(panelId));
}
static isTabVisible(docker, panelId) {
let panelData = docker.find(panelId);
return panelData?.parent?.activeId == panelData.id;
}
static openTab(docker, panelData, refTabId, direction, forceRerender=false) {
let panel = docker.find(panelData.id);
if(panel) {
@ -203,6 +211,43 @@ export class LayoutHelper {
docker.dockMove(LayoutHelper.getPanel(panelData), tgtPanel, direction);
}
}
static moveTo(direction) {
let dockBar = document.activeElement.closest('.dock')?.querySelector('.dock-bar.drag-initiator');
if(dockBar) {
let key = {
key: 'ArrowRight', keyCode: 39, which: 39, code: 'ArrowRight',
metaKey: false, ctrlKey: false, shiftKey: false, altKey: false,
bubbles: true,
};
if(direction == 'right') {
key = {
...key,
key: 'ArrowRight', keyCode: 39, which: 39, code: 'ArrowRight'
};
} else if(direction == 'left') {
key = {
...key,
key: 'ArrowLeft', keyCode: 37, which: 37, code: 'ArrowLeft',
};
}
dockBar.dispatchEvent(new KeyboardEvent('keydown', key));
}
}
static switchPanel() {
let currDockPanel = document.activeElement.closest('.dock-panel.dock-style-default');
let dockLayoutPanels = currDockPanel?.closest('.dock-layout').querySelectorAll('.dock-panel.dock-style-default');
if(dockLayoutPanels?.length > 1) {
for(let i=0; i<dockLayoutPanels.length; i++) {
if(dockLayoutPanels[i] == currDockPanel) {
let newPanelIdx = (i+1)%dockLayoutPanels.length;
dockLayoutPanels[newPanelIdx]?.querySelector('.dock-tab.dock-tab-active .dock-tab-btn')?.focus();
break;
}
}
}
}
}
function saveLayout(layoutObj, layoutId) {

View File

@ -0,0 +1,83 @@
.slick-row .cell-actions {
text-align: left;
}
.slick-row.selected .cell-selection {
background-color: transparent; /* show default selected row background */
}
.slick-cell span[data-cell-type="row-header-selector"] {
display: block;
text-align: center;
}
/*
SlickGrid, To fix the issue of width misalignment between Column Header &
actual Column in Mozilla Firefox browser
Ref: https://github.com/mleibman/SlickGrid/issues/742
*/
.slickgrid, .slickgrid *, .slick-header-column {
box-sizing: content-box;
-moz-box-sizing: content-box;
-webkit-box-sizing: content-box;
-ms-box-sizing: content-box;
}
.slick-cell.selected span[data-cell-type="row-header-selector"] {
color: $color-primary-fg;
}
.slick-cell.cell-move-handle {
font-weight: bold;
text-align: right;
border-right: solid $border-color;
background: $color-gray-lighter;
cursor: move;
&:hover {
background: $color-gray-light;
}
}
.cell-selection {
border-right-color: $border-color;
border-right-style: solid;
background: $color-gray-lighter;
color: $color-gray;
text-align: right;
font-size: 10px;
}
.slick-row.selected .cell-move-handle {
background: $color-warning-light;
}
.slick-row.complete {
background-color: $color-success-light;
color: $color-gray-dark;
}
.slick-row:hover .slick-cell{
border-top: $table-hover-border;
border-bottom: $table-hover-border;
background-color: $table-hover-bg-color;
}
.slick-row .slick-cell {
border-bottom: $panel-border;
border-right: $panel-border;
z-index: 0;
}
.slick-cell.active {
border: 1px solid transparent;
border-right: 1px solid $color-gray-light;
border-bottom-color: $color-gray-light;
}
.ui-widget-content.slick-row {
&.even, &.odd {
background: none;
background-color: $table-bg;
}
}

View File

@ -35,5 +35,6 @@ $theme-colors: (
@import 'jsoneditor.overrides';
@import 'pgadmin4-tree.overrides';
@import 'pgadmin4-tree/src/css/styles';
@import 'slickgrid.overrides';
@import 'rc-dock/dist/rc-dock.css';
@import '@szhsin/react-menu/dist/index.css';

View File

@ -60,6 +60,13 @@
.slick-header-sortable {
cursor: pointer !important;
.slick-sort-indicator {
width: 0px;
height: 0px;
position: relative;
top: -2px;
}
.slick-sort-indicator-asc {
background: none;
border-top: none;

View File

@ -348,7 +348,7 @@ export default class SQLEditor {
<input name="close_url" value="${closeUrl}" hidden />`;
if(sURL){
if(sURL && typeof(sURL) === 'string'){
queryToolForm +=`<input name="query_url" value="${sURL}" hidden />`;
}
if(sql_filter) {

View File

@ -487,7 +487,7 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN
const onClose = ()=>LayoutHelper.close(docker.current, 'new-conn');
LayoutHelper.openDialog(docker.current, {
id: 'new-conn',
title: gettext('Add new connection'),
title: gettext('Add New Connection'),
content: <NewConnectionDialog onSave={(_isNew, data)=>{
let connectionData = {
sgid: 0,

View File

@ -173,6 +173,10 @@ export function TextEditor({row, column, onRowChange, onClose}) {
if(column.is_array && !isValidArray(localVal)) {
Notifier.error(gettext('Arrays must start with "{" and end with "}"'));
} else {
if(value == localVal) {
onClose(false);
return;
}
let columnVal = textColumnFinalVal(localVal, column);
onRowChange({ ...row, [column.key]: columnVal}, true);
onClose();
@ -320,6 +324,10 @@ export function JsonTextEditor({row, column, onRowChange, onClose}) {
setLocalVal(newVal);
}, []);
const onOK = ()=>{
if(value == localVal) {
onClose(false);
return;
}
onRowChange({ ...row, [column.key]: localVal}, true);
onClose();
};

View File

@ -105,7 +105,7 @@ function CustomRow(props) {
const rowInfoValue = {
rowIdx: props.rowIdx,
getCellElement: (colIdx)=>{
return rowRef.current.querySelector(`.rdg-cell[aria-colindex="${colIdx+1}"]`);
return rowRef.current?.querySelector(`.rdg-cell[aria-colindex="${colIdx+1}"]`);
}
};
return (

View File

@ -67,7 +67,8 @@ class NewConnectionSchema extends BaseUISchema {
return;
}
/* initial selection */
_.find(v, (o)=>o.value==obj.params.sid).selected = true;
let foundServer = _.find(v, (o)=>o.value==obj.params.sid);
foundServer && (foundServer.selected = true);
groupedOptions.push({
label: k,
options: v,

View File

@ -129,7 +129,7 @@ export function ConnectionBar({connected, connecting, connectionStatus, connecti
onClick={onConnItemClick}>{conn.conn_title}</PgMenuItem>
);
})}
<PgMenuItem onClick={onNewConnClick}>{`< ${gettext('New connection...')} >`}</PgMenuItem>
<PgMenuItem onClick={onNewConnClick}>{`< ${gettext('New Connection...')} >`}</PgMenuItem>
</PgMenu>
</>
);

View File

@ -19,6 +19,10 @@ import gettext from 'sources/gettext';
import Theme from 'sources/Theme';
import clsx from 'clsx';
import PropTypes from 'prop-types';
import { Box } from '@material-ui/core';
import { LayoutHelper } from '../../../../../../static/js/helpers/Layout';
import { PANELS } from '../QueryToolConstants';
import { QueryToolContext } from '../QueryToolComponent';
const useStyles = makeStyles((theme)=>({
mapContainer: {
@ -233,7 +237,7 @@ function GeoJsonLayer({data}) {
} else {
mapObj.setView(bounds.getCenter(), mapObj.getZoom());
}
});
}, [data]);
return (
<GeoJSON
@ -278,6 +282,7 @@ GeoJsonLayer.propTypes = {
function TheMap({data}) {
const mapObj = useMap();
const infoControl = useRef(null);
const resetLayersKey = useRef(0);
useEffect(()=>{
infoControl.current = Leaflet.control({position: 'topright'});
infoControl.current.onAdd = function () {
@ -288,70 +293,71 @@ function TheMap({data}) {
if(data.infoList.length > 0) {
infoControl.current.addTo(mapObj);
}
resetLayersKey.current++;
return ()=>{infoControl.current && infoControl.current.remove();};
}, [data]);
return (
<>
{data.selectedSRID === 4326 &&
<LayersControl position="topright">
<LayersControl.BaseLayer checked name={gettext('Empty')}>
<TileLayer
url=""
/>
</LayersControl.BaseLayer>
<LayersControl.BaseLayer checked name={gettext('Street')}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
maxZoom={19}
attribution='&copy; <a href="http://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>'
/>
</LayersControl.BaseLayer>
<LayersControl.BaseLayer name={gettext('Topography')}>
<TileLayer
url="https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png"
maxZoom={17}
attribution={
'&copy; <a href="http://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>,'
+ ' &copy; <a href="http://viewfinderpanoramas.org" target="_blank">SRTM</a>,'
+ ' &copy; <a href="https://opentopomap.org" target="_blank">OpenTopoMap</a>'
}
/>
</LayersControl.BaseLayer>
<LayersControl.BaseLayer name={gettext('Gray Style')}>
<TileLayer
url="https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}{r}.png"
maxZoom={19}
attribution={
'&copy; <a href="http://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>,'
+ ' &copy; <a href="http://cartodb.com/attributions" target="_blank">CartoDB</a>'
}
subdomains='abcd'
/>
</LayersControl.BaseLayer>
<LayersControl.BaseLayer name={gettext('Light Color')}>
<TileLayer
url="https://cartodb-basemaps-{s}.global.ssl.fastly.net/rastertiles/voyager/{z}/{x}/{y}{r}.pn"
maxZoom={19}
attribution={
'&copy; <a href="http://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>,'
+ ' &copy; <a href="http://cartodb.com/attributions" target="_blank">CartoDB</a>'
}
subdomains='abcd'
/>
</LayersControl.BaseLayer>
<LayersControl.BaseLayer name={gettext('Dark Matter')}>
<TileLayer
url="https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}{r}.png"
maxZoom={19}
attribution={
'&copy; <a href="http://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>,'
+ ' &copy; <a href="http://cartodb.com/attributions" target="_blank">CartoDB</a>'
}
subdomains='abcd'
/>
</LayersControl.BaseLayer>
</LayersControl>}
<GeoJsonLayer key={data.geoJSONs.length} data={data}/>
<LayersControl position="topright">
<LayersControl.BaseLayer checked name={gettext('Empty')}>
<TileLayer
url=""
/>
</LayersControl.BaseLayer>
<LayersControl.BaseLayer checked name={gettext('Street')}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
maxZoom={19}
attribution='&copy; <a href="http://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>'
/>
</LayersControl.BaseLayer>
<LayersControl.BaseLayer name={gettext('Topography')}>
<TileLayer
url="https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png"
maxZoom={17}
attribution={
'&copy; <a href="http://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>,'
+ ' &copy; <a href="http://viewfinderpanoramas.org" target="_blank">SRTM</a>,'
+ ' &copy; <a href="https://opentopomap.org" target="_blank">OpenTopoMap</a>'
}
/>
</LayersControl.BaseLayer>
<LayersControl.BaseLayer name={gettext('Gray Style')}>
<TileLayer
url="https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}{r}.png"
maxZoom={19}
attribution={
'&copy; <a href="http://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>,'
+ ' &copy; <a href="http://cartodb.com/attributions" target="_blank">CartoDB</a>'
}
subdomains='abcd'
/>
</LayersControl.BaseLayer>
<LayersControl.BaseLayer name={gettext('Light Color')}>
<TileLayer
url="https://cartodb-basemaps-{s}.global.ssl.fastly.net/rastertiles/voyager/{z}/{x}/{y}{r}.pn"
maxZoom={19}
attribution={
'&copy; <a href="http://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>,'
+ ' &copy; <a href="http://cartodb.com/attributions" target="_blank">CartoDB</a>'
}
subdomains='abcd'
/>
</LayersControl.BaseLayer>
<LayersControl.BaseLayer name={gettext('Dark Matter')}>
<TileLayer
url="https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}{r}.png"
maxZoom={19}
attribution={
'&copy; <a href="http://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>,'
+ ' &copy; <a href="http://cartodb.com/attributions" target="_blank">CartoDB</a>'
}
subdomains='abcd'
/>
</LayersControl.BaseLayer>
</LayersControl>}
<GeoJsonLayer key={resetLayersKey.current} data={data} />
</>
);
}
@ -367,18 +373,40 @@ TheMap.propTypes = {
export function GeometryViewer({rows, columns, column}) {
const classes = useStyles();
const mapRef = React.useRef();
const contentRef = React.useRef();
const data = parseData(rows, columns, column);
const queryToolCtx = React.useContext(QueryToolContext);
const crs = data.selectedSRID === 4326 ? CRS.EPSG3857 : CRS.Simple;
useEffect(()=>{
let timeoutId;
const contentResizeObserver = new ResizeObserver(()=>{
clearTimeout(timeoutId);
if(LayoutHelper.isTabVisible(queryToolCtx.docker, PANELS.GEOMETRY)) {
timeoutId = setTimeout(function () {
mapRef.current?.invalidateSize();
}, 100);
}
});
contentResizeObserver.observe(contentRef.current);
}, []);
return (
<MapContainer
crs={crs}
zoom={2} center={[20, 100]}
preferCanvas={true}
scrollWheelZoom={false}
className={classes.mapContainer}
>
<TheMap data={data} />
</MapContainer>
<Box ref={contentRef} width="100%" height="100%">
<MapContainer
crs={crs}
zoom={2} center={[20, 100]}
preferCanvas={true}
scrollWheelZoom={false}
className={classes.mapContainer}
whenCreated={(map)=>{
mapRef.current = map;
}}
>
<TheMap data={data}/>
</MapContainer>
</Box>
);
}

View File

@ -34,6 +34,7 @@ import PropTypes from 'prop-types';
import CustomPropTypes from '../../../../../../static/js/custom_prop_types';
import ConfirmTransactionContent from '../dialogs/ConfirmTransactionContent';
import { isMac } from '../../../../../../static/js/keyboard_shortcuts';
import { LayoutHelper } from '../../../../../../static/js/helpers/Layout';
const useStyles = makeStyles((theme)=>({
root: {
@ -435,6 +436,34 @@ export function MainToolBar({containerRef, onFilterClick, onManageMacros}) {
containerRef
);
/* Panel shortcuts */
useKeyboardShortcuts([
{
shortcut: queryToolPref.move_previous,
options: {
callback: ()=>{
LayoutHelper.moveTo('left');
}
}
},
{
shortcut: queryToolPref.move_next,
options: {
callback: ()=>{
LayoutHelper.moveTo('right');
}
}
},
{
shortcut: queryToolPref.switch_panel,
options: {
callback: ()=>{
LayoutHelper.switchPanel(queryToolCtx.docker);
}
}
},
], containerRef);
return (
<>
<Box className={classes.root}>

View File

@ -311,7 +311,7 @@ export default function Query() {
key.metaKey = true;
key.ctrlKey = false;
key.shiftKey = false;
key.altKey = true;
key.altKey = replace;
}
editor.current?.triggerOnKeyDown(
new KeyboardEvent('keydown', key)

View File

@ -373,7 +373,7 @@ export function QueryHistory() {
React.useEffect(async ()=>{
layoutEvenBus.registerListener(LAYOUT_EVENTS.ACTIVE, (currentTabId)=>{
currentTabId == PANELS.HISTORY && listRef.current.focus();
currentTabId == PANELS.HISTORY && listRef.current?.focus();
});
setLoaderText(gettext('Fetching history...'));
@ -399,7 +399,7 @@ export function QueryHistory() {
refresh({});
};
listRef.current.focus();
listRef.current?.focus();
eventBus.registerListener(QUERY_TOOL_EVENTS.PUSH_HISTORY, pushHistory);
return ()=>eventBus.deregisterListener(QUERY_TOOL_EVENTS.PUSH_HISTORY, pushHistory);
}, []);

View File

@ -1116,8 +1116,8 @@ export function ResultSet() {
}, [selectedRows, selectedColumns, queryData, dataChangeStore, selectedCell.current]);
useEffect(()=>{
const triggerAddRows = (_rows)=>{
let newRows = rsu.current.processRows(_rows, columns, true);
const triggerAddRows = (_rows, fromClipboard)=>{
let newRows = rsu.current.processRows(_rows, columns, fromClipboard);
setRows((prev)=>[...newRows, ...prev]);
let add = {};
newRows.forEach((row)=>{
@ -1191,7 +1191,7 @@ export function ResultSet() {
const rowKeyGetter = React.useCallback((row)=>row[rsu.current.clientPK]);
return (
<Box className={classes.root} ref={containerRef}>
<Box className={classes.root} ref={containerRef} tabIndex="0">
<Loader message={loaderText} />
<Loader message={isLoadingMore ? gettext('Loading more rows...') : null} style={{top: 'unset', right: 'unset', padding: '0.5rem 1rem'}}/>
{(columns.length == 0 && rows.length == 0) &&

View File

@ -70,7 +70,7 @@ export function ResultSetToolbar({containerRef, canEdit}) {
field_separator: queryToolPref.results_grid_field_separator,
});
let copiedRows = copyUtils.getCopiedRows();
eventBus.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_ADD_ROWS, copiedRows);
eventBus.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_ADD_ROWS, copiedRows, true);
}, [queryToolPref]);
const copyData = ()=>{
eventBus.fireEvent(QUERY_TOOL_EVENTS.COPY_DATA, checkedMenuItems['copy_with_headers']);

View File

@ -8880,10 +8880,10 @@ react-checkbox-tree@^1.7.2:
nanoid "^3.0.0"
prop-types "^15.5.8"
react-data-grid@^7.0.0-beta.11:
version "7.0.0-beta.11"
resolved "https://registry.yarnpkg.com/react-data-grid/-/react-data-grid-7.0.0-beta.11.tgz#16e87f87ac2d1f2c33816837f1be3c4210f1e4b2"
integrity sha512-IjJf3GZ7HxH7uSoDaQhKXV9+L8I64xRKgLVQNCblSgvEY20mg2XlMmEjiV9KqROTUM2MqI+IlEpeBLCZRB3mEw==
react-data-grid@^7.0.0-beta.12:
version "7.0.0-beta.12"
resolved "https://registry.yarnpkg.com/react-data-grid/-/react-data-grid-7.0.0-beta.12.tgz#a6310a83a7ad4913a595a8b2a667e4951a95dc58"
integrity sha512-cgKE4fl/glKllpfY444H1ZF4mNDUfIU7kyrSYVUy8W1npTvGk9CL++ASs1pTSSi2Eg2Sx7vqnC1gEx6C92Kqjw==
dependencies:
clsx "^1.1.1"