antd#FormInstance TypeScript Examples

The following examples show how to use antd#FormInstance. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: utils.ts    From posthog-foss with MIT License 7 votes vote down vote up
doFieldRequirementsMatch = (
    form: FormInstance<any>,
    targetFieldName: string | undefined,
    targetFieldValue: string | undefined
): boolean => {
    const formActualValue = form.getFieldValue(targetFieldName || '') || ''
    const targetAnyValue = typeof targetFieldValue === 'undefined'
    const formValueSet = !!formActualValue

    return (targetAnyValue && formValueSet) || targetFieldValue === formActualValue
}
Example #2
Source File: useAntdForm.ts    From jmix-frontend with Apache License 2.0 6 votes vote down vote up
export function useAntdForm<TEntity>(form: FormInstance, item: EntityInstance<TEntity>, entityName: string) {
  const metadata = useMetadata();

  useEffect(() => {
    if (item != null && metadata != null) {
      form.setFieldsValue(
        jmixFront_to_ant(item, entityName, metadata)
      );
    }
  }, [form, item, metadata, entityName]);
}
Example #3
Source File: MultipleFilesForm.tsx    From next-basics with GNU General Public License v3.0 6 votes vote down vote up
export function LegacyMultipleFilesForm(
  { fieldList, onFinish, onFinishFailed }: MultipleFilesFormProps,
  ref: React.Ref<FormInstance>
): React.ReactElement {
  const [form] = Form.useForm();

  useImperativeHandle(ref, () => form);

  const handleFinish = (data: UploadFormData): void => {
    const formData = processFileData(data);
    onFinish?.(formData);
  };

  return (
    <Form
      form={form}
      name="filesForm"
      data-testid="files-form"
      onFinish={handleFinish}
      onFinishFailed={onFinishFailed}
    >
      {fieldList?.map((item) => (
        <Form.Item key={item.name} name={item.name} label={item.name}>
          <Upload
            maxCount={isMultipleFiles(item.type) ? null : 1}
            beforeUpload={() => false}
          >
            <Button icon={<UploadOutlined />}>upload</Button>
          </Upload>
        </Form.Item>
      ))}
    </Form>
  );
}
Example #4
Source File: index.tsx    From erda-ui with GNU Affero General Public License v3.0 6 votes vote down vote up
formatSearchList = () => {
    const searchList = this.props.searchList.map((search) => {
      return {
        required: false,
        initialValue: this.getFormInitialValue(search.type),
        extraProps: {
          colon: false,
          required: false,
        },
        ...search,
      };
    });
    searchList.push({
      getComp: ({ form }: { form: FormInstance }) => (
        <React.Fragment>
          <Button
            className="ops-bar-btn ops-bar-default-btn ops-bar-reset-btn"
            type="default"
            onClick={(e: any) => this.handleReset(e, form)}
          >
            {i18n.t('reset')}
          </Button>
          <Button className="ops-bar-btn" type="primary" ghost onClick={() => this.handleSubmit(form)}>
            {i18n.t('search')}
          </Button>
        </React.Fragment>
      ),
    });

    return searchList;
  };
Example #5
Source File: SourcePage.utils.tsx    From jitsu with MIT License 6 votes vote down vote up
getAntdFormAndKeyByOauthFieldKey = (
  forms: {
    [key: string]: {
      form: FormInstance<PlainObjectWithPrimitiveValues>
      patchConfigOnFormValuesChange?: (values: PlainObjectWithPrimitiveValues) => void
    }
  },
  oauthFieldKey: string
): [
  {
    form: FormInstance<PlainObjectWithPrimitiveValues>
    patchConfigOnFormValuesChange?: (values: PlainObjectWithPrimitiveValues) => void
  } | null,
  string | null
] => {
  let allFormsKeys: string[] = []
  const allFormsWithValues: {
    [key: string]: {
      form: {
        form: FormInstance<PlainObjectWithPrimitiveValues>
        patchConfigOnFormValuesChange?: (values: PlainObjectWithPrimitiveValues) => void
      }
      values: PlainObjectWithPrimitiveValues
    }
  } = Object.entries(forms).reduce((result, [formKey, formData]) => {
    const values = formData.form.getFieldsValue()
    allFormsKeys = [...allFormsKeys, ...Object.keys(values)]
    return {
      ...result,
      [formKey]: {
        form: formData,
        values,
      },
    }
  }, {})

  const formKey =
    allFormsKeys.find(_formKey => {
      const formKeyNameEnd = _formKey.split(".").pop() // gets access_token from config.config.access_token
      const formKey = formKeyNameEnd.replace("_", "").toLowerCase() // viewid <- viewId, accesstoken <- access_token
      const parsedOauthFieldKey = oauthFieldKey.replace("_", "").toLowerCase()
      return formKey === parsedOauthFieldKey
    }) ?? null

  const { form } = formKey ? Object.values(allFormsWithValues).find(({ values }) => formKey in values) : { form: null }

  return [form, formKey]
}
Example #6
Source File: useMultipleFiltersModal.tsx    From condo with MIT License 6 votes vote down vote up
function getModalComponents <T> (filters: IFilters, filterMetas: Array<FiltersMeta<T>>, form: FormInstance): React.ReactElement[] {
    if (!form) return

    return filterMetas.map(filterMeta => {
        const { keyword, component } = filterMeta

        const modalFilterComponentWrapper = get(component, 'modalFilterComponentWrapper')
        if (!modalFilterComponentWrapper) return

        const size = get(modalFilterComponentWrapper, 'size')
        const label = get(modalFilterComponentWrapper, 'label')
        const formItemProps = get(modalFilterComponentWrapper, 'formItemProps')
        const type = get(component, 'type')

        let Component
        if (type === ComponentType.Custom) {
            const componentGetter = get(component, 'modalFilterComponent')
            Component = isFunction(componentGetter) ? componentGetter(form) : componentGetter
        }
        else
            Component = getModalFilterComponentByMeta(filters, keyword, component, form)

        const queryToValueProcessor = getQueryToValueProcessorByType(type)

        return (
            <FilterComponent
                key={keyword}
                name={keyword}
                filters={filters}
                size={size}
                label={label}
                formItemProps={formItemProps}
                queryToValueProcessor={queryToValueProcessor}
            >
                {Component}
            </FilterComponent>
        )
    })
}
Example #7
Source File: FilterAction.tsx    From datart with Apache License 2.0 6 votes vote down vote up
FilterAction: FC<{
  config: ChartDataSectionField;
  dataset?: ChartDataSetDTO;
  dataView?: ChartDataView;
  dataConfig?: ChartDataConfig;
  aggregation?: boolean;
  onConfigChange: (
    config: ChartDataSectionField,
    needRefresh?: boolean,
  ) => void;
  form?: FormInstance;
}> = memo(
  ({
    config,
    dataset,
    dataView,
    dataConfig,
    onConfigChange,
    aggregation,
    form,
  }) => {
    const handleFetchDataFromField = async fieldId => {
      // TODO: to be implement to get fields
      return await Promise.resolve(['a', 'b', 'c'].map(f => `${fieldId}-${f}`));
    };
    return (
      <FilterControlPanel
        aggregation={aggregation}
        config={config}
        dataset={dataset}
        dataConfig={dataConfig}
        dataView={dataView}
        onConfigChange={onConfigChange}
        fetchDataByField={handleFetchDataFromField}
        form={form}
      />
    );
  },
)
Example #8
Source File: createUseAntdForm.ts    From jmix-frontend with Apache License 2.0 5 votes vote down vote up
export function createUseAntdForm<TEntity>(form: FormInstance): (item: EntityInstance<TEntity>, entityName: string) => void {
  return useAntdForm.bind(null, form);
}
Example #9
Source File: DendronLookupPanel.tsx    From dendron with GNU Affero General Public License v3.0 5 votes vote down vote up
function LookupViewForm({
  ide,
  form,
}: {
  ide: ideSlice.IDEState;
  form: FormInstance;
}) {
  const pressed = ide.lookupModifiers
    ? ide.lookupModifiers.filter((mod) => {
        return mod.pressed;
      })
    : [];

  // update selection type form
  const selectionTypeState = pressed.filter((mod) => {
    if (Object.keys(LookupSelectionTypeEnum).includes(mod.type)) {
      form.setFieldsValue({ selection: mod.type });
      return true;
    }
    return false;
  });

  if (selectionTypeState.length === 0) {
    form.setFieldsValue({ selection: undefined });
  }

  // update note type form
  const noteTypeState = pressed.filter((mod) => {
    if (Object.keys(LookupNoteTypeEnum).includes(mod.type)) {
      form.setFieldsValue({ note: mod.type });
      return true;
    }
    return false;
  });

  if (noteTypeState.length === 0) {
    form.setFieldsValue({ note: undefined });
  }

  // update effect type form
  form.setFieldsValue({
    effect: pressed
      .filter((mod) => {
        return Object.keys(LookupEffectTypeEnum).includes(mod.type);
      })
      .map((mod) => mod.type),
  });

  // update horizontal split switch
  form.setFieldsValue({
    horizontalSplit:
      pressed.filter((mod) => {
        return Object.keys(LookupSplitTypeEnum).includes(mod.type);
      }).length === 1,
  });

  // update direct child only switch
  form.setFieldsValue({
    directChildOnly:
      pressed.filter((mod) => {
        return Object.keys(LookupFilterTypeEnum).includes(mod.type);
      }).length === 1,
  });

  return (
    <>
      <Form form={form}>
        <SelectionTypeFormItem />
        <EffectTypeFormItem />
        <SplitTypeFormItem />
        <FilterTypeFormItem />
      </Form>
    </>
  );
}
Example #10
Source File: index.tsx    From next-basics with GNU General Public License v3.0 5 votes vote down vote up
private _form = createRef<FormInstance>();
Example #11
Source File: index.tsx    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
handleSubmit = (form: FormInstance) => {
    const formValue = form.getFieldsValue();
    this.props.onUpdateOps && this.props.onUpdateOps(formValue);
  };
