Factor out 'FormFieldDuration' for re-use

This commit is contained in:
infinite-persistence 2021-09-07 15:50:00 +08:00
parent 6be519374a
commit 89577950c2
No known key found for this signature in database
GPG key ID: B9C3252EDC3D0AA0
3 changed files with 96 additions and 60 deletions

View file

@ -0,0 +1,3 @@
import FormFieldDuration from './view';
export default FormFieldDuration;

View file

@ -0,0 +1,90 @@
// @flow
import type { Node } from 'react';
import React from 'react';
import parseDuration from 'parse-duration';
import { FormField } from 'component/common/form';
import Icon from 'component/common/icon';
import * as ICONS from 'constants/icons';
const INPUT_EXAMPLES = '\n- 30s\n- 10m\n- 1h\n- 2d\n- 3mo\n- 1y';
const ONE_HUNDRED_YEARS_IN_SECONDS = 3154000000;
type Props = {
name: string,
label?: string | Node,
placeholder?: string | number,
disabled?: boolean,
value: string | number,
onChange: (any) => void,
onResolve: (valueInSeconds: number) => void, // Returns parsed/resolved value in seconds; "-1" for invalid input.
maxDurationInSeconds?: number,
};
export default function FormFieldDuration(props: Props) {
const { name, label, placeholder, disabled, value, onChange, onResolve, maxDurationInSeconds } = props;
const [valueSec, setValueSec] = React.useState(-1);
const [valueErr, setValueErr] = React.useState('');
React.useEffect(() => {
const handleInvalidInput = (errMsg: string) => {
if (valueSec !== -1) {
setValueSec(-1);
}
if (valueErr !== errMsg) {
setValueErr(errMsg);
}
onResolve(-1);
};
const handleValidInput = (seconds) => {
if (seconds !== valueSec) {
setValueSec(seconds);
onResolve(seconds);
}
if (valueErr) {
setValueErr('');
}
};
if (!value) {
handleValidInput(-1); // Reset
return;
}
const seconds = parseDuration(value, 's');
if (Number.isInteger(seconds) && seconds > 0) {
const max = maxDurationInSeconds || ONE_HUNDRED_YEARS_IN_SECONDS;
if (seconds > max) {
handleInvalidInput(__('Value exceeded maximum.'));
} else {
handleValidInput(seconds);
}
} else {
handleInvalidInput(__('Invalid duration.'));
}
}, [value, valueSec, valueErr, maxDurationInSeconds, onResolve]);
return (
<FormField
name={name}
type="text"
disabled={disabled}
label={
<>
{label || __('Duration')}
<Icon
customTooltipText={__('Examples: %examples%', { examples: INPUT_EXAMPLES })}
className="icon--help"
icon={ICONS.HELP}
tooltip
size={16}
/>
</>
}
placeholder={placeholder || '30s, 10m, 1h, 2d, 3mo, 1y'}
value={value}
onChange={onChange}
error={valueErr}
/>
);
}

View file

@ -1,14 +1,12 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import parseDuration from 'parse-duration';
import Button from 'component/button'; import Button from 'component/button';
import ChannelThumbnail from 'component/channelThumbnail'; import ChannelThumbnail from 'component/channelThumbnail';
import ClaimPreview from 'component/claimPreview'; import ClaimPreview from 'component/claimPreview';
import Card from 'component/common/card'; import Card from 'component/common/card';
import { FormField } from 'component/common/form'; import { FormField } from 'component/common/form';
import Icon from 'component/common/icon'; import FormFieldDuration from 'component/formFieldDuration';
import * as ICONS from 'constants/icons';
import usePersistedState from 'effects/use-persisted-state'; import usePersistedState from 'effects/use-persisted-state';
import { Modal } from 'modal/modal'; import { Modal } from 'modal/modal';
import { getChannelFromClaim } from 'util/claim'; import { getChannelFromClaim } from 'util/claim';
@ -76,7 +74,6 @@ export default function ModalBlockChannel(props: Props) {
const [tab, setTab] = usePersistedState('ModalBlockChannel:tab', TAB.PERSONAL); const [tab, setTab] = usePersistedState('ModalBlockChannel:tab', TAB.PERSONAL);
const [blockType, setBlockType] = usePersistedState('ModalBlockChannel:blockType', BLOCK.PERMANENT); const [blockType, setBlockType] = usePersistedState('ModalBlockChannel:blockType', BLOCK.PERMANENT);
const [timeoutInput, setTimeoutInput] = usePersistedState('ModalBlockChannel:timeoutInput', '10m'); const [timeoutInput, setTimeoutInput] = usePersistedState('ModalBlockChannel:timeoutInput', '10m');
const [timeoutInputErr, setTimeoutInputErr] = React.useState('');
const [timeoutSec, setTimeoutSec] = React.useState(-1); const [timeoutSec, setTimeoutSec] = React.useState(-1);
const isPersonalTheOnlyTab = !activeChannelIsModerator && !activeChannelIsAdmin; const isPersonalTheOnlyTab = !activeChannelIsModerator && !activeChannelIsAdmin;
@ -101,45 +98,6 @@ export default function ModalBlockChannel(props: Props) {
} }
}, []); // eslint-disable-line react-hooks/exhaustive-deps }, []); // eslint-disable-line react-hooks/exhaustive-deps
// 'timeoutInput' to 'timeoutSec' conversion.
React.useEffect(() => {
const handleInvalidInput = (errMsg: string) => {
if (timeoutSec !== -1) {
setTimeoutSec(-1);
}
if (timeoutInputErr !== errMsg) {
setTimeoutInputErr(errMsg);
}
};
const handleValidInput = (seconds) => {
if (seconds !== timeoutSec) {
setTimeoutSec(seconds);
}
if (timeoutInputErr) {
setTimeoutInputErr('');
}
};
if (!timeoutInput) {
handleValidInput(-1); // Reset
return;
}
const ONE_HUNDRED_YEARS_IN_SECONDS = 3154000000;
const seconds = parseDuration(timeoutInput, 's');
if (Number.isInteger(seconds) && seconds > 0) {
if (seconds > ONE_HUNDRED_YEARS_IN_SECONDS) {
handleInvalidInput(__('Wow, banned for more than 100 years?'));
} else {
handleValidInput(seconds);
}
} else {
handleInvalidInput(__('Invalid duration.'));
}
}, [timeoutInput, timeoutInputErr, timeoutSec]);
// ************************************************************************** // **************************************************************************
// ************************************************************************** // **************************************************************************
@ -187,27 +145,12 @@ export default function ModalBlockChannel(props: Props) {
} }
function getTimeoutDurationElem() { function getTimeoutDurationElem() {
const examples = '\n- 30s\n- 10m\n- 1h\n- 2d\n- 3mo\n- 1y';
return ( return (
<FormField <FormFieldDuration
name="time_out" name="time_out"
label={
<>
{__('Duration')}
<Icon
customTooltipText={__('Enter the timeout duration. Examples: %examples%', { examples })}
className="icon--help"
icon={ICONS.HELP}
tooltip
size={16}
/>
</>
}
type="text"
placeholder="30s, 10m, 1h, 2d, 3mo, 1y"
value={timeoutInput} value={timeoutInput}
onChange={(e) => setTimeoutInput(e.target.value)} onChange={(e) => setTimeoutInput(e.target.value)}
error={timeoutInputErr} onResolve={(valueInSeconds) => setTimeoutSec(valueInSeconds)}
/> />
); );
} }