Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat-600 add context menu for cells #623

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/assets/icons/Paste.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import SvgIcon, { SvgIconProps } from "@mui/material/SvgIcon";
import { mdiContentPaste } from "@mdi/js";

export default function Paste(props: SvgIconProps) {
return (
<SvgIcon {...props}>
<path d={mdiContentPaste} />
</SvgIcon>
);
}
10 changes: 10 additions & 0 deletions src/assets/icons/PasteFromClipboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import SvgIcon, { SvgIconProps } from "@mui/material/SvgIcon";
import { mdiClipboardArrowDownOutline } from "@mdi/js";

export default function PasteFromClipboard(props: SvgIconProps) {
return (
<SvgIcon {...props}>
<path d={mdiClipboardArrowDownOutline} />
</SvgIcon>
);
}
2 changes: 1 addition & 1 deletion src/components/SideDrawer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ export default function SideDrawer() {

const [cell, setCell] = useState<SelectedCell>(null);
const [open, setOpen] = useState(false);
if (sideDrawerRef) sideDrawerRef.current = { cell, setCell, open, setOpen };

if (sideDrawerRef) sideDrawerRef.current = { cell, setCell, open, setOpen };
const handleNavigate = (direction: "up" | "down") => () => {
if (!tableState?.rows) return;

Expand Down
42 changes: 42 additions & 0 deletions src/components/Table/CellMenu/MenuContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Menu } from "@mui/material";
import MenuRow, { IMenuRow } from "./MenuRow";

interface IMenuContents {
anchorEl: HTMLElement;
open: boolean;
handleClose: () => void;
items: IMenuRow[];
}

export function MenuContents({
anchorEl,
open,
handleClose,
items,
}: IMenuContents) {
const handleContext = (e: React.MouseEvent) => e.preventDefault();
return (
<Menu
id="cell-context-menu"
aria-labelledby="cell-context-menu"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
transformOrigin={{
vertical: "top",
horizontal: "left",
}}
sx={{ "& .MuiMenu-paper": { backgroundColor: "background.default" } }}
MenuListProps={{ disablePadding: true }}
onContextMenu={handleContext}
>
{items.map((item, indx: number) => (
<MenuRow key={indx} {...item} />
))}
</Menu>
);
}
16 changes: 16 additions & 0 deletions src/components/Table/CellMenu/MenuRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ListItemIcon, ListItemText, MenuItem } from "@mui/material";

export interface IMenuRow {
onClick: () => void;
icon: JSX.Element;
label: string;
}

export default function MenuRow({ onClick, icon, label }: IMenuRow) {
return (
<MenuItem onClick={onClick}>
<ListItemIcon>{icon} </ListItemIcon>
<ListItemText> {label} </ListItemText>
</MenuItem>
);
}
96 changes: 96 additions & 0 deletions src/components/Table/CellMenu/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React from "react";
import { PopoverProps } from "@mui/material";
import CopyCells from "@src/assets/icons/CopyCells";
import Paste from "@src/assets/icons/Paste";
import { useProjectContext } from "@src/contexts/ProjectContext";
import { MenuContents } from "./MenuContent";
import _find from "lodash/find";
import { atom, useAtom } from "jotai";
import PasteFromClipboard from "@src/assets/icons/PasteFromClipboard";

export const cellMenuDataAtom = atom("");

export type SelectedCell = {
rowIndex: number;
colIndex: number;
};

export type CellMenuRef = {
selectedCell: SelectedCell;
setSelectedCell: React.Dispatch<React.SetStateAction<SelectedCell | null>>;
anchorEl: HTMLElement | null;
setAnchorEl: React.Dispatch<
React.SetStateAction<PopoverProps["anchorEl"] | null>
>;
};

export default function CellMenu() {
const { cellMenuRef, tableState, updateCell }: any = useProjectContext();
const [anchorEl, setAnchorEl] = React.useState<any | null>(null);
const [selectedCell, setSelectedCell] = React.useState<any | null>();
const [cellMenuData, setCellMenuData] = useAtom(cellMenuDataAtom);
const open = Boolean(anchorEl);

if (cellMenuRef)
cellMenuRef.current = {
anchorEl,
setAnchorEl,
selectedCell,
setSelectedCell,
} as {};

const handleClose = () => setAnchorEl(null);
const handleCopy = () => {
const cols = _find(tableState.columns, { index: selectedCell.colIndex });
const rows = tableState.rows[selectedCell.rowIndex];
const cell = rows[cols.key];
const formatData = typeof cell === "object" ? JSON.stringify(cell) : cell;
setCellMenuData(formatData);
const onFail = () => console.log("Fail to copy");
const onSuccess = () => console.log;

const copy = navigator.clipboard.writeText(formatData);
copy.then(onSuccess, onFail);

handleClose();
};
const handlePaste = () => {
const targetCol = _find(tableState.columns, {
index: selectedCell.colIndex,
});
const targetRow = tableState.rows[selectedCell.rowIndex];
if (cellMenuData) updateCell(targetRow.ref, targetCol.key, cellMenuData);
handleClose();
};

const handlePasteFromClipboard = () => {
const paste = navigator.clipboard.readText();
const targetCol = _find(tableState.columns, {
index: selectedCell.colIndex,
});
const targetRow = tableState.rows[selectedCell.rowIndex];
paste.then((clipText) =>
updateCell(targetRow.ref, targetCol.key, clipText)
);
};

const cellMenuAction = [
{ label: "Copy", icon: <CopyCells />, onClick: handleCopy },
{ label: "Paste", icon: <Paste />, onClick: handlePaste },
{
label: "Paste from Clipboard",
icon: <PasteFromClipboard />,
onClick: handlePasteFromClipboard,
},
];

if (!cellMenuRef.current || !open) return <></>;
return (
<MenuContents
anchorEl={anchorEl}
open={open}
handleClose={handleClose}
items={cellMenuAction}
/>
);
}
22 changes: 20 additions & 2 deletions src/components/Table/TableRow.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useProjectContext } from "@src/contexts/ProjectContext";
import { Fragment } from "react";
import { Row, RowRendererProps } from "react-data-grid";

