diff --git a/package.json b/package.json index ee74bb8..d8237d9 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "@emotion/styled": "^11.11.5", "@mui/icons-material": "^5.18.0", "@mui/material": "^5.18.0", - "@vertexvis/api-client-node": "^0.33.2", + "@vertexvis/api-client-node": "^0.40.0", "@vertexvis/geometry": "^0.24.2", "@vertexvis/viewer-react": "^0.24.2", "lodash.debounce": "^4.0", @@ -17,6 +17,7 @@ "next": "^15.5.7", "next-connect": "^0.13.0", "next-iron-session": "^4.2", + "pretty-bytes": "^7.1.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-hook-form": "^7.63.0", diff --git a/src/components/file/FileDetailsDrawer.tsx b/src/components/file/FileDetailsDrawer.tsx new file mode 100644 index 0000000..4dd2f43 --- /dev/null +++ b/src/components/file/FileDetailsDrawer.tsx @@ -0,0 +1,169 @@ +import { Close } from "@mui/icons-material"; +import { + Box, + Drawer, + IconButton, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography, +} from "@mui/material"; +import React from "react"; + +import { toLocaleString } from "../../lib/dates"; +import { File } from "../../lib/files"; +import { toDisplayValue, toFileSizeDisplay } from "../../lib/formatting"; +import { RightDrawerWidth } from "../shared/Layout"; + +interface Props { + readonly file?: File; + readonly onClose: () => void; + readonly open: boolean; +} + +export function FileDetailsDrawer({ + file, + onClose, + open, +}: Props): JSX.Element { + return ( + + + + File Details + + + + + + {file ? ( + + + + + + + + + + + + +
+
+ ) : ( + <> + )} +
+ ); +} + +function DetailsRow({ + label, + value, +}: { + readonly label: string; + readonly value?: string; +}): JSX.Element { + return ( + + + {label} + + {toDisplayValue(value)} + + + + ); +} + +function MetadataRow({ + metadata, +}: { + readonly metadata?: Record; +}): JSX.Element { + const entries = metadata == null ? [] : Object.entries(metadata); + + return ( + + + Metadata + {entries.length > 0 ? ( + + + + + Key + + + Value + + + + + {entries.map(([key, value]) => ( + + + + {toDisplayValue(key)} + + + + + {toDisplayValue(value)} + + + + ))} + +
+ ) : ( + + N/A + + )} +
+
+ ); +} diff --git a/src/components/file/FileTable.tsx b/src/components/file/FileTable.tsx index bd3a167..a1e69cb 100644 --- a/src/components/file/FileTable.tsx +++ b/src/components/file/FileTable.tsx @@ -20,7 +20,7 @@ import React from "react"; import useSWR from "swr"; import { toLocaleString } from "../../lib/dates"; -import { toFilePage } from "../../lib/files"; +import { File, toFilePage } from "../../lib/files"; import { SwrProps } from "../../lib/paging"; import { DataLoadError } from "../shared/DataLoadError"; import { DefaultPageSize, DefaultRowHeight } from "../shared/Layout"; @@ -46,7 +46,15 @@ function useFiles({ cursor, pageSize, suppliedId }: SwrProps) { ); } -export default function FilesTable(): JSX.Element { +interface Props { + readonly activeFileId?: string; + readonly onFileSelected: (file: File) => void; +} + +export default function FilesTable({ + activeFileId, + onFileSelected, +}: Props): JSX.Element { const pageSize = DefaultPageSize; const [curPage, setCurPage] = React.useState(0); const [cursor, setCursor] = React.useState(); @@ -173,17 +181,23 @@ export default function FilesTable(): JSX.Element { ) : ( page.items.map((row) => { const isSel = selected.has(row.id); + const isActive = activeFileId === row.id; + return ( onFileSelected(row)} > handleCheck(row.id)} + onClick={(e) => { + e.stopPropagation(); + handleCheck(row.id); + }} > diff --git a/src/lib/formatting.ts b/src/lib/formatting.ts new file mode 100644 index 0000000..16cf72d --- /dev/null +++ b/src/lib/formatting.ts @@ -0,0 +1,11 @@ +import prettyBytes from "pretty-bytes"; + +export function toDisplayValue(value?: string): string { + return value == null || value.trim().length === 0 ? "N/A" : value; +} + +export function toFileSizeDisplay(size?: number): string | undefined { + if (size == null) return undefined; + + return prettyBytes(size); +} diff --git a/src/pages/files.tsx b/src/pages/files.tsx index ed57d3f..c03a2bd 100644 --- a/src/pages/files.tsx +++ b/src/pages/files.tsx @@ -1,7 +1,9 @@ import dynamic from "next/dynamic"; import React from "react"; +import { FileDetailsDrawer } from "../components/file/FileDetailsDrawer"; import { Layout } from "../components/shared/Layout"; +import { File } from "../lib/files"; import { defaultServerSideProps } from "../lib/with-session"; const FilesTable = dynamic(() => import("../components/file/FileTable"), { @@ -9,7 +11,24 @@ const FilesTable = dynamic(() => import("../components/file/FileTable"), { }); export default function Files(): JSX.Element { - return } />; + const [file, setFile] = React.useState(); + const drawerOpen = Boolean(file); + + return ( + + } + rightDrawer={ + setFile(undefined)} + open={drawerOpen} + /> + } + rightDrawerOpen={drawerOpen} + /> + ); } export const getServerSideProps = defaultServerSideProps; diff --git a/yarn.lock b/yarn.lock index c603f2b..6e8f734 100644 --- a/yarn.lock +++ b/yarn.lock @@ -994,12 +994,12 @@ "@typescript-eslint/types" "4.33.0" eslint-visitor-keys "^2.0.0" -"@vertexvis/api-client-node@^0.33.2": - version "0.33.2" - resolved "https://registry.yarnpkg.com/@vertexvis/api-client-node/-/api-client-node-0.33.2.tgz#bfb15f972270b1aaf5ae80abb146b28b00a2559b" - integrity sha512-u4Hso8ADOMbk7s/8Qhm0vGzY1sR2MDQ3mWiFc0xxJ051rL2S/UdKjSQA/Yjm3eU2ut1cTmso8ZXWVqvuAqcv6w== +"@vertexvis/api-client-node@^0.40.0": + version "0.40.0" + resolved "https://registry.yarnpkg.com/@vertexvis/api-client-node/-/api-client-node-0.40.0.tgz#9563245ab3247ae3789bc972a739f2638b2681da" + integrity sha512-z/WtLR296nffQc047nHIiFhC68Vhw4Xs5354t0LWA4j+jPm8ybnf6UkObuJcJv1Nm13qsRe+NAJ08zQzUbtGzQ== dependencies: - axios "^1.6.4" + axios "1.11.0" p-limit "^3" "@vertexvis/frame-streaming-protos@^0.15.1": @@ -1293,7 +1293,7 @@ axe-core@^4.10.0: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.2.tgz#85228e3e1d8b8532a27659b332e39b7fa0e022df" integrity sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w== -axios@1.8.2, axios@^1.6.4: +axios@1.11.0, axios@1.8.2: version "1.8.2" resolved "https://registry.yarnpkg.com/axios/-/axios-1.8.2.tgz#fabe06e241dfe83071d4edfbcaa7b1c3a40f7979" integrity sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg== @@ -3253,6 +3253,11 @@ prettier@^2.4: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== +pretty-bytes@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-7.1.0.tgz#d788c9906241dbdcd4defab51b6d7470243db9bd" + integrity sha512-nODzvTiYVRGRqAOvE84Vk5JDPyyxsVk0/fbA/bq7RqlnhksGpset09XTxbpvLTIjoaF7K8Z8DG8yHtKGTPSYRw== + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"