diff --git a/src/components/CodeEditor/useMonacoCustomizations.ts b/src/components/CodeEditor/useMonacoCustomizations.ts index b1a9dce9..68abab40 100644 --- a/src/components/CodeEditor/useMonacoCustomizations.ts +++ b/src/components/CodeEditor/useMonacoCustomizations.ts @@ -165,7 +165,6 @@ export default function useMonacoCustomizations({ rendererOptions: { "just-types": "true" }, }); const newLib = result.lines.join("\n").replaceAll("export ", ""); - // console.log(newLib); monaco?.languages.typescript.javascriptDefaults.addExtraLib(newLib); } }; @@ -191,7 +190,6 @@ export default function useMonacoCustomizations({ // Set row definitions useEffect(() => { if (!monaco || !rowyRun || !tableState?.columns) return; - console.log("setting row definitions"); try { const rowDefinition = Object.keys(tableState.columns) diff --git a/src/components/Table/ContextMenu/MenuContent.tsx b/src/components/Table/ContextMenu/MenuContent.tsx index f092ef2d..579b909e 100644 --- a/src/components/Table/ContextMenu/MenuContent.tsx +++ b/src/components/Table/ContextMenu/MenuContent.tsx @@ -1,4 +1,4 @@ -import { Menu } from "@mui/material"; +import { Divider, Menu } from "@mui/material"; import { default as MenuItem } from "./MenuItem"; import { IContextMenuItem } from "./MenuItem"; @@ -6,14 +6,14 @@ interface IMenuContents { anchorEl: HTMLElement; open: boolean; handleClose: () => void; - items: IContextMenuItem[]; + groups: IContextMenuItem[][]; } export function MenuContents({ anchorEl, open, handleClose, - items, + groups, }: IMenuContents) { const handleContext = (e: React.MouseEvent) => e.preventDefault(); @@ -41,8 +41,13 @@ export function MenuContents({ }} onContextMenu={handleContext} > - {items.map((item, indx: number) => ( - + {groups.map((items, groupIndex) => ( + <> + {groupIndex > 0 && } + {items.map((item, index: number) => ( + + ))} + ))} ); diff --git a/src/components/Table/ContextMenu/index.tsx b/src/components/Table/ContextMenu/index.tsx index 620b27be..caddca08 100644 --- a/src/components/Table/ContextMenu/index.tsx +++ b/src/components/Table/ContextMenu/index.tsx @@ -3,26 +3,88 @@ import { getColumnType, getFieldProp } from "@src/components/fields"; import { useProjectContext } from "@src/contexts/ProjectContext"; import { MenuContents } from "./MenuContent"; import { useContextMenuAtom } from "@src/atoms/ContextMenu"; - +import { FieldType } from "@src/constants/fields"; +import DuplicateIcon from "@src/assets/icons/Copy"; +import DeleteIcon from "@mui/icons-material/DeleteOutlined"; +import { useAppContext } from "@src/contexts/AppContext"; +import { IContextMenuItem } from "./MenuItem"; +import { useConfirmation } from "@src/components/ConfirmationDialog/Context"; export default function ContextMenu() { - const { tableState } = useProjectContext(); + const { requestConfirmation } = useConfirmation(); + const { tableState, deleteRow, addRow } = useProjectContext(); + const { userRoles } = useAppContext(); const { anchorEle, selectedCell, resetContextMenu } = useContextMenuAtom(); const columns = tableState?.columns; const selectedColIndex = selectedCell?.colIndex; const selectedColumn = _find(columns, { index: selectedColIndex }); if (!selectedColumn) return <>; - const configActions = - getFieldProp("contextMenuActions", selectedColumn.type) || - function empty() {}; - const actions = configActions(selectedCell, resetContextMenu) || []; + const menuActions = getFieldProp("contextMenuActions", selectedColumn.type); - if (!anchorEle || actions.length === 0) return <>; + const actionGroups: IContextMenuItem[][] = []; + + const actions = menuActions + ? menuActions(selectedCell, resetContextMenu) + : []; + if (actions.length > 0) actionGroups.push(actions); + + let hasRenderedFieldActions = false; + if (selectedColumn.type === FieldType.derivative) { + const renderedFieldMenuActions = getFieldProp( + "contextMenuActions", + selectedColumn.config.renderFieldType + ); + if (renderedFieldMenuActions) { + actionGroups.push( + renderedFieldMenuActions(selectedCell, resetContextMenu) + ); + hasRenderedFieldActions = true; + } + } + if (!anchorEle || (actions.length === 0 && !hasRenderedFieldActions)) + return <>; + const row = tableState?.rows[selectedCell!.rowIndex]; + if (userRoles.includes("ADMIN") && row) { + const rowActions = [ + { + label: "Duplicate Row", + icon: , + onClick: () => { + const { ref, ...clonedRow } = row; + addRow!(clonedRow, undefined, { type: "smaller" }); + }, + }, + { + label: "Delete Row", + variant: "secondary", + icon: , + onClick: () => { + requestConfirmation({ + title: "Delete row?", + customBody: ( + <> + Row path: +
+ + {row.ref.path} + + + ), + confirm: "Delete", + confirmColor: "error", + handleConfirm: () => deleteRow?.(row.id), + }); + resetContextMenu(); + }, + }, + ]; + actionGroups.push(rowActions); + } return ( ); } diff --git a/src/components/Table/formatters/FinalColumn.tsx b/src/components/Table/formatters/FinalColumn.tsx index 534bf983..afe1935d 100644 --- a/src/components/Table/formatters/FinalColumn.tsx +++ b/src/components/Table/formatters/FinalColumn.tsx @@ -45,14 +45,8 @@ export default function FinalColumn({ row }: FormatterProps) { color="inherit" disabled={!addRow} onClick={() => { - const clonedRow = { ...row }; - // remove metadata - delete clonedRow.ref; - delete clonedRow.rowHeight; - Object.keys(clonedRow).forEach((key) => { - if (clonedRow[key] === undefined) delete clonedRow[key]; - }); - if (addRow) addRow!(clonedRow, undefined, { type: "smaller" }); + const { ref, ...clonedRow } = row; + addRow!(clonedRow, undefined, { type: "smaller" }); }} aria-label="Duplicate row" className="row-hover-iconButton" diff --git a/src/components/fields/Derivative/ContextMenuActions.tsx b/src/components/fields/Derivative/ContextMenuActions.tsx index e05f22f1..626da1ad 100644 --- a/src/components/fields/Derivative/ContextMenuActions.tsx +++ b/src/components/fields/Derivative/ContextMenuActions.tsx @@ -1,15 +1,11 @@ import _find from "lodash/find"; import _get from "lodash/get"; -import Cut from "@mui/icons-material/ContentCut"; -import CopyCells from "@src/assets/icons/CopyCells"; -import Paste from "@mui/icons-material/ContentPaste"; -import EvalIcon from "@mui/icons-material/Replay"; - +import ReEvalIcon from "@mui/icons-material/Replay"; +import EvalIcon from "@mui/icons-material/PlayCircle"; import { useProjectContext } from "@src/contexts/ProjectContext"; import { useSnackbar } from "notistack"; import { SelectedCell } from "@src/atoms/ContextMenu"; -import { getFieldProp, getColumnType } from "@src/components/fields"; import { runRoutes } from "@src/constants/runRoutes"; export interface IContextMenuActions { @@ -22,7 +18,7 @@ export default function ContextMenuActions( selectedCell: SelectedCell, reset: () => void | Promise ): IContextMenuActions[] { - const { tableState, deleteCell, updateCell, rowyRun } = useProjectContext(); + const { tableState, rowyRun } = useProjectContext(); const { enqueueSnackbar } = useSnackbar(); const columns = tableState?.columns; const rows = tableState?.rows; @@ -30,18 +26,18 @@ export default function ContextMenuActions( const selectedColIndex = selectedCell?.colIndex; const selectedCol = _find(columns, { index: selectedColIndex }); if (!selectedCol) return []; + // don't show evalute button if function has external dependency + const code = + selectedCol.config.derivativeFn ?? selectedCol.config.script ?? ""; + if (code.includes("require(")) return []; const selectedRow = rows?.[selectedRowIndex]; const cellValue = _get(selectedRow, selectedCol.key); - console.log({ - selectedCol, - schemaDocPath: tableState?.config.tableConfig.path, - }); const handleClose = async () => await reset?.(); const handleEvaluate = async () => { try { if (!selectedCol || !rowyRun || !selectedRow) return; - rowyRun({ + const result = await rowyRun({ route: runRoutes.evaluateDerivative, body: { ref: { @@ -51,13 +47,22 @@ export default function ContextMenuActions( columnKey: selectedCol.key, }, }); + if (result.success === false) { + enqueueSnackbar(result.message, { variant: "error" }); + } } catch (error) { enqueueSnackbar(`Failed: ${error}`, { variant: "error" }); } handleClose(); }; + const isEmpty = + cellValue === "" || cellValue === null || cellValue === undefined; const contextMenuActions = [ - { label: "evalute", icon: , onClick: handleEvaluate }, + { + label: isEmpty ? "Evaluate" : "Re-evaluate", + icon: isEmpty ? : , + onClick: handleEvaluate, + }, ]; return contextMenuActions; diff --git a/src/hooks/useTable/useTableData.tsx b/src/hooks/useTable/useTableData.tsx index e1e2b657..a0087a78 100644 --- a/src/hooks/useTable/useTableData.tsx +++ b/src/hooks/useTable/useTableData.tsx @@ -35,8 +35,6 @@ const rowsReducer = (prevRows: any, update: any) => { switch (update.type) { case "onSnapshot": const snapshotDocs = update.docs; - console.log("onSnapshot", snapshotDocs); - // Get rows that may not be part of the snapshot // Rows with missing required fields haven’t been written to the db yet // Out of order rows will appear on top @@ -168,7 +166,6 @@ const useTableData = () => { const unsubscribe = query.limit(limit).onSnapshot( (snapshot) => { - console.log("snapshot", snapshot); // if (snapshot.docs.length > 0) { rowsDispatch({ type: "onSnapshot",