go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/ui/src/gitiles/components/commit_table/commit_table_row.tsx (about) 1 // Copyright 2023 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 import { ChevronRight, ExpandMore } from '@mui/icons-material'; 16 import { IconButton, TableCell, TableRow, styled } from '@mui/material'; 17 import markdownIt from 'markdown-it'; 18 import { ReactNode, useEffect, useMemo, useState } from 'react'; 19 20 import { SanitizedHtml } from '@/common/components/sanitized_html'; 21 import { bugLine } from '@/common/tools/markdown/plugins/bug_line'; 22 import { bugnizerLink } from '@/common/tools/markdown/plugins/bugnizer_link'; 23 import { crbugLink } from '@/common/tools/markdown/plugins/crbug_link'; 24 import { defaultTarget } from '@/common/tools/markdown/plugins/default_target'; 25 import { reviewerLine } from '@/common/tools/markdown/plugins/reviewer_line'; 26 import { OutputCommit } from '@/gitiles/types'; 27 28 import { CommitProvider, useDefaultExpandedState } from './context'; 29 30 const md = markdownIt('zero', { breaks: true, linkify: true }) 31 .enable(['linkify', 'newline']) 32 .use(bugLine) 33 .use(reviewerLine) 34 .use(crbugLink) 35 .use(bugnizerLink) 36 .use(defaultTarget, '_blank'); 37 38 const SummaryContainer = styled(SanitizedHtml)({ 39 '& > p:first-of-type': { 40 marginBlockStart: 0, 41 }, 42 '& > p:last-of-type': { 43 marginBlockEnd: 0, 44 }, 45 }); 46 47 export interface CommitTableRowProps { 48 readonly commit: OutputCommit; 49 readonly children: ReactNode; 50 } 51 52 export function CommitTableRow({ commit, children }: CommitTableRowProps) { 53 const [defaultExpanded] = useDefaultExpandedState(); 54 const [expanded, setExpanded] = useState(() => defaultExpanded); 55 useEffect(() => { 56 setExpanded(defaultExpanded); 57 }, [defaultExpanded]); 58 59 const { descriptionHtml, changedFiles } = useMemo( 60 () => ({ 61 descriptionHtml: md.render(commit.message), 62 changedFiles: commit.treeDiff.map((diff) => 63 // If a file was moved, there is both an old and a new path, from which 64 // we take only the new path. 65 // If a file was deleted, its new path is /dev/null. In that case, we're 66 // only interested in the old path. 67 !diff.newPath || diff.newPath === '/dev/null' 68 ? diff.oldPath 69 : diff.newPath, 70 ), 71 }), 72 [commit], 73 ); 74 75 return ( 76 <> 77 <TableRow 78 sx={{ 79 '& > td': { 80 whiteSpace: 'nowrap', 81 }, 82 }} 83 > 84 <TableCell> 85 <IconButton 86 aria-label="toggle-row" 87 size="small" 88 onClick={() => setExpanded(!expanded)} 89 > 90 {expanded ? <ExpandMore /> : <ChevronRight />} 91 </IconButton> 92 </TableCell> 93 {/* Pass commit to cells via context so composing a row require less 94 ** boilerplate. */} 95 <CommitProvider value={commit}>{children}</CommitProvider> 96 </TableRow> 97 {/* Always render the content row to DOM to ensure a stable DOM structure. 98 **/} 99 <TableRow 100 data-testid="content-row" 101 sx={{ display: expanded ? '' : 'none' }} 102 > 103 <TableCell colSpan={100} sx={{ 'tr > &': { padding: 0 } }}> 104 <div css={{ padding: '10px 20px' }}> 105 <SummaryContainer html={descriptionHtml} /> 106 <h4 css={{ marginBlockEnd: '0px' }}> 107 Changed files: {changedFiles.length} 108 </h4> 109 <ul> 110 {changedFiles.map((filename, i) => ( 111 <li key={i}>{filename}</li> 112 ))} 113 </ul> 114 </div> 115 </TableCell> 116 </TableRow> 117 </> 118 ); 119 }