mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-18 13:20:43 +00:00
CE-551 Add keyboard shortcuts to Record Query screen; fix to not take keyboard commands while keyboard-help screen is up
This commit is contained in:
@ -574,6 +574,7 @@ export default function App()
|
|||||||
const [tableMetaData, setTableMetaData] = useState(null);
|
const [tableMetaData, setTableMetaData] = useState(null);
|
||||||
const [tableProcesses, setTableProcesses] = useState(null);
|
const [tableProcesses, setTableProcesses] = useState(null);
|
||||||
const [dotMenuOpen, setDotMenuOpen] = useState(false);
|
const [dotMenuOpen, setDotMenuOpen] = useState(false);
|
||||||
|
const [keyboardHelpOpen, setKeyboardHelpOpen] = useState(false);
|
||||||
return (
|
return (
|
||||||
|
|
||||||
appRoutes && (
|
appRoutes && (
|
||||||
@ -583,11 +584,13 @@ export default function App()
|
|||||||
tableMetaData: tableMetaData,
|
tableMetaData: tableMetaData,
|
||||||
tableProcesses: tableProcesses,
|
tableProcesses: tableProcesses,
|
||||||
dotMenuOpen: dotMenuOpen,
|
dotMenuOpen: dotMenuOpen,
|
||||||
|
keyboardHelpOpen: keyboardHelpOpen,
|
||||||
setPageHeader: (header: string | JSX.Element) => setPageHeader(header),
|
setPageHeader: (header: string | JSX.Element) => setPageHeader(header),
|
||||||
setAccentColor: (accentColor: string) => setAccentColor(accentColor),
|
setAccentColor: (accentColor: string) => setAccentColor(accentColor),
|
||||||
setTableMetaData: (tableMetaData: QTableMetaData) => setTableMetaData(tableMetaData),
|
setTableMetaData: (tableMetaData: QTableMetaData) => setTableMetaData(tableMetaData),
|
||||||
setTableProcesses: (tableProcesses: QProcessMetaData[]) => setTableProcesses(tableProcesses),
|
setTableProcesses: (tableProcesses: QProcessMetaData[]) => setTableProcesses(tableProcesses),
|
||||||
setDotMenuOpen: (dotMenuOpent: boolean) => setDotMenuOpen(dotMenuOpent),
|
setDotMenuOpen: (dotMenuOpent: boolean) => setDotMenuOpen(dotMenuOpent),
|
||||||
|
setKeyboardHelpOpen: (keyboardHelpOpen: boolean) => setKeyboardHelpOpen(keyboardHelpOpen),
|
||||||
pathToLabelMap: pathToLabelMap,
|
pathToLabelMap: pathToLabelMap,
|
||||||
branding: branding
|
branding: branding
|
||||||
}}>
|
}}>
|
||||||
|
@ -67,9 +67,8 @@ const CommandMenu = ({metaData}: Props) =>
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const pathParts = location.pathname.replace(/\/+$/, "").split("/");
|
const pathParts = location.pathname.replace(/\/+$/, "").split("/");
|
||||||
|
|
||||||
const {accentColor, tableMetaData, dotMenuOpen, setDotMenuOpen, setTableMetaData, tableProcesses} = useContext(QContext);
|
const {accentColor, tableMetaData, dotMenuOpen, setDotMenuOpen, keyboardHelpOpen, setKeyboardHelpOpen, setTableMetaData, tableProcesses} = useContext(QContext);
|
||||||
|
|
||||||
const [keyboardHelpOpen, setKeyboardHelpOpen] = useState(false)
|
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
function evalueKeyPress(e: KeyboardEvent)
|
function evalueKeyPress(e: KeyboardEvent)
|
||||||
@ -351,6 +350,14 @@ const CommandMenu = ({metaData}: Props) =>
|
|||||||
<Grid item xs={6} className={classes.item}><span className={classes.keyboardKey}>?</span>Open Keyboard Shortcuts Help</Grid>
|
<Grid item xs={6} className={classes.item}><span className={classes.keyboardKey}>?</span>Open Keyboard Shortcuts Help</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
<Typography variant="h6" pt={3}>Table Query</Typography>
|
||||||
|
<Grid container columnSpacing={5} rowSpacing={1}>
|
||||||
|
<Grid item xs={6} className={classes.item}><span className={classes.keyboardKey}>n</span>Create a New Record</Grid>
|
||||||
|
<Grid item xs={6} className={classes.item}><span className={classes.keyboardKey}>r</span>Refresh the Query</Grid>
|
||||||
|
<Grid item xs={6} className={classes.item}><span className={classes.keyboardKey}>c</span>Open the Columns Panel</Grid>
|
||||||
|
<Grid item xs={6} className={classes.item}><span className={classes.keyboardKey}>f</span>Open the Filter Panel</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<Typography variant="h6" pt={3}>Record View</Typography>
|
<Typography variant="h6" pt={3}>Record View</Typography>
|
||||||
<Grid container columnSpacing={5} rowSpacing={1}>
|
<Grid container columnSpacing={5} rowSpacing={1}>
|
||||||
<Grid item xs={6} className={classes.item}><span className={classes.keyboardKey}>n</span>Create a New Record</Grid>
|
<Grid item xs={6} className={classes.item}><span className={classes.keyboardKey}>n</span>Create a New Record</Grid>
|
||||||
|
@ -37,6 +37,9 @@ interface QContext
|
|||||||
dotMenuOpen: boolean;
|
dotMenuOpen: boolean;
|
||||||
setDotMenuOpen?: (dotMenuOpen: boolean) => void;
|
setDotMenuOpen?: (dotMenuOpen: boolean) => void;
|
||||||
|
|
||||||
|
keyboardHelpOpen: boolean;
|
||||||
|
setKeyboardHelpOpen?: (keyboardHelpOpen: boolean) => void;
|
||||||
|
|
||||||
tableMetaData?: QTableMetaData;
|
tableMetaData?: QTableMetaData;
|
||||||
setTableMetaData?: (tableMetaData: QTableMetaData) => void;
|
setTableMetaData?: (tableMetaData: QTableMetaData) => void;
|
||||||
|
|
||||||
@ -54,6 +57,7 @@ const defaultState = {
|
|||||||
pageHeader: "",
|
pageHeader: "",
|
||||||
accentColor: "#0062FF",
|
accentColor: "#0062FF",
|
||||||
dotMenuOpen: false,
|
dotMenuOpen: false,
|
||||||
|
keyboardHelpOpen: false,
|
||||||
pathToLabelMap: {},
|
pathToLabelMap: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ import MenuItem from "@mui/material/MenuItem";
|
|||||||
import Modal from "@mui/material/Modal";
|
import Modal from "@mui/material/Modal";
|
||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
import Tooltip from "@mui/material/Tooltip";
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
import {DataGridPro, GridCallbackDetails, GridColDef, GridColumnMenuContainer, GridColumnMenuProps, GridColumnOrderChangeParams, GridColumnPinningMenuItems, GridColumnsMenuItem, GridColumnVisibilityModel, GridDensity, GridEventListener, GridExportMenuItemProps, GridFilterMenuItem, GridFilterModel, GridPinnedColumns, gridPreferencePanelStateSelector, GridRowId, GridRowParams, GridRowsProp, GridSelectionModel, GridSortItem, GridSortModel, GridState, GridToolbarColumnsButton, GridToolbarContainer, GridToolbarDensitySelector, GridToolbarExportContainer, GridToolbarFilterButton, HideGridColMenuItem, MuiEvent, SortGridMenuItems, useGridApiContext, useGridApiEventHandler, useGridSelector} from "@mui/x-data-grid-pro";
|
import {DataGridPro, GridCallbackDetails, GridColDef, GridColumnMenuContainer, GridColumnMenuProps, GridColumnOrderChangeParams, GridColumnPinningMenuItems, GridColumnsMenuItem, GridColumnVisibilityModel, GridDensity, GridEventListener, GridExportMenuItemProps, GridFilterMenuItem, GridFilterModel, GridPinnedColumns, gridPreferencePanelStateSelector, GridRowId, GridRowParams, GridRowsProp, GridSelectionModel, GridSortItem, GridSortModel, GridState, GridToolbarColumnsButton, GridToolbarContainer, GridToolbarDensitySelector, GridToolbarExportContainer, GridToolbarFilterButton, HideGridColMenuItem, MuiEvent, SortGridMenuItems, useGridApiContext, useGridApiEventHandler, useGridSelector, useGridApiRef, GridPreferencePanelsValue} from "@mui/x-data-grid-pro";
|
||||||
import {GridRowModel} from "@mui/x-data-grid/models/gridRows";
|
import {GridRowModel} from "@mui/x-data-grid/models/gridRows";
|
||||||
import FormData from "form-data";
|
import FormData from "form-data";
|
||||||
import React, {forwardRef, useContext, useEffect, useReducer, useRef, useState} from "react";
|
import React, {forwardRef, useContext, useEffect, useReducer, useRef, useState} from "react";
|
||||||
@ -252,12 +252,65 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
const [queryErrors, setQueryErrors] = useState({} as any);
|
const [queryErrors, setQueryErrors] = useState({} as any);
|
||||||
const [receivedQueryErrorTimestamp, setReceivedQueryErrorTimestamp] = useState(new Date());
|
const [receivedQueryErrorTimestamp, setReceivedQueryErrorTimestamp] = useState(new Date());
|
||||||
|
|
||||||
const {setPageHeader} = useContext(QContext);
|
const {setPageHeader, dotMenuOpen, keyboardHelpOpen} = useContext(QContext);
|
||||||
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||||
|
|
||||||
const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget);
|
const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget);
|
||||||
const closeActionsMenu = () => setActionsMenu(null);
|
const closeActionsMenu = () => setActionsMenu(null);
|
||||||
|
|
||||||
|
const gridApiRef = useGridApiRef();
|
||||||
|
|
||||||
|
///////////////////////
|
||||||
|
// Keyboard handling //
|
||||||
|
///////////////////////
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
if(tableMetaData == null)
|
||||||
|
{
|
||||||
|
(async() =>
|
||||||
|
{
|
||||||
|
const tableMetaData = await qController.loadTableMetaData(tableName);
|
||||||
|
setTableMetaData(tableMetaData);
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
const down = (e: KeyboardEvent) =>
|
||||||
|
{
|
||||||
|
const type = (e.target as any).type;
|
||||||
|
const validType = (type !== "text" && type !== "textarea" && type !== "input" && type !== "search");
|
||||||
|
|
||||||
|
if(validType && !dotMenuOpen && !keyboardHelpOpen && !activeModalProcess)
|
||||||
|
{
|
||||||
|
if (! e.metaKey && e.key === "n" && table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission)
|
||||||
|
{
|
||||||
|
e.preventDefault()
|
||||||
|
navigate(`${metaData?.getTablePathByName(tableName)}/create`);
|
||||||
|
}
|
||||||
|
else if (! e.metaKey && e.key === "r")
|
||||||
|
{
|
||||||
|
e.preventDefault()
|
||||||
|
updateTable();
|
||||||
|
}
|
||||||
|
else if (! e.metaKey && e.key === "c")
|
||||||
|
{
|
||||||
|
e.preventDefault()
|
||||||
|
gridApiRef.current.showPreferences(GridPreferencePanelsValue.columns)
|
||||||
|
}
|
||||||
|
else if (! e.metaKey && e.key === "f")
|
||||||
|
{
|
||||||
|
e.preventDefault()
|
||||||
|
gridApiRef.current.showFilterPanel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("keydown", down)
|
||||||
|
return () =>
|
||||||
|
{
|
||||||
|
document.removeEventListener("keydown", down)
|
||||||
|
}
|
||||||
|
}, [dotMenuOpen, keyboardHelpOpen, metaData, activeModalProcess])
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// monitor location changes - if our url looks like a process, then open that process. //
|
// monitor location changes - if our url looks like a process, then open that process. //
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -1934,6 +1987,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
<Card>
|
<Card>
|
||||||
<Box height="100%">
|
<Box height="100%">
|
||||||
<DataGridPro
|
<DataGridPro
|
||||||
|
apiRef={gridApiRef}
|
||||||
components={{
|
components={{
|
||||||
Toolbar: CustomToolbar,
|
Toolbar: CustomToolbar,
|
||||||
Pagination: CustomPagination,
|
Pagination: CustomPagination,
|
||||||
|
@ -116,7 +116,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget);
|
const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget);
|
||||||
const closeActionsMenu = () => setActionsMenu(null);
|
const closeActionsMenu = () => setActionsMenu(null);
|
||||||
|
|
||||||
const {accentColor, setPageHeader, tableMetaData, setTableMetaData, tableProcesses, setTableProcesses, dotMenuOpen} = useContext(QContext);
|
const {accentColor, setPageHeader, tableMetaData, setTableMetaData, tableProcesses, setTableProcesses, dotMenuOpen, keyboardHelpOpen} = useContext(QContext);
|
||||||
|
|
||||||
if (localStorage.getItem(tableVariantLocalStorageKey))
|
if (localStorage.getItem(tableVariantLocalStorageKey))
|
||||||
{
|
{
|
||||||
@ -138,7 +138,9 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
setShowAudit(false);
|
setShowAudit(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Toggle the menu when ⌘K is pressed
|
///////////////////////
|
||||||
|
// Keyboard handling //
|
||||||
|
///////////////////////
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
if(tableMetaData == null)
|
if(tableMetaData == null)
|
||||||
@ -155,7 +157,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
const type = (e.target as any).type;
|
const type = (e.target as any).type;
|
||||||
const validType = (type !== "text" && type !== "textarea" && type !== "input" && type !== "search");
|
const validType = (type !== "text" && type !== "textarea" && type !== "input" && type !== "search");
|
||||||
|
|
||||||
if(validType && !dotMenuOpen && !showAudit && !showEditChildForm)
|
if(validType && !dotMenuOpen && !keyboardHelpOpen && !showAudit && !showEditChildForm)
|
||||||
{
|
{
|
||||||
if (! e.metaKey && e.key === "n" && table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission)
|
if (! e.metaKey && e.key === "n" && table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission)
|
||||||
{
|
{
|
||||||
@ -190,7 +192,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
{
|
{
|
||||||
document.removeEventListener("keydown", down)
|
document.removeEventListener("keydown", down)
|
||||||
}
|
}
|
||||||
}, [dotMenuOpen, showEditChildForm, showAudit, metaData])
|
}, [dotMenuOpen, keyboardHelpOpen, showEditChildForm, showAudit, metaData])
|
||||||
|
|
||||||
const gotoCreate = () =>
|
const gotoCreate = () =>
|
||||||
{
|
{
|
||||||
|
Reference in New Issue
Block a user