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  );