mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-18 05:10:45 +00:00
CTLE-214: initial checkin of 'dot menu'
This commit is contained in:
26
src/App.tsx
26
src/App.tsx
@ -26,6 +26,8 @@ import {QAppTreeNode} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QApp
|
||||
import {QAuthenticationMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAuthenticationMetaData";
|
||||
import {QBrandingMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QBrandingMetaData";
|
||||
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
|
||||
import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
|
||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||
import Avatar from "@mui/material/Avatar";
|
||||
import CssBaseline from "@mui/material/CssBaseline";
|
||||
import Icon from "@mui/material/Icon";
|
||||
@ -35,7 +37,7 @@ import React, {JSXElementConstructor, Key, ReactElement, useEffect, useState,} f
|
||||
import {useCookies} from "react-cookie";
|
||||
import {Navigate, Route, Routes, useLocation,} from "react-router-dom";
|
||||
import {Md5} from "ts-md5/dist/md5";
|
||||
import CommandMenu from "Command";
|
||||
import CommandMenu from "CommandMenu";
|
||||
import QContext from "QContext";
|
||||
import Sidenav from "qqq/components/horseshoe/sidenav/SideNav";
|
||||
import theme from "qqq/components/legacy/Theme";
|
||||
@ -60,7 +62,7 @@ export const SESSION_ID_COOKIE_NAME = "sessionId";
|
||||
export default function App()
|
||||
{
|
||||
const [, setCookie, removeCookie] = useCookies([SESSION_ID_COOKIE_NAME]);
|
||||
const {user, getAccessTokenSilently, getIdTokenClaims, logout} = useAuth0();
|
||||
const {user, getAccessTokenSilently, logout} = useAuth0();
|
||||
const [loadingToken, setLoadingToken] = useState(false);
|
||||
const [isFullyAuthenticated, setIsFullyAuthenticated] = useState(false);
|
||||
const [profileRoutes, setProfileRoutes] = useState({});
|
||||
@ -309,14 +311,14 @@ export default function App()
|
||||
name: `${app.label}`,
|
||||
key: `${app.name}.edit`,
|
||||
route: `${path}/:id/edit`,
|
||||
component: <EntityEdit table={table} isDuplicate={false} />,
|
||||
component: <EntityEdit table={table} isCopy={false} />,
|
||||
});
|
||||
|
||||
routeList.push({
|
||||
name: `${app.label}`,
|
||||
key: `${app.name}.duplicate`,
|
||||
route: `${path}/:id/duplicate`,
|
||||
component: <EntityEdit table={table} isDuplicate={true} />,
|
||||
key: `${app.name}.copy`,
|
||||
route: `${path}/:id/copy`,
|
||||
component: <EntityEdit table={table} isCopy={true} />,
|
||||
});
|
||||
|
||||
routeList.push({
|
||||
@ -560,17 +562,23 @@ export default function App()
|
||||
|
||||
const [pageHeader, setPageHeader] = useState("" as string | JSX.Element);
|
||||
const [accentColor, setAccentColor] = useState("#0062FF");
|
||||
const [allowShortcuts, setAllowShortcuts] = useState(true);
|
||||
const [tableMetaData, setTableMetaData] = useState(null);
|
||||
const [tableProcesses, setTableProcesses] = useState(null);
|
||||
const [dotMenuOpen, setDotMenuOpen] = useState(false);
|
||||
return (
|
||||
|
||||
appRoutes && (
|
||||
<QContext.Provider value={{
|
||||
pageHeader: pageHeader,
|
||||
accentColor: accentColor,
|
||||
allowShortcuts: allowShortcuts,
|
||||
tableMetaData: tableMetaData,
|
||||
tableProcesses: tableProcesses,
|
||||
dotMenuOpen: dotMenuOpen,
|
||||
setPageHeader: (header: string | JSX.Element) => setPageHeader(header),
|
||||
setAccentColor: (accentColor: string) => setAccentColor(accentColor),
|
||||
setAllowShortcuts: (allowShortcuts: boolean) => setAllowShortcuts(allowShortcuts)
|
||||
setTableMetaData: (tableMetaData: QTableMetaData) => setTableMetaData(tableMetaData),
|
||||
setTableProcesses: (tableProcesses: QProcessMetaData[]) => setTableProcesses(tableProcesses),
|
||||
setDotMenuOpen: (dotMenuOpent: boolean) => setDotMenuOpen(dotMenuOpent)
|
||||
}}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
|
192
src/Command.tsx
192
src/Command.tsx
@ -1,192 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {QAppMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppMetaData";
|
||||
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
|
||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||
import Box from "@mui/material/Box";
|
||||
import Button from "@mui/material/Button";
|
||||
import Icon from "@mui/material/Icon";
|
||||
import {Command} from "cmdk";
|
||||
import React, {useContext, useEffect, useReducer, useRef, useState} from "react";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
import QContext from "QContext";
|
||||
import HistoryUtils, {QHistoryEntry} from "qqq/utils/HistoryUtils";
|
||||
|
||||
interface Props
|
||||
{
|
||||
metaData?: QInstance;
|
||||
}
|
||||
|
||||
|
||||
const CommandMenu = ({metaData}: Props) =>
|
||||
{
|
||||
const [open, setOpen] = useState(false)
|
||||
const navigate = useNavigate();
|
||||
const {setAllowShortcuts, allowShortcuts, accentColor} = useContext(QContext);
|
||||
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||
|
||||
function evalueKeyPress(e: KeyboardEvent)
|
||||
{
|
||||
forceUpdate();
|
||||
if (e.key === "." && allowShortcuts)
|
||||
{
|
||||
e.preventDefault();
|
||||
setOpen((open) => !open)
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////
|
||||
// Toggle the menu when ⌘K is pressed //
|
||||
////////////////////////////////////////
|
||||
useEffect(() =>
|
||||
{
|
||||
const down = (e: KeyboardEvent) =>
|
||||
{
|
||||
evalueKeyPress(e);
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", down)
|
||||
return () =>
|
||||
{
|
||||
document.removeEventListener("keydown", down)
|
||||
}
|
||||
}, [allowShortcuts])
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
// change allowing shortcuts based on open's value //
|
||||
/////////////////////////////////////////////////////
|
||||
useEffect(() =>
|
||||
{
|
||||
setAllowShortcuts(!open);
|
||||
}, [open])
|
||||
|
||||
function goToItem(path: string)
|
||||
{
|
||||
navigate(path, {replace: true});
|
||||
setOpen(false);
|
||||
}
|
||||
|
||||
function TablesSection()
|
||||
{
|
||||
let tableNames : string[]= [];
|
||||
metaData.tables.forEach((value: QTableMetaData, key: string) =>
|
||||
{
|
||||
tableNames.push(value.name);
|
||||
})
|
||||
tableNames = tableNames.sort((a: string, b:string) =>
|
||||
{
|
||||
return (metaData.tables.get(a).label.localeCompare(metaData.tables.get(b).label));
|
||||
})
|
||||
return(
|
||||
<Command.Group heading="Tables">
|
||||
{
|
||||
tableNames.map((tableName: string, index: number) =>
|
||||
!metaData.tables.get(tableName).isHidden && metaData.getTablePath(metaData.tables.get(tableName)) &&
|
||||
(
|
||||
<Command.Item onSelect={() => goToItem(`${metaData.getTablePath(metaData.tables.get(tableName))}`)} key={`${tableName}-${index}`}><Icon sx={{color: accentColor}}>{metaData.tables.get(tableName).iconName ? metaData.tables.get(tableName).iconName : "table_rows"}</Icon>{metaData.tables.get(tableName).label}</Command.Item>
|
||||
)
|
||||
)
|
||||
}
|
||||
</Command.Group>
|
||||
);
|
||||
}
|
||||
|
||||
function AppsSection()
|
||||
{
|
||||
let appNames: string[] = [];
|
||||
metaData.apps.forEach((value: QAppMetaData, key: string) =>
|
||||
{
|
||||
appNames.push(value.name);
|
||||
})
|
||||
|
||||
appNames = appNames.sort((a: string, b:string) =>
|
||||
{
|
||||
return (metaData.apps.get(a).label.localeCompare(metaData.apps.get(b).label));
|
||||
})
|
||||
|
||||
return(
|
||||
<Command.Group heading="Apps">
|
||||
{
|
||||
appNames.map((appName: string, index: number) =>
|
||||
metaData.getAppPath(metaData.apps.get(appName)) &&
|
||||
(
|
||||
<Command.Item onSelect={() => goToItem(`${metaData.getAppPath(metaData.apps.get(appName))}`)} key={`${appName}-${index}`}><Icon sx={{color: accentColor}}>{metaData.apps.get(appName).iconName ? metaData.apps.get(appName).iconName : "apps"}</Icon>{metaData.apps.get(appName).label}</Command.Item>
|
||||
)
|
||||
)
|
||||
}
|
||||
</Command.Group>
|
||||
);
|
||||
}
|
||||
|
||||
function RecentlyViewedSection()
|
||||
{
|
||||
const history = HistoryUtils.get();
|
||||
const options = [] as any;
|
||||
history.entries.reverse().forEach((entry, index) =>
|
||||
options.push({label: `${entry.label} index`, id: index, key: index, path: entry.path, iconName: entry.iconName})
|
||||
)
|
||||
|
||||
let appNames: string[] = [];
|
||||
metaData.apps.forEach((value: QAppMetaData, key: string) =>
|
||||
{
|
||||
appNames.push(value.name);
|
||||
})
|
||||
|
||||
appNames = appNames.sort((a: string, b:string) =>
|
||||
{
|
||||
return (metaData.apps.get(a).label.localeCompare(metaData.apps.get(b).label));
|
||||
})
|
||||
|
||||
return(
|
||||
<Command.Group heading="Recently Viewed Records">
|
||||
{
|
||||
history.entries.reverse().map((entry: QHistoryEntry, index: number) =>
|
||||
<Command.Item onSelect={() => goToItem(`${entry.path}`)} key={`${entry.label}-${index}`}><Icon sx={{color: accentColor}}>{entry.iconName}</Icon>{entry.label}</Command.Item>
|
||||
)
|
||||
}
|
||||
</Command.Group>
|
||||
);
|
||||
}
|
||||
|
||||
const containerElement = useRef(null)
|
||||
return (
|
||||
<Box ref={containerElement} className="raycast" sx={{position: "relative", zIndex: 10_000}}>
|
||||
<Command.Dialog open={open} onOpenChange={setOpen} container={containerElement.current} label="Test Global Command Menu">
|
||||
<Box sx={{display: "flex"}}>
|
||||
<Command.Input placeholder="Search for Tables, Actions, or Recently Viewed Items..."/>
|
||||
<Button onClick={() => setOpen(false)}><Icon>close</Icon></Button>
|
||||
</Box>
|
||||
<Command.Loading />
|
||||
<Command.Separator />
|
||||
<Command.List>
|
||||
<Command.Empty>No results found.</Command.Empty>
|
||||
<TablesSection />
|
||||
<Command.Separator />
|
||||
<AppsSection />
|
||||
<Command.Separator />
|
||||
<RecentlyViewedSection />
|
||||
</Command.List>
|
||||
</Command.Dialog>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
export default CommandMenu;
|
295
src/CommandMenu.tsx
Normal file
295
src/CommandMenu.tsx
Normal file
@ -0,0 +1,295 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Capability} from "@kingsrook/qqq-frontend-core/lib/model/metaData/Capability";
|
||||
import {QAppMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppMetaData";
|
||||
import {QAppNodeType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppNodeType";
|
||||
import {QAppTreeNode} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppTreeNode";
|
||||
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
|
||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||
import Box from "@mui/material/Box";
|
||||
import Button from "@mui/material/Button";
|
||||
import Icon from "@mui/material/Icon";
|
||||
import {Command} from "cmdk";
|
||||
import React, {useContext, useEffect, useRef} from "react";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
import QContext from "QContext";
|
||||
import HistoryUtils, {QHistoryEntry} from "qqq/utils/HistoryUtils";
|
||||
|
||||
interface Props
|
||||
{
|
||||
metaData?: QInstance;
|
||||
}
|
||||
|
||||
|
||||
const CommandMenu = ({metaData}: Props) =>
|
||||
{
|
||||
const navigate = useNavigate();
|
||||
const pathParts = location.pathname.replace(/\/+$/, "").split("/");
|
||||
|
||||
const {accentColor, tableMetaData, dotMenuOpen, setDotMenuOpen, setTableMetaData, tableProcesses} = useContext(QContext);
|
||||
|
||||
function evalueKeyPress(e: KeyboardEvent)
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// if a dot pressed, not from a "text" element, then toggle command menu //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
const type = (e.target as any).type;
|
||||
if (e.key === "." && type !== "text")
|
||||
{
|
||||
e.preventDefault();
|
||||
setDotMenuOpen(!dotMenuOpen);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////
|
||||
// Toggle the menu when period is pressed //
|
||||
////////////////////////////////////////////
|
||||
useEffect(() =>
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// if we are not in the right table, clear the table meta data //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
if (metaData && tableMetaData && !location.pathname.startsWith(`${metaData.getTablePath(tableMetaData)}/`))
|
||||
{
|
||||
setTableMetaData(null);
|
||||
}
|
||||
|
||||
const down = (e: KeyboardEvent) =>
|
||||
{
|
||||
evalueKeyPress(e);
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", down)
|
||||
return () =>
|
||||
{
|
||||
document.removeEventListener("keydown", down)
|
||||
}
|
||||
}, [tableMetaData, dotMenuOpen])
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setDotMenuOpen(false);
|
||||
}, [location.pathname])
|
||||
|
||||
function goToItem(path: string)
|
||||
{
|
||||
navigate(path, {replace: true});
|
||||
setDotMenuOpen(false);
|
||||
}
|
||||
|
||||
function getIconName(iconName: string, defaultIconName: string)
|
||||
{
|
||||
return iconName ?? defaultIconName;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
function getFullAppLabel(nodes: QAppTreeNode[] | undefined, name: string, depth: number, path: string): string | null
|
||||
{
|
||||
if (nodes === undefined)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
for (let i = 0; i < nodes.length; i++)
|
||||
{
|
||||
if (nodes[i].type === QAppNodeType.APP && nodes[i].name === name)
|
||||
{
|
||||
return (`${path} > ${nodes[i].label}`);
|
||||
}
|
||||
else if (nodes[i].type === QAppNodeType.APP)
|
||||
{
|
||||
const result = getFullAppLabel(nodes[i].children, name, depth + 1, `${path} ${nodes[i].label}`);
|
||||
if (result !== null)
|
||||
{
|
||||
return (result);
|
||||
}
|
||||
}
|
||||
}
|
||||
return (null);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
function ActionsSection()
|
||||
{
|
||||
let tableNames : string[]= [];
|
||||
metaData.tables.forEach((value: QTableMetaData, key: string) =>
|
||||
{
|
||||
tableNames.push(value.name);
|
||||
})
|
||||
tableNames = tableNames.sort((a: string, b:string) =>
|
||||
{
|
||||
return (metaData.tables.get(a).label.localeCompare(metaData.tables.get(b).label));
|
||||
})
|
||||
|
||||
const path = location.pathname;
|
||||
return tableMetaData && !path.endsWith("/edit") && !path.endsWith("/create") && !path.endsWith("#audit") && ! path.endsWith("copy") &&
|
||||
(
|
||||
<Command.Group heading={`${tableMetaData.label} Actions`}>
|
||||
{
|
||||
tableMetaData.capabilities.has(Capability.TABLE_INSERT) && tableMetaData.insertPermission &&
|
||||
<Command.Item onSelect={() => goToItem(`${pathParts.slice(0, -1).join("/")}/create`)} key={`${tableMetaData.label}-new`} value="New"><Icon sx={{color: accentColor}}>add</Icon>New</Command.Item>
|
||||
}
|
||||
{
|
||||
tableMetaData.capabilities.has(Capability.TABLE_INSERT) && tableMetaData.insertPermission &&
|
||||
<Command.Item onSelect={() => goToItem(`${pathParts.join("/")}/copy`)} key={`${tableMetaData.label}-copy`} value="Copy"><Icon sx={{color: accentColor}}>copy</Icon>Copy</Command.Item>
|
||||
}
|
||||
{
|
||||
tableMetaData.capabilities.has(Capability.TABLE_UPDATE) && tableMetaData.editPermission &&
|
||||
<Command.Item onSelect={() => goToItem(`${pathParts.join("/")}/edit`)} key={`${tableMetaData.label}-edit`} value="Edit"><Icon sx={{color: accentColor}}>edit</Icon>Edit</Command.Item>
|
||||
}
|
||||
{
|
||||
metaData && metaData.tables.has("audit") &&
|
||||
<Command.Item onSelect={() => goToItem(`${pathParts.join("/")}#audit`)} key={`${tableMetaData.label}-audit`} value="Audit"><Icon sx={{color: accentColor}}>checklist</Icon>Audit</Command.Item>
|
||||
}
|
||||
{
|
||||
tableProcesses && tableProcesses.length > 0 &&
|
||||
(
|
||||
tableProcesses.map((process) => (
|
||||
<Command.Item onSelect={() => goToItem(`${pathParts.join("/")}/${process.name}`)} key={`${process.name}`} value={`${process.label}`}><Icon sx={{color: accentColor}}>{getIconName(process.iconName, "play_arrow")}</Icon>{process.label}</Command.Item>
|
||||
))
|
||||
)
|
||||
}
|
||||
</Command.Group>
|
||||
);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
function TablesSection()
|
||||
{
|
||||
let tableNames : string[]= [];
|
||||
metaData.tables.forEach((value: QTableMetaData, key: string) =>
|
||||
{
|
||||
tableNames.push(value.name);
|
||||
})
|
||||
tableNames = tableNames.sort((a: string, b:string) =>
|
||||
{
|
||||
return (metaData.tables.get(a).label.localeCompare(metaData.tables.get(b).label));
|
||||
})
|
||||
return(
|
||||
<Command.Group heading="Tables">
|
||||
{
|
||||
tableNames.map((tableName: string, index: number) =>
|
||||
!metaData.tables.get(tableName).isHidden && metaData.getTablePath(metaData.tables.get(tableName)) &&
|
||||
(
|
||||
<Command.Item onSelect={() => goToItem(`${metaData.getTablePath(metaData.tables.get(tableName))}`)} key={`${tableName}-${index}`} value={metaData.tables.get(tableName).label}><Icon sx={{color: accentColor}}>{getIconName(metaData.tables.get(tableName).iconName, "table_rows")}</Icon>{metaData.tables.get(tableName).label}</Command.Item>
|
||||
)
|
||||
)
|
||||
}
|
||||
</Command.Group>
|
||||
);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
function AppsSection()
|
||||
{
|
||||
let appNames: string[] = [];
|
||||
metaData.apps.forEach((value: QAppMetaData, key: string) =>
|
||||
{
|
||||
appNames.push(value.name);
|
||||
})
|
||||
|
||||
appNames = appNames.sort((a: string, b:string) =>
|
||||
{
|
||||
return (getFullAppLabel(metaData.appTree, a, 1, "").localeCompare(getFullAppLabel(metaData.appTree, b, 1, "")));
|
||||
})
|
||||
|
||||
return(
|
||||
<Command.Group heading="Apps">
|
||||
{
|
||||
appNames.map((appName: string, index: number) =>
|
||||
metaData.getAppPath(metaData.apps.get(appName)) &&
|
||||
(
|
||||
<Command.Item onSelect={() => goToItem(`${metaData.getAppPath(metaData.apps.get(appName))}`)} key={`${appName}-${index}`} value={getFullAppLabel(metaData.appTree, appName, 1, "")}><Icon sx={{color: accentColor}}>{getIconName(metaData.apps.get(appName).iconName, "apps")}</Icon>{getFullAppLabel(metaData.appTree, appName, 1, "")}</Command.Item>
|
||||
)
|
||||
)
|
||||
}
|
||||
</Command.Group>
|
||||
);
|
||||
}
|
||||
|
||||
function RecentlyViewedSection()
|
||||
{
|
||||
const history = HistoryUtils.get();
|
||||
const options = [] as any;
|
||||
history.entries.reverse().forEach((entry, index) =>
|
||||
options.push({label: `${entry.label} index`, id: index, key: index, path: entry.path, iconName: entry.iconName})
|
||||
)
|
||||
|
||||
let appNames: string[] = [];
|
||||
metaData.apps.forEach((value: QAppMetaData, key: string) =>
|
||||
{
|
||||
appNames.push(value.name);
|
||||
})
|
||||
|
||||
appNames = appNames.sort((a: string, b:string) =>
|
||||
{
|
||||
return (metaData.apps.get(a).label.localeCompare(metaData.apps.get(b).label));
|
||||
})
|
||||
|
||||
const entryMap = new Map<string, boolean>();
|
||||
return(
|
||||
<Command.Group heading="Recently Viewed Records">
|
||||
{
|
||||
history.entries.reverse().map((entry: QHistoryEntry, index: number) =>
|
||||
! entryMap.has(entry.label) && entryMap.set(entry.label, true) && (
|
||||
<Command.Item onSelect={() => goToItem(`${entry.path}`)} key={`${entry.label}-${index}`} value={entry.label}><Icon sx={{color: accentColor}}>{entry.iconName}</Icon>{entry.label}</Command.Item>
|
||||
)
|
||||
)
|
||||
}
|
||||
</Command.Group>
|
||||
);
|
||||
}
|
||||
|
||||
const containerElement = useRef(null)
|
||||
return (
|
||||
<Box ref={containerElement} className="raycast" sx={{position: "relative", zIndex: 10_000}}>
|
||||
<Command.Dialog open={dotMenuOpen} onOpenChange={setDotMenuOpen} container={containerElement.current} label="Test Global Command Menu">
|
||||
<Box sx={{display: "flex"}}>
|
||||
<Command.Input placeholder="Search for Tables, Actions, or Recently Viewed Items..."/>
|
||||
<Button onClick={() => setDotMenuOpen(false)}><Icon>close</Icon></Button>
|
||||
</Box>
|
||||
<Command.Loading />
|
||||
<Command.Separator />
|
||||
<Command.List>
|
||||
<Command.Empty>No results found.</Command.Empty>
|
||||
<ActionsSection />
|
||||
<Command.Separator />
|
||||
<TablesSection />
|
||||
<Command.Separator />
|
||||
<AppsSection />
|
||||
<Command.Separator />
|
||||
<RecentlyViewedSection />
|
||||
</Command.List>
|
||||
</Command.Dialog>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
export default CommandMenu;
|
@ -21,6 +21,7 @@
|
||||
|
||||
import {QAppMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppMetaData";
|
||||
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
|
||||
import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
|
||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||
import {createContext} from "react";
|
||||
|
||||
@ -31,17 +32,20 @@ interface QContext
|
||||
setPageHeader?: (header: string | JSX.Element) => void;
|
||||
accentColor: string;
|
||||
setAccentColor?: (header: string) => void;
|
||||
dotMenuOpen: boolean;
|
||||
qInstance?: QInstance;
|
||||
appMetaData?: QAppMetaData;
|
||||
tableMetaData?: QTableMetaData;
|
||||
allowShortcuts?: boolean;
|
||||
setAllowShortcuts?: (allowShortcuts: boolean) => void;
|
||||
setTableMetaData?: (tableMetaData: QTableMetaData) => void;
|
||||
tableProcesses?: QProcessMetaData[];
|
||||
setTableProcesses?: (tableProcesses: QProcessMetaData[]) => void;
|
||||
setDotMenuOpen?: (dotMenuOpen: boolean) => void;
|
||||
}
|
||||
|
||||
const defaultState = {
|
||||
pageHeader: "",
|
||||
accentColor: "#0062FF",
|
||||
allowShortcuts: true
|
||||
dotMenuOpen: false
|
||||
};
|
||||
|
||||
const QContext = createContext<QContext>(defaultState);
|
||||
|
@ -23,9 +23,8 @@ import {InputAdornment, InputLabel} from "@mui/material";
|
||||
import Box from "@mui/material/Box";
|
||||
import Switch from "@mui/material/Switch";
|
||||
import {ErrorMessage, Field, useFormikContext} from "formik";
|
||||
import React, {useContext, useState} from "react";
|
||||
import React, {useState} from "react";
|
||||
import AceEditor from "react-ace";
|
||||
import QContext from "QContext";
|
||||
import BooleanFieldSwitch from "qqq/components/forms/BooleanFieldSwitch";
|
||||
import MDInput from "qqq/components/legacy/MDInput";
|
||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||
@ -53,7 +52,6 @@ function QDynamicFormField({
|
||||
{
|
||||
const [switchChecked, setSwitchChecked] = useState(false);
|
||||
const [isDisabled, setIsDisabled] = useState(!isEditable || bulkEditMode);
|
||||
const {setAllowShortcuts} = useContext(QContext);
|
||||
|
||||
const {setFieldValue} = useFormikContext();
|
||||
|
||||
@ -127,14 +125,6 @@ function QDynamicFormField({
|
||||
field = (
|
||||
<>
|
||||
<Field {...rest} onWheel={handleOnWheel} name={name} type={type} as={MDInput} variant="standard" label={label} InputLabelProps={inputLabelProps} InputProps={inputProps} fullWidth disabled={isDisabled}
|
||||
onBlur={(e: any) =>
|
||||
{
|
||||
setAllowShortcuts(true);
|
||||
}}
|
||||
onFocus={(e: any) =>
|
||||
{
|
||||
setAllowShortcuts(false);
|
||||
}}
|
||||
onKeyPress={(e: any) =>
|
||||
{
|
||||
if (e.key === "Enter")
|
||||
|
@ -22,8 +22,6 @@
|
||||
import {AdornmentType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/AdornmentType";
|
||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||
import {QPossibleValue} from "@kingsrook/qqq-frontend-core/lib/model/QPossibleValue";
|
||||
import CheckBoxIcon from "@mui/icons-material/CheckBox";
|
||||
import CheckBoxOutlineBlankIcon from "@mui/icons-material/CheckBoxOutlineBlank";
|
||||
import {Checkbox, Chip, CircularProgress, FilterOptionsState, Icon} from "@mui/material";
|
||||
import Autocomplete from "@mui/material/Autocomplete";
|
||||
import Box from "@mui/material/Box";
|
||||
|
@ -26,8 +26,9 @@ import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QT
|
||||
import {QTableSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableSection";
|
||||
import {QPossibleValue} from "@kingsrook/qqq-frontend-core/lib/model/QPossibleValue";
|
||||
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||
import {Alert, Box} from "@mui/material";
|
||||
import {Alert} from "@mui/material";
|
||||
import Avatar from "@mui/material/Avatar";
|
||||
import Box from "@mui/material/Box";
|
||||
import Card from "@mui/material/Card";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import Icon from "@mui/material/Icon";
|
||||
@ -54,7 +55,7 @@ interface Props
|
||||
closeModalHandler?: (event: object, reason: string) => void;
|
||||
defaultValues: { [key: string]: string };
|
||||
disabledFields: { [key: string]: boolean } | string[];
|
||||
isDuplicate?: boolean;
|
||||
isCopy?: boolean;
|
||||
}
|
||||
|
||||
EntityForm.defaultProps = {
|
||||
@ -64,7 +65,7 @@ EntityForm.defaultProps = {
|
||||
closeModalHandler: null,
|
||||
defaultValues: {},
|
||||
disabledFields: {},
|
||||
isDuplicate: false
|
||||
isCopy: false
|
||||
};
|
||||
|
||||
function EntityForm(props: Props): JSX.Element
|
||||
@ -175,9 +176,9 @@ function EntityForm(props: Props): JSX.Element
|
||||
fieldArray.push(fieldMetaData);
|
||||
});
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if doing an edit or duplicate, fetch the record and pre-populate the form values from it //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if doing an edit or copy, fetch the record and pre-populate the form values from it //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
let record: QRecord = null;
|
||||
let defaultDisplayValues = new Map<string, string>();
|
||||
if (props.id !== null)
|
||||
@ -185,7 +186,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
record = await qController.get(tableName, props.id);
|
||||
setRecord(record);
|
||||
|
||||
const titleVerb = props.isDuplicate ? "Duplicate" : "Edit";
|
||||
const titleVerb = props.isCopy ? "Copy" : "Edit";
|
||||
setFormTitle(`${titleVerb} ${tableMetaData?.label}: ${record?.recordLabel}`);
|
||||
|
||||
if (!props.isModal)
|
||||
@ -195,13 +196,18 @@ function EntityForm(props: Props): JSX.Element
|
||||
|
||||
tableMetaData.fields.forEach((fieldMetaData, key) =>
|
||||
{
|
||||
if (props.isDuplicate && fieldMetaData.name == tableMetaData.primaryKeyField)
|
||||
if (props.isCopy && fieldMetaData.name == tableMetaData.primaryKeyField)
|
||||
{
|
||||
return;
|
||||
}
|
||||
initialValues[key] = record.values.get(key);
|
||||
});
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// these checks are only for updating records, if copying, it is actually an insert, which is checked after this block //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(! props.isCopy)
|
||||
{
|
||||
if (!tableMetaData.capabilities.has(Capability.TABLE_UPDATE))
|
||||
{
|
||||
setNotAllowedError("Records may not be edited in this table");
|
||||
@ -211,6 +217,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
setNotAllowedError(`You do not have permission to edit ${tableMetaData.label} records`);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
///////////////////////////////////////////
|
||||
@ -256,7 +263,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
//////////////////////////////////////
|
||||
// check capabilities & permissions //
|
||||
//////////////////////////////////////
|
||||
if (props.isDuplicate || !props.id)
|
||||
if (props.isCopy || !props.id)
|
||||
{
|
||||
if (!tableMetaData.capabilities.has(Capability.TABLE_INSERT))
|
||||
{
|
||||
@ -341,11 +348,11 @@ function EntityForm(props: Props): JSX.Element
|
||||
const fieldName = section.fieldNames[j];
|
||||
const field = tableMetaData.fields.get(fieldName);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if id !== null (and we're not duplicating) - means we're on the edit screen -- show all fields on the edit screen. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if id !== null (and we're not copying) - means we're on the edit screen -- show all fields on the edit screen. //
|
||||
// || (or) we're on the insert screen in which case, only show editable fields. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if ((props.id !== null && !props.isDuplicate) || field.isEditable)
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if ((props.id !== null && !props.isCopy) || field.isEditable)
|
||||
{
|
||||
sectionDynamicFormFields.push(dynamicFormFields[fieldName]);
|
||||
}
|
||||
@ -393,9 +400,9 @@ function EntityForm(props: Props): JSX.Element
|
||||
// but if the user used the anchors on the page, this doesn't effectively cancel... //
|
||||
// what we have here pushed a new history entry (I think?), so could be better //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
if (props.id !== null && props.isDuplicate)
|
||||
if (props.id !== null && props.isCopy)
|
||||
{
|
||||
const path = `${location.pathname.replace(/\/duplicate$/, "")}`;
|
||||
const path = `${location.pathname.replace(/\/copy$/, "")}`;
|
||||
navigate(path, {replace: true});
|
||||
}
|
||||
else if (props.id !== null)
|
||||
@ -458,7 +465,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
}
|
||||
}
|
||||
|
||||
if (props.id !== null && !props.isDuplicate)
|
||||
if (props.id !== null && !props.isCopy)
|
||||
{
|
||||
// todo - audit that it's a dupe
|
||||
await qController
|
||||
@ -504,8 +511,8 @@ function EntityForm(props: Props): JSX.Element
|
||||
}
|
||||
else
|
||||
{
|
||||
const path = props.isDuplicate ?
|
||||
location.pathname.replace(new RegExp(`/${props.id}/duplicate$`), "/" + record.values.get(tableMetaData.primaryKeyField))
|
||||
const path = props.isCopy ?
|
||||
location.pathname.replace(new RegExp(`/${props.id}/copy$`), "/" + record.values.get(tableMetaData.primaryKeyField))
|
||||
: location.pathname.replace(/create$/, record.values.get(tableMetaData.primaryKeyField));
|
||||
navigate(path, {state: {createSuccess: true}});
|
||||
}
|
||||
@ -514,8 +521,8 @@ function EntityForm(props: Props): JSX.Element
|
||||
{
|
||||
if(error.message.toLowerCase().startsWith("warning"))
|
||||
{
|
||||
const path = props.isDuplicate ?
|
||||
location.pathname.replace(new RegExp(`/${props.id}/duplicate$`), "/" + record.values.get(tableMetaData.primaryKeyField))
|
||||
const path = props.isCopy ?
|
||||
location.pathname.replace(new RegExp(`/${props.id}/copy$`), "/" + record.values.get(tableMetaData.primaryKeyField))
|
||||
: location.pathname.replace(/create$/, record.values.get(tableMetaData.primaryKeyField));
|
||||
navigate(path, {state: {createSuccess: true, warning: error.message}});
|
||||
}
|
||||
|
@ -30,9 +30,8 @@ import ListItemIcon from "@mui/material/ListItemIcon";
|
||||
import Menu from "@mui/material/Menu";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import Toolbar from "@mui/material/Toolbar";
|
||||
import React, {useContext, useEffect, useState} from "react";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {useLocation, useNavigate} from "react-router-dom";
|
||||
import QContext from "QContext";
|
||||
import QBreadcrumbs, {routeToLabel} from "qqq/components/horseshoe/Breadcrumbs";
|
||||
import {navbar, navbarContainer, navbarIconButton, navbarRow,} from "qqq/components/horseshoe/Styles";
|
||||
import {setTransparentNavbar, useMaterialUIController,} from "qqq/context";
|
||||
@ -63,7 +62,6 @@ function NavBar({absolute, light, isMini}: Props): JSX.Element
|
||||
const [autocompleteValue, setAutocompleteValue] = useState<any>(null);
|
||||
const route = useLocation().pathname.split("/").slice(1);
|
||||
const navigate = useNavigate();
|
||||
const {setAllowShortcuts} = useContext(QContext);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
@ -122,15 +120,9 @@ function NavBar({absolute, light, isMini}: Props): JSX.Element
|
||||
|
||||
function handleHistoryOnOpen()
|
||||
{
|
||||
setAllowShortcuts(false);
|
||||
buildHistoryEntries();
|
||||
}
|
||||
|
||||
function handleHistoryOnClose()
|
||||
{
|
||||
setAllowShortcuts(true);
|
||||
}
|
||||
|
||||
const handleOpenMenu = (event: any) => setOpenMenu(event.currentTarget);
|
||||
const handleCloseMenu = () => setOpenMenu(false);
|
||||
|
||||
@ -165,7 +157,6 @@ function NavBar({absolute, light, isMini}: Props): JSX.Element
|
||||
blurOnSelect
|
||||
style={{width: "200px"}}
|
||||
onOpen={handleHistoryOnOpen}
|
||||
onClose={handleHistoryOnClose}
|
||||
onChange={handleAutocompleteOnChange}
|
||||
PopperComponent={CustomPopper}
|
||||
isOptionEqualToValue={(option, value) => option.id === value.id}
|
||||
|
@ -29,15 +29,15 @@ import BaseLayout from "qqq/layouts/BaseLayout";
|
||||
interface Props
|
||||
{
|
||||
table?: QTableMetaData;
|
||||
isDuplicate?: boolean
|
||||
isCopy?: boolean
|
||||
}
|
||||
|
||||
EntityEdit.defaultProps = {
|
||||
table: null,
|
||||
isDuplicate: false
|
||||
isCopy: false
|
||||
};
|
||||
|
||||
function EntityEdit({table, isDuplicate}: Props): JSX.Element
|
||||
function EntityEdit({table, isCopy}: Props): JSX.Element
|
||||
{
|
||||
const {id} = useParams();
|
||||
|
||||
@ -49,7 +49,7 @@ function EntityEdit({table, isDuplicate}: Props): JSX.Element
|
||||
<Box mb={3}>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<EntityForm table={table} id={id} isDuplicate={isDuplicate} />
|
||||
<EntityForm table={table} id={id} isCopy={isCopy} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
|
@ -88,21 +88,18 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
const [sectionFieldElements, setSectionFieldElements] = useState(null as Map<string, JSX.Element[]>);
|
||||
const [adornmentFieldsMap, setAdornmentFieldsMap] = useState(new Map<string, boolean>);
|
||||
const [deleteConfirmationOpen, setDeleteConfirmationOpen] = useState(false);
|
||||
const [tableMetaData, setTableMetaData] = useState(null);
|
||||
const [metaData, setMetaData] = useState(null as QInstance);
|
||||
const [record, setRecord] = useState(null as QRecord);
|
||||
const [tableSections, setTableSections] = useState([] as QTableSection[]);
|
||||
const [t1SectionName, setT1SectionName] = useState(null as string);
|
||||
const [t1SectionElement, setT1SectionElement] = useState(null as JSX.Element);
|
||||
const [nonT1TableSections, setNonT1TableSections] = useState([] as QTableSection[]);
|
||||
const [tableProcesses, setTableProcesses] = useState([] as QProcessMetaData[]);
|
||||
const [allTableProcesses, setAllTableProcesses] = useState([] as QProcessMetaData[]);
|
||||
const [actionsMenu, setActionsMenu] = useState(null);
|
||||
const [notFoundMessage, setNotFoundMessage] = useState(null as string);
|
||||
const [errorMessage, setErrorMessage] = useState(null as string)
|
||||
const [successMessage, setSuccessMessage] = useState(null as string);
|
||||
const [warningMessage, setWarningMessage] = useState(null as string);
|
||||
const {accentColor, setPageHeader, allowShortcuts} = useContext(QContext);
|
||||
const [activeModalProcess, setActiveModalProcess] = useState(null as QProcessMetaData);
|
||||
const [reloadCounter, setReloadCounter] = useState(0);
|
||||
|
||||
@ -113,6 +110,8 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget);
|
||||
const closeActionsMenu = () => setActionsMenu(null);
|
||||
|
||||
const {accentColor, setPageHeader, tableMetaData, setTableMetaData, tableProcesses, setTableProcesses, dotMenuOpen} = useContext(QContext);
|
||||
|
||||
|
||||
|
||||
const reload = () =>
|
||||
@ -133,11 +132,25 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
// Toggle the menu when ⌘K is pressed
|
||||
useEffect(() =>
|
||||
{
|
||||
if(tableMetaData == null)
|
||||
{
|
||||
(async() =>
|
||||
{
|
||||
const tableMetaData = await qController.loadTableMetaData(tableName);
|
||||
setTableMetaData(tableMetaData);
|
||||
})();
|
||||
}
|
||||
|
||||
const down = (e: { key: string; metaKey: any; ctrlKey: any; preventDefault: () => void; }) =>
|
||||
{
|
||||
if(allowShortcuts)
|
||||
if(!dotMenuOpen)
|
||||
{
|
||||
if (e.key === "e" && table.capabilities.has(Capability.TABLE_UPDATE) && table.editPermission)
|
||||
if (e.key === "n" && table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission)
|
||||
{
|
||||
e.preventDefault()
|
||||
gotoCreate();
|
||||
}
|
||||
else if (e.key === "e" && table.capabilities.has(Capability.TABLE_UPDATE) && table.editPermission)
|
||||
{
|
||||
e.preventDefault()
|
||||
navigate("edit");
|
||||
@ -145,19 +158,27 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
else if (e.key === "c" && table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission)
|
||||
{
|
||||
e.preventDefault()
|
||||
gotoCreate();
|
||||
navigate("copy");
|
||||
}
|
||||
else if (e.key === "d" && table.capabilities.has(Capability.TABLE_DELETE) && table.deletePermission)
|
||||
{
|
||||
e.preventDefault()
|
||||
handleClickDeleteButton();
|
||||
}
|
||||
else if (e.key === "a" && metaData && metaData.tables.has("audit"))
|
||||
{
|
||||
e.preventDefault()
|
||||
navigate("#audit");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", down)
|
||||
return () => document.removeEventListener("keydown", down)
|
||||
}, [allowShortcuts])
|
||||
return () =>
|
||||
{
|
||||
document.removeEventListener("keydown", down)
|
||||
}
|
||||
}, [dotMenuOpen])
|
||||
|
||||
const gotoCreate = () =>
|
||||
{
|
||||
@ -568,14 +589,14 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission &&
|
||||
<MenuItem onClick={() => gotoCreate()}>
|
||||
<ListItemIcon><Icon>add</Icon></ListItemIcon>
|
||||
Create New
|
||||
New
|
||||
</MenuItem>
|
||||
}
|
||||
{
|
||||
table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission &&
|
||||
<MenuItem onClick={() => navigate("duplicate")}>
|
||||
<MenuItem onClick={() => navigate("copy")}>
|
||||
<ListItemIcon><Icon>copy</Icon></ListItemIcon>
|
||||
Create Duplicate
|
||||
Copy
|
||||
</MenuItem>
|
||||
}
|
||||
{
|
||||
@ -597,14 +618,14 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
Delete
|
||||
</MenuItem>
|
||||
}
|
||||
{tableProcesses.length > 0 && hasEditOrDelete && <Divider />}
|
||||
{tableProcesses.map((process) => (
|
||||
{tableProcesses?.length > 0 && hasEditOrDelete && <Divider />}
|
||||
{tableProcesses?.map((process) => (
|
||||
<MenuItem key={process.name} onClick={() => processClicked(process)}>
|
||||
<ListItemIcon><Icon>{process.iconName ?? "arrow_forward"}</Icon></ListItemIcon>
|
||||
{process.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
{(tableProcesses.length > 0 || hasEditOrDelete) && <Divider />}
|
||||
{(tableProcesses?.length > 0 || hasEditOrDelete) && <Divider />}
|
||||
<MenuItem onClick={() => navigate("dev")}>
|
||||
<ListItemIcon><Icon>data_object</Icon></ListItemIcon>
|
||||
Developer Mode
|
||||
|
@ -282,10 +282,16 @@
|
||||
[cmdk-group-heading] {
|
||||
user-select: none;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: var(--gray11);
|
||||
padding: 0 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position:sticky;
|
||||
top: -1;
|
||||
padding-bottom: 4px;
|
||||
background: white;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
[cmdk-raycast-footer] {
|
||||
|
Reference in New Issue
Block a user