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;