Example #12
Source File: SourcePage.utils.tsx    From jitsu with MIT License 5 votes vote down vote up
sourcePageUtils = {
  getSourceType: (sourceConnector: SourceConnector) =>
    sourceConnector?.protoType ? sourceConnector?.protoType : snakeCase(sourceConnector?.id),
  getSourcePrototype: (sourceConnector: SourceConnector): string => snakeCase(sourceConnector?.id),
  getSourceId: (sourceProtoType: string, sourcesIds: string[]) => {
    sourceProtoType = sourceProtoType.replace("airbyte-source-", "").replace("singer-tap-", "")
    const isUniqueSourceId = !sourcesIds.find(id => id === sourceProtoType)

    if (isUniqueSourceId) {
      return sourceProtoType
    }

    return getUniqueAutoIncId(sourceProtoType, sourcesIds)
  },
  getPromptMessage: (tabs: Tab[]) => () =>
    tabs.some(tab => tab.touched) ? "You have unsaved changes. Are you sure you want to leave the page?" : undefined,

  testConnection: async (
    src: SourceData,
    hideMessage?: boolean,
    _options?: { skipHandleError?: boolean }
  ): Promise<TestConnectionResponse> => {
    const options = _options ?? {}
    let connectionTestMessagePrefix: string | undefined
    try {
      const response = await ApplicationServices.get().backendApiClient.post("/sources/test", Marshal.toPureJson(src))

      if (response["status"] === "pending") {
        actionNotification.loading(
          "Please, allow some time for the connector source installation to complete. Once the connector source is installed, we will test the connection and send a push notification with the result."
        )

        connectionTestMessagePrefix = `Source ${src.sourceId} connection test result: `

        const POLLING_INTERVAL_MS = 2000
        const POLLING_TIMEOUT_MS = 60_000

        const poll = new Poll<void>(
          (end, fail) => async () => {
            try {
              const response = await ApplicationServices.get().backendApiClient.post(
                "/sources/test",
                Marshal.toPureJson(src)
              )
              const status = response["status"]
              if (status !== "pending") end()
              else if (status !== "ok")
                fail(new Error(`Tap connection test returned an error. ${response["error"] ?? "Unknown error."}`))
            } catch (error) {
              fail(error)
            }
          },
          POLLING_INTERVAL_MS,
          POLLING_TIMEOUT_MS
        )

        poll.start()
        await poll.wait()
      }

      if (!hideMessage) {
        const message = "Successfully connected"
        actionNotification.success(
          connectionTestMessagePrefix ? `${connectionTestMessagePrefix}${message.toLowerCase()}` : message
        )
      }

      return {
        connected: true,
        connectedErrorType: undefined,
        connectedErrorMessage: undefined,
        connectedErrorPayload: undefined,
      }
    } catch (error) {
      if (!hideMessage) {
        const message = "Connection test failed"
        const prefixedMessage = connectionTestMessagePrefix
          ? `${connectionTestMessagePrefix}${message.toLowerCase()}`
          : message
        if (!options.skipHandleError) handleError(error, prefixedMessage)
      }

      const errorType: TestConnectionErrorType = `${error}`.includes("selected streams unavailable")
        ? "streams_changed"
        : "general"

      return {
        connected: false,
        connectedErrorType: errorType,
        connectedErrorMessage: error.message ?? "Failed to connect",
        connectedErrorPayload: error._response?.payload,
      }
    }
  },
  applyOauthValuesToAntdForms: (
    forms: {
      [key: string]: {
        form: FormInstance<PlainObjectWithPrimitiveValues>
        patchConfigOnFormValuesChange?: (values: PlainObjectWithPrimitiveValues) => void
      }
    },
    oauthValues: PlainObjectWithPrimitiveValues
  ): boolean => {
    const oauthFieldsSuccessfullySet: string[] = []
    const oauthFieldsNotSet: string[] = []
    Object.entries(oauthValues).forEach(([oauthFieldKey, oauthFieldValue]) => {
      const [formToApplyValue, fieldKeyToApplyValue] = getAntdFormAndKeyByOauthFieldKey(forms, oauthFieldKey)

      if (!formToApplyValue || !fieldKeyToApplyValue) {
        oauthFieldsNotSet.push(oauthFieldKey)
        return
      }

      const newValues = { ...formToApplyValue.form.getFieldsValue() }
      newValues[fieldKeyToApplyValue] = oauthFieldValue
      formToApplyValue.form.setFieldsValue(newValues)
      formToApplyValue.patchConfigOnFormValuesChange?.(newValues)
      oauthFieldsSuccessfullySet.push(oauthFieldKey)
    })

    if (oauthFieldsSuccessfullySet.length > 0) {
      actionNotification.success(`Authorization Successful`)
      return true
    }

    /* handles the case when failed to set all fields */
    if (oauthFieldsNotSet.length > 0 && oauthFieldsSuccessfullySet.length === 0) {
      const messagePostfix =
        "Did you forget to select OAuth authorization type in the form below? If you believe that this is an error, please, contact us at [email protected] or file an issue to our github."
      const secretsNamesSeparator = oauthFieldsNotSet.length === 2 ? " and " : ", "
      const message = `Failed to paste ${oauthFieldsNotSet
        .map(key => toTitleCase(key, { separator: "_" }))
        .join(secretsNamesSeparator)} secret${oauthFieldsNotSet.length > 1 ? "s" : ""}. ${messagePostfix}`
      actionNotification.warn(message)
      return false
    }
  },
}
Example #13
Source File: index.tsx    From ant-simple-draw with MIT License 5 votes vote down vote up
OlUl: FC<{ keyName: string; form: FormInstance<Store>; showEditPropsData: any }> = memo(
  ({ keyName, form, showEditPropsData }) => {
    const [type, setType] = useState<string | undefined>(undefined);
    const ulType = [
      { label: '小圆点', value: 'disc' },
      { label: '空心圆圈', value: 'circle' },
      { label: '小方块', value: 'square' },
    ];
    const olType = [
      { label: '1', value: '1' },
      { label: 'A', value: 'A' },
      { label: 'I', value: 'I' },
    ];

    useEffect(() => {
      if (showEditPropsData[keyName]) {
        // 将数据流的数据同步一下
        setType(showEditPropsData[keyName].type);
      }
    }, [showEditPropsData, keyName]);

    const selectData = useMemo(() => {
      return type === 'ol' ? olType : ulType;
    }, [type]);

    return (
      <>
        <Form.Item label={null} name={[keyName, 'type']} style={{ marginBottom: '16px' }}>
          <Radio.Group
            onChange={(val) => {
              setType(val.target.value);
              form.setFieldsValue({
                [keyName]: {
                  attrType: undefined,
                },
              });
            }}
          >
            <Radio value={'ol'}>有序列表</Radio>
            <Radio value={'ul'}>无序列表</Radio>
          </Radio.Group>
        </Form.Item>
        <Form.Item label={'序列'} name={[keyName, 'attrType']} style={{ marginBottom: '16px' }}>
          <Selects data={selectData} valKey="value" valName="label" />
        </Form.Item>
        <Form.List name={[keyName, 'list']}>
          {(fields, { add, remove }) => (
            <>
              {fields.map(({ key, name, ...restField }) => (
                <Space
                  key={key}
                  style={{ display: 'flex', marginBottom: 8, justifyContent: 'space-around' }}
                  align="baseline"
                >
                  <Form.Item {...restField} name={[name, 'text']} style={{ marginBottom: '16px' }}>
                    <Input />
                  </Form.Item>
                  <MinusCircleOutlined onClick={() => remove(name)} title="移除" />
                </Space>
              ))}
              <Form.Item style={{ marginBottom: '16px' }}>
                <Button onClick={() => add()} block icon={<PlusOutlined />}>
                  添加数据
                </Button>
              </Form.Item>
            </>
          )}
        </Form.List>
      </>
    );
  },
)
Example #14
Source File: useModalFilterClassifiers.tsx    From condo with MIT License 5 votes vote down vote up
useTicketClassifierSelect = ({
    onChange,
    keyword,
}) => {
    const intl = useIntl()
    const SelectMessage = intl.formatMessage({ id: 'Select' })

    const [classifiers, setClassifiersFromRules] = useState([])
    const [searchClassifiers, setSearchClassifiers] = useState([])
    const [stateForm, setForm] = useState<FormInstance>(null)

    const classifiersRef = useRef(null)
    const optionsRef = useRef([])

    const setClassifiers = (classifiers) => {
        setClassifiersFromRules(classifiers)
        setSearchClassifiers([])
    }

    function setSelected (value) {
        stateForm && stateForm.setFieldsValue({ [keyword]: value })
    }

    const Setter = useMemo(() => ({
        all: setClassifiers,
        one: setSelected,
        search: setSearchClassifiers,
    }), [])

    useEffect(() => {
        optionsRef.current = uniqBy([...classifiers, ...searchClassifiers], 'id')
    }, [classifiers, searchClassifiers])

    const handleChange = (form: FormInstance, value) => {
        if (isFunction(onChange)) onChange(value)

        form.setFieldsValue({ [keyword]: value })
    }

    const SelectComponent = useCallback( (props) => {
        const { disabled, style, form } = props

        if (!stateForm)
            setForm(stateForm)

        return (
            <Select
                showSearch
                showArrow
                style={style}
                onChange={(value) => handleChange(form, value)}
                optionFilterProp={'title'}
                disabled={disabled}
                value={form.getFieldValue(keyword)}
                showAction={SHOW_SELECT_ACTIONS}
                mode={'multiple'}
                placeholder={SelectMessage}
                getPopupContainer={getFiltersModalPopupContainer}
            >
                {
                    Array.isArray(optionsRef.current) && optionsRef.current.map(classifier => (
                        <Option value={classifier.id} key={classifier.id} title={classifier.name}>
                            {classifier.name}
                        </Option>
                    ))
                }
            </Select>
        )
    }, [SelectMessage, handleChange, keyword, stateForm])

    return {
        SelectComponent,
        set: Setter,
        ref: classifiersRef,
    }
}
Example #15
Source File: create.tsx    From dashboard with Apache License 2.0 5 votes vote down vote up
CreateCustomerMassMsg: React.FC = () => {
  const [currentCustomerMassMsg] = useState<CustomerMassMsgItem>();
  const formRef = useRef<FormInstance>();

  return (
    <PageContainer
      onBack={() => history.goBack()}
      backIcon={<LeftOutlined />}
      header={{
        title: '创建群发',
      }}
    >
      <ProCard>
        <CustomerMassMsgForm
          formRef={formRef}
          mode={'create'}
          onFinish={async (values) => {
            const params = { ...values };
            const hide = message.loading('处理中');
            const res: CommonResp = await Create(params);
            hide();
            if (res.code === 0) {
              history.push('/staff-admin/customer-conversion/customer-mass-msg');
              message.success('添加成功');
              return true;
            }

            if (res.message) {
              message.error(res.message);
              return false;
            }

            message.error('添加失败');
            return false;
          }}
          initialValues={currentCustomerMassMsg}
        />
      </ProCard>
    </PageContainer>
  );
}
Example #16
Source File: InviteForm.tsx    From datart with Apache License 2.0 5 votes vote down vote up
InviteForm = memo(
  ({ formProps, afterClose, ...modalProps }: ModalFormProps) => {
    const [options, setOptions] = useState<ValueType[]>([]);
    const formRef = useRef<FormInstance>();
    const t = useI18NPrefix('member.form');
    const tgv = useI18NPrefix('global.validation');

    const debouncedSearchUser = useMemo(() => {
      const searchUser = async (val: string) => {
        if (!val.trim()) {
          setOptions([]);
        } else {
          const { data } = await request<User[]>(
            `/users/search?keyword=${val}`,
          );
          setOptions(
            data.map(({ email, username, name }) => ({
              key: username,
              value: email,
              label: `${name ? `[${name}]` : ''}${email}`,
            })),
          );
        }
      };
      return debounce(searchUser, DEFAULT_DEBOUNCE_WAIT);
    }, []);

    const filterOption = useCallback((keywords: string, option) => {
      const { key, value, label } = option;
      return (
        key?.includes(keywords) ||
        value.toString().includes(keywords) ||
        label?.toString().includes(keywords)
      );
    }, []);

    const onAfterClose = useCallback(() => {
      formRef.current?.resetFields();
      setOptions([]);
      afterClose && afterClose();
    }, [afterClose]);

    return (
      <ModalForm
        formProps={formProps}
        {...modalProps}
        afterClose={onAfterClose}
        ref={formRef}
      >
        <Form.Item
          name="emails"
          rules={[
            { required: true, message: `${t('email')}${tgv('required')}` },
          ]}
        >
          <Select<ValueType>
            mode="tags"
            placeholder={t('search')}
            options={options}
            filterOption={filterOption}
            onSearch={debouncedSearchUser}
          />
        </Form.Item>
        <Form.Item name="sendMail" valuePropName="checked" initialValue={true}>
          <Checkbox>{t('needConfirm')}</Checkbox>
        </Form.Item>
      </ModalForm>
    );
  },
)
Example #17
Source File: interfaceJobsLogic.ts    From posthog-foss with MIT License 4 votes vote down vote up
interfaceJobsLogic = kea<interfaceJobsLogicType>({
    props: {} as {
        jobName: string
        pluginConfigId: number
        pluginId: number
        jobSpecPayload: JobSpec['payload']
    },
    key: (props) => {
        return `${props.pluginId}_${props.jobName}`
    },
    path: (key) => ['scenes', 'plugins', 'edit', 'interface-jobs', 'interfaceJobsLogic', key],
    connect: {
        actions: [pluginsLogic, ['showPluginLogs']],
    },
    actions: {
        setIsJobModalOpen: (isOpen: boolean) => ({ isOpen }),
        setRunJobAvailable: (isAvailable: boolean) => ({ isAvailable }),
        runJob: (form: FormInstance<any>) => ({ form }),
        playButtonOnClick: (form: FormInstance<any>, jobHasEmptyPayload: boolean) => ({ form, jobHasEmptyPayload }),
        setRunJobAvailableTimeout: (timeout: NodeJS.Timeout) => ({ timeout }),
    },
    reducers: {
        isJobModalOpen: [
            false,
            {
                setIsJobModalOpen: (_, { isOpen }) => isOpen,
            },
        ],
        runJobAvailable: [
            true,
            {
                setRunJobAvailable: (_, { isAvailable }) => isAvailable,
            },
        ],
        runJobAvailableTimeout: [
            null as NodeJS.Timeout | null,
            {
                setRunJobAvailableTimeout: (_, { timeout }) => timeout,
            },
        ],
    },
    listeners: ({ actions, props, values }) => ({
        runJob: async ({ form }) => {
            try {
                await form.validateFields()
            } catch {
                return
            }
            actions.setIsJobModalOpen(false)
            const formValues = form.getFieldsValue()

            for (const [fieldKey, fieldValue] of Object.entries(formValues)) {
                if (props.jobSpecPayload?.[fieldKey].type === 'date') {
                    if (!!formValues[fieldKey]) {
                        formValues[fieldKey] = (fieldValue as moment.Moment).toISOString()
                    } else {
                        formValues[fieldKey] = null
                    }
                }
            }
            try {
                await api.create(`api/plugin_config/${props.pluginConfigId}/job`, {
                    job: {
                        type: props.jobName,
                        payload: form.getFieldsValue(),
                    },
                })
            } catch (error) {
                errorToast(`Enqueuing job '${props.jobName}' failed`)
                return
            }

            actions.showPluginLogs(props.pluginId)

            // temporary handling to prevent people from rage
            // clicking and creating multiple jobs - this will be
            // subsituted by better feedback tools like progress bars
            actions.setRunJobAvailable(false)
            if (values.runJobAvailableTimeout) {
                clearTimeout(values.runJobAvailableTimeout)
            }
            setTimeout(() => {
                const timeout = actions.setRunJobAvailable(true)
                actions.setRunJobAvailableTimeout(timeout)
            }, 15000)

            toast.success('Job enqueued succesfully.')
        },
        playButtonOnClick: ({ form, jobHasEmptyPayload }) => {
            if (!values.runJobAvailable) {
                return
            }
            if (jobHasEmptyPayload) {
                actions.runJob(form)
                return
            }
            actions.setIsJobModalOpen(true)
        },
    }),
    events: ({ values }) => ({
        beforeUnmount: () => {
            if (values.runJobAvailableTimeout) {
                clearTimeout(values.runJobAvailableTimeout)
            }
        },
    }),
})
Example #18
Source File: cmd-rename-node-modal.tsx    From XFlow with MIT License 4 votes vote down vote up
function showModal(node: NsGraph.INodeConfig, getAppContext: IGetAppCtx) {
  /** showModal 返回一个Promise */
  const defer = new Deferred<string | void>()

  /** modal确认保存逻辑 */
  class ModalCache {
    static modal: IModalInstance
    static form: FormInstance<IFormProps>
  }

  /** modal确认保存逻辑 */
  const onOk = async () => {
    const { form, modal } = ModalCache
    const appContext = getAppContext()
    const { updateNodeNameService, graphMeta } = appContext
    try {
      modal.update({ okButtonProps: { loading: true } })
      await form.validateFields()
      const values = await form.getFieldsValue()
      const newName: string = values.newNodeName
      /** 执行 backend service */
      if (updateNodeNameService) {
        const { err, nodeName } = await updateNodeNameService(newName, node, graphMeta)
        if (err) {
          throw new Error(err)
        }
        defer.resolve(nodeName)
      }
      /** 更新成功后,关闭modal */
      onHide()
    } catch (error) {
      console.error(error)
      /** 如果resolve空字符串则不更新 */
      modal.update({ okButtonProps: { loading: false } })
    }
  }

  /** modal销毁逻辑 */
  const onHide = () => {
    modal.destroy()
    ModalCache.form = null as any
    ModalCache.modal = null as any
    container.destroy()
  }

  /** modal内容 */
  const ModalContent = () => {
    const [form] = Form.useForm<IFormProps>()
    /** 缓存form实例 */
    ModalCache.form = form

    return (
      <div>
        <ConfigProvider>
          <Form form={form} {...layout} initialValues={{ newNodeName: node.label }}>
            <Form.Item
              name="newNodeName"
              label="节点名"
              rules={[
                { required: true, message: '请输入新节点名' },
                { min: 3, message: '节点名不能少于3个字符' },
              ]}
            >
              <Input />
            </Form.Item>
          </Form>
        </ConfigProvider>
      </div>
    )
  }
  /** 创建modal dom容器 */
  const container = createContainer()
  /** 创建modal */
  const modal = Modal.confirm({
    title: '重命名',
    content: <ModalContent />,
    getContainer: () => {
      return container.element
    },
    okButtonProps: {
      onClick: e => {
        e.stopPropagation()
        onOk()
      },
    },
    onCancel: () => {
      onHide()
    },
    afterClose: () => {
      onHide()
    },
  })

  /** 缓存modal实例 */
  ModalCache.modal = modal

  /** showModal 返回一个Promise,用于await */
  return defer.promise
}
Example #19
Source File: index.test.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
describe('ConfigurableFilter', () => {
  const externalFieldsList = [
    {
      label: '',
      type: 'input',
      outside: true,
      key: 'keyword',
      placeholder: 'search by keywords',
      customProps: {
        autoComplete: 'off',
      },
    },
  ];
  const fieldsList = [
    {
      label: 'APP_NAME',
      type: 'input',
      key: 'keyword',
      placeholder: 'search by keywords',
      customProps: {
        autoComplete: 'off',
      },
    },
    {
      label: 'ITERATION',
      type: 'select',
      key: 'iteration',
      placeholder: 'please select iteration',
      options: [
        { label: 'iteration-1.1', value: 123 },
        { label: 'iteration-1.2', value: 124 },
        { label: 'iteration-1.3', value: 125 },
      ],
    },
    {
      key: 'createdAtStartEnd',
      label: 'CREATE_AT',
      type: 'dateRange',
    },
  ];
  const initialInsideFieldsValue = fieldsList.reduce(
    (prev, curr) => ({
      ...prev,
      [curr.key]: undefined,
    }),
    {},
  );
  beforeAll(() => {
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    _.debounce = (fn: Function) => fn;
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    _.throttle = (fn: Function) => fn;
  });
  afterAll(() => {
    jest.resetAllMocks();
  });
  const setUp = (props?: Partial<IProps>) => {
    const filterFn = jest.fn();
    const closeFn = jest.fn();
    const clearFn = jest.fn();
    const configChangeFn = jest.fn();
    const saveFilterFn = jest.fn();
    const deleteFilterFn = jest.fn();
    const filterRef: React.Ref<{ form: FormInstance }> = React.createRef();
    let firstOpen = true;
    const Comp = (c_props: Partial<IProps>) => {
      const newProps = {
        fieldsList,
        ...c_props,
      } as IProps;
      return (
        <ConfigurableFilter
          {...newProps}
          onFilter={filterFn}
          onClose={closeFn}
          onSaveFilter={saveFilterFn}
          onDeleteFilter={deleteFilterFn}
          onClear={clearFn}
          onConfigChange={configChangeFn}
          ref={filterRef}
        />
      );
    };
    const result = render(<Comp {...props} />);
    const rerender = (n_props: Partial<IProps>) => {
      result.rerender(<Comp {...n_props} />);
    };
    const openFilter = async () => {
      fireEvent.click(result.baseElement.querySelector('.erda-configurable-filter-btn')!);
      if (firstOpen) {
        await waitFor(() =>
          expect(result.baseElement.querySelector('.erda-configurable-filter')).not.toHaveStyle({ display: 'none' }),
        );
      } else {
        await waitFor(() =>
          expect(result.baseElement).not.isExistClass('.erda-configurable-filter', 'ant-popover-hidden'),
        );
      }
      firstOpen = false;
    };
    return {
      result,
      rerender,
      filterRef,
      filterFn,
      closeFn,
      clearFn,
      configChangeFn,
      saveFilterFn,
      deleteFilterFn,
      openFilter,
    };
  };
  it('should work well without insideFields', async () => {
    const { result, rerender, filterFn } = setUp({ fieldsList: externalFieldsList });
    expect(result.container).isExist('.erda-configurable-filter-btn', 0);
    await act(async () => {
      fireEvent.change(result.getByPlaceholderText('Search by keywords'), { target: { value: 'erda' } });
      await flushPromises();
    });
    expect(filterFn).toHaveBeenLastCalledWith({ keyword: 'erda' });
    filterFn.mockReset();
    rerender({
      fieldsList: externalFieldsList,
      value: { keyword: 'erda cloud' },
    });
    await act(async () => {
      fireEvent.change(result.getByPlaceholderText('Search by keywords'), { target: { value: 'erda cloud' } });
      await flushPromises();
    });
    expect(filterFn).not.toHaveBeenCalled();
  });
  it('should work well with insideFields', async () => {
    const { result, rerender, filterFn, closeFn, openFilter, clearFn } = setUp({ zIndex: 10, hideSave: true });
    await openFilter();
    fireEvent.click(result.getByText('Cancel'));
    await waitFor(() => expect(result.baseElement).isExistClass('.erda-configurable-filter', 'ant-popover-hidden'));
    expect(closeFn).toHaveBeenCalledTimes(1);
    await openFilter();
    fireEvent.click(result.baseElement.querySelector('[name="guanbi"]')!);
    expect(closeFn).toHaveBeenCalledTimes(2);
    await openFilter();
    fireEvent.change(result.getByPlaceholderText('Search by keywords'), { target: { value: 'erda' } });
    fireEvent.mouseDown(result.baseElement.querySelector('.ant-select-selector')!);
    await waitFor(() => expect(result.baseElement).isExist('.ant-select-dropdown', 1));
    fireEvent.click(result.getByText('iteration-1.1'));
    await act(async () => {
      fireEvent.click(result.getByText('Filter', { selector: 'button span' }));
      await flushPromises();
    });
    expect(filterFn).toHaveBeenLastCalledWith({
      ...initialInsideFieldsValue,
      iteration: [123],
      keyword: 'erda',
    });
    rerender({
      zIndex: 10,
      hideSave: true,
      value: {
        ...initialInsideFieldsValue,
        iteration: [123],
        keyword: 'erda',
      },
    });
    expect(result.baseElement).isExist('.erda-configurable-filter-clear-btn', 1);
    await act(async () => {
      fireEvent.click(result.baseElement.querySelector('.erda-configurable-filter-clear-btn')!);
      await flushPromises();
    });
    expect(filterFn).toHaveBeenLastCalledWith(initialInsideFieldsValue);
    expect(clearFn).toHaveBeenCalledTimes(1);
    rerender({
      zIndex: 10,
      hideSave: true,
      value: {
        ...initialInsideFieldsValue,
      },
    });
    expect(result.baseElement).isExist('.erda-configurable-filter-clear-btn', 0);
  });
  it('should work well with insideFields and haveSave', async () => {
    const configList = [
      {
        id: 'all',
        isPreset: true,
        label: 'openAll',
        values: {},
      },
      {
        id: 'defaultState',
        isPreset: true,
        label: 'presetFilter',
        values: {
          iteration: [123],
        },
      },
      {
        id: 'customFilter',
        isPreset: false,
        label: 'customFilter',
        values: {
          keyword: 'cloud',
        },
      },
    ];
    const { result, rerender, filterFn, closeFn, openFilter, clearFn, configChangeFn, saveFilterFn } = setUp({
      zIndex: 10,
      hideSave: false,
      configList,
    });
    await openFilter();
    expect(result.baseElement).isExistClass('.erda-configurable-filter', 'w-[960px]');
    await act(async () => {
      fireEvent.click(result.getByText('presetFilter').closest('.filter-config-selector-item')!);
      await flushPromises();
    });
    expect(filterFn).toHaveBeenLastCalledWith({
      ...initialInsideFieldsValue,
      iteration: [123],
    });
    fireEvent.change(result.getByPlaceholderText('Search by keywords'), { target: { value: 'erda' } });
    fireEvent.click(result.getByText('new filter'));
    await waitFor(() => expect(result.baseElement).isExist('.erda-configurable-filter-add', 1));
    fireEvent.click(result.getByText('Cancel', { selector: '.erda-configurable-filter-add button span' }));
    expect(result.baseElement).isExistClass('.erda-configurable-filter-add', 'ant-popover-hidden');
    fireEvent.click(result.getByText('new filter'));
    expect(result.baseElement).not.isExistClass('.erda-configurable-filter-add', 'ant-popover-hidden');
    fireEvent.change(result.getByPlaceholderText('Please enter, within 10 characters'), {
      target: { value: 'ErdaFilter' },
    });
    await act(async () => {
      fireEvent.click(result.getByText('OK'));
      await flushPromises();
    });
    expect(saveFilterFn).toHaveBeenLastCalledWith('ErdaFilter', {
      createdAtStartEnd: undefined,
      iteration: [123],
      keyword: 'erda',
    });
    await act(async () => {
      fireEvent.click(
        result
          .getByText('openAll', { selector: '.filter-config-selector-item .truncate' })
          .closest('.filter-config-selector-item')!,
      );
      await flushPromises();
    });
    fireEvent.change(result.getByPlaceholderText('Search by keywords'), { target: { value: 'cloud' } });
    expect(result.getByText('customFilter').closest('.filter-config-selector-item')).toHaveClass('bg-default-04');
  });
});
Example #20
Source File: index.tsx    From react-resume-site with GNU General Public License v3.0 4 votes vote down vote up
HeaderBar = observer(() => {
  const { templateStore } = useStores();
  const { setTempTheme , tempTheme, theme, color, setColor, setTheme, setPreview, mdContent, isPreview } = templateStore;
  const [isModalVisible, setIsModalVisible] = useState(false);
  const [isExportVisible, setIsExportVisible] = useState(false);
  const [isUsageVisible, setIsUsageVisible] = useState(false);
  const [isUpdateVisible, setIsUpdateVisible] = useState(is_update);

  const formRef = useRef<FormInstance>(null);

  const handleOk = async () => {
    // 更新模板
    await updateTempalte(tempTheme, color, setColor);
    // 设置模板
    setTheme(tempTheme);
    // 关闭弹窗
    setIsModalVisible(false);
  };

  const uploadMdFile = useCallback((e: any) => {
    let resultFile = e.target.files[0];
    var reader = new FileReader();
    reader.readAsText(resultFile);
    reader.onload = (e) => {
      if (e.target?.result) {
        mdEditorRef && (mdEditorRef.setValue(e.target.result));
        setPreview(false);
        renderViewStyle(color);
      }
    };
  }, []);

  const exportMdFile = useCallback(() => {
    const file = new Blob([mdContent]);
    const url = URL.createObjectURL(file);
    downloadDirect(url, "木及简历.md");
  }, [mdContent]);

  const templateContent = (
    <div className="template-wrapper">
      {themes.map((item) => {
        return (
          <div
            className={`template ${item.id === tempTheme ? "active" : ""}`}
            key={item.id}
            onClick={(e) => {
              e.preventDefault();
              setTempTheme(item.id);
            }}
          >
            <img className="template-img" src={item.src}></img>
            <p className="template-title">{item.name}
              {item.isColor && <Tag color="#2db7f5">可换色</Tag>}
            </p>
          </div>
        );
      })}
    </div>
  );

  const filesMenu = (
    <Menu>
      <Menu.Item>
        <label htmlFor="uploadMdFile">
          <a rel="noopener noreferrer">导入md</a>
          <input
            type="file"
            id="uploadMdFile"
            accept=".md"
            className="uploadMd"
            onChange={uploadMdFile}
          ></input>
        </label>
      </Menu.Item>
      <Menu.Item>
        <a rel="noopener noreferrer" onClick={exportMdFile}>
          导出md
        </a>
      </Menu.Item>
    </Menu>
  );

  const handleExport = () => {
    formRef.current?.submit();
    setIsExportVisible(false);
  };

  const exportPdf = async ({
    name,
    isOnePage,
    isMark,
  }: {
    name: string;
    isOnePage: boolean;
    isMark: boolean;
  }) => {
    // 设置渲染
    const rsViewer = document.querySelector(".rs-view") as HTMLElement;
    if (!isPreview) {
      setPreview(true);
      htmlParser(rsViewer);
    }
    const pages = rsViewer.dataset.pages || '1';
    const rsLine = document.querySelectorAll('.rs-line-split');
    rsLine.forEach(item => item.parentNode?.removeChild(item));
    const content = localStorage.getItem(LOCAL_STORE.MD_RESUME);

    if (content) {
      const htmlContent = document.querySelector('.rs-view-inner')?.innerHTML.replace(/(\n|\r)/g, "");
      let hide = message.loading("正在为你生成简历...", 0);
      if (globalEditorCount < 2) {
        try {
          hide();
          const curThemes = themes.filter(item => item.id === theme);
          await downloadFetch(curThemes[0].defaultUrl, name ? `${name}.pdf` : "木及简历.pdf");
        } catch (e) {
          hide();
        }
        return;
      }
      const themeColor = getComputedStyle(document.body).getPropertyValue(
        "--bg"
      );
      try {
        let data = await getPdf({
          htmlContent: String(htmlContent),
          theme,
          themeColor,
          isMark,
          isOnePage,
          pages
        });
        await downloadFetch(data.url, name ? `${name}.pdf` : "木及简历.pdf");
        hide();
        message.success("恭喜你,导出成功!")
      } catch (e) {
        hide();
        message.error("生成简历出错,请稍再试!");
      }
      setPreview(false);
      renderViewStyle(color);
    }
  };

  useEffect(() => {
    getTheme(theme);
  }, []);
  return (
    <div className="rs-header-bar rs-link">
      <div className="rs-header-bar__left">
        {/* <a className="rs-logo rs-link">
          <img src="https://s3.qiufeng.blue/muji/muji-logo.jpg" alt=""/>
          木及简历
        </a> */}
        <Dropdown overlay={filesMenu} trigger={["click"]}>
          <a
            className="ant-dropdown-link rs-link"
            onClick={(e) => e.preventDefault()}
          >
            文件
          </a>
        </Dropdown>
        <a className="ant-dropdown-link rs-link" onClick={() => {
          setIsModalVisible(true);
        }}>
          选择模板
        </a>
        <a className="ant-dropdown-link rs-link" onClick={() => {
          setIsUsageVisible(true);
        }}>
          使用教程
        </a>
        <Shortcuts></Shortcuts>
        <History></History>
        <a
          href="#"
          className="rs-link"
          onClick={() => {
            setIsExportVisible(true);
          }}
        >
          导出 pdf
        </a>
      </div>
      <Modal
        title="请选择模板"
        visible={isModalVisible}
        onOk={handleOk}
        onCancel={() => {
          setTempTheme(theme);
          setIsModalVisible(false);
        }}
        cancelText="取消"
        okText="确定"
        width={1100}
      >
        {templateContent}
      </Modal>
      <Modal
        title="使用教程"
        visible={isUsageVisible}
        width={700}
        cancelText="取消"
        okText="确定"
        onOk={() => {
          setIsUsageVisible(false);
        }}
        onCancel={() => {
          setIsUsageVisible(false);
        }}
      >
        <div className="rs-article-container" dangerouslySetInnerHTML={{
          __html: markdownParserArticle.render(TUTORIALS_GUIDE)
        }}></div>
      </Modal>
      {<Modal
        title="更新日志"
        visible={isUpdateVisible}
        cancelText="取消"
        okText="确定"
        width={700}
        onOk={() => {
          localStorage.setItem(LOCAL_STORE.MD_UPDATE_LOG, `${UPDATE_LOG_VERSION}`);
          setIsUpdateVisible(false);
        }}
        onCancel={() => {
          localStorage.setItem(LOCAL_STORE.MD_UPDATE_LOG, `${UPDATE_LOG_VERSION}`);
          setIsUpdateVisible(false);
        }}
      >
        <div className="rs-article-container" dangerouslySetInnerHTML={{
          __html: markdownParserArticle.render(UPDATE_CONTENT)
        }}></div>
      </Modal>}
      {isExportVisible && (
        <Modal
          title="导出确认"
          visible={isExportVisible}
          onOk={handleExport}
          onCancel={() => {
            setIsExportVisible(false);
          }}
          cancelText="取消"
          okText="确认"
        >
          <Form
            ref={formRef}
            labelCol={{ span: 6 }}
            wrapperCol={{ span: 14 }}
            layout="horizontal"
            initialValues={{
              isMark: true,
            }}
            onFinish={(values: any) => {
              exportPdf({
                ...values,
                isMark: false
              });
            }}
          >
            <Form.Item name="name" label="简历名称">
              <Input placeholder="不填则系统命名" />
            </Form.Item>
            <Form.Item name="isOnePage" label="是否一页纸" valuePropName="checked">
              <Switch />
            </Form.Item>
          </Form>
        </Modal>
      )}
    </div>
  );
})
Example #21
Source File: CommentForm.tsx    From condo with MIT License 4 votes vote down vote up
CommentForm: React.FC<ICommentFormProps> = ({
    ticket,
    initialValue,
    action,
    fieldName,
    editableComment,
    sending,
    FileModel,
    relationField,
    setSending,
}) => {
    const intl = useIntl()
    const PlaceholderMessage = intl.formatMessage({ id: 'Comments.form.placeholder' })
    const HelperMessage = intl.formatMessage({ id: 'Comments.form.helper' })

    const { InputWithCounter, Counter, setTextLength: setCommentLength, textLength: commentLength } = useInputWithCounter(Input.TextArea, MAX_COMMENT_LENGTH)
    const [form, setForm] = useState<FormInstance>()

    const { organization } = useOrganization()

    const editableCommentFiles = get(editableComment, 'files')
    const { UploadComponent, syncModifiedFiles, resetModifiedFiles, filesCount } = useMultipleFileUploadHook({
        Model: FileModel,
        relationField: relationField,
        initialFileList: editableCommentFiles,
        initialCreateValues: { organization: organization.id, ticket: ticket.id },
        dependenciesForRerenderUploadComponent: [editableComment],
    })

    useEffect(() => {
        if (editableComment && form) {
            const editableCommentContent = editableComment.content

            form.setFieldsValue({ [fieldName]: editableCommentContent })
            setCommentLength(get(editableCommentContent, 'length', 0))
        }
    }, [editableComment, fieldName, form, setCommentLength])

    const handleKeyUp = useCallback(async (event, form) => {
        if (event.keyCode === ENTER_KEY_CODE && !event.shiftKey) {
            const content = form.getFieldValue(fieldName)
            if (content && content.trim().length > 0 || filesCount > 0) {
                setSending(true)
            }

            form.submit()
            setCommentLength(0)
        }
    }, [fieldName, filesCount, setCommentLength, setSending])

    const handleKeyDown = useCallback((event) => {
        if (event.keyCode === ENTER_KEY_CODE) {
            event.preventDefault()
        }
    }, [])

    const { requiredValidator, trimValidator } = useValidations()

    const validations = useMemo(() => ({
        comment: filesCount > 0 ? [] : [requiredValidator, trimValidator],
    }), [filesCount, requiredValidator, trimValidator])

    const actionWithSyncComments = useCallback(async (values) => {
        values.content = form.getFieldValue(fieldName)
        form.setFieldsValue({ [fieldName]: null })

        await action(values, syncModifiedFiles)
        await resetModifiedFiles()
        setSending(false)
    }, [action, fieldName, form, resetModifiedFiles, setSending, syncModifiedFiles])

    const MemoizedUploadComponent = useCallback(() => (
        <UploadComponent
            initialFileList={editableCommentFiles}
            UploadButton={
                <Button type={'text'}>
                    <ClipIcon />
                </Button>
            }
            uploadProps={{
                iconRender: (file) => {
                    return getIconByMimetype(file.type)
                },
            }}
        />
    ), [UploadComponent, editableComment, sending])

    const initialCommentFormValues = useMemo(() => ({
        [fieldName]: initialValue,
    }), [fieldName, initialValue])

    const showHelperMessage = useMemo(() => commentLength > 0 || editableComment, [commentLength, editableComment])

    return (
        <FormWithAction
            initialValues={initialCommentFormValues}
            action={actionWithSyncComments}
            resetOnComplete={true}
        >
            {({ handleSave, isLoading, form: formInstance }) => {
                if (!form) {
                    setForm(formInstance)
                }

                return (
                    <Holder>
                        {
                            showHelperMessage && (
                                <Row justify={'space-between'} style={COMMENT_HELPERS_ROW_STYLES}>
                                    <CommentHelperWrapper>
                                        <Typography.Text>
                                            {HelperMessage}
                                        </Typography.Text>
                                    </CommentHelperWrapper>
                                    <CommentHelperWrapper>
                                        <Counter />
                                    </CommentHelperWrapper>
                                </Row>
                            )
                        }
                        <div className={'wrapper'}>
                            <Form.Item
                                name={fieldName}
                                rules={validations.comment}
                            >
                                <InputWithCounter
                                    maxLength={MAX_COMMENT_LENGTH}
                                    placeholder={PlaceholderMessage}
                                    className="white"
                                    autoSize={INPUT_WITH_COUNTER_AUTOSIZE_CONFIG}
                                    onKeyDown={handleKeyDown}
                                    onKeyUp={(event) => {handleKeyUp(event, form)}}
                                />
                            </Form.Item>
                            <MemoizedUploadComponent />
                        </div>
                    </Holder>
                )
            }}
        </FormWithAction>
    )
}
Example #22
Source File: index.tsx    From dashboard with Apache License 2.0 4 votes vote down vote up
AutoReply: React.FC<AutoReplyProps> = (props) => {
  const {welcomeMsg, setWelcomeMsg, isFetchDone} = props;
  const [modalVisible, setModalVisible] = useState(false);
  const [attachments, setAttachments] = useState<Attachment[]>([]);
  const [currentIndex, setCurrentIndex] = useState<number>(0);
  const [currentMode, setCurrentMode] = useState<MsgType>('image');
  const [linkFetching, setLinkFetching] = useState(false);
  const [content, setContent] = useState('');
  const contentRef = useRef<React.RefObject<HTMLElement>>();
  const imageModalFormRef = useRef<FormInstance>();
  const linkModalFormRef = useRef<FormInstance>();
  const miniAppModalFormRef = useRef<FormInstance>();

  const UploadFileFn = async (req: UploadRequestOption, ref: MutableRefObject<any | undefined>, inputName: string) => {
    const file = req.file as File;
    if (!file.name) {
      message.error('非法参数');
      return;
    }

    const hide = message.loading('上传中');
    try {
      const res = await GetSignedURL(file.name)
      const data = res.data as GetSignedURLResult
      if (res.code === 0) {
        const uploadRes = (await fetch(data.upload_url, {
          method: 'PUT',
          body: file
        }));
        hide();
        if (uploadRes.ok && ref) {
          ref.current?.setFieldsValue({[inputName]: data.download_url});
          return;
        }

        message.error('上传图片失败');

        return;
      }

      hide();
      message.error('获取上传地址失败');
      return;

    } catch (e) {
      message.error('上传图片失败');
      console.log(e);
    }
  };

  useEffect(() => {
    const formData = itemDataToFormData(welcomeMsg);
    setAttachments(formData.attachments || []);
    setContent(formData.text || '');
  }, [isFetchDone]);

  useEffect(() => {
    setWelcomeMsg({
      text: content || '',
      attachments: attachments || [],
    });
  }, [content, attachments]);

  return (
    <>
      <div className={styles.replyEditor}>
        <div className={'preview-container'}>
          <div className={styles.replyEditorPreview}>
            <img src={phoneImage} className='bg'/>
            <div className='content'>
              <ul className='reply-list'>
                {content && (
                  <li><img
                    src={avatarDefault}/>
                    <div className='msg text' dangerouslySetInnerHTML={{__html: content}}/>
                  </li>
                )}
                {attachments && attachments.length > 0 && (
                  attachments.map((attachment) => {
                    if (attachment.msgtype === 'image') {
                      return (
                        <li key={attachment.id}>
                          <img src={avatarDefault}/>
                          <div className={`msg image`}>
                            <img src={attachment.image?.pic_url}/>
                          </div>
                        </li>
                      );
                    }

                    if (attachment.msgtype === 'link') {
                      return (
                        <li key={attachment.id}>
                          <img src={avatarDefault}/>
                          <div className='msg link'><p className='title'>{attachment.link?.title}</p>
                            <div className='link-inner'><p
                              className='desc'>{attachment.link?.desc}</p>
                              <img src={attachment.link?.picurl}/>
                            </div>
                          </div>
                        </li>
                      );
                    }

                    if (attachment.msgtype === 'miniprogram') {
                      return (
                        <li key={attachment.id}>
                          <img src={avatarDefault}/>
                          <div className='msg miniprogram'>
                            <p className='m-title'>
                              <IconFont
                                type={'icon-weixin-mini-app'}
                                style={{marginRight: 4, fontSize: 14}}
                              />
                              {attachment.miniprogram?.title}
                            </p>
                            <img src={attachment.miniprogram?.pic_media_id}/>
                            <p className='l-title'>
                              <IconFont type={'icon-weixin-mini-app'} style={{marginRight: 4}}/>
                              小程序
                            </p>
                          </div>
                        </li>
                      );
                    }

                    return '';
                  })
                )}
              </ul>
            </div>
          </div>
        </div>

        <div className='text-area-container'>
          <div className={styles.msgTextareaContainer} style={{border: 'none'}}>
            {props.enableQuickInsert && (
              <div className='insert-btn '>
                    <span
                      className='clickable no-select'
                      onClick={() => {
                        setContent(`${content}[客户昵称]`);
                      }}
                    >[插入客户昵称]</span>
              </div>
            )}
            <div className='textarea-container '>
              <ContentEditable
                // @ts-ignore
                innerRef={contentRef}
                onKeyDown={(event) => {
                  if (event.key === 'Enter') {
                    document.execCommand('insertLineBreak');
                    event.preventDefault();
                  }
                }}
                className={'textarea'}
                html={content}
                onChange={(e) => {
                  setContent(e.target.value);
                }}/>
              <div className='flex-row align-side'>
                <p className='text-cnt'>{content.length}/600</p>
              </div>
            </div>
          </div>
        </div>
        <div className='option-area-container'>
          {attachments && attachments.length > 0 && (
            <ReactSortable handle={'.draggable-button'} tag='ul' className={'select-msg-options'} list={attachments} setList={setAttachments}>
              {attachments.map((attachment, index) => (
                <li key={attachment.id} className='flex-row'>
                      <span>
                        <MinusCircleOutlined
                          onClick={() => {
                            const items = [...attachments];
                            items.splice(index, 1);
                            setAttachments(items);
                          }}
                        />
                        【{msgTypes[attachment.msgtype]}】:
                        <span
                          className='col-1'>{attachment?.name}</span>
                      </span>
                  <span className='d-action-container'>
                      <EditOutlined
                        onClick={() => {
                          setCurrentMode(attachment.msgtype);
                          imageModalFormRef.current?.setFieldsValue(attachment.image);
                          linkModalFormRef.current?.setFieldsValue(attachment.link);
                          miniAppModalFormRef.current?.setFieldsValue(attachment.miniprogram);
                          setCurrentIndex(index);
                          setModalVisible(true);
                        }}
                      />
                      <DragOutlined
                        className={'draggable-button'}
                        style={{cursor: 'grabbing'}}
                      />
                    </span>
                </li>
              ))}
            </ReactSortable>
          )}
          <div className='option-container'>
            <Dropdown
              placement='topLeft'
              trigger={['click']}
              overlay={(
                <Menu style={{minWidth: 120}}>
                  <Menu.Item
                    key={'image'}
                    icon={<FileImageOutlined/>}
                    onClick={() => {
                      setCurrentMode('image');
                      setCurrentIndex(attachments.length);
                      imageModalFormRef.current?.resetFields();
                      setModalVisible(true);
                    }}
                  >
                    图片
                  </Menu.Item>
                  <Menu.Item
                    key={'link'}
                    icon={<LinkOutlined/>}
                    onClick={() => {
                      setCurrentMode('link');
                      setCurrentIndex(attachments.length);
                      setModalVisible(true);
                    }}
                  >
                    链接
                  </Menu.Item>
                  <Menu.Item
                    key={'miniApp'}
                    icon={<IconFont type={'icon-weixin-mini-app'}/>}
                    onClick={() => {
                      setCurrentMode('miniprogram');
                      setCurrentIndex(attachments.length);
                      setModalVisible(true);
                    }}
                  >
                    小程序
                  </Menu.Item>
                </Menu>
              )}
            >
              <a className='ant-dropdown-link' onClick={e => e.preventDefault()}>
                <PlusCircleOutlined/> 添加附件
              </a>
            </Dropdown>
          </div>
        </div>
      </div>

      <ModalForm
        formRef={imageModalFormRef}
        className={'dialog from-item-label-100w'}
        layout={'horizontal'}
        width={'560px'}
        visible={currentMode === 'image' && modalVisible}
        onVisibleChange={setModalVisible}
        onFinish={async (params: { title: string, pic_url: string, msgtype: MsgType }) => {
          attachments[currentIndex] = {
            id: new Date().getTime().toString(),
            msgtype: params.msgtype,
            name: params.title,
            image: {...params},
          };
          setAttachments(attachments);
          return true;
        }}
      >
        <h2 className='dialog-title'> 添加图片附件 </h2>
        <ProForm.Item initialValue={'image'} name={'msgtype'} noStyle={true}>
          <input type={'hidden'}/>
        </ProForm.Item>
        <ProFormText
          name='title'
          label='图片名称'
          placeholder={'请输入图片名称'}
          width='md'
          rules={[
            {
              required: true,
              message: '请输入图片名称!',
            },
          ]}
        />
        <Form.Item
          label='上传图片'
          name='pic_url'
          rules={[
            {
              required: true,
              message: '请上传图片!',
            },
          ]}
        >
          <ImageUploader
            customRequest={async (req) => {
              await UploadFileFn(req, imageModalFormRef, 'pic_url')
            }}
          />
        </Form.Item>
      </ModalForm>


      <ModalForm
        formRef={linkModalFormRef}
        className={'dialog from-item-label-100w'}
        layout={'horizontal'}
        width={'560px'}
        visible={currentMode === 'link' && modalVisible}
        onVisibleChange={setModalVisible}
        onFinish={async (params) => {
          attachments[currentIndex] = {
            id: new Date().getTime().toString(),
            msgtype: params.msgtype,
            name: params.title,
            // @ts-ignore
            link: {...params},
          };
          setAttachments(attachments);
          return true;
        }}
      >
        <Spin spinning={linkFetching}>
          <h2 className='dialog-title'> 添加链接附件 </h2>
          <ProForm.Item initialValue={'link'} name={'msgtype'} noStyle={true}>
            <input type={'hidden'}/>
          </ProForm.Item>
          <ProFormText
            name='url'
            label='链接地址'
            width='md'
            fieldProps={{
              disabled: linkFetching,
              addonAfter: (
                <Tooltip title="点击抓取远程链接,自动填充标题,描述,图片">
                  <div
                    onClick={async () => {
                      setLinkFetching(true);
                      const res = await ParseURL(linkModalFormRef.current?.getFieldValue('url'))
                      setLinkFetching(false);
                      if (res.code !== 0) {
                        message.error(res.message);
                      } else {
                        message.success('解析链接成功');
                        linkModalFormRef?.current?.setFieldsValue({
                          customer_link_enable: 1,
                          title: res.data.title,
                          desc: res.data.desc,
                          picurl: res.data.img_url,
                        })
                      }
                    }}
                    style={{
                      cursor: "pointer",
                      width: 32,
                      height: 30,
                      display: 'flex',
                      alignItems: 'center',
                      justifyContent: 'center'
                    }}>
                    <SyncOutlined/>
                  </div>
                </Tooltip>
              )
            }}
            rules={[
              {
                required: true,
                message: '请输入链接地址',
              },
              {
                type: 'url',
                message: '请填写正确的的URL,必须是http或https开头',
              },
            ]}
          />
          <ProFormSwitch
            label={'高级设置'}
            checkedChildren='开启'
            unCheckedChildren='关闭'
            name='customer_link_enable'
            tooltip={'开启后可以自定义链接所有信息'}
          />
          <ProFormDependency name={['customer_link_enable']}>
            {({customer_link_enable}) => {
              if (customer_link_enable) {
                return (
                  <>
                    <ProFormText
                      name='title'
                      label='链接标题'
                      width='md'
                      rules={[
                        {
                          required: true,
                          message: '请输入链接标题',
                        },
                      ]}
                    />
                    <ProFormTextArea
                      name='desc'
                      label='链接描述'
                      width='md'
                    />
                    <Form.Item
                      label='链接封面'
                      name='picurl'
                      rules={[
                        {
                          required: true,
                          message: '请上传链接图片!',
                        },
                      ]}
                    >
                      <ImageUploader
                        customRequest={async (req) => {
                          await UploadFileFn(req, linkModalFormRef, 'picurl')
                        }}
                      />
                    </Form.Item>
                  </>
                );
              }
              return <></>;
            }}
          </ProFormDependency>
        </Spin>
      </ModalForm>


      <ModalForm
        formRef={miniAppModalFormRef}
        className={'dialog from-item-label-100w'}
        layout={'horizontal'}
        width={'560px'}
        labelCol={{
          md: 6,
        }}
        visible={currentMode === 'miniprogram' && modalVisible}
        onVisibleChange={setModalVisible}
        onFinish={async (params) => {
          attachments[currentIndex] = {
            id: new Date().getTime().toString(),
            msgtype: params.msgtype,
            name: params.title,
            // @ts-ignore
            miniprogram: {...params},
          };
          setAttachments(attachments);
          return true;
        }}
      >
        <h2 className='dialog-title'> 添加小程序附件 </h2>

        <Alert
          showIcon={true}
          type='info'
          message={
            '请填写企业微信后台绑定的小程序id和路径,否则会造成发送失败'
          }
          style={{marginBottom: 20}}
        />

        <ProForm.Item initialValue={'miniprogram'} name={'msgtype'} noStyle={true}>
          <input type={'hidden'}/>
        </ProForm.Item>

        <ProFormText
          name='title'
          label='小程序标题'
          width='md'
          rules={[
            {
              required: true,
              message: '请输入链接标题',
            },
          ]}
        />

        <ProFormText
          // 帮助指引
          name='app_id'
          label='小程序AppID'
          width='md'
          rules={[
            {
              required: true,
              message: '请输入小程序AppID',
            },
          ]}
        />

        <ProFormText
          name='page'
          label='小程序路径'
          width='md'
          rules={[
            {
              required: true,
              message: '请输入小程序路径',
            },
          ]}
        />

        <Form.Item
          label='小程序封面'
          name='pic_media_id'
          rules={[
            {
              required: true,
              message: '请小程序封面!',
            },
          ]}
        >
          <ImageUploader
            customRequest={async (req) => {
              await UploadFileFn(req, miniAppModalFormRef, 'pic_media_id')
            }}
          />
        </Form.Item>

      </ModalForm>

    </>
  );
}