Fix: copy/paste is picking up all fields in a subtable as subtable type

This commit is contained in:
Anish Roy
2023-01-14 11:19:55 +00:00
parent 0675ddcfa4
commit 5ce182a0ec
5 changed files with 140 additions and 70 deletions

View File

@@ -10,7 +10,6 @@
"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

@@ -30,12 +30,14 @@ import {
tableNextPageAtom,
tablePageAtom,
updateColumnAtom,
selectedCellAtom,
} from "@src/atoms/tableScope";
import { useMenuAction } from "@src/contexts/TableKbShortcutContext";
import { getFieldType, getFieldProp } from "@src/components/fields";
import { useKeyboardNavigation } from "./useKeyboardNavigation";
import { useSaveColumnSizing } from "./useSaveColumnSizing";
import useHotKeys from "./useHotKeys";
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;
@@ -182,6 +184,13 @@ export default function Table({
tableRows,
leafColumns,
});
const [selectedCell] = useAtom(selectedCellAtom, tableScope);
const { handleCopy, handlePaste, handleCut } = useMenuAction(selectedCell);
const { handler } = useHotKeys([
["mod+C", handleCopy],
["mod+X", handleCut],
["mod+V", handlePaste],
]);
// Handle prompt to save local column sizes if user `canEditColumns`
useSaveColumnSizing(columnSizing, canEditColumns);
@@ -243,7 +252,10 @@ export default function Table({
"--row-height": `${tableSchema.rowHeight || DEFAULT_ROW_HEIGHT}px`,
} as any
}
onKeyDown={handleKeyDown}
onKeyDown={(e) => {
handleKeyDown(e);
handler(e);
}}
>
<div
className="thead"
@@ -268,17 +280,15 @@ export default function Table({
{tableRows.length === 0 ? (
emptyState ?? <EmptyState sx={{ py: 8 }} />
) : (
<TableKbShortcutProvider>
<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}
/>
)}
</StyledTable>

View File

@@ -0,0 +1,99 @@
import { useCallback } from "react";
type HotKeysAction = [
string,
(event: React.KeyboardEvent<HTMLElement> | KeyboardEvent) => void
];
export default function useHotKeys(actions: HotKeysAction[]) {
// master event handler
const handler = useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
const event_ = "nativeEvent" in event ? event.nativeEvent : event;
actions.forEach(([hotkey, handler_]) => {
if (getHotkeyMatcher(hotkey)(event_)) {
event.preventDefault();
handler_(event_);
}
});
},
[actions]
);
return { handler };
}
type KeyboardModifiers = {
alt: boolean;
ctrl: boolean;
meta: boolean;
mod: boolean;
shift: boolean;
};
export type Hotkey = KeyboardModifiers & {
key?: string;
};
function isExactHotkey(hotkey: Hotkey, event: KeyboardEvent): boolean {
const { alt, ctrl, meta, mod, shift, key } = hotkey;
const { altKey, ctrlKey, metaKey, shiftKey, key: pressedKey } = event;
if (alt !== altKey) {
return false;
}
if (mod) {
if (!ctrlKey && !metaKey) {
return false;
}
} else {
if (ctrl !== ctrlKey) {
return false;
}
if (meta !== metaKey) {
return false;
}
}
if (shift !== shiftKey) {
return false;
}
if (
key &&
(pressedKey.toLowerCase() === key.toLowerCase() ||
event.code.replace("Key", "").toLowerCase() === key.toLowerCase())
) {
return true;
}
return false;
}
type CheckHotkeyMatch = (event: KeyboardEvent) => boolean;
export function getHotkeyMatcher(hotkey: string): CheckHotkeyMatch {
return (event) => isExactHotkey(parseHotkey(hotkey), event);
}
function parseHotkey(hotkey: string): Hotkey {
const keys = hotkey
.toLowerCase()
.split("+")
.map((part) => part.trim());
const modifiers: KeyboardModifiers = {
alt: keys.includes("alt"),
ctrl: keys.includes("ctrl"),
meta: keys.includes("meta"),
mod: keys.includes("mod"),
shift: keys.includes("shift"),
};
const reservedKeys = ["alt", "ctrl", "meta", "shift", "mod"];
const freeKey = keys.find((key) => !reservedKeys.includes(key));
return {
...modifiers,
key: freeKey,
};
}

View File

@@ -1,21 +1,13 @@
import {
createContext,
useContext,
useCallback,
useState,
useEffect,
} from "react";
import { 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";
@@ -45,7 +37,6 @@ export function useMenuAction(
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 {
@@ -134,10 +125,6 @@ export function useMenuAction(
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];
@@ -147,20 +134,23 @@ export function useMenuAction(
setCellValue(get(selectedRow, selectedCol.fieldName));
}, [selectedCell, tableSchema, tableRows]);
const checkEnabled = (func: Function) => {
return function () {
if (enableAction) {
return func();
} else {
enqueueSnackbar(
`${selectedCol?.type} field cannot be copied using keyboard shortcut`,
{
variant: "info",
}
);
}
};
};
const checkEnabled = useCallback(
(func: Function) => {
return function () {
if (SUPPORTED_TYPES.has(selectedCol?.type)) {
return func();
} else {
enqueueSnackbar(
`${selectedCol?.type} field cannot be copied using keyboard shortcut`,
{
variant: "info",
}
);
}
};
},
[selectedCol]
);
return {
handleCopy: checkEnabled(handleCopy),
@@ -169,26 +159,3 @@ export function useMenuAction(
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,11 +2217,6 @@
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"