import React, { useEffect, useState, useContext, useRef } from 'react'; import cls from 'classnames'; import { Search as SearchIcon, CloseCircleFilled } from '@ant-design/icons'; import Fuse from 'fuse.js'; import { Button, Form, Input, Spin, message, Popconfirm } from 'antd'; import { IUiApi } from 'umi-types'; import isEmpty from 'lodash/isEmpty'; import serialize from 'serialize-javascript'; import Context from '../Context'; import useToggle from './common/useToggle'; import Toc from './common/Toc'; import { getDiffItems, arrayToObject, getChangedDiff, getToc } from './utils'; import styles from './BasicConfig.module.less'; interface IBasicConfigProps { api: IUiApi; } const BasicConfig: React.FC = props => { const containerRef = useRef(); const [data, setData] = useState(); const [loading, setLoading] = useState(false); const [form] = Form.useForm(); const [search, setSearch] = useState(''); const searchInputRef = useRef(); const [showSearch, toggleSearch] = useToggle(false); // const [submitLoading, setSubmitLoading] = useState(false); // const [disabled, setDisabled] = useState(true); const { api, theme, debug: _log } = useContext(Context); const { _, intl, Field } = api; const handleSearch = (vv = '') => { setSearch(vv); }; const handleSearchDebounce = _.debounce(handleSearch, 150); const resetSearch = () => { toggleSearch(false); setSearch(''); if (searchInputRef.current) { searchInputRef.current.handleReset(); } }; const handleSearchShow = () => { toggleSearch(); if (searchInputRef.current) { searchInputRef.current.focus(); } }; useEffect(() => { setLoading(true); (async () => { await updateData(); })(); setLoading(false); return () => { handleSearchDebounce.cancel(); }; }, []); async function updateData() { const { data } = await api.callRemote({ type: 'org.umi.config.list', }); setData(data); } function formatValue(value) { if (typeof value === 'object') { return serialize(value); } return value.toString(); } const fuse = React.useMemo( () => new Fuse(data || [], { caseSensitive: true, shouldSort: true, threshold: 0.6, keys: ['name', 'title', 'description', 'group'], }), [data], ); const searchData = React.useMemo( () => { _log('searchsearchsearch', search); const result = fuse.search(search); _log('resultresultresult', result); if (result.length > 0) { return result; } return data || []; }, [search, data], ); _log('searchData', searchData); const groupedData = {}; _log('data', data); searchData.forEach(item => { const { group } = item; if (!groupedData[group]) { groupedData[group] = []; } groupedData[group].push(item); }); const initialValues = arrayToObject(searchData); const [allValues, setAllValues] = useState(); if (loading) { return ; } const getChangedValue = React.useCallback( vv => { return getDiffItems(vv, arrayToObject(data, false)); }, [data], ); const getResetChangedValue = React.useCallback( vv => { return getChangedDiff(arrayToObject(data), vv); }, [data], ); const handleFinish = async values => { _log('handleFinish values', values); const changedValues = getChangedValue(values); _log('before changedValues', changedValues); if (!Object.keys(changedValues).length) { // no edit config return false; } const loadingMsg = message.loading('正在保存配置', 0); Object.keys(changedValues).forEach(name => { changedValues[name] = formatValue(changedValues[name]); }); _log('after changedValues', changedValues); try { await api.callRemote({ type: 'org.umi.config.edit', payload: { key: changedValues, value: '', }, }); await updateData(); message.success('配置修改成功'); } catch (e) { loadingMsg(); message.error(e.message); console.error(e.message, e); if (Array.isArray(e.errors) && e.errors.length > 0) { const [firstField] = e.errors; form.setFields(e.errors); form.scrollToField(firstField.name); } } finally { loadingMsg(); // setSubmitLoading(false); } }; const handleFinishFailed = ({ errorFields }) => { const [firstErrorField] = errorFields; const [firstErrorFieldName] = firstErrorField.name; form.scrollToField(firstErrorFieldName); }; const handleReset = () => { form.resetFields(); setAllValues({}); }; const handleSubmit = () => { form.submit(); }; const themeCls = cls(styles.basicConfig, styles[`basicConfig-${theme}`]); const tocAnchors = getToc(groupedData, isEmpty(allValues) ? initialValues : allValues); const searchIconCls = cls(styles['basicConfig-header-searchIcon'], { [styles['basicConfig-header-searchIcon-hide']]: !!showSearch, }); const inputCls = cls(styles['basicConfig-header-input'], { [styles['basicConfig-header-input-active']]: !!showSearch, }); const changedValueArr = Object.keys(getResetChangedValue(allValues)); const ResetTitle = (

{intl({ id: 'org.umi.ui.configuration.reset.tooltip' })}

{changedValueArr.length > 0 ? intl( { id: 'org.umi.ui.configuration.reset.tooltip.desc' }, { value: changedValueArr.join('、'), }, ) : intl({ id: 'org.umi.ui.configuration.reset.tooltip.desc.empty' })}
); _log('searchData', searchData); _log('datadata', data); return ( <>

{intl({ id: 'org.umi.ui.configuration.project.config.title' })}

} ref={searchInputRef} suffix={search && } placeholder={intl({ id: 'org.umi.ui.configuration.search.placeholder' })} className={inputCls} onChange={e => handleSearchDebounce(e.target.value)} />
{!data ? ( ) : ( searchData.length > 0 && (
{ setAllValues(allValues); }} > {Object.keys(groupedData).map(group => { return (

{group}

{groupedData[group].map( ({ default: defaultValue, name, title, choices = [], description, link, ...restItemProps }) => { const label = { title, description, link, }; return ( ); }, )}
); })} {({ getFieldsValue }) => { // TODO: remove before publish _log('Form values', JSON.stringify(getFieldsValue(), null, 2)); }}
) )}
containerRef && containerRef.current} />
{}} okText={intl({ id: 'org.umi.ui.configuration.okText' })} cancelText={intl({ id: 'org.umi.ui.configuration.cancelText' })} >
); }; export default BasicConfig;