diff --git a/frontend/src/components/RowList.tsx b/frontend/src/components/RowList.tsx index 6934b0c..59b2ef4 100644 --- a/frontend/src/components/RowList.tsx +++ b/frontend/src/components/RowList.tsx @@ -12,11 +12,15 @@ import { useAppStore } from "@/stores/useAppStore"; import { useUIStore } from "@/stores/useUIStore"; import { formatDisplayValue } from "@/utils/formatDisplayValue"; import { getRowId } from "@/utils/getRowId"; -import { ChevronUp, Key } from "lucide-react"; +import { Check, ChevronUp, Copy, Key } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { cn } from "@/utils/cn"; import { useShallow } from "zustand/shallow"; import { formatDisplayType } from "@/utils/formatDisplayType"; +import { Button } from "@/components/shadcn-ui/button"; +import { toast } from "sonner"; +import { formatCopyValue } from "@/utils/formatCopyValue"; +import type { RowResponse, TableFullResponse } from "@catwallon/pgview-types"; const CELL_HEIGHT = 40; @@ -144,25 +148,14 @@ export function RowList() { }} > {Object.entries(row).map(([key, value]) => ( - -

- {isEmpty(value) - ? "empty" - : isNull(value) - ? "null" - : table?.columns && - formatDisplayValue( - table.columns.find((col) => col.name === key), - value, - )} -

-
+ col.name === key)} + isEmpty={isEmpty(value)} + isNull={isNull(value)} + key={key} + value={value} + /> ))} ))} @@ -178,3 +171,67 @@ export function RowList() { ); } + +function CopyableCell({ + cellHeight, + column, + isEmpty, + isNull, + value, +}: { + cellHeight: number; + column: TableFullResponse["columns"][number] | undefined; + isEmpty: boolean; + isNull: boolean; + value: RowResponse[string]; +}) { + const [copied, setCopied] = useState(false); + const canCopy = !isNull && !isEmpty; + + function handleCopy(e: React.MouseEvent) { + e.stopPropagation(); + + if (!canCopy) { + return; + } + + navigator.clipboard.writeText(formatCopyValue(column, value)).then(() => { + setCopied(true); + toast.success("Copied value"); + window.setTimeout(() => setCopied(false), 1000); + }); + } + + return ( + +
+

+ {isEmpty + ? "empty" + : isNull + ? "null" + : formatDisplayValue(column, value)} +

+ {canCopy && ( + + )} +
+
+ ); +} diff --git a/frontend/src/utils/formatCopyValue.ts b/frontend/src/utils/formatCopyValue.ts new file mode 100644 index 0000000..bc1d6e7 --- /dev/null +++ b/frontend/src/utils/formatCopyValue.ts @@ -0,0 +1,18 @@ +import type { TableFullResponse } from "@catwallon/pgview-types"; + +export function formatCopyValue( + col: TableFullResponse["columns"][number] | undefined, + value: unknown, +): string { + const type = col?.type; + + if (type === "json" || type === "jsonb") { + return JSON.stringify(value); + } + + if (col?.isArray && Array.isArray(value)) { + return JSON.stringify(value); + } + + return String(value); +}