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>  </> 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 }