Expand All @@ -8,9 +9,26 @@ export default function TableRow(props: RowRendererProps<any>) {
return (
<Fragment key={props.row.id}>
<OutOfOrderIndicator top={props.top} height={props.height} />
<Row {...props} />
<ContextMenu>
<Row {...props} />
</ContextMenu>
</Fragment>
);

return <Row {...props} />;
return (
<ContextMenu>
<Row {...props} />
</ContextMenu>
);
}

const ContextMenu = (props: any) => {
const { cellMenuRef }: any = useProjectContext();

const handleClick = (e: any) => {
e.preventDefault();
const input = e?.target as HTMLElement;
cellMenuRef.current.setAnchorEl(input);
};
return <span onContextMenu={handleClick}>{props.children}</span>;
};
19 changes: 16 additions & 3 deletions src/components/Table/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import DataGrid, {
import Loading from "@src/components/Loading";
import TableContainer, { OUT_OF_ORDER_MARGIN } from "./TableContainer";
import TableHeader from "../TableHeader";
import CellMenu from "./CellMenu";
import ColumnHeader from "./ColumnHeader";
import ColumnMenu from "./ColumnMenu";
import FinalColumnHeader from "./FinalColumnHeader";
Expand All @@ -43,8 +44,14 @@ const rowClass = (row: any) => (row._rowy_outOfOrder ? "out-of-order" : "");
//const SelectColumn = { ..._SelectColumn, width: 42, maxWidth: 42 };

export default function Table() {
const { tableState, tableActions, dataGridRef, sideDrawerRef, updateCell } =
useProjectContext();
const {
tableState,
tableActions,
dataGridRef,
cellMenuRef,
sideDrawerRef,
updateCell,
} = useProjectContext();
const { userDoc } = useAppContext();

const userDocHiddenFields =
Expand Down Expand Up @@ -245,14 +252,20 @@ export default function Table() {
});
}
}}
onSelectedCellChange={({ rowIdx, idx }) => {
cellMenuRef?.current?.setSelectedCell({
rowIndex: rowIdx,
colIndex: idx,
});
}}
/>
</DndProvider>
) : (
<Loading message="Fetching columns" />
)}
</TableContainer>

<ColumnMenu />
<CellMenu />
<BulkActions
selectedRows={selectedRows}
columns={columns}
Expand Down
5 changes: 5 additions & 0 deletions src/contexts/ProjectContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import useTable, { TableActions, TableState } from "@src/hooks/useTable";
import useSettings from "@src/hooks/useSettings";
import { useAppContext } from "./AppContext";
import { SideDrawerRef } from "@src/components/SideDrawer";
import { CellMenuRef } from "@src/components/Table/CellMenu";
import { ColumnMenuRef } from "@src/components/Table/ColumnMenu";
import { ImportWizardRef } from "@src/components/Wizards/ImportWizard";

Expand Down Expand Up @@ -99,6 +100,8 @@ export interface IProjectContext {
dataGridRef: React.RefObject<DataGridHandle>;
// A ref to the side drawer state. Prevents unnecessary re-renders
sideDrawerRef: React.MutableRefObject<SideDrawerRef | undefined>;
//A ref to the cell menu. Prevents unnecessary re-render
cellMenuRef: React.MutableRefObject<CellMenuRef | undefined>;
// A ref to the column menu. Prevents unnecessary re-renders
columnMenuRef: React.MutableRefObject<ColumnMenuRef | undefined>;
// A ref ot the import wizard. Prevents unnecessary re-renders
Expand Down Expand Up @@ -385,6 +388,7 @@ export const ProjectContextProvider: React.FC = ({ children }) => {
const dataGridRef = useRef<DataGridHandle>(null);
const sideDrawerRef = useRef<SideDrawerRef>();
const columnMenuRef = useRef<ColumnMenuRef>();
const cellMenuRef = useRef<CellMenuRef>();
const importWizardRef = useRef<ImportWizardRef>();

return (
Expand All @@ -403,6 +407,7 @@ export const ProjectContextProvider: React.FC = ({ children }) => {
table,
dataGridRef,
sideDrawerRef,
cellMenuRef,
columnMenuRef,
importWizardRef,
rowyRun: _rowyRun,
Expand Down