github.com/argoproj/argo-cd/v3@v3.2.1/ui/src/app/shared/components/editable-panel/editable-panel.tsx (about) 1 import {ErrorNotification, NotificationType} from 'argo-ui'; 2 import * as classNames from 'classnames'; 3 import * as React from 'react'; 4 import {Form, FormApi} from 'react-form'; 5 import {helpTip} from '../../../applications/components/utils'; 6 import {Consumer} from '../../context'; 7 import {Spinner} from '../spinner'; 8 9 export interface EditablePanelItem { 10 title: string; 11 customTitle?: string | React.ReactNode; 12 key?: string; 13 before?: React.ReactNode; 14 view: string | React.ReactNode; 15 edit?: (formApi: FormApi) => React.ReactNode; 16 titleEdit?: (formApi: FormApi) => React.ReactNode; 17 } 18 19 export interface EditablePanelProps<T> { 20 title?: string | React.ReactNode; 21 titleCollapsed?: string | React.ReactNode; 22 floatingTitle?: string | React.ReactNode; 23 values: T; 24 validate?: (values: T) => any; 25 save?: (input: T, query: {validate?: boolean}) => Promise<any>; 26 items: EditablePanelItem[]; 27 onModeSwitch?: () => any; 28 noReadonlyMode?: boolean; 29 view?: string | React.ReactNode; 30 edit?: (formApi: FormApi) => React.ReactNode; 31 hasMultipleSources?: boolean; 32 collapsible?: boolean; 33 collapsed?: boolean; 34 collapsedDescription?: string; 35 } 36 37 interface EditablePanelState { 38 edit: boolean; 39 saving: boolean; 40 collapsed: boolean; 41 } 42 43 require('./editable-panel.scss'); 44 45 export class EditablePanel<T = {}> extends React.Component<EditablePanelProps<T>, EditablePanelState> { 46 private formApi: FormApi; 47 48 constructor(props: EditablePanelProps<T>) { 49 super(props); 50 this.state = {edit: !!props.noReadonlyMode, saving: false, collapsed: this.props.collapsed}; 51 } 52 53 public UNSAFE_componentWillReceiveProps(nextProps: EditablePanelProps<T>) { 54 if (this.formApi && JSON.stringify(this.props.values) !== JSON.stringify(nextProps.values)) { 55 if (nextProps.noReadonlyMode) { 56 this.formApi.setAllValues(nextProps.values); 57 } 58 } 59 } 60 61 public render() { 62 return ( 63 <Consumer> 64 {ctx => 65 this.props.collapsible && this.state.collapsed ? ( 66 <div className='settings-overview__redirect-panel' style={{marginTop: 0}} onClick={() => this.setState({collapsed: !this.state.collapsed})}> 67 <div className='settings-overview__redirect-panel__content'> 68 <div className='settings-overview__redirect-panel__title'>{this.props.titleCollapsed ? this.props.titleCollapsed : this.props.title}</div> 69 <div className='settings-overview__redirect-panel__description'>{this.props.collapsedDescription}</div> 70 </div> 71 <div className='settings-overview__redirect-panel__arrow'> 72 <i className='fa fa-angle-down' /> 73 </div> 74 </div> 75 ) : ( 76 <div className={classNames('white-box editable-panel', {'editable-panel--disabled': this.state.saving})}> 77 {this.props.floatingTitle && <div className='white-box--additional-top-space editable-panel__sticky-title'>{this.props.floatingTitle}</div>} 78 <div className='white-box__details'> 79 {!this.props.noReadonlyMode && this.props.save && ( 80 <div className='editable-panel__buttons' style={{right: this.props.collapsible ? '5em' : '3em'}}> 81 {!this.state.edit && ( 82 <button 83 onClick={() => { 84 this.setState({edit: true}); 85 this.onModeSwitch(); 86 }} 87 disabled={this.props.hasMultipleSources} 88 className='argo-button argo-button--base'> 89 {this.props.hasMultipleSources && 90 helpTip('Sources are not editable for applications with multiple sources. You can edit them in the "Manifest" tab.')}{' '} 91 Edit 92 </button> 93 )} 94 {this.state.edit && ( 95 <React.Fragment> 96 <button 97 disabled={this.state.saving} 98 onClick={() => !this.state.saving && this.formApi.submitForm(null)} 99 className='argo-button argo-button--base'> 100 <Spinner show={this.state.saving} style={{marginRight: '5px'}} /> 101 Save 102 </button>{' '} 103 <button 104 onClick={() => { 105 this.setState({edit: false}); 106 this.onModeSwitch(); 107 }} 108 className='argo-button argo-button--base-o'> 109 Cancel 110 </button> 111 </React.Fragment> 112 )} 113 </div> 114 )} 115 {this.props.collapsible && ( 116 <div className='editable-panel__collapsible-button'> 117 <i 118 className={`fa fa-angle-${this.state.collapsed ? 'down' : 'up'} filter__collapse`} 119 onClick={() => { 120 this.setState({collapsed: !this.state.collapsed}); 121 }} 122 /> 123 </div> 124 )} 125 {this.props.title && <p>{this.props.title}</p>} 126 {(!this.state.edit && ( 127 <React.Fragment> 128 {this.props.view} 129 {this.props.items 130 .filter(item => item.view) 131 .map(item => ( 132 <React.Fragment key={item.key || item.title}> 133 {item.before} 134 <div className='row white-box__details-row'> 135 <div className='columns small-3'>{item.customTitle || item.title}</div> 136 <div className='columns small-9'>{item.view}</div> 137 </div> 138 </React.Fragment> 139 ))} 140 </React.Fragment> 141 )) || ( 142 <Form 143 getApi={api => (this.formApi = api)} 144 formDidUpdate={async form => { 145 if (this.props.noReadonlyMode && this.props.save) { 146 await this.props.save(form.values as any, {}); 147 } 148 }} 149 onSubmit={async input => { 150 try { 151 this.setState({saving: true}); 152 await this.props.save(input as any, {}); 153 this.setState({edit: false, saving: false}); 154 this.onModeSwitch(); 155 } catch (e) { 156 ctx.notifications.show({ 157 content: <ErrorNotification title='Unable to save changes' e={e} />, 158 type: NotificationType.Error 159 }); 160 } finally { 161 this.setState({saving: false}); 162 } 163 }} 164 defaultValues={this.props.values} 165 validateError={this.props.validate}> 166 {api => ( 167 <React.Fragment> 168 {this.props.edit && this.props.edit(api)} 169 {this.props.items.map(item => ( 170 <React.Fragment key={item.key || item.title}> 171 {item.before} 172 <div className='row white-box__details-row'> 173 <div className='columns small-3'>{(item.titleEdit && item.titleEdit(api)) || item.customTitle || item.title}</div> 174 <div className='columns small-9'>{(item.edit && item.edit(api)) || item.view}</div> 175 </div> 176 </React.Fragment> 177 ))} 178 </React.Fragment> 179 )} 180 </Form> 181 )} 182 </div> 183 </div> 184 ) 185 } 186 </Consumer> 187 ); 188 } 189 190 private onModeSwitch() { 191 if (this.props.onModeSwitch) { 192 this.props.onModeSwitch(); 193 } 194 } 195 }