mirror of
https://gitee.com/nocobase/nocobase.git
synced 2024-11-30 03:08:31 +08:00
refactor(client): allow to select null value explicitly in variable input (#4869)
* refactor(client): allow select null value explicitly in variable input * fix(client): fix no constant group when null selected * fix(client): fix test case
This commit is contained in:
parent
7ee6eac0f7
commit
394558c520
@ -44,6 +44,11 @@ function parseValue(value: any): string | string[] {
|
||||
return type === 'object' && value instanceof Date ? 'date' : type;
|
||||
}
|
||||
|
||||
function NullComponent() {
|
||||
const { t } = useTranslation();
|
||||
return <AntInput style={{ width: '100%' }} readOnly placeholder={`<${t('Null')}>`} className="null-value" />;
|
||||
}
|
||||
|
||||
const ConstantTypes = {
|
||||
string: {
|
||||
label: `{{t("String")}}`,
|
||||
@ -76,6 +81,7 @@ const ConstantTypes = {
|
||||
{ value: false, label: t('False') },
|
||||
]}
|
||||
{...otherProps}
|
||||
className={classNames(otherProps.className, 'auto-width')}
|
||||
/>
|
||||
);
|
||||
},
|
||||
@ -100,19 +106,17 @@ const ConstantTypes = {
|
||||
return new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0);
|
||||
})(),
|
||||
},
|
||||
// NOTE: keep null option here for compatibility
|
||||
null: {
|
||||
label: `{{t("Null")}}`,
|
||||
label: '{{t("Null")}}',
|
||||
value: 'null',
|
||||
component: function NullComponent() {
|
||||
const { t } = useTranslation();
|
||||
return <AntInput style={{ width: '100%' }} readOnly placeholder={t('Null')} className="null-value" />;
|
||||
},
|
||||
component: NullComponent,
|
||||
default: null,
|
||||
},
|
||||
};
|
||||
|
||||
function getTypedConstantOption(type: string, types: true | string[], fieldNames) {
|
||||
const allTypes = Object.values(ConstantTypes);
|
||||
const allTypes = Object.values(ConstantTypes).filter((item) => item.value !== 'null');
|
||||
const children = (
|
||||
types ? allTypes.filter((item) => (Array.isArray(types) && types.includes(item.value)) || types === true) : allTypes
|
||||
).map((item) =>
|
||||
@ -127,10 +131,10 @@ function getTypedConstantOption(type: string, types: true | string[], fieldNames
|
||||
),
|
||||
);
|
||||
return {
|
||||
value: '',
|
||||
value: ' ',
|
||||
label: '{{t("Constant")}}',
|
||||
children,
|
||||
[fieldNames.value]: '',
|
||||
[fieldNames.value]: ' ',
|
||||
[fieldNames.label]: '{{t("Constant")}}',
|
||||
[fieldNames.children]: children,
|
||||
component: ConstantTypes[type]?.component,
|
||||
@ -174,6 +178,7 @@ export function Input(props: VariableInputProps) {
|
||||
const form = useForm();
|
||||
const [options, setOptions] = React.useState<DefaultOptionType[]>([]);
|
||||
const [variableText, setVariableText] = React.useState([]);
|
||||
const [isFieldValue, setIsFieldValue] = React.useState(children && value != null ? true : false);
|
||||
|
||||
const parsed = useMemo(() => parseValue(value), [value]);
|
||||
const isConstant = typeof parsed === 'string';
|
||||
@ -188,35 +193,50 @@ export function Input(props: VariableInputProps) {
|
||||
fieldNames ?? {},
|
||||
);
|
||||
|
||||
const { component: ConstantComponent, ...constantOption }: DefaultOptionType & { component?: React.FC<any> } =
|
||||
useMemo(() => {
|
||||
if (children) {
|
||||
return {
|
||||
value: '',
|
||||
label: t('Constant'),
|
||||
[names.value]: '',
|
||||
[names.label]: t('Constant'),
|
||||
};
|
||||
}
|
||||
if (useTypedConstant) {
|
||||
return getTypedConstantOption(type, useTypedConstant, names);
|
||||
}
|
||||
const constantOption: DefaultOptionType & { component?: React.FC<any> } = useMemo(() => {
|
||||
if (children) {
|
||||
return {
|
||||
value: '$',
|
||||
label: t('Constant'),
|
||||
[names.value]: '$',
|
||||
[names.label]: t('Constant'),
|
||||
};
|
||||
}
|
||||
if (useTypedConstant) {
|
||||
return getTypedConstantOption(type, useTypedConstant, names);
|
||||
}
|
||||
return null;
|
||||
}, [type, useTypedConstant]);
|
||||
|
||||
const ConstantComponent = constantOption && !children ? constantOption.component : NullComponent;
|
||||
let cValue;
|
||||
if (value == null) {
|
||||
if (children && isFieldValue) {
|
||||
cValue = ['$'];
|
||||
} else {
|
||||
cValue = [''];
|
||||
}
|
||||
} else {
|
||||
cValue = children ? ['$'] : [' ', type];
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const { component, ...cOption } = constantOption ?? {};
|
||||
const options = [
|
||||
{
|
||||
value: '',
|
||||
label: t('Null'),
|
||||
[names.value]: '',
|
||||
[names.label]: t('Null'),
|
||||
component: ConstantTypes.null.component,
|
||||
};
|
||||
}, [type, useTypedConstant]);
|
||||
|
||||
useEffect(() => {
|
||||
const options = [compile(constantOption), ...(scope ? [...scope] : [])].filter((item) => {
|
||||
},
|
||||
...(constantOption ? [compile(cOption)] : []),
|
||||
...(scope ? [...scope] : []),
|
||||
].filter((item) => {
|
||||
return !item.deprecated || variable?.[0] === item[names.value];
|
||||
});
|
||||
|
||||
setOptions(options);
|
||||
}, [scope, variable]);
|
||||
}, [scope, variable, constantOption]);
|
||||
|
||||
const loadData = async (selectedOptions: DefaultOptionType[]) => {
|
||||
const option = selectedOptions[selectedOptions.length - 1];
|
||||
@ -237,7 +257,20 @@ export function Input(props: VariableInputProps) {
|
||||
|
||||
const onSwitch = useCallback(
|
||||
(next, optionPath: any[]) => {
|
||||
if (next[0] === '$') {
|
||||
setIsFieldValue(true);
|
||||
if (variable) {
|
||||
onChange(null, optionPath);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
setIsFieldValue(false);
|
||||
}
|
||||
if (next[0] === '') {
|
||||
onChange(null);
|
||||
return;
|
||||
}
|
||||
if (next[0] === ' ') {
|
||||
if (next[1]) {
|
||||
if (next[1] !== type) {
|
||||
onChange(ConstantTypes[next[1]]?.default ?? null, optionPath);
|
||||
@ -331,17 +364,7 @@ export function Input(props: VariableInputProps) {
|
||||
role="button"
|
||||
aria-label="variable-tag"
|
||||
style={{ overflow: 'hidden' }}
|
||||
onInput={(e) => e.preventDefault()}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key !== 'Backspace') {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
onChange(null);
|
||||
}}
|
||||
className={cx('ant-input', { 'ant-input-disabled': disabled }, hashId)}
|
||||
contentEditable={!disabled}
|
||||
suppressContentEditableWarning
|
||||
>
|
||||
<Tag contentEditable={false} color="blue">
|
||||
{variableText.map((item, index) => {
|
||||
@ -361,7 +384,10 @@ export function Input(props: VariableInputProps) {
|
||||
className={cx('clear-button')}
|
||||
// eslint-disable-next-line react/no-unknown-property
|
||||
unselectable="on"
|
||||
onClick={() => onChange(null)}
|
||||
onClick={() => {
|
||||
setIsFieldValue(false);
|
||||
onChange(null);
|
||||
}}
|
||||
>
|
||||
<CloseCircleFilled />
|
||||
</span>
|
||||
@ -369,14 +395,16 @@ export function Input(props: VariableInputProps) {
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ flex: 1 }}>
|
||||
{children ?? (
|
||||
{children && isFieldValue ? (
|
||||
children
|
||||
) : (
|
||||
<ConstantComponent role="button" aria-label="variable-constant" value={value} onChange={onChange} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<Cascader
|
||||
options={options}
|
||||
value={variable ?? ['', ...(children || !constantOption.children?.length ? [] : [type])]}
|
||||
value={variable ?? cValue}
|
||||
onChange={onSwitch}
|
||||
loadData={loadData as any}
|
||||
changeOnSelect={changeOnSelect}
|
||||
|
@ -17,7 +17,7 @@ describe('Variable', () => {
|
||||
it('Variable.Input', async () => {
|
||||
render(<App1 />);
|
||||
|
||||
expect(screen.getByPlaceholderText('Null')).toBeInTheDocument();
|
||||
expect(screen.getByPlaceholderText('<Null>')).toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByText('x'));
|
||||
await userEvent.click(screen.getByText('v1'));
|
||||
|
Loading…
Reference in New Issue
Block a user