From e59a10c944262e5b6df9b992b855252195c8872e Mon Sep 17 00:00:00 2001
From: Anish Roy <62830866+iamanishroy@users.noreply.github.com>
Date: Wed, 4 Jan 2023 09:35:40 +0000
Subject: [PATCH] worked on copy/paste feature
---
package.json | 1 +
.../BasicCellContextMenuActions.tsx | 87 +--------
src/components/Table/Table.tsx | 21 +-
src/contexts/TableKbShortcutContext.tsx | 179 ++++++++++++++++++
yarn.lock | 5 +
5 files changed, 203 insertions(+), 90 deletions(-)
create mode 100644 src/contexts/TableKbShortcutContext.tsx
diff --git a/package.json b/package.json
index 4fac6672..53fb464e 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
"dependencies": {
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
+ "@mantine/hooks": "^5.10.0",
"@mdi/js": "^6.6.96",
"@monaco-editor/react": "^4.4.4",
"@mui/icons-material": "^5.10.16",
diff --git a/src/components/Table/ContextMenu/BasicCellContextMenuActions.tsx b/src/components/Table/ContextMenu/BasicCellContextMenuActions.tsx
index fdaa1557..a82c6e78 100644
--- a/src/components/Table/ContextMenu/BasicCellContextMenuActions.tsx
+++ b/src/components/Table/ContextMenu/BasicCellContextMenuActions.tsx
@@ -1,94 +1,19 @@
-import { useAtom, useSetAtom } from "jotai";
-import { useSnackbar } from "notistack";
-import { get, find } from "lodash-es";
-
-// import Cut from "@mui/icons-material/ContentCut";
import { Copy as CopyCells } from "@src/assets/icons";
+// import Cut from "@mui/icons-material/ContentCut";
import Paste from "@mui/icons-material/ContentPaste";
-
-import {
- tableScope,
- tableSchemaAtom,
- tableRowsAtom,
- updateFieldAtom,
-} from "@src/atoms/tableScope";
-import { getFieldProp, getFieldType } from "@src/components/fields";
import { IFieldConfig } from "@src/components/fields/types";
+import { useMenuAction } from "@src/contexts/TableKbShortcutContext";
// TODO: Remove this and add `handlePaste` function to column config
export const BasicContextMenuActions: IFieldConfig["contextMenuActions"] = (
selectedCell,
reset
) => {
- const { enqueueSnackbar } = useSnackbar();
-
- const [tableSchema] = useAtom(tableSchemaAtom, tableScope);
- const [tableRows] = useAtom(tableRowsAtom, tableScope);
- const updateField = useSetAtom(updateFieldAtom, tableScope);
-
- const selectedCol = tableSchema.columns?.[selectedCell.columnKey];
- if (!selectedCol) return [];
-
- const selectedRow = find(tableRows, ["_rowy_ref.path", selectedCell.path]);
- const cellValue = get(selectedRow, selectedCol.fieldName);
-
const handleClose = async () => await reset?.();
-
- const handleCopy = async () => {
- try {
- await navigator.clipboard.writeText(cellValue);
- enqueueSnackbar("Copied");
- } catch (error) {
- enqueueSnackbar(`Failed to copy:${error}`, { variant: "error" });
- }
- handleClose();
- };
-
- // const handleCut = async () => {
- // try {
- // await navigator.clipboard.writeText(cellValue);
- // if (typeof cellValue !== "undefined")
- // updateField({
- // path: selectedCell.path,
- // fieldName: selectedCol.fieldName,
- // value: undefined,
- // deleteField: true,
- // });
- // } catch (error) {
- // enqueueSnackbar(`Failed to cut: ${error}`, { variant: "error" });
- // }
- // handleClose();
- // };
-
- const handlePaste = async () => {
- try {
- if (!selectedCol) return;
- const text = await navigator.clipboard.readText();
- const cellDataType = getFieldProp("dataType", getFieldType(selectedCol));
- let parsed;
- switch (cellDataType) {
- case "number":
- parsed = Number(text);
- if (isNaN(parsed)) throw new Error(`${text} is not a number`);
- break;
- case "string":
- parsed = text;
- break;
- default:
- parsed = JSON.parse(text);
- break;
- }
- updateField({
- path: selectedCell.path,
- fieldName: selectedCol.fieldName,
- value: parsed,
- });
- } catch (error) {
- enqueueSnackbar(`Failed to paste: ${error}`, { variant: "error" });
- }
-
- handleClose();
- };
+ const { handleCopy, handlePaste, cellValue } = useMenuAction(
+ selectedCell,
+ handleClose
+ );
const contextMenuActions = [
// { label: "Cut", icon: , onClick: handleCut },
diff --git a/src/components/Table/Table.tsx b/src/components/Table/Table.tsx
index 092ff0ab..0b772a2b 100644
--- a/src/components/Table/Table.tsx
+++ b/src/components/Table/Table.tsx
@@ -35,6 +35,7 @@ import { getFieldType, getFieldProp } from "@src/components/fields";
import { useKeyboardNavigation } from "./useKeyboardNavigation";
import { useSaveColumnSizing } from "./useSaveColumnSizing";
import type { TableRow, ColumnConfig } from "@src/types/table";
+import { TableKbShortcutProvider } from "@src/contexts/TableKbShortcutContext";
export const DEFAULT_ROW_HEIGHT = 41;
export const DEFAULT_COL_WIDTH = 150;
@@ -267,15 +268,17 @@ export default function Table({
{tableRows.length === 0 ? (
emptyState ??
) : (
-
+
+
+
)}
diff --git a/src/contexts/TableKbShortcutContext.tsx b/src/contexts/TableKbShortcutContext.tsx
new file mode 100644
index 00000000..507ed5eb
--- /dev/null
+++ b/src/contexts/TableKbShortcutContext.tsx
@@ -0,0 +1,179 @@
+import {
+ createContext,
+ useContext,
+ useCallback,
+ useState,
+ useEffect,
+} from "react";
+import { useAtom, useSetAtom } from "jotai";
+import { useSnackbar } from "notistack";
+import { get, find } from "lodash-es";
+import { useHotkeys } from "@mantine/hooks";
+
+import {
+ tableScope,
+ tableSchemaAtom,
+ tableRowsAtom,
+ updateFieldAtom,
+ selectedCellAtom,
+ SelectedCell,
+} from "@src/atoms/tableScope";
+import { getFieldProp, getFieldType } from "@src/components/fields";
+import { ColumnConfig } from "@src/types/table";
+
+import { FieldType } from "@src/constants/fields";
+
+const SUPPORTED_TYPES = new Set([
+ FieldType.shortText,
+ FieldType.longText,
+ FieldType.number,
+ FieldType.email,
+ FieldType.percentage,
+ FieldType.phone,
+ FieldType.richText,
+ FieldType.url,
+]);
+
+export function useMenuAction(
+ selectedCell: SelectedCell | null,
+ handleClose?: Function
+) {
+ const { enqueueSnackbar } = useSnackbar();
+ const [tableSchema] = useAtom(tableSchemaAtom, tableScope);
+ const [tableRows] = useAtom(tableRowsAtom, tableScope);
+ const updateField = useSetAtom(updateFieldAtom, tableScope);
+ const [cellValue, setCellValue] = useState();
+ const [selectedCol, setSelectedCol] = useState();
+ const [enableAction, setEnableAction] = useState(false);
+
+ const handleCopy = useCallback(async () => {
+ try {
+ if (cellValue !== undefined && cellValue !== null && cellValue !== "") {
+ await navigator.clipboard.writeText(
+ typeof cellValue === "object" ? JSON.stringify(cellValue) : cellValue
+ );
+ enqueueSnackbar("Copied");
+ } else {
+ await navigator.clipboard.writeText("");
+ }
+ } catch (error) {
+ enqueueSnackbar(`Failed to copy:${error}`, { variant: "error" });
+ }
+ if (handleClose) handleClose();
+ }, [cellValue, enqueueSnackbar, handleClose]);
+
+ const handleCut = useCallback(async () => {
+ try {
+ if (!selectedCell || !selectedCol || !cellValue) return;
+ if (cellValue !== undefined && cellValue !== null && cellValue !== "") {
+ await navigator.clipboard.writeText(
+ typeof cellValue === "object" ? JSON.stringify(cellValue) : cellValue
+ );
+ enqueueSnackbar("Copied");
+ } else {
+ await navigator.clipboard.writeText("");
+ }
+ if (cellValue !== undefined)
+ updateField({
+ path: selectedCell.path,
+ fieldName: selectedCol.fieldName,
+ value: undefined,
+ deleteField: true,
+ });
+ } catch (error) {
+ enqueueSnackbar(`Failed to cut: ${error}`, { variant: "error" });
+ }
+ if (handleClose) handleClose();
+ }, [
+ cellValue,
+ selectedCell,
+ selectedCol,
+ updateField,
+ enqueueSnackbar,
+ handleClose,
+ ]);
+
+ const handlePaste = useCallback(async () => {
+ try {
+ if (!selectedCell || !selectedCol) return;
+ const text = await navigator.clipboard.readText();
+ const cellDataType = getFieldProp("dataType", getFieldType(selectedCol));
+ let parsed;
+ switch (cellDataType) {
+ case "number":
+ parsed = Number(text);
+ if (isNaN(parsed)) throw new Error(`${text} is not a number`);
+ break;
+ case "string":
+ parsed = text;
+ break;
+ default:
+ parsed = JSON.parse(text);
+ break;
+ }
+ updateField({
+ path: selectedCell.path,
+ fieldName: selectedCol.fieldName,
+ value: parsed,
+ });
+ } catch (error) {
+ enqueueSnackbar(`Failed to paste: ${error}`, { variant: "error" });
+ }
+ if (handleClose) handleClose();
+ }, [selectedCell, selectedCol, updateField, enqueueSnackbar, handleClose]);
+
+ useEffect(() => {
+ setEnableAction(SUPPORTED_TYPES.has(selectedCol?.type));
+ }, [selectedCol]);
+
+ useEffect(() => {
+ if (!selectedCell) return setCellValue("");
+ const selectedCol = tableSchema.columns?.[selectedCell.columnKey];
+ if (!selectedCol) return setCellValue("");
+ setSelectedCol(selectedCol);
+ const selectedRow = find(tableRows, ["_rowy_ref.path", selectedCell.path]);
+ setCellValue(get(selectedRow, selectedCol.fieldName));
+ }, [selectedCell, tableSchema, tableRows]);
+
+ const checkEnabled = (func: Function) => {
+ return function () {
+ if (enableAction) {
+ return func();
+ } else {
+ enqueueSnackbar(`Simple copy not supported with this type.`, {
+ variant: "info",
+ });
+ }
+ };
+ };
+
+ return {
+ handleCopy: checkEnabled(handleCopy),
+ handleCut: checkEnabled(handleCut),
+ handlePaste: handlePaste,
+ cellValue,
+ };
+}
+
+const TableKbShortcutContext = createContext(null);
+
+export function useTableKbShortcut() {
+ return useContext(TableKbShortcutContext);
+}
+
+export function TableKbShortcutProvider(props: { children: any }) {
+ const [selectedCell] = useAtom(selectedCellAtom, tableScope);
+ const { handleCopy, handlePaste, handleCut } = useMenuAction(selectedCell);
+
+ useHotkeys([
+ ["mod+C", handleCopy],
+ ["mod+X", handleCut],
+ ["mod+V", handlePaste],
+ ]);
+
+ return (
+
+ {props.children}
+
+ );
+}
diff --git a/yarn.lock b/yarn.lock
index 003c37e7..61ea6da5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2217,6 +2217,11 @@
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b"
integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==
+"@mantine/hooks@^5.10.0":
+ version "5.10.0"
+ resolved "https://registry.yarnpkg.com/@mantine/hooks/-/hooks-5.10.0.tgz#e7886025a11dfa25f99c8c7fb7186d6a065c9d5c"
+ integrity sha512-dAefxpvqjFtXNeKse+awkIa4U1XGnMMOqWg1+07Y2Ino2G6EiT8AEnYqQyTXgcPoNaWwG9533Q/DDadmyweqaQ==
+
"@mark.probst/unicode-properties@~1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@mark.probst/unicode-properties/-/unicode-properties-1.1.0.tgz#5caafeab4737df93163d6d288007df33f9939b80"