github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/webui/src/lib/components/repository/treeRows.jsx (about)

     1  import React, {useState} from "react";
     2  import {Link} from "../nav";
     3  import {
     4      ChevronDownIcon,
     5      ChevronRightIcon, CircleSlashIcon,
     6      ClockIcon,
     7      FileDirectoryIcon,
     8      HistoryIcon, PencilIcon, FileIcon, TableIcon, TrashIcon
     9  } from "@primer/octicons-react";
    10  import ChangeSummary from "./ChangeSummary";
    11  import {ConfirmationModal} from "../modals";
    12  import {OverlayTrigger} from "react-bootstrap";
    13  import Tooltip from "react-bootstrap/Tooltip";
    14  import Button from "react-bootstrap/Button";
    15  import {TreeRowType} from "../../../constants";
    16  
    17  class RowAction {
    18      /**
    19       * @param {JSX.Element} icon
    20       * @param {string} tooltip
    21       * @param {string} text
    22       * @param {()=>void} onClick
    23       */
    24      constructor(icon, tooltip= "", text, onClick) {
    25          this.icon = icon
    26          this.tooltip = tooltip
    27          this.text = text
    28          this.onClick = onClick
    29      }
    30  }
    31  
    32  /**
    33   * @param {[RowAction]} actions
    34   */
    35  const ChangeRowActions = ({actions}) => <>
    36      {
    37          actions.map(action => (
    38              <><OverlayTrigger placement="bottom" overlay={<Tooltip hidden={!action.tooltip}>{action.tooltip}</Tooltip>}>
    39                  <Button variant="link" disabled={false}
    40                          onClick={(e) => {
    41                              e.preventDefault();
    42                              action.onClick()
    43                          }}>
    44                      {action.icon
    45                          ? action.icon
    46                          : action.text}
    47                  </Button>
    48              </OverlayTrigger>&#160;&#160;</>
    49          ))}
    50  </>;
    51  
    52  export const ObjectTreeEntryRow = ({entry, relativeTo = "", diffExpanded, depth = 0, loading = false, onRevert, onClickExpandDiff = null}) => {
    53      const [showRevertConfirm, setShowRevertConfirm] = useState(false)
    54      let rowClass = 'tree-entry-row ' + diffType(entry);
    55      let pathSection = extractPathText(entry, relativeTo);
    56      const diffIndicator = <DiffIndicationIcon entry={entry} rowType={TreeRowType.Object}/>;
    57  
    58      const rowActions = []
    59      if (onClickExpandDiff) {
    60          rowActions.push(new RowAction(null, null, diffExpanded ? "Hide object changes" : "Show object changes", onClickExpandDiff))
    61      }
    62      if (onRevert) {
    63          rowActions.push(new RowAction(<HistoryIcon/>, "Revert changes", null, () => {
    64              setShowRevertConfirm(true)
    65          }))
    66      }
    67      return (
    68          <TableRow className={rowClass} entry={entry} diffIndicator={diffIndicator} rowActions={rowActions}
    69                    onRevert={onRevert} depth={depth} loading={loading} pathSection={pathSection}
    70                    showRevertConfirm={showRevertConfirm} setShowRevertConfirm={() => setShowRevertConfirm(false)}/>
    71      );
    72  };
    73  
    74  export const PrefixTreeEntryRow = ({entry, relativeTo = "", dirExpanded, depth = 0, onClick, loading = false, onRevert, onNavigate, getMore}) => {
    75      const [showRevertConfirm, setShowRevertConfirm] = useState(false)
    76      let rowClass = 'tree-entry-row ' + diffType(entry);
    77      let pathSection = extractPathText(entry, relativeTo);
    78      let diffIndicator = <DiffIndicationIcon entry={entry} rowType={TreeRowType.Prefix}/>;
    79      const [showSummary, setShowSummary] = useState(false);
    80      if (entry.path_type === "common_prefix") {
    81          pathSection = <Link href={onNavigate(entry)}>{pathSection}</Link>
    82      }
    83      const rowActions = []
    84      rowActions.push(new RowAction(null, null, showSummary ? "Hide change summary" : "Calculate change summary", () => setShowSummary(!showSummary)))
    85      if (onRevert) {
    86          rowActions.push(new RowAction(<HistoryIcon/>, "Revert changes", null, () => {
    87              setShowRevertConfirm(true)
    88          }))
    89      }
    90  
    91      return (
    92          <TableRow className={rowClass} entry={entry} diffIndicator={diffIndicator} getMore={getMore} rowActions={rowActions}
    93                    onRevert={onRevert} depth={depth} loading={loading} pathSection={pathSection} showSummary={showSummary}
    94                    dirExpanded={dirExpanded} onExpand={onClick}
    95                    showRevertConfirm={showRevertConfirm} setShowRevertConfirm={() => setShowRevertConfirm(false)}
    96          />
    97      );
    98  };
    99  const PrefixExpansionSection = ({dirExpanded, onClick}) => {
   100      return (<span onClick={onClick}>
   101                  {dirExpanded ? <ChevronDownIcon/> : <ChevronRightIcon/>}
   102              </span>)
   103  }
   104  
   105  const TableRow = ({diffIndicator, depth, loading, showSummary, entry, getMore, rowActions,
   106                        showRevertConfirm, setShowRevertConfirm, pathSection, onRevert, dirExpanded, onExpand, ...rest}) => {
   107      return (<tr {...rest}>
   108              <td className="entry-type-indicator">{diffIndicator}</td>
   109              <td className="tree-path">
   110                          <span style={{marginLeft: (depth * 20) + "px"}}>
   111                              {pathSection}
   112                              {onExpand && <PrefixExpansionSection dirExpanded={dirExpanded} onClick={onExpand}/>}
   113                              {loading ? <ClockIcon/> : ""}
   114                          </span>
   115              </td>
   116              <td className={"change-summary"}>{showSummary && <ChangeSummary prefix={entry.path} getMore={getMore}/>}</td>
   117              <td className={"change-entry-row-actions"}>
   118                  <ChangeRowActions actions={rowActions} />
   119                  <ConfirmationModal show={showRevertConfirm} onHide={setShowRevertConfirm}
   120                                     msg={`Are you sure you wish to revert "${entry.path}" (${entry.type})?`}
   121                                     onConfirm={() => onRevert(entry)}/>
   122              </td>
   123          </tr>
   124      )
   125  }
   126  
   127  function extractPathText(entry, relativeTo) {
   128      let pathText = entry.path;
   129      if (pathText.startsWith(relativeTo)) {
   130          pathText = pathText.substr(relativeTo.length);
   131      }
   132      return pathText;
   133  }
   134  
   135  function diffType(entry) {
   136      switch (entry.type) {
   137          case 'changed':
   138          case 'prefix_changed':
   139              return 'diff-changed';
   140          case 'added':
   141              return 'diff-added';
   142          case 'removed':
   143              return 'diff-removed';
   144          case 'conflict':
   145              return 'diff-conflict';
   146          default:
   147              return '';
   148      }
   149  }
   150  export const DiffIndicationIcon = ({entry, rowType}) => {
   151      let diffIcon;
   152      let tooltipId;
   153      let tooltipText;
   154      if (rowType === TreeRowType.Prefix) {
   155          diffIcon = <FileDirectoryIcon/>;
   156          tooltipId = "tooltip-prefix";
   157          tooltipText = "Changes under prefix";
   158      } else if (rowType === TreeRowType.Table) {
   159          diffIcon = <TableIcon/>;
   160          tooltipId = "tooltip-table";
   161          tooltipText = "Table changed"
   162      } else {
   163          switch (entry.type) {
   164              case 'removed':
   165                  diffIcon = <TrashIcon/>;
   166                  tooltipId = "tooltip-removed";
   167                  tooltipText = "Removed";
   168                  break;
   169              case 'added':
   170                  diffIcon = <FileIcon/>;
   171                  tooltipId = "tooltip-added";
   172                  tooltipText = "Added";
   173                  break;
   174              case 'changed':
   175                  diffIcon = <PencilIcon/>;
   176                  tooltipId = "tooltip-changed";
   177                  tooltipText = "Changed";
   178                  break;
   179              case 'conflict':
   180                  diffIcon = <CircleSlashIcon/>;
   181                  tooltipId = "tooltip-conflict";
   182                  tooltipText = "Conflict";
   183                  break;
   184              default:
   185          }
   186      }
   187  
   188      return <OverlayTrigger placement="bottom" overlay={(<Tooltip id={tooltipId}>{tooltipText}</Tooltip>)}>
   189                  <span>
   190                      {diffIcon}
   191                  </span>
   192      </OverlayTrigger>;
   193  }