github.com/argoproj/argo-cd/v2@v2.10.9/ui/src/app/shared/components/array-input/array-input.tsx (about)

     1  import * as React from 'react';
     2  import * as ReactForm from 'react-form';
     3  import {FormValue} from 'react-form';
     4  
     5  /*
     6      This provide a way to may a form field to an array of items. It allows you to
     7  
     8      * Add a new (maybe duplicate) item.
     9      * Replace an item.
    10      * Remove an item.
    11  
    12      E.g.
    13      env:
    14      - name: FOO
    15        value: bar
    16      - name: BAZ
    17        value: qux
    18      # You can have dup items
    19      - name: FOO
    20        value: bar
    21  
    22      It does not allow re-ordering of elements (maybe in a v2).
    23   */
    24  
    25  export interface NameValue {
    26      name: string;
    27      value: string;
    28  }
    29  
    30  export const NameValueEditor = (item: NameValue, onChange?: (item: NameValue) => any) => {
    31      return (
    32          <React.Fragment>
    33              <input
    34                  // disable chrome autocomplete
    35                  autoComplete='fake'
    36                  className='argo-field'
    37                  style={{width: '40%', borderColor: !onChange ? '#eff3f5' : undefined}}
    38                  placeholder='Name'
    39                  value={item.name}
    40                  onChange={e => onChange({...item, name: e.target.value})}
    41                  // onBlur={e=>onChange({...item, name: e.target.value})}
    42                  title='Name'
    43                  readOnly={!onChange}
    44              />
    45              &nbsp; = &nbsp;
    46              <input
    47                  // disable chrome autocomplete
    48                  autoComplete='fake'
    49                  className='argo-field'
    50                  style={{width: '40%', borderColor: !onChange ? '#eff3f5' : undefined}}
    51                  placeholder='Value'
    52                  value={item.value || ''}
    53                  onChange={e => onChange({...item, value: e.target.value})}
    54                  title='Value'
    55                  readOnly={!onChange}
    56              />
    57              &nbsp;
    58          </React.Fragment>
    59      );
    60  };
    61  
    62  export const ValueEditor = (item: string, onChange: (item: string) => any) => {
    63      return (
    64          <input
    65              // disable chrome autocomplete
    66              autoComplete='fake'
    67              className='argo-field'
    68              style={{width: '40%', borderColor: !onChange ? '#eff3f5' : undefined}}
    69              placeholder='Value'
    70              value={item || ''}
    71              onChange={e => onChange(e.target.value)}
    72              title='Value'
    73              readOnly={!onChange}
    74          />
    75      );
    76  };
    77  
    78  interface Props<T> {
    79      items: T[];
    80      onChange: (items: T[]) => void;
    81      editor: (item: T, onChange: (updated: T) => any) => React.ReactNode;
    82  }
    83  
    84  export function ArrayInput<T>(props: Props<T>) {
    85      const addItem = (item: T) => {
    86          props.onChange([...props.items, item]);
    87      };
    88  
    89      const replaceItem = (item: T, i: number) => {
    90          const items = props.items.slice();
    91          items[i] = item;
    92          props.onChange(items);
    93      };
    94  
    95      const removeItem = (i: number) => {
    96          const items = props.items.slice();
    97          items.splice(i, 1);
    98          props.onChange(items);
    99      };
   100  
   101      return (
   102          <div className='argo-field' style={{border: 0, marginTop: '15px', zIndex: 1}}>
   103              {props.items.map((item, i) => (
   104                  <div key={`item-${i}`} style={{marginBottom: '5px'}}>
   105                      {props.editor(item, (updated: T) => replaceItem(updated, i))}
   106                      &nbsp;
   107                      <button>
   108                          <i className='fa fa-times' style={{cursor: 'pointer'}} onClick={() => removeItem(i)} />
   109                      </button>{' '}
   110                  </div>
   111              ))}
   112              {props.items.length === 0 && <label>No items</label>}
   113              <div>
   114                  <button className='argo-button argo-button--base argo-button--short' onClick={() => addItem({} as T)}>
   115                      <i style={{cursor: 'pointer'}} className='fa fa-plus' />
   116                  </button>
   117              </div>
   118          </div>
   119      );
   120  }
   121  
   122  export const ResetOrDeleteButton = (props: {
   123      isPluginPar: boolean;
   124      getValue: () => FormValue;
   125      name: string;
   126      index: number;
   127      setValue: (value: FormValue) => void;
   128      setAppParamsDeletedState: any;
   129  }) => {
   130      const handleDeleteChange = () => {
   131          if (props.index >= 0) {
   132              props.setAppParamsDeletedState((val: string[]) => val.concat(props.name));
   133          }
   134      };
   135  
   136      const handleResetChange = () => {
   137          if (props.index >= 0) {
   138              const items = [...props.getValue()];
   139              items.splice(props.index, 1);
   140              props.setValue(items);
   141          }
   142      };
   143  
   144      const disabled = props.index === -1;
   145  
   146      const content = props.isPluginPar ? 'Reset' : 'Delete';
   147      let tooltip = '';
   148      if (content === 'Reset' && !disabled) {
   149          tooltip = 'Resets the parameter to the value provided by the plugin. This removes the parameter override from the application manifest';
   150      } else if (content === 'Delete' && !disabled) {
   151          tooltip = 'Deletes this parameter values from the application manifest.';
   152      }
   153  
   154      return (
   155          <button
   156              className='argo-button argo-button--base'
   157              disabled={disabled}
   158              title={tooltip}
   159              style={{fontSize: '12px', display: 'flex', marginLeft: 'auto', marginTop: '8px'}}
   160              onClick={props.isPluginPar ? handleResetChange : handleDeleteChange}>
   161              {content}
   162          </button>
   163      );
   164  };
   165  
   166  export const ArrayInputField = ReactForm.FormField((props: {fieldApi: ReactForm.FieldApi}) => {
   167      const {
   168          fieldApi: {getValue, setValue}
   169      } = props;
   170      return <ArrayInput editor={NameValueEditor} items={getValue() || []} onChange={setValue} />;
   171  });
   172  
   173  export const ArrayValueField = ReactForm.FormField(
   174      (props: {fieldApi: ReactForm.FieldApi; name: string; defaultVal: string[]; isPluginPar: boolean; setAppParamsDeletedState: any}) => {
   175          const {
   176              fieldApi: {getValue, setValue}
   177          } = props;
   178  
   179          let liveParamArray;
   180          const liveParam = getValue()?.find((val: {name: string; array: object}) => val.name === props.name);
   181          if (liveParam) {
   182              liveParamArray = liveParam?.array ?? [];
   183          }
   184          const index = getValue()?.findIndex((val: {name: string; array: object}) => val.name === props.name) ?? -1;
   185          const values = liveParamArray ?? props.defaultVal ?? [];
   186  
   187          return (
   188              <React.Fragment>
   189                  <ResetOrDeleteButton
   190                      isPluginPar={props.isPluginPar}
   191                      getValue={getValue}
   192                      name={props.name}
   193                      index={index}
   194                      setValue={setValue}
   195                      setAppParamsDeletedState={props.setAppParamsDeletedState}
   196                  />
   197                  <ArrayInput
   198                      editor={ValueEditor}
   199                      items={values || []}
   200                      onChange={change => {
   201                          const update = change.map((val: string | object) => (typeof val !== 'string' ? '' : val));
   202                          if (index >= 0) {
   203                              getValue()[index].array = update;
   204                              setValue([...getValue()]);
   205                          } else {
   206                              setValue([...(getValue() || []), {name: props.name, array: update}]);
   207                          }
   208                      }}
   209                  />
   210              </React.Fragment>
   211          );
   212      }
   213  );
   214  
   215  export const StringValueField = ReactForm.FormField(
   216      (props: {fieldApi: ReactForm.FieldApi; name: string; defaultVal: string; isPluginPar: boolean; setAppParamsDeletedState: any}) => {
   217          const {
   218              fieldApi: {getValue, setValue}
   219          } = props;
   220          let liveParamString;
   221          const liveParam = getValue()?.find((val: {name: string; string: string}) => val.name === props.name);
   222          if (liveParam) {
   223              liveParamString = liveParam?.string ? liveParam?.string : '';
   224          }
   225          const values = liveParamString ?? props.defaultVal ?? '';
   226          const index = getValue()?.findIndex((val: {name: string; string: string}) => val.name === props.name) ?? -1;
   227  
   228          return (
   229              <React.Fragment>
   230                  <ResetOrDeleteButton
   231                      isPluginPar={props.isPluginPar}
   232                      getValue={getValue}
   233                      name={props.name}
   234                      index={index}
   235                      setValue={setValue}
   236                      setAppParamsDeletedState={props.setAppParamsDeletedState}
   237                  />
   238                  <div>
   239                      <input
   240                          // disable chrome autocomplete
   241                          autoComplete='fake'
   242                          className='argo-field'
   243                          style={{width: '40%', display: 'inline-block', marginTop: 25}}
   244                          placeholder='Value'
   245                          value={values || ''}
   246                          onChange={e => {
   247                              if (index >= 0) {
   248                                  getValue()[index].string = e.target.value;
   249                                  setValue([...getValue()]);
   250                              } else {
   251                                  setValue([...(getValue() || []), {name: props.name, string: e.target.value}]);
   252                              }
   253                          }}
   254                          title='Value'
   255                      />
   256                  </div>
   257              </React.Fragment>
   258          );
   259      }
   260  );
   261  
   262  export const MapInputField = ReactForm.FormField((props: {fieldApi: ReactForm.FieldApi}) => {
   263      const {
   264          fieldApi: {getValue, setValue}
   265      } = props;
   266      const items = new Array<NameValue>();
   267      const map = getValue() || {};
   268      Object.keys(map).forEach(key => items.push({name: key, value: map[key]}));
   269      return (
   270          <ArrayInput
   271              editor={NameValueEditor}
   272              items={items}
   273              onChange={array => {
   274                  const newMap = {} as any;
   275                  array.forEach(item => (newMap[item.name || ''] = item.value || ''));
   276                  setValue(newMap);
   277              }}
   278          />
   279      );
   280  });
   281  
   282  export const MapValueField = ReactForm.FormField(
   283      (props: {fieldApi: ReactForm.FieldApi; name: string; defaultVal: Map<string, string>; isPluginPar: boolean; setAppParamsDeletedState: any}) => {
   284          const {
   285              fieldApi: {getValue, setValue}
   286          } = props;
   287          const items = new Array<NameValue>();
   288          const liveParam = getValue()?.find((val: {name: string; map: object}) => val.name === props.name);
   289          const index = getValue()?.findIndex((val: {name: string; map: object}) => val.name === props.name) ?? -1;
   290          if (liveParam) {
   291              liveParam.map = liveParam.map ? liveParam.map : new Map<string, string>();
   292          }
   293          if (liveParam?.array) {
   294              items.push(...liveParam.array);
   295          } else {
   296              const map = liveParam?.map ?? props.defaultVal ?? new Map<string, string>();
   297              Object.keys(map).forEach(item => items.push({name: item || '', value: map[item] || ''}));
   298              if (liveParam?.map) {
   299                  getValue()[index].array = items;
   300              }
   301          }
   302  
   303          return (
   304              <React.Fragment>
   305                  <ResetOrDeleteButton
   306                      isPluginPar={props.isPluginPar}
   307                      getValue={getValue}
   308                      name={props.name}
   309                      index={index}
   310                      setValue={setValue}
   311                      setAppParamsDeletedState={props.setAppParamsDeletedState}
   312                  />
   313  
   314                  <ArrayInput
   315                      editor={NameValueEditor}
   316                      items={items || []}
   317                      onChange={change => {
   318                          if (index === -1) {
   319                              getValue().push({
   320                                  name: props.name,
   321                                  array: change
   322                              });
   323                          } else {
   324                              getValue()[index].array = change;
   325                          }
   326                          setValue([...getValue()]);
   327                      }}
   328                  />
   329              </React.Fragment>
   330          );
   331      }
   332  );