mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-17 21:00:45 +00:00
Switch to write recordIds/queryFilter to IndexedDB when launching process (instead of passing on query string).
This commit is contained in:
@ -44,9 +44,10 @@ import LinearProgress from "@mui/material/LinearProgress";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Modal from "@mui/material/Modal";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import {ColumnHeaderFilterIconButtonProps, DataGridPro, GridCallbackDetails, GridColDef, GridColumnHeaderParams, GridColumnMenuContainer, GridColumnMenuProps, GridColumnOrderChangeParams, GridColumnPinningMenuItems, GridColumnResizeParams, GridColumnsMenuItem, GridColumnVisibilityModel, GridDensity, GridEventListener, GridFilterMenuItem, GridPinnedColumns, gridPreferencePanelStateSelector, GridPreferencePanelsValue, GridRowId, GridRowParams, GridRowsProp, GridSelectionModel, GridSortItem, GridSortModel, GridState, GridToolbarColumnsButton, GridToolbarContainer, GridToolbarDensitySelector, GridToolbarExportContainer, HideGridColMenuItem, MuiEvent, SortGridMenuItems, useGridApiContext, useGridApiEventHandler, useGridApiRef, useGridSelector} from "@mui/x-data-grid-pro";
|
||||
import {ColumnHeaderFilterIconButtonProps, DataGridPro, GridCallbackDetails, GridColDef, GridColumnHeaderParams, GridColumnMenuContainer, GridColumnMenuProps, GridColumnOrderChangeParams, GridColumnPinningMenuItems, GridColumnResizeParams, GridColumnVisibilityModel, GridDensity, GridEventListener, GridPinnedColumns, gridPreferencePanelStateSelector, GridPreferencePanelsValue, GridRowId, GridRowParams, GridRowsProp, GridSelectionModel, GridSortItem, GridSortModel, GridState, GridToolbarContainer, GridToolbarDensitySelector, HideGridColMenuItem, MuiEvent, SortGridMenuItems, useGridApiContext, useGridApiEventHandler, useGridApiRef, useGridSelector} from "@mui/x-data-grid-pro";
|
||||
import {GridRowModel} from "@mui/x-data-grid/models/gridRows";
|
||||
import FormData from "form-data";
|
||||
import {IDBPDatabase, openDB} from "idb";
|
||||
import React, {forwardRef, useContext, useEffect, useReducer, useRef, useState} from "react";
|
||||
import {useLocation, useNavigate, useSearchParams} from "react-router-dom";
|
||||
import QContext from "QContext";
|
||||
@ -101,6 +102,13 @@ RecordQuery.defaultProps = {
|
||||
///////////////////////////////////////////////////////
|
||||
type PageState = "initial" | "loadingMetaData" | "loadedMetaData" | "loadingView" | "loadedView" | "preparingGrid" | "ready";
|
||||
|
||||
//////////////////////////////////////////
|
||||
// define IndexedDB store & field names //
|
||||
//////////////////////////////////////////
|
||||
const recordIdsForProcessesDBName = "qqq.recordIdsForProcesses";
|
||||
const recordIdsForProcessesStoreName = "recordIdsForProcesses";
|
||||
const timestampIndexAndFieldName = "timestamp";
|
||||
|
||||
const qController = Client.getInstance();
|
||||
|
||||
/*******************************************************************************
|
||||
@ -114,7 +122,6 @@ const getLoadingScreen = () =>
|
||||
</BaseLayout>);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QQQ Record Query Screen component.
|
||||
**
|
||||
@ -1407,17 +1414,114 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** launch/open a modal process. Ends up navigating to the process's path w/
|
||||
** records selected via query string.
|
||||
** For the various functions that work with the recordIdsForProcess indexedDB,
|
||||
** open that database (creating if needed).
|
||||
*******************************************************************************/
|
||||
const openModalProcess = (process: QProcessMetaData = null) =>
|
||||
const openRecordIdsForProcessIndexedDB = async () =>
|
||||
{
|
||||
return await openDB(recordIdsForProcessesDBName, 1, {
|
||||
upgrade(db)
|
||||
{
|
||||
const store = db.createObjectStore(recordIdsForProcessesStoreName, {
|
||||
keyPath: "uuid"
|
||||
});
|
||||
store.createIndex(timestampIndexAndFieldName, timestampIndexAndFieldName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** clean up old indexedDB records that were created to launch processes in the past.
|
||||
*******************************************************************************/
|
||||
const manageRecordIdsForProcessIndexedDB = async (db: IDBPDatabase) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
const now = new Date().getTime();
|
||||
const limit = now - (1000 * 60 * 60 * 24 * 7); // now minus 1 week
|
||||
|
||||
const expiredKeys = await db.getAllKeysFromIndex(recordIdsForProcessesStoreName, timestampIndexAndFieldName, IDBKeyRange.upperBound(limit, true));
|
||||
for (let expiredKey of expiredKeys)
|
||||
{
|
||||
db.delete(recordIdsForProcessesStoreName, expiredKey);
|
||||
}
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
console.log("Error managing recordIdsForProcess in indexeddb: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** we used to pass recordIds (that is, either an array of ids (from checkboxes)
|
||||
** or a json-query-filter) on the query string... but that gets too big, so,
|
||||
** instead, write it ... not to local storage (5MB limit, and we want to keep
|
||||
** them around for ... some period of time, for reloading processes), so,
|
||||
** write it to indexedDB instead.
|
||||
*******************************************************************************/
|
||||
const storeRecordIdsForProcessInIndexedDB = async (recordIds: string[] | QQueryFilter): Promise<string> =>
|
||||
{
|
||||
const uuid = crypto.randomUUID()
|
||||
|
||||
let db = await openRecordIdsForProcessIndexedDB();
|
||||
await db.add(recordIdsForProcessesStoreName, {
|
||||
uuid: uuid,
|
||||
json: JSON.stringify(recordIds),
|
||||
timestamp: new Date().getTime()
|
||||
});
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// we shouldn't need to await this function - it's just good to run the cleanup "sometimes" //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
manageRecordIdsForProcessIndexedDB(db);
|
||||
|
||||
return (uuid);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** when launching a process, if we're to use recordIds(/filter) from indexedDB,
|
||||
** then do that read (async), and open the modal process after it completes.
|
||||
*******************************************************************************/
|
||||
const launchModalProcessUsingRecordIdsFromIndexedDB = async (uuid: string, processMetaData: QProcessMetaData): Promise<void> =>
|
||||
{
|
||||
let db = await openRecordIdsForProcessIndexedDB();
|
||||
const recordIds = await db.get(recordIdsForProcessesStoreName, uuid)
|
||||
|
||||
if(recordIds)
|
||||
{
|
||||
const recordIdsObject = JSON.parse(recordIds.json);
|
||||
setRecordIdsForProcess(recordIdsObject);
|
||||
setActiveModalProcess(processMetaData);
|
||||
}
|
||||
else
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// closing the process will do a navigate - so we can't just set an alert, we need it to be passed in the navigation call as state - //
|
||||
// so pass it as a param to closeModalProcess, which will set it in the navigation state. //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
closeModalProcess({}, "failed to start", "Could not find query filter to start this process. Please try again.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** launch/open a modal process. Writes the records (ids or filter) to indexedDB,
|
||||
** identified by a UUID. Then navigates to the process's path w/
|
||||
** that UUID in the query string.
|
||||
*******************************************************************************/
|
||||
const openModalProcess = async (process: QProcessMetaData = null) =>
|
||||
{
|
||||
let uuid = "";
|
||||
if (selectFullFilterState === "filter")
|
||||
{
|
||||
const filterForBackend = prepQueryFilterForBackend(queryFilter);
|
||||
filterForBackend.skip = 0;
|
||||
filterForBackend.limit = null;
|
||||
setRecordIdsForProcess(filterForBackend);
|
||||
uuid = await storeRecordIdsForProcessInIndexedDB(filterForBackend);
|
||||
}
|
||||
else if (selectFullFilterState === "filterSubset")
|
||||
{
|
||||
@ -1425,24 +1529,27 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
filterForBackend.skip = 0;
|
||||
filterForBackend.limit = selectionSubsetSize;
|
||||
setRecordIdsForProcess(filterForBackend);
|
||||
uuid = await storeRecordIdsForProcessInIndexedDB(filterForBackend);
|
||||
}
|
||||
else if (selectedIds.length > 0)
|
||||
{
|
||||
setRecordIdsForProcess(selectedIds);
|
||||
uuid = await storeRecordIdsForProcessInIndexedDB(selectedIds);
|
||||
}
|
||||
else
|
||||
{
|
||||
setRecordIdsForProcess([]);
|
||||
uuid = await storeRecordIdsForProcessInIndexedDB([]);
|
||||
}
|
||||
|
||||
navigate(`${metaData?.getTablePathByName(tableName)}/${process.name}${getRecordsQueryString()}`);
|
||||
navigate(`${metaData?.getTablePathByName(tableName)}/${process.name}?recordsParam=recordsKey&recordsKey=${uuid}`);
|
||||
};
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** close callback for modal processes
|
||||
*******************************************************************************/
|
||||
const closeModalProcess = (event: object, reason: string) =>
|
||||
const closeModalProcess = (event: object, reason: string, warning?: string) =>
|
||||
{
|
||||
if (reason === "backdropClick" || reason === "escapeKeyDown")
|
||||
{
|
||||
@ -1454,7 +1561,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
const newPath = location.pathname.split("/");
|
||||
newPath.pop();
|
||||
navigate(newPath.join("/"));
|
||||
navigate(newPath.join("/"), warning ? {state: {warning: warning}} : undefined);
|
||||
|
||||
updateTable("close modal process");
|
||||
};
|
||||
@ -2333,22 +2440,38 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
/////////////////////////////////////////////////////////////////
|
||||
if (pathParts[pathParts.length - 2] === tableName)
|
||||
{
|
||||
////////////////////////////////////////
|
||||
// try to find a process by this name //
|
||||
////////////////////////////////////////
|
||||
const processName = pathParts[pathParts.length - 1];
|
||||
const processList = allTableProcesses.filter(p => p.name == processName);
|
||||
if (processList.length > 0)
|
||||
let process = processList.length > 0 ? processList[0] : null;
|
||||
if(!process)
|
||||
{
|
||||
process = metaData?.processes.get(processName)
|
||||
}
|
||||
|
||||
if(process)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// check if a recordsKey UUID (e.g., from indexedDB) is in the query string //
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
const urlSearchParams = new URLSearchParams(location.search);
|
||||
const uuid = urlSearchParams.get("recordsKey")
|
||||
|
||||
if(uuid)
|
||||
{
|
||||
await launchModalProcessUsingRecordIdsFromIndexedDB(uuid, processList[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
setActiveModalProcess(processList[0]);
|
||||
}
|
||||
else if (metaData?.processes.has(processName))
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// check for generic processes - should this be a specific attribute on the process? //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
setActiveModalProcess(metaData?.processes.get(processName));
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log(`Couldn't find process named ${processName}`);
|
||||
setWarningAlert(`Couldn't find process named ${processName}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user