|
|
|
@ -6,13 +6,90 @@ import { Duration } from "@common/Duration";
@@ -6,13 +6,90 @@ import { Duration } from "@common/Duration";
|
|
|
|
|
|
|
|
|
|
import "@client/styles/DurationView"; |
|
|
|
|
|
|
|
|
|
export default class DurationView extends React.Component<{ |
|
|
|
|
export interface DurationViewProps { |
|
|
|
|
label?: string; |
|
|
|
|
inline?: boolean; |
|
|
|
|
duration: Duration; |
|
|
|
|
onDurationChange?: (newDuration: Duration) => void; |
|
|
|
|
className?: string; |
|
|
|
|
}> { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function roundOrString(val: number | string): number | string { |
|
|
|
|
if (typeof val === "number") { |
|
|
|
|
return Math.round(val); |
|
|
|
|
} else { |
|
|
|
|
return val; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
interface NumberInputProps { |
|
|
|
|
className?: string; |
|
|
|
|
label?: string; |
|
|
|
|
value: number; |
|
|
|
|
max?: number; |
|
|
|
|
onChange: (value: number) => void; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function NumberInput(props: NumberInputProps): React.ReactElement { |
|
|
|
|
const [valueState, setValueState] = React.useState<number | string>(props.value); |
|
|
|
|
const [elementId, setElementId] = React.useState(() => `NumberInput-${Math.round(Math.random() * 100000000)}`); |
|
|
|
|
const [isWheelChange, setIsWheelChange] = React.useState(false); |
|
|
|
|
|
|
|
|
|
const onChange: InputProps["onChange"] = (_e, data) => { |
|
|
|
|
setValueState(data.value); |
|
|
|
|
const newValue = parseFloat(data.value); |
|
|
|
|
if (!isNaN(newValue) && data.value.length > 0 && isWheelChange) { |
|
|
|
|
props.onChange(Math.round(newValue)); |
|
|
|
|
setIsWheelChange(false); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const onBlur: React.FocusEventHandler = () => { |
|
|
|
|
const newValue = (typeof valueState === "number") ? valueState : parseFloat(valueState); |
|
|
|
|
if (!props.onChange || isNaN(newValue)) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
if (props.value !== newValue) { |
|
|
|
|
props.onChange(Math.round(newValue)); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const onWheel = (e: Event) => { |
|
|
|
|
// do nothing
|
|
|
|
|
setIsWheelChange(true); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
React.useEffect(() => { |
|
|
|
|
const el = document.getElementById(elementId); |
|
|
|
|
if (el) { |
|
|
|
|
// Not passive events
|
|
|
|
|
el.addEventListener("wheel", onWheel); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
React.useEffect(() => { |
|
|
|
|
if (props.value !== valueState) { |
|
|
|
|
setValueState(props.value); |
|
|
|
|
} |
|
|
|
|
}, [props.value]); |
|
|
|
|
|
|
|
|
|
return <Input |
|
|
|
|
id={elementId} |
|
|
|
|
type="number" |
|
|
|
|
pattern="[0-9\.]*" // for safari
|
|
|
|
|
inputMode="numeric" |
|
|
|
|
value={roundOrString(valueState)} |
|
|
|
|
onChange={onChange} |
|
|
|
|
// onMouseOut={onBlur}
|
|
|
|
|
onBlur={onBlur} |
|
|
|
|
className={props.className} |
|
|
|
|
label={props.label} |
|
|
|
|
max={props.max} |
|
|
|
|
labelPosition="right" |
|
|
|
|
/> |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export default class DurationView extends React.Component<DurationViewProps> { |
|
|
|
|
render() { |
|
|
|
|
const { duration, label, inline, onDurationChange, className } = this.props; |
|
|
|
|
const inputsClassName = classNames("durationInputs", { inline }); |
|
|
|
@ -22,24 +99,18 @@ export default class DurationView extends React.Component<{
@@ -22,24 +99,18 @@ export default class DurationView extends React.Component<{
|
|
|
|
|
<Form.Field inline={inline} className={className}> |
|
|
|
|
{label && <label>{label}</label>} |
|
|
|
|
<div className={inputsClassName}> |
|
|
|
|
<Input |
|
|
|
|
type="number" |
|
|
|
|
<NumberInput |
|
|
|
|
className="durationInput minutes" |
|
|
|
|
value={duration.minutes} |
|
|
|
|
value={this.props.duration.minutes} |
|
|
|
|
onChange={this.onMinutesChange} |
|
|
|
|
label="M" |
|
|
|
|
labelPosition="right" |
|
|
|
|
onWheel={this.onWheel} |
|
|
|
|
/> |
|
|
|
|
<Input |
|
|
|
|
type="number" |
|
|
|
|
<NumberInput |
|
|
|
|
className="durationInput seconds" |
|
|
|
|
value={duration.seconds} |
|
|
|
|
value={this.props.duration.seconds} |
|
|
|
|
onChange={this.onSecondsChange} |
|
|
|
|
max="60" |
|
|
|
|
max={60} |
|
|
|
|
label="S" |
|
|
|
|
labelPosition="right" |
|
|
|
|
onWheel={this.onWheel} |
|
|
|
|
/> |
|
|
|
|
</div> |
|
|
|
|
</Form.Field> |
|
|
|
@ -55,23 +126,25 @@ export default class DurationView extends React.Component<{
@@ -55,23 +126,25 @@ export default class DurationView extends React.Component<{
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private onMinutesChange: InputProps["onChange"] = (e, { value }) => { |
|
|
|
|
if (!this.props.onDurationChange || isNaN(Number(value))) { |
|
|
|
|
return; |
|
|
|
|
componentWillReceiveProps(nextProps: Readonly<DurationViewProps>) { |
|
|
|
|
if (nextProps.duration.minutes !== this.props.duration.minutes || |
|
|
|
|
nextProps.duration.seconds !== this.props.duration.seconds) { |
|
|
|
|
this.setState({ |
|
|
|
|
minutes: nextProps.duration.minutes, |
|
|
|
|
seconds: nextProps.duration.seconds, |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
const newMinutes = Number(value); |
|
|
|
|
this.props.onDurationChange(this.props.duration.withMinutes(newMinutes)); |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private onSecondsChange: InputProps["onChange"] = (e, { value }) => { |
|
|
|
|
if (!this.props.onDurationChange || isNaN(Number(value))) { |
|
|
|
|
return; |
|
|
|
|
private onMinutesChange = (newMinutes: number) => { |
|
|
|
|
if (this.props.onDurationChange) { |
|
|
|
|
this.props.onDurationChange(this.props.duration.withMinutes(newMinutes)); |
|
|
|
|
} |
|
|
|
|
const newSeconds = Number(value); |
|
|
|
|
this.props.onDurationChange(this.props.duration.withSeconds(newSeconds)); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
private onWheel = () => { |
|
|
|
|
// do nothing
|
|
|
|
|
private onSecondsChange = (newSeconds: number) => { |
|
|
|
|
if (this.props.onDurationChange) { |
|
|
|
|
this.props.onDurationChange(this.props.duration.withSeconds(newSeconds)); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|