mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	timeline component (#2963)
This commit is contained in:
		
							parent
							
								
									bfb038c725
								
							
						
					
					
						commit
						bc627b428e
					
				| @ -17,14 +17,41 @@ import useToast from 'hooks/useToast'; | ||||
| import { ImportOptions } from './ImportOptions'; | ||||
| import { ImportExplanation } from './ImportExplanation'; | ||||
| import { PulsingAvatar } from './PulsingAvatar'; | ||||
| import Timeline from '@mui/lab/Timeline'; | ||||
| import TimelineItem, { timelineItemClasses } from '@mui/lab/TimelineItem'; | ||||
| import TimelineSeparator from '@mui/lab/TimelineSeparator'; | ||||
| import TimelineConnector from '@mui/lab/TimelineConnector'; | ||||
| import TimelineContent from '@mui/lab/TimelineContent'; | ||||
| import TimelineDot from '@mui/lab/TimelineDot'; | ||||
| import { ImportTimeline } from './ImportTimeline'; | ||||
| 
 | ||||
| const LayoutContainer = styled('div')(({ theme }) => ({ | ||||
|     backgroundColor: '#fff', | ||||
| const ModalContentContainer = styled('div')(({ theme }) => ({ | ||||
|     minHeight: '100vh', | ||||
|     display: 'flex', | ||||
| })); | ||||
| 
 | ||||
| const TimelineContainer = styled('div')(({ theme }) => ({ | ||||
|     backgroundColor: theme.palette.primary.main, | ||||
|     padding: theme.spacing(6, 8, 3, 4), | ||||
|     flexBasis: '30%', | ||||
| })); | ||||
| 
 | ||||
| const TimelineHeader = styled('div')(({ theme }) => ({ | ||||
|     textTransform: 'uppercase', | ||||
|     fontSize: theme.fontSizes.smallBody, | ||||
|     color: theme.palette.text.tertiaryContrast, | ||||
|     fontWeight: theme.typography.fontWeightBold, | ||||
|     paddingLeft: theme.spacing(2), | ||||
|     marginBottom: theme.spacing(4), | ||||
| })); | ||||
| 
 | ||||
| const ImportLayoutContainer = styled('div')(({ theme }) => ({ | ||||
|     backgroundColor: '#fff', | ||||
|     padding: theme.spacing(3, 8, 3, 8), | ||||
|     display: 'flex', | ||||
|     flexDirection: 'column', | ||||
|     gap: theme.spacing(3), | ||||
|     flexBasis: '70%', | ||||
| })); | ||||
| 
 | ||||
| const StyledTextField = styled(TextField)(({ theme }) => ({ | ||||
| @ -91,95 +118,101 @@ export const ImportModal = ({ open, setOpen, project }: IImportModalProps) => { | ||||
|             }} | ||||
|             label="Import toggles" | ||||
|         > | ||||
|             <LayoutContainer> | ||||
|                 <Box | ||||
|                     sx={{ | ||||
|                         borderBottom: 1, | ||||
|                         borderColor: 'divider', | ||||
|                     }} | ||||
|                 > | ||||
|                     <Tabs value={activeTab}> | ||||
|                         <Tab | ||||
|                             label="Upload file" | ||||
|                             value="file" | ||||
|                             onClick={() => setActiveTab('file')} | ||||
|                         /> | ||||
|                         <Tab | ||||
|                             label="Code editor" | ||||
|                             value="code" | ||||
|                             onClick={() => setActiveTab('code')} | ||||
|                         /> | ||||
|                     </Tabs> | ||||
|                 </Box> | ||||
|                 <ImportOptions | ||||
|                     project={project} | ||||
|                     environment={environment} | ||||
|                     onChange={setEnvironment} | ||||
|                 /> | ||||
|                 <ConditionallyRender | ||||
|                     condition={activeTab === 'file'} | ||||
|                     show={ | ||||
|                         <StyledFileDropZone | ||||
|                             onSuccess={data => { | ||||
|                                 setImportPayload(data); | ||||
|                                 setActiveTab('code'); | ||||
|                                 setToastData({ | ||||
|                                     type: 'success', | ||||
|                                     title: 'File uploaded', | ||||
|                                 }); | ||||
|                             }} | ||||
|                             onError={error => { | ||||
|                                 setToastData({ | ||||
|                                     type: 'error', | ||||
|                                     title: error, | ||||
|                                 }); | ||||
|                             }} | ||||
|                             onDragStatusChange={setDragActive} | ||||
|                         > | ||||
|                             <PulsingAvatar active={dragActive}> | ||||
|                                 <ArrowUpward fontSize="large" /> | ||||
|                             </PulsingAvatar> | ||||
|                             <DropMessage> | ||||
|                                 {dragActive | ||||
|                                     ? 'Drop your file to upload' | ||||
|                                     : 'Drop your file here'} | ||||
|                             </DropMessage> | ||||
|                             <SelectFileMessage> | ||||
|                                 or select a file from your device | ||||
|                             </SelectFileMessage> | ||||
|                             <Button variant="outlined">Select file</Button> | ||||
|                             <MaxSizeMessage> | ||||
|                                 JSON format: max 500 kB | ||||
|                             </MaxSizeMessage> | ||||
|                         </StyledFileDropZone> | ||||
|                     } | ||||
|                     elseShow={ | ||||
|                         <StyledTextField | ||||
|                             label="Exported toggles" | ||||
|                             variant="outlined" | ||||
|                             onChange={event => | ||||
|                                 setImportPayload(event.target.value) | ||||
|                             } | ||||
|                             value={importPayload} | ||||
|                             multiline | ||||
|                             minRows={13} | ||||
|                             maxRows={13} | ||||
|                         /> | ||||
|                     } | ||||
|                 /> | ||||
|                 <ImportExplanation /> | ||||
|                 <ActionsContainer> | ||||
|                     <Button | ||||
|                         sx={{ position: 'static' }} | ||||
|                         variant="contained" | ||||
|                         color="primary" | ||||
|                         type="submit" | ||||
|                         onClick={onSubmit} | ||||
|             <ModalContentContainer> | ||||
|                 <TimelineContainer> | ||||
|                     <TimelineHeader>Process</TimelineHeader> | ||||
|                     <ImportTimeline stage={'configure'} /> | ||||
|                 </TimelineContainer> | ||||
|                 <ImportLayoutContainer> | ||||
|                     <Box | ||||
|                         sx={{ | ||||
|                             borderBottom: 1, | ||||
|                             borderColor: 'divider', | ||||
|                         }} | ||||
|                     > | ||||
|                         Import | ||||
|                     </Button> | ||||
|                 </ActionsContainer> | ||||
|             </LayoutContainer> | ||||
|                         <Tabs value={activeTab}> | ||||
|                             <Tab | ||||
|                                 label="Upload file" | ||||
|                                 value="file" | ||||
|                                 onClick={() => setActiveTab('file')} | ||||
|                             /> | ||||
|                             <Tab | ||||
|                                 label="Code editor" | ||||
|                                 value="code" | ||||
|                                 onClick={() => setActiveTab('code')} | ||||
|                             /> | ||||
|                         </Tabs> | ||||
|                     </Box> | ||||
|                     <ImportOptions | ||||
|                         project={project} | ||||
|                         environment={environment} | ||||
|                         onChange={setEnvironment} | ||||
|                     /> | ||||
|                     <ConditionallyRender | ||||
|                         condition={activeTab === 'file'} | ||||
|                         show={ | ||||
|                             <StyledFileDropZone | ||||
|                                 onSuccess={data => { | ||||
|                                     setImportPayload(data); | ||||
|                                     setActiveTab('code'); | ||||
|                                     setToastData({ | ||||
|                                         type: 'success', | ||||
|                                         title: 'File uploaded', | ||||
|                                     }); | ||||
|                                 }} | ||||
|                                 onError={error => { | ||||
|                                     setToastData({ | ||||
|                                         type: 'error', | ||||
|                                         title: error, | ||||
|                                     }); | ||||
|                                 }} | ||||
|                                 onDragStatusChange={setDragActive} | ||||
|                             > | ||||
|                                 <PulsingAvatar active={dragActive}> | ||||
|                                     <ArrowUpward fontSize="large" /> | ||||
|                                 </PulsingAvatar> | ||||
|                                 <DropMessage> | ||||
|                                     {dragActive | ||||
|                                         ? 'Drop your file to upload' | ||||
|                                         : 'Drop your file here'} | ||||
|                                 </DropMessage> | ||||
|                                 <SelectFileMessage> | ||||
|                                     or select a file from your device | ||||
|                                 </SelectFileMessage> | ||||
|                                 <Button variant="outlined">Select file</Button> | ||||
|                                 <MaxSizeMessage> | ||||
|                                     JSON format: max 500 kB | ||||
|                                 </MaxSizeMessage> | ||||
|                             </StyledFileDropZone> | ||||
|                         } | ||||
|                         elseShow={ | ||||
|                             <StyledTextField | ||||
|                                 label="Exported toggles" | ||||
|                                 variant="outlined" | ||||
|                                 onChange={event => | ||||
|                                     setImportPayload(event.target.value) | ||||
|                                 } | ||||
|                                 value={importPayload} | ||||
|                                 multiline | ||||
|                                 minRows={13} | ||||
|                                 maxRows={13} | ||||
|                             /> | ||||
|                         } | ||||
|                     /> | ||||
|                     <ImportExplanation /> | ||||
|                     <ActionsContainer> | ||||
|                         <Button | ||||
|                             sx={{ position: 'static' }} | ||||
|                             variant="contained" | ||||
|                             color="primary" | ||||
|                             type="submit" | ||||
|                             onClick={onSubmit} | ||||
|                         > | ||||
|                             Import | ||||
|                         </Button> | ||||
|                     </ActionsContainer> | ||||
|                 </ImportLayoutContainer> | ||||
|             </ModalContentContainer> | ||||
|         </SidebarModal> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
							
								
								
									
										117
									
								
								frontend/src/component/project/Project/Import/ImportTimeline.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								frontend/src/component/project/Project/Import/ImportTimeline.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,117 @@ | ||||
| import TimelineSeparator from '@mui/lab/TimelineSeparator'; | ||||
| import TimelineItem, { timelineItemClasses } from '@mui/lab/TimelineItem'; | ||||
| import React, { FC } from 'react'; | ||||
| import { Box, styled } from '@mui/material'; | ||||
| import TimelineConnector from '@mui/lab/TimelineConnector'; | ||||
| import TimelineDot from '@mui/lab/TimelineDot'; | ||||
| import TimelineContent from '@mui/lab/TimelineContent'; | ||||
| import Timeline from '@mui/lab/Timeline'; | ||||
| 
 | ||||
| const StyledTimeline = styled(Timeline)(() => ({ | ||||
|     [`& .${timelineItemClasses.root}:before`]: { | ||||
|         flex: 0, | ||||
|         padding: 0, | ||||
|     }, | ||||
| })); | ||||
| 
 | ||||
| const StyledTimelineConnector = styled(TimelineConnector)(({ theme }) => ({ | ||||
|     width: '1px', | ||||
|     backgroundColor: theme.palette.neutral.border, | ||||
| })); | ||||
| 
 | ||||
| const StyledTimelineDot = styled(TimelineDot, { | ||||
|     shouldForwardProp: prop => prop !== 'active', | ||||
| })<{ active: boolean }>(({ theme, active }) => ({ | ||||
|     color: active ? theme.palette.primary.main : theme.palette.neutral.border, | ||||
|     backgroundColor: active ? theme.palette.text.tertiaryContrast : 'initial', | ||||
|     fontWeight: active ? theme.fontWeight.bold : theme.fontWeight.medium, | ||||
|     borderColor: theme.palette.neutral.border, | ||||
|     width: '40px', | ||||
|     height: '40px', | ||||
|     borderWidth: '1px', | ||||
|     display: 'flex', | ||||
|     alignItems: 'center', | ||||
|     justifyContent: 'center', | ||||
| })); | ||||
| 
 | ||||
| const StyledTimelineContent = styled(TimelineContent, { | ||||
|     shouldForwardProp: prop => prop !== 'active', | ||||
| })<{ active: boolean }>(({ theme, active }) => ({ | ||||
|     marginBottom: theme.spacing(6), | ||||
|     color: active | ||||
|         ? theme.palette.text.tertiaryContrast | ||||
|         : theme.palette.neutral.border, | ||||
|     marginTop: theme.spacing(2), | ||||
| })); | ||||
| 
 | ||||
| const TimelineItemTitle = styled(Box)(({ theme }) => ({ | ||||
|     fontWeight: theme.fontWeight.bold, | ||||
|     fontSize: theme.fontSizes.bodySize, | ||||
| })); | ||||
| 
 | ||||
| const TimelineItemDescription = styled(Box)(({ theme }) => ({ | ||||
|     fontSize: theme.fontSizes.smallerBody, | ||||
| })); | ||||
| 
 | ||||
| export const ImportTimeline: FC<{ | ||||
|     stage: 'configure' | 'validate' | 'import'; | ||||
| }> = ({ stage }) => { | ||||
|     return ( | ||||
|         <StyledTimeline> | ||||
|             <TimelineItem> | ||||
|                 <TimelineSeparator> | ||||
|                     <StyledTimelineDot | ||||
|                         variant="outlined" | ||||
|                         active={stage === 'configure'} | ||||
|                     > | ||||
|                         1 | ||||
|                     </StyledTimelineDot> | ||||
|                     <StyledTimelineConnector /> | ||||
|                 </TimelineSeparator> | ||||
|                 <StyledTimelineContent active={stage === 'configure'}> | ||||
|                     <TimelineItemTitle>Import file</TimelineItemTitle> | ||||
|                     <TimelineItemDescription> | ||||
|                         Import previously exported toggle configuration from | ||||
|                         another Unleash instance as a JSON file | ||||
|                     </TimelineItemDescription> | ||||
|                 </StyledTimelineContent> | ||||
|             </TimelineItem> | ||||
|             <TimelineItem> | ||||
|                 <TimelineSeparator> | ||||
|                     <StyledTimelineDot | ||||
|                         variant="outlined" | ||||
|                         active={stage === 'validate'} | ||||
|                     > | ||||
|                         2 | ||||
|                     </StyledTimelineDot> | ||||
|                     <StyledTimelineConnector /> | ||||
|                 </TimelineSeparator> | ||||
|                 <StyledTimelineContent active={stage === 'validate'}> | ||||
|                     <TimelineItemTitle> | ||||
|                         Validate configuration | ||||
|                     </TimelineItemTitle> | ||||
|                     <TimelineItemDescription> | ||||
|                         Check the errors and warnings from the import process | ||||
|                     </TimelineItemDescription> | ||||
|                 </StyledTimelineContent> | ||||
|             </TimelineItem> | ||||
|             <TimelineItem> | ||||
|                 <TimelineSeparator> | ||||
|                     <StyledTimelineDot | ||||
|                         variant="outlined" | ||||
|                         active={stage === 'import'} | ||||
|                     > | ||||
|                         3 | ||||
|                     </StyledTimelineDot> | ||||
|                 </TimelineSeparator> | ||||
|                 <StyledTimelineContent active={stage === 'import'}> | ||||
|                     <TimelineItemTitle>Finish import</TimelineItemTitle> | ||||
|                     <TimelineItemDescription> | ||||
|                         Feature toggle configuration will be imported to your | ||||
|                         new Unleash instance | ||||
|                     </TimelineItemDescription> | ||||
|                 </StyledTimelineContent> | ||||
|             </TimelineItem> | ||||
|         </StyledTimeline> | ||||
|     ); | ||||
| }; | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user