go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/ui/src/build/legacy/build_page/change_config_dialog.tsx (about) 1 // Copyright 2022 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 { 16 Button, 17 Dialog, 18 DialogActions, 19 DialogContent, 20 DialogTitle, 21 MenuItem, 22 OutlinedInput, 23 Select, 24 Typography, 25 } from '@mui/material'; 26 import { untracked } from 'mobx'; 27 import { observer } from 'mobx-react-lite'; 28 import { useEffect, useState } from 'react'; 29 30 import { useStore } from '@/common/store'; 31 32 import { BuildPageTab, INITIAL_DEFAULT_TAB, parseTab } from './common'; 33 34 export interface ChangeConfigDialogProps { 35 readonly open: boolean; 36 readonly onClose?: () => void; 37 readonly container?: HTMLDivElement; 38 } 39 40 // An array of [buildTabName, buildTabLabel] tuples. 41 // Use an array of tuples instead of an Object to ensure order. 42 const TAB_NAME_LABEL_TUPLES = Object.freeze([ 43 [BuildPageTab.Overview, 'Overview'], 44 [BuildPageTab.TestResults, 'Test Results'], 45 [BuildPageTab.Steps, 'Steps & Logs'], 46 [BuildPageTab.RelatedBuilds, 'Related Builds'], 47 [BuildPageTab.Timeline, 'Timeline'], 48 [BuildPageTab.Blamelist, 'Blamelist'], 49 ] as const); 50 51 export const ChangeConfigDialog = observer( 52 ({ open, onClose, container }: ChangeConfigDialogProps) => { 53 const buildConfig = useStore().userConfig.build; 54 const [tab, setTabName] = useState(() => 55 untracked(() => parseTab(buildConfig.defaultTab) || INITIAL_DEFAULT_TAB), 56 ); 57 58 // Sync the local state with the global config whenever the dialog is 59 // (re-)opened. Without this 60 // 1. the uncommitted config left in the last edit won't be discarded, and 61 // 2. config changes due to other reason won't be reflected in the dialog, 62 // causing unintentional overwriting some configs. 63 useEffect(() => { 64 // No point updating the tab name when the dialog is not shown. 65 if (!open) { 66 return; 67 } 68 setTabName(parseTab(buildConfig.defaultTab) || INITIAL_DEFAULT_TAB); 69 }, [open, buildConfig]); 70 71 return ( 72 <Dialog 73 onClose={onClose} 74 open={open} 75 fullWidth 76 maxWidth="sm" 77 container={container} 78 > 79 <DialogTitle>Settings</DialogTitle> 80 <DialogContent 81 sx={{ display: 'grid', gridTemplateColumns: 'auto 1fr', gap: 1 }} 82 > 83 <Typography 84 sx={{ 85 display: 'flex', 86 justifyContent: 'center', 87 alignContent: 'center', 88 flexDirection: 'column', 89 }} 90 > 91 Default tab: 92 </Typography> 93 <Select 94 value={tab} 95 onChange={(e) => setTabName(e.target.value as BuildPageTab)} 96 input={<OutlinedInput size="small" />} 97 MenuProps={{ disablePortal: true }} 98 sx={{ width: '180px' }} 99 > 100 {TAB_NAME_LABEL_TUPLES.map(([tab, label]) => ( 101 <MenuItem key={tab} value={tab}> 102 {label} 103 </MenuItem> 104 ))} 105 </Select> 106 </DialogContent> 107 <DialogActions> 108 <Button onClick={onClose} variant="text"> 109 Dismiss 110 </Button> 111 <Button 112 onClick={() => { 113 buildConfig.setDefaultTab(tab); 114 onClose?.(); 115 }} 116 variant="contained" 117 > 118 Confirm 119 </Button> 120 </DialogActions> 121 </Dialog> 122 ); 123 }, 124 );