worked on copy/paste feature

This commit is contained in:
Anish Roy
2023-01-04 09:35:40 +00:00
parent 6d81bbfd50
commit e59a10c944
5 changed files with 203 additions and 90 deletions

View File

@@ -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",

View File

@@ -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: <Cut />, onClick: handleCut },

View File

@@ -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 ?? <EmptyState sx={{ py: 8 }} />
) : (
<TableBody
containerEl={containerEl}
containerRef={containerRef}
leafColumns={leafColumns}
rows={rows}
canEditCells={canEditCells}
lastFrozen={lastFrozen}
columnSizing={columnSizing}
/>
<TableKbShortcutProvider>
<TableBody
containerEl={containerEl}
containerRef={containerRef}
leafColumns={leafColumns}
rows={rows}
canEditCells={canEditCells}
lastFrozen={lastFrozen}
columnSizing={columnSizing}
/>
</TableKbShortcutProvider>
)}
</StyledTable>

View File

@@ -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<string | undefined>();
const [selectedCol, setSelectedCol] = useState<ColumnConfig>();
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>(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 (
<TableKbShortcutContext.Provider value={null}>
{props.children}
</TableKbShortcutContext.Provider>
);
}

View File

@@ -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"