github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/webui/src/pages/repositories/repository/objectViewer.tsx (about) 1 import React, { FC } from "react"; 2 import { useParams } from "react-router-dom"; 3 import { Box } from "@mui/material"; 4 import Alert from "react-bootstrap/Alert"; 5 import Card from "react-bootstrap/Card"; 6 7 import { useAPI } from "../../../lib/hooks/api"; 8 import { useQuery } from "../../../lib/hooks/router"; 9 import { objects } from "../../../lib/api"; 10 import { ObjectRenderer } from "./fileRenderers"; 11 import { AlertError } from "../../../lib/components/controls"; 12 import { URINavigator } from "../../../lib/components/repository/tree"; 13 import { RefTypeBranch } from "../../../constants"; 14 import { RefContextProvider } from "../../../lib/hooks/repo"; 15 import { useStorageConfig } from "../../../lib/hooks/storageConfig"; 16 import { linkToPath } from "../../../lib/api"; 17 18 import "../../../styles/quickstart.css"; 19 20 type ObjectViewerPathParams = { 21 objectName: string; 22 repoId: string; 23 }; 24 25 interface ObjectViewerQueryString { 26 ref: string; 27 path: string; 28 } 29 30 interface FileContentsProps { 31 repoId: string; 32 reference: { id: string; type: string }; 33 path: string; 34 loading: boolean; 35 error: Error | null; 36 contentType?: string | null; 37 fileExtension: string; 38 sizeBytes: number; 39 showFullNavigator?: boolean; 40 presign?: boolean; 41 } 42 43 export const Loading: FC = () => { 44 return <Alert variant={"info"}>Loading...</Alert>; 45 }; 46 47 export const getFileExtension = (objectName: string): string => { 48 const objectNameParts = objectName.split("."); 49 return objectNameParts[objectNameParts.length - 1]; 50 }; 51 52 export const getContentType = (headers: Headers): string | null => { 53 if (!headers) return null; 54 55 return headers.get("Content-Type") ?? null; 56 }; 57 58 const FileObjectsViewerPage = () => { 59 const config = useStorageConfig(); 60 const { repoId } = useParams<ObjectViewerPathParams>(); 61 const queryString = useQuery<ObjectViewerQueryString>(); 62 const refId = queryString["ref"] ?? ""; 63 const path = queryString["path"] ?? ""; 64 const { response, error, loading } = useAPI(() => { 65 return objects.head(repoId, refId, path); 66 }, [repoId, refId, path]); 67 68 let content; 69 if (loading || config.loading) { 70 content = <Loading />; 71 } else if (error) { 72 content = <AlertError error={error} />; 73 } else { 74 const fileExtension = getFileExtension(path); 75 // We'll need to convert the API service to get rid of this any 76 // eslint-disable-next-line @typescript-eslint/no-explicit-any 77 const contentType = getContentType((response as any)?.headers); 78 const sizeBytes = parseInt( 79 // eslint-disable-next-line @typescript-eslint/no-explicit-any 80 (response as any)?.headers.get("Content-Length") 81 ); 82 content = ( 83 <FileContents 84 repoId={repoId || ""} 85 // ref type is unknown since we lost that context while reaching here (and it's not worth a url param). 86 // Effectively it means that if the ref is commit, we won't truncate it in the URI navigator, 87 // which is a better behaviour than truncating it when it's a branch/tag. 88 reference={{ id: refId, type: RefTypeBranch}} 89 path={path} 90 fileExtension={fileExtension} 91 contentType={contentType} 92 sizeBytes={sizeBytes} 93 error={error} 94 loading={loading} 95 presign={config.pre_sign_support_ui} 96 /> 97 ); 98 } 99 100 return ( 101 <RefContextProvider> 102 {content} 103 </RefContextProvider> 104 ); 105 }; 106 107 export const FileContents: FC<FileContentsProps> = ({ 108 repoId, 109 reference, 110 path, 111 loading, 112 error, 113 contentType = null, 114 fileExtension = "", 115 sizeBytes = -1, 116 showFullNavigator = true, 117 presign = false, 118 }) => { 119 const objectUrl = linkToPath(repoId, reference.id, path, presign); 120 121 if (loading || error) { 122 return <></>; 123 } 124 125 const repo = { 126 id: repoId, 127 }; 128 129 const titleComponent = showFullNavigator ? ( 130 <URINavigator 131 path={path} 132 repo={repo} 133 reference={reference} 134 isPathToFile={true} 135 downloadUrl={objectUrl} 136 hasCopyButton={true} 137 /> 138 ) : ( 139 <span>{path}</span> 140 ); 141 142 return ( 143 <Card className={"file-content-card"}> 144 <Card.Header className={"file-content-heading"}> 145 {titleComponent} 146 </Card.Header> 147 <Card.Body className={"file-content-body"}> 148 <Box sx={{ mx: 1 }}> 149 <ObjectRenderer 150 repoId={repoId} 151 refId={reference.id} 152 path={path} 153 fileExtension={fileExtension} 154 contentType={contentType} 155 sizeBytes={sizeBytes} 156 presign={presign} 157 /> 158 </Box> 159 </Card.Body> 160 </Card> 161 ); 162 }; 163 164 export default FileObjectsViewerPage;