Skip to content

Commit

Permalink
Fix bug with RestrictedInput TGUI component (cmss13-devs#5651)
Browse files Browse the repository at this point in the history
# About the pull request

<!-- Remove this text and explain what the purpose of your PR is.

Mention if you have tested your changes. If you changed a map, make sure
you used the mapmerge tool.
If this is an Issue Correction, you can type "Fixes Issue #169420" to
link the PR to the corresponding Issue number #169420.

Remember: something that is self-evident to you might not be to others.
Explain your rationale fully, even if you feel it goes without saying.
-->

Should fix bug in restricted input TGUI component. The number was being
clamped on every input, which meant that applying a number of 30 into a
range of 19-90, 3 would be bounded to 19 and then the 0 would make the
value 190, being bounded to 90.

This change performs the check on blur.

# Explain why it's good for the game
Fixes cmss13-devs#5610

# Testing Photographs and Procedure
<details>
<summary>Screenshots & Videos</summary>

Put screenshots and videos here with an empty line between the
screenshots and the `<details>` tags.

</details>


# Changelog
:cl:
code: rework logic of restrictedinput component to reduce checks
/:cl:
  • Loading branch information
mullenpaul authored Feb 16, 2024
1 parent bcee416 commit f54018f
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 24 deletions.
1 change: 1 addition & 0 deletions code/modules/tgui/tgui_number_input.dm
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
"min_value" = min_value,
"preferences" = list(),
"title" = title,
"integer_only" = integer_only
)

/datum/tgui_input_number/ui_data(mob/user)
Expand Down
63 changes: 43 additions & 20 deletions tgui/packages/tgui/components/RestrictedInput.jsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,56 @@
import { classes } from 'common/react';
import { KEY_ENTER, KEY_ESCAPE } from 'common/keycodes';
import { clamp } from 'common/math';
import { classes } from 'common/react';
import { Component, createRef } from 'react';

import { Box } from './Box';
import { KEY_ESCAPE, KEY_ENTER } from 'common/keycodes';

const DEFAULT_MIN = -16777216;
const DEFAULT_MAX = 16777216;
const DEFAULT_MIN = 0;
const DEFAULT_MAX = 10000;

/**
* Takes a string input and parses integers from it.
* Takes a string input and parses integers or floats from it.
* If none: Minimum is set.
* Else: Clamps it to the given range.
*/
const getClampedNumber = (value, minValue, maxValue) => {
const getClampedNumber = (value, minValue, maxValue, allowFloats) => {
const minimum = minValue || DEFAULT_MIN;
const maximum = maxValue || maxValue === 0 ? maxValue : DEFAULT_MAX;
const defaultValue = maximum < 0 ? minimum : minimum > 0 ? minimum : 0;
if (!value || !value.length) {
return String(defaultValue);
return String(minimum);
}
let parsedValue = parseFloat(value.replace(/[^\-.\d]/g, ''), 10);
let parsedValue = allowFloats
? parseFloat(value.replace(/[^\-\d.]/g, ''))
: parseInt(value.replace(/[^\-\d]/g, ''), 10);
if (isNaN(parsedValue)) {
return String(defaultValue);
return String(minimum);
} else {
return String(clamp(parsedValue, minimum, maximum));
}
};

export class RestrictedInput extends Component {
constructor() {
super();
constructor(props) {
super(props);
this.inputRef = createRef();
this.state = {
editing: false,
};
this.handleBlur = (e) => {
const { maxValue, minValue, allowFloats } = this.props;
const { editing } = this.state;
if (editing) {
e.target.value = getClampedNumber(
e.target.value,
minValue,
maxValue,
allowFloats
);
this.setEditing(false);
}
};
this.handleChange = (e) => {
const { maxValue, minValue, onChange } = this.props;
e.target.value = getClampedNumber(e.target.value, minValue, maxValue);
const { onChange } = this.props;
if (onChange) {
onChange(e, +e.target.value);
}
Expand All @@ -64,9 +72,14 @@ export class RestrictedInput extends Component {
}
};
this.handleKeyDown = (e) => {
const { maxValue, minValue, onChange, onEnter } = this.props;
const { maxValue, minValue, onChange, onEnter, allowFloats } = this.props;
if (e.keyCode === KEY_ENTER) {
const safeNum = getClampedNumber(e.target.value, minValue, maxValue);
const safeNum = getClampedNumber(
e.target.value,
minValue,
maxValue,
allowFloats
);
this.setEditing(false);
if (onChange) {
onChange(e, +safeNum);
Expand All @@ -91,11 +104,16 @@ export class RestrictedInput extends Component {
}

componentDidMount() {
const { maxValue, minValue } = this.props;
const { maxValue, minValue, allowFloats } = this.props;
const nextValue = this.props.value?.toString();
const input = this.inputRef.current;
if (input) {
input.value = getClampedNumber(nextValue, minValue, maxValue);
input.value = getClampedNumber(
nextValue,
minValue,
maxValue,
allowFloats
);
}
if (this.props.autoFocus || this.props.autoSelect) {
setTimeout(() => {
Expand All @@ -109,14 +127,19 @@ export class RestrictedInput extends Component {
}

componentDidUpdate(prevProps, _) {
const { maxValue, minValue } = this.props;
const { maxValue, minValue, allowFloats } = this.props;
const { editing } = this.state;
const prevValue = prevProps.value?.toString();
const nextValue = this.props.value?.toString();
const input = this.inputRef.current;
if (input && !editing) {
if (nextValue !== prevValue && nextValue !== input.value) {
input.value = getClampedNumber(nextValue, minValue, maxValue);
input.value = getClampedNumber(
nextValue,
minValue,
maxValue,
allowFloats
);
}
}
}
Expand Down
22 changes: 18 additions & 4 deletions tgui/packages/tgui/interfaces/NumberInputModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,19 @@ type NumberInputData = {
min_value: number | null;
timeout: number;
title: string;
integer_only: 0 | 1;
};

export const NumberInputModal = (props) => {
export const NumberInputModal = () => {
const { act, data } = useBackend<NumberInputData>();
const { init_value, large_buttons, message = '', timeout, title } = data;
const {
init_value,
large_buttons,
message = '',
timeout,
title,
integer_only,
} = data;
const [input, setInput] = useLocalState('input', init_value);
const onChange = (value: number) => {
if (value === input) {
Expand Down Expand Up @@ -56,7 +64,12 @@ export const NumberInputModal = (props) => {
<Box color="label">{message}</Box>
</Stack.Item>
<Stack.Item>
<InputArea input={input} onClick={onClick} onChange={onChange} />
<InputArea
input={input}
onClick={onClick}
onChange={onChange}
integer_only={integer_only}
/>
</Stack.Item>
<Stack.Item>
<InputButtons input={input} />
Expand All @@ -72,7 +85,7 @@ export const NumberInputModal = (props) => {
const InputArea = (props) => {
const { act, data } = useBackend<NumberInputData>();
const { min_value, max_value, init_value } = data;
const { input, onClick, onChange } = props;
const { input, onClick, onChange, integer_only } = props;

return (
<Stack fill>
Expand All @@ -94,6 +107,7 @@ const InputArea = (props) => {
onChange={(_, value) => onChange(value)}
onEnter={(_, value) => act('submit', { entry: value })}
value={input}
allowFloats={integer_only === 1 ? false : true}
/>
</Stack.Item>
<Stack.Item>
Expand Down

0 comments on commit f54018f

Please sign in to comment.