github.com/argoproj/argo-cd/v3@v3.2.1/ui/src/app/shared/components/editable-panel/editable-section.tsx (about) 1 import {ErrorNotification, NotificationType} from 'argo-ui'; 2 import * as React from 'react'; 3 import {Form, FormApi} from 'react-form'; 4 import {ContextApis} from '../../context'; 5 import {EditablePanelItem} from './editable-panel'; 6 import {Spinner} from '../spinner'; 7 import {helpTip} from '../../../applications/components/utils'; 8 9 export interface EditableSectionProps<T> { 10 title?: string | React.ReactNode; 11 uniqueId: string; 12 values: T; 13 validate?: (values: T) => any; 14 save?: (input: T, query: {validate?: boolean}) => Promise<any>; 15 items: EditablePanelItem[]; 16 onModeSwitch?: () => any; 17 noReadonlyMode?: boolean; 18 view?: string | React.ReactNode; 19 edit?: (formApi: FormApi) => React.ReactNode; 20 collapsible?: boolean; 21 ctx: ContextApis; 22 isTopSection?: boolean; 23 disabledState?: boolean; 24 disabledDelete?: boolean; 25 updateButtons?: (pressed: boolean) => void; 26 deleteSource?: () => void; 27 } 28 29 interface EditableSectionState { 30 isEditing: boolean; 31 isSaving: boolean; 32 } 33 34 // Similar to editable-panel but it should be part of a white-box, editable-panel HOC and it can be reused one after another 35 export class EditableSection<T = {}> extends React.Component<EditableSectionProps<T>, EditableSectionState> { 36 private formApi: FormApi; 37 38 constructor(props: EditableSectionProps<T>) { 39 super(props); 40 this.state = {isEditing: !!props.noReadonlyMode, isSaving: false}; 41 } 42 43 public UNSAFE_componentWillReceiveProps(nextProps: EditableSectionProps<T>) { 44 if (this.formApi && JSON.stringify(this.props.values) !== JSON.stringify(nextProps.values)) { 45 if (nextProps.noReadonlyMode) { 46 this.formApi.setAllValues(nextProps.values); 47 } 48 } 49 } 50 51 public render() { 52 return ( 53 <div key={this.props.uniqueId}> 54 {!this.props.noReadonlyMode && this.props.save && ( 55 <div 56 key={this.props.uniqueId + '__panel__buttons'} 57 className={this.props.isTopSection ? 'editable-panel__buttons' : 'row white-box__details-row editable-panel__buttons-relative'} 58 style={{ 59 top: this.props.isTopSection ? '25px' : '', 60 right: this.props.isTopSection ? (this.props.collapsible ? '4.5em' : '3.5em') : this.props.collapsible ? '1.0em' : '0em' 61 }}> 62 {!this.state.isEditing && ( 63 <div className='editable-panel__buttons-relative-button'> 64 <button 65 key={'edit_button_' + this.props.uniqueId} 66 onClick={() => { 67 this.setState({isEditing: true}); 68 this.props.updateButtons(true); 69 this.props.onModeSwitch(); 70 }} 71 disabled={this.props.disabledState} 72 className='argo-button argo-button--base'> 73 Edit 74 </button>{' '} 75 {this.props.isTopSection && this.props.deleteSource && ( 76 <button 77 key={'delete_button_' + this.props.uniqueId} 78 onClick={() => { 79 this.props.deleteSource(); 80 }} 81 disabled={this.props.disabledDelete} 82 className='argo-button argo-button--base'> 83 {helpTip('Delete the source from the sources field')} 84 <span style={{marginRight: '8px'}} /> 85 Delete 86 </button> 87 )} 88 </div> 89 )} 90 {this.state.isEditing && ( 91 <div key={'buttons_' + this.props.uniqueId} className={!this.props.isTopSection ? 'editable-panel__buttons-relative-button' : ''}> 92 <React.Fragment key={'fragment_' + this.props.uniqueId}> 93 <button 94 key={'save_button_' + this.props.uniqueId} 95 disabled={this.state.isSaving} 96 onClick={() => !this.state.isSaving && this.formApi.submitForm(null)} 97 className='argo-button argo-button--base'> 98 <Spinner show={this.state.isSaving} style={{marginRight: '5px'}} /> 99 Save 100 </button>{' '} 101 <button 102 key={'cancel_button_' + this.props.uniqueId} 103 onClick={() => { 104 this.setState({isEditing: false}); 105 this.props.updateButtons(false); 106 this.props.onModeSwitch(); 107 }} 108 className='argo-button argo-button--base-o'> 109 Cancel 110 </button> 111 </React.Fragment> 112 </div> 113 )} 114 </div> 115 )} 116 117 {this.props.title && ( 118 <div className='row white-box__details-row'> 119 <p>{this.props.title}</p> 120 </div> 121 )} 122 123 {(!this.state.isEditing && ( 124 <React.Fragment key={'read_' + this.props.uniqueId}> 125 {this.props.view} 126 {this.props.items 127 .filter(item => item.view) 128 .map(item => ( 129 <React.Fragment key={'read_' + this.props.uniqueId + '_' + (item.key || item.title)}> 130 {item.before} 131 <div className='row white-box__details-row'> 132 <div className='columns small-3'>{item.customTitle || item.title}</div> 133 <div className='columns small-9'>{item.view}</div> 134 </div> 135 </React.Fragment> 136 ))} 137 </React.Fragment> 138 )) || ( 139 <Form 140 getApi={api => (this.formApi = api)} 141 formDidUpdate={async form => { 142 if (this.props.noReadonlyMode && this.props.save) { 143 await this.props.save(form.values as any, {}); 144 } 145 }} 146 onSubmit={async input => { 147 try { 148 this.setState({isSaving: true}); 149 await this.props.save(input as any, {}); 150 this.setState({isEditing: false, isSaving: false}); 151 this.props.onModeSwitch(); 152 } catch (e) { 153 this.props.ctx.notifications.show({ 154 content: <ErrorNotification title='Unable to save changes' e={e} />, 155 type: NotificationType.Error 156 }); 157 } finally { 158 this.setState({isSaving: false}); 159 } 160 }} 161 defaultValues={this.props.values} 162 validateError={this.props.validate}> 163 {api => ( 164 <React.Fragment key={'edit_' + this.props.uniqueId}> 165 {this.props.edit && this.props.edit(api)} 166 {this.props.items?.map(item => ( 167 <React.Fragment key={'edit_' + this.props.uniqueId + '_' + (item.key || item.title)}> 168 {item.before} 169 <div className='row white-box__details-row'> 170 <div className='columns small-3'>{(item.titleEdit && item.titleEdit(api)) || item.customTitle || item.title}</div> 171 <div className='columns small-9'>{(item.edit && item.edit(api)) || item.view}</div> 172 </div> 173 </React.Fragment> 174 ))} 175 </React.Fragment> 176 )} 177 </Form> 178 )} 179 </div> 180 ); 181 } 182 }