diff --git a/src/App.tsx b/src/App.tsx
index b73376c..a611734 100644
--- a/src/App.tsx
+++ b/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: ,
+ component: ,
});
routeList.push({
name: `${app.label}`,
- key: `${app.name}.duplicate`,
- route: `${path}/:id/duplicate`,
- component: ,
+ key: `${app.name}.copy`,
+ route: `${path}/:id/copy`,
+ component: ,
});
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 && (
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)
}}>
diff --git a/src/Command.tsx b/src/Command.tsx
deleted file mode 100644
index 3da324d..0000000
--- a/src/Command.tsx
+++ /dev/null
@@ -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 .
- */
-
-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(
-
- {
- tableNames.map((tableName: string, index: number) =>
- !metaData.tables.get(tableName).isHidden && metaData.getTablePath(metaData.tables.get(tableName)) &&
- (
- goToItem(`${metaData.getTablePath(metaData.tables.get(tableName))}`)} key={`${tableName}-${index}`}>{metaData.tables.get(tableName).iconName ? metaData.tables.get(tableName).iconName : "table_rows"}{metaData.tables.get(tableName).label}
- )
- )
- }
-
- );
- }
-
- 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(
-
- {
- appNames.map((appName: string, index: number) =>
- metaData.getAppPath(metaData.apps.get(appName)) &&
- (
- goToItem(`${metaData.getAppPath(metaData.apps.get(appName))}`)} key={`${appName}-${index}`}>{metaData.apps.get(appName).iconName ? metaData.apps.get(appName).iconName : "apps"}{metaData.apps.get(appName).label}
- )
- )
- }
-
- );
- }
-
- 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(
-
- {
- history.entries.reverse().map((entry: QHistoryEntry, index: number) =>
- goToItem(`${entry.path}`)} key={`${entry.label}-${index}`}>{entry.iconName}{entry.label}
- )
- }
-
- );
- }
-
- const containerElement = useRef(null)
- return (
-
-
-
-
-
-
-
-
-
- No results found.
-
-
-
-
-
-
-
-
- )
-}
-export default CommandMenu;
diff --git a/src/CommandMenu.tsx b/src/CommandMenu.tsx
new file mode 100644
index 0000000..8d28f84
--- /dev/null
+++ b/src/CommandMenu.tsx
@@ -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 .
+ */
+
+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") &&
+ (
+
+ {
+ tableMetaData.capabilities.has(Capability.TABLE_INSERT) && tableMetaData.insertPermission &&
+ goToItem(`${pathParts.slice(0, -1).join("/")}/create`)} key={`${tableMetaData.label}-new`} value="New">addNew
+ }
+ {
+ tableMetaData.capabilities.has(Capability.TABLE_INSERT) && tableMetaData.insertPermission &&
+ goToItem(`${pathParts.join("/")}/copy`)} key={`${tableMetaData.label}-copy`} value="Copy">copyCopy
+ }
+ {
+ tableMetaData.capabilities.has(Capability.TABLE_UPDATE) && tableMetaData.editPermission &&
+ goToItem(`${pathParts.join("/")}/edit`)} key={`${tableMetaData.label}-edit`} value="Edit">editEdit
+ }
+ {
+ metaData && metaData.tables.has("audit") &&
+ goToItem(`${pathParts.join("/")}#audit`)} key={`${tableMetaData.label}-audit`} value="Audit">checklistAudit
+ }
+ {
+ tableProcesses && tableProcesses.length > 0 &&
+ (
+ tableProcesses.map((process) => (
+ goToItem(`${pathParts.join("/")}/${process.name}`)} key={`${process.name}`} value={`${process.label}`}>{getIconName(process.iconName, "play_arrow")}{process.label}
+ ))
+ )
+ }
+
+ );
+ }
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ 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(
+
+ {
+ tableNames.map((tableName: string, index: number) =>
+ !metaData.tables.get(tableName).isHidden && metaData.getTablePath(metaData.tables.get(tableName)) &&
+ (
+ goToItem(`${metaData.getTablePath(metaData.tables.get(tableName))}`)} key={`${tableName}-${index}`} value={metaData.tables.get(tableName).label}>{getIconName(metaData.tables.get(tableName).iconName, "table_rows")}{metaData.tables.get(tableName).label}
+ )
+ )
+ }
+
+ );
+ }
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ 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(
+
+ {
+ appNames.map((appName: string, index: number) =>
+ metaData.getAppPath(metaData.apps.get(appName)) &&
+ (
+ goToItem(`${metaData.getAppPath(metaData.apps.get(appName))}`)} key={`${appName}-${index}`} value={getFullAppLabel(metaData.appTree, appName, 1, "")}>{getIconName(metaData.apps.get(appName).iconName, "apps")}{getFullAppLabel(metaData.appTree, appName, 1, "")}
+ )
+ )
+ }
+
+ );
+ }
+
+ 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();
+ return(
+
+ {
+ history.entries.reverse().map((entry: QHistoryEntry, index: number) =>
+ ! entryMap.has(entry.label) && entryMap.set(entry.label, true) && (
+ goToItem(`${entry.path}`)} key={`${entry.label}-${index}`} value={entry.label}>{entry.iconName}{entry.label}
+ )
+ )
+ }
+
+ );
+ }
+
+ const containerElement = useRef(null)
+ return (
+
+
+
+
+
+
+
+
+
+ No results found.
+
+
+
+
+
+
+
+
+
+
+ )
+}
+export default CommandMenu;
diff --git a/src/QContext.tsx b/src/QContext.tsx
index c5c9e26..ed205b4 100644
--- a/src/QContext.tsx
+++ b/src/QContext.tsx
@@ -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(defaultState);
diff --git a/src/qqq/components/forms/DynamicFormField.tsx b/src/qqq/components/forms/DynamicFormField.tsx
index 57a71f5..e2fb6f8 100644
--- a/src/qqq/components/forms/DynamicFormField.tsx
+++ b/src/qqq/components/forms/DynamicFormField.tsx
@@ -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 = (
<>
- {
- setAllowShortcuts(true);
- }}
- onFocus={(e: any) =>
- {
- setAllowShortcuts(false);
- }}
onKeyPress={(e: any) =>
{
if (e.key === "Enter")
diff --git a/src/qqq/components/forms/DynamicSelect.tsx b/src/qqq/components/forms/DynamicSelect.tsx
index ba22d4b..ac558c8 100644
--- a/src/qqq/components/forms/DynamicSelect.tsx
+++ b/src/qqq/components/forms/DynamicSelect.tsx
@@ -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";
diff --git a/src/qqq/components/forms/EntityForm.tsx b/src/qqq/components/forms/EntityForm.tsx
index 4719711..1d3c2ef 100644
--- a/src/qqq/components/forms/EntityForm.tsx
+++ b/src/qqq/components/forms/EntityForm.tsx
@@ -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();
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,20 +196,26 @@ 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);
});
- if (!tableMetaData.capabilities.has(Capability.TABLE_UPDATE))
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // these checks are only for updating records, if copying, it is actually an insert, which is checked after this block //
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ if(! props.isCopy)
{
- setNotAllowedError("Records may not be edited in this table");
- }
- else if (!tableMetaData.editPermission)
- {
- setNotAllowedError(`You do not have permission to edit ${tableMetaData.label} records`);
+ if (!tableMetaData.capabilities.has(Capability.TABLE_UPDATE))
+ {
+ setNotAllowedError("Records may not be edited in this table");
+ }
+ else if (!tableMetaData.editPermission)
+ {
+ 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. //
- // || (or) we're on the insert screen in which case, only show editable fields. //
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- if ((props.id !== null && !props.isDuplicate) || field.isEditable)
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // 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.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}});
}
diff --git a/src/qqq/components/horseshoe/NavBar.tsx b/src/qqq/components/horseshoe/NavBar.tsx
index 51963ba..bdfc7bd 100644
--- a/src/qqq/components/horseshoe/NavBar.tsx
+++ b/src/qqq/components/horseshoe/NavBar.tsx
@@ -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(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}
diff --git a/src/qqq/pages/records/edit/RecordEdit.tsx b/src/qqq/pages/records/edit/RecordEdit.tsx
index 66dd34d..9d84298 100644
--- a/src/qqq/pages/records/edit/RecordEdit.tsx
+++ b/src/qqq/pages/records/edit/RecordEdit.tsx
@@ -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
-
+
diff --git a/src/qqq/pages/records/view/RecordView.tsx b/src/qqq/pages/records/view/RecordView.tsx
index 780aecd..2903e5f 100644
--- a/src/qqq/pages/records/view/RecordView.tsx
+++ b/src/qqq/pages/records/view/RecordView.tsx
@@ -88,21 +88,18 @@ function RecordView({table, launchProcess}: Props): JSX.Element
const [sectionFieldElements, setSectionFieldElements] = useState(null as Map);
const [adornmentFieldsMap, setAdornmentFieldsMap] = useState(new Map);
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 &&
}
{
table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission &&
-
}
- {tableProcesses.length > 0 && hasEditOrDelete && }
- {tableProcesses.map((process) => (
+ {tableProcesses?.length > 0 && hasEditOrDelete && }
+ {tableProcesses?.map((process) => (
processClicked(process)}>
{process.iconName ?? "arrow_forward"}
{process.label}
))}
- {(tableProcesses.length > 0 || hasEditOrDelete) && }
+ {(tableProcesses?.length > 0 || hasEditOrDelete) && }
navigate("dev")}>
data_object
Developer Mode
diff --git a/src/qqq/styles/raycast.scss b/src/qqq/styles/raycast.scss
index d4e5227..5a74cb5 100644
--- a/src/qqq/styles/raycast.scss
+++ b/src/qqq/styles/raycast.scss
@@ -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] {