mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-17 21:00:45 +00:00
Merge pull request #39 from Kingsrook/feature/select-text-on-child-tables
refactored mouse events on tables into DataGridUtils to fix text sele…
This commit is contained in:
@ -27,8 +27,8 @@ import Button from "@mui/material/Button";
|
|||||||
import Icon from "@mui/material/Icon";
|
import Icon from "@mui/material/Icon";
|
||||||
import Tooltip from "@mui/material/Tooltip/Tooltip";
|
import Tooltip from "@mui/material/Tooltip/Tooltip";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import {DataGridPro, GridCallbackDetails, GridRowParams, MuiEvent} from "@mui/x-data-grid-pro";
|
import {DataGridPro, GridCallbackDetails, GridEventListener, GridFilterModel, gridPreferencePanelStateSelector, GridRowParams, GridSelectionModel, GridToolbarColumnsButton, GridToolbarContainer, GridToolbarDensitySelector, GridToolbarExportContainer, GridToolbarFilterButton, MuiEvent, useGridApiContext, useGridApiEventHandler, useGridSelector} from "@mui/x-data-grid-pro";
|
||||||
import React, {useEffect, useState} from "react";
|
import React, {useEffect, useRef, useState} from "react";
|
||||||
import {useNavigate, Link} from "react-router-dom";
|
import {useNavigate, Link} from "react-router-dom";
|
||||||
import Widget, {AddNewRecordButton, LabelComponent} from "qqq/components/widgets/Widget";
|
import Widget, {AddNewRecordButton, LabelComponent} from "qqq/components/widgets/Widget";
|
||||||
import DataGridUtils from "qqq/utils/DataGridUtils";
|
import DataGridUtils from "qqq/utils/DataGridUtils";
|
||||||
@ -48,12 +48,15 @@ const qController = Client.getInstance();
|
|||||||
|
|
||||||
function RecordGridWidget({widgetMetaData, data}: Props): JSX.Element
|
function RecordGridWidget({widgetMetaData, data}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
|
const instance = useRef({timer: null});
|
||||||
const [rows, setRows] = useState([]);
|
const [rows, setRows] = useState([]);
|
||||||
const [records, setRecords] = useState([] as QRecord[])
|
const [records, setRecords] = useState([] as QRecord[])
|
||||||
const [columns, setColumns] = useState([]);
|
const [columns, setColumns] = useState([]);
|
||||||
const [allColumns, setAllColumns] = useState([])
|
const [allColumns, setAllColumns] = useState([])
|
||||||
const [csv, setCsv] = useState(null as string);
|
const [csv, setCsv] = useState(null as string);
|
||||||
const [fileName, setFileName] = useState(null as string);
|
const [fileName, setFileName] = useState(null as string);
|
||||||
|
const [gridMouseDownX, setGridMouseDownX] = useState(0);
|
||||||
|
const [gridMouseDownY, setGridMouseDownY] = useState(0);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
@ -196,19 +199,50 @@ function RecordGridWidget({widgetMetaData, data}: Props): JSX.Element
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// if a grid preference window is open, ignore and reset timer //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
const handleRowClick = (params: GridRowParams, event: MuiEvent<React.MouseEvent>, details: GridCallbackDetails) =>
|
const handleRowClick = (params: GridRowParams, event: MuiEvent<React.MouseEvent>, details: GridCallbackDetails) =>
|
||||||
{
|
{
|
||||||
(async () =>
|
(async () =>
|
||||||
{
|
{
|
||||||
const qInstance = await qController.loadMetaData()
|
const qInstance = await qController.loadMetaData()
|
||||||
const tablePath = qInstance.getTablePathByName(data.childTableMetaData.name)
|
let tablePath = qInstance.getTablePathByName(data.childTableMetaData.name)
|
||||||
if(tablePath)
|
if(tablePath)
|
||||||
{
|
{
|
||||||
navigate(`${tablePath}/${params.id}`);
|
tablePath = `${tablePath}/${params.id}`;
|
||||||
|
DataGridUtils.handleRowClick(tablePath, event, gridMouseDownX, gridMouseDownY, navigate, instance);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** So that we can useGridApiContext to add event handlers for mouse down and
|
||||||
|
** row double-click (to make it so you don't accidentally click into records),
|
||||||
|
** we have to define a grid component, so even though we don't want a custom
|
||||||
|
** toolbar, that's why we have this (and why it returns empty)
|
||||||
|
*******************************************************************************/
|
||||||
|
function CustomToolbar()
|
||||||
|
{
|
||||||
|
const handleMouseDown: GridEventListener<"cellMouseDown"> = ( params, event, details ) =>
|
||||||
|
{
|
||||||
|
setGridMouseDownX(event.clientX);
|
||||||
|
setGridMouseDownY(event.clientY);
|
||||||
|
clearTimeout(instance.current.timer);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDoubleClick: GridEventListener<"rowDoubleClick"> = (event: any) =>
|
||||||
|
{
|
||||||
|
clearTimeout(instance.current.timer);
|
||||||
|
};
|
||||||
|
|
||||||
|
const apiRef = useGridApiContext();
|
||||||
|
useGridApiEventHandler(apiRef, "cellMouseDown", handleMouseDown);
|
||||||
|
useGridApiEventHandler(apiRef, "rowDoubleClick", handleDoubleClick);
|
||||||
|
|
||||||
|
return (<GridToolbarContainer />);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Widget
|
<Widget
|
||||||
@ -233,7 +267,9 @@ function RecordGridWidget({widgetMetaData, data}: Props): JSX.Element
|
|||||||
getRowClassName={(params) => (params.indexRelativeToCurrentPage % 2 === 0 ? "even" : "odd")}
|
getRowClassName={(params) => (params.indexRelativeToCurrentPage % 2 === 0 ? "even" : "odd")}
|
||||||
onRowClick={handleRowClick}
|
onRowClick={handleRowClick}
|
||||||
// getRowHeight={() => "auto"} // maybe nice? wraps values in cells...
|
// getRowHeight={() => "auto"} // maybe nice? wraps values in cells...
|
||||||
// components={{Toolbar: CustomToolbar, Pagination: CustomPagination, LoadingOverlay: Loading}}
|
components={{
|
||||||
|
Toolbar: CustomToolbar
|
||||||
|
}}
|
||||||
// pinnedColumns={pinnedColumns}
|
// pinnedColumns={pinnedColumns}
|
||||||
// onPinnedColumnsChange={handlePinnedColumnsChange}
|
// onPinnedColumnsChange={handlePinnedColumnsChange}
|
||||||
// pagination
|
// pagination
|
||||||
|
@ -948,37 +948,13 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
let id = encodeURIComponent(params.id);
|
||||||
// strategy for when to trigger or not trigger a row click: //
|
if (table.primaryKeyField !== "id")
|
||||||
// To avoid a drag-event that highlighted text in a cell: //
|
|
||||||
// - we capture the x & y upon mouse-down - then compare them in this method (which fires when the mouse is up) //
|
|
||||||
// if they are more than 5 pixels away from the mouse-down, then assume it's a drag, not a click. //
|
|
||||||
// - avoid clicking the row upon double-click, by setting a 500ms timer here - and in the onDoubleClick handler, //
|
|
||||||
// cancelling the timer. //
|
|
||||||
// - also avoid a click, then click-again-and-start-dragging, by always cancelling the timer in mouse-down. //
|
|
||||||
// All in, these seem to have good results - the only downside being the half-second delay... //
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
const diff = Math.max(Math.abs(event.clientX - gridMouseDownX), Math.abs(event.clientY - gridMouseDownY));
|
|
||||||
if (diff < 5)
|
|
||||||
{
|
{
|
||||||
console.log("clearing timeout");
|
id = encodeURIComponent(params.row[tableMetaData.primaryKeyField]);
|
||||||
clearTimeout(instance.current.timer);
|
|
||||||
instance.current.timer = setTimeout(() =>
|
|
||||||
{
|
|
||||||
if (table.primaryKeyField !== "id")
|
|
||||||
{
|
|
||||||
navigate(`${metaData.getTablePathByName(tableName)}/${encodeURIComponent(params.row[tableMetaData.primaryKeyField])}`);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
navigate(`${metaData.getTablePathByName(tableName)}/${encodeURIComponent(params.id)}`);
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
console.log(`row-click mouse-up happened ${diff} x or y pixels away from the mouse-down - so not considering it a click.`);
|
|
||||||
}
|
}
|
||||||
|
const tablePath = `${metaData.getTablePathByName(table.name)}/${id}`;
|
||||||
|
DataGridUtils.handleRowClick(tablePath, event, gridMouseDownX, gridMouseDownY, navigate, instance);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,11 +26,11 @@ import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstan
|
|||||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||||
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||||
import Tooltip from "@mui/material/Tooltip/Tooltip";
|
import Tooltip from "@mui/material/Tooltip/Tooltip";
|
||||||
import {GridColDef, GridFilterItem, GridRowsProp} from "@mui/x-data-grid-pro";
|
import {GridColDef, GridFilterItem, GridRowsProp, MuiEvent} from "@mui/x-data-grid-pro";
|
||||||
import {GridFilterOperator} from "@mui/x-data-grid/models/gridFilterOperator";
|
import {GridFilterOperator} from "@mui/x-data-grid/models/gridFilterOperator";
|
||||||
import {GridColumnHeaderParams} from "@mui/x-data-grid/models/params/gridColumnHeaderParams";
|
import {GridColumnHeaderParams} from "@mui/x-data-grid/models/params/gridColumnHeaderParams";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Link} from "react-router-dom";
|
import {Link, NavigateFunction} from "react-router-dom";
|
||||||
import HelpContent, {hasHelpContent} from "qqq/components/misc/HelpContent";
|
import HelpContent, {hasHelpContent} from "qqq/components/misc/HelpContent";
|
||||||
import {buildQGridPvsOperators, QGridBlobOperators, QGridBooleanOperators, QGridNumericOperators, QGridStringOperators} from "qqq/pages/records/query/GridFilterOperators";
|
import {buildQGridPvsOperators, QGridBlobOperators, QGridBooleanOperators, QGridNumericOperators, QGridStringOperators} from "qqq/pages/records/query/GridFilterOperators";
|
||||||
import ValueUtils from "qqq/utils/qqq/ValueUtils";
|
import ValueUtils from "qqq/utils/qqq/ValueUtils";
|
||||||
@ -84,6 +84,36 @@ const QGridDateTimeOperators = [
|
|||||||
|
|
||||||
export default class DataGridUtils
|
export default class DataGridUtils
|
||||||
{
|
{
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static handleRowClick = (path: string, event: MuiEvent<React.MouseEvent>, gridMouseDownX: number, gridMouseDownY: number, navigate: NavigateFunction, instance: any) =>
|
||||||
|
{
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// strategy for when to trigger or not trigger a row click: //
|
||||||
|
// To avoid a drag-event that highlighted text in a cell: //
|
||||||
|
// - we capture the x & y upon mouse-down - then compare them in this method (which fires when the mouse is up) //
|
||||||
|
// if they are more than 5 pixels away from the mouse-down, then assume it's a drag, not a click. //
|
||||||
|
// - avoid clicking the row upon double-click, by setting a 500ms timer here - and in the onDoubleClick handler, //
|
||||||
|
// cancelling the timer. //
|
||||||
|
// - also avoid a click, then click-again-and-start-dragging, by always cancelling the timer in mouse-down. //
|
||||||
|
// All in, these seem to have good results - the only downside being the half-second delay... //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
const diff = Math.max(Math.abs(event.clientX - gridMouseDownX), Math.abs(event.clientY - gridMouseDownY));
|
||||||
|
if (diff < 5)
|
||||||
|
{
|
||||||
|
console.log("clearing timeout");
|
||||||
|
clearTimeout(instance.current.timer);
|
||||||
|
instance.current.timer = setTimeout(() =>
|
||||||
|
{
|
||||||
|
navigate(path);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.log(`row-click mouse-up happened ${diff} x or y pixels away from the mouse-down - so not considering it a click.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
|
Reference in New Issue
Block a user