mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-11 18:53:06 +00:00
TextBox Shift-Enter (Multilines)
This commit is contained in:
@@ -122,8 +122,8 @@
|
|||||||
data["multiline"] = multiline
|
data["multiline"] = multiline
|
||||||
data["placeholder"] = default // Default is a reserved keyword
|
data["placeholder"] = default // Default is a reserved keyword
|
||||||
data["swapped_buttons"] = !user.client.prefs.tgui_swapped_buttons
|
data["swapped_buttons"] = !user.client.prefs.tgui_swapped_buttons
|
||||||
|
// CHOMPedit - prevent_enter should be completely removed in the future
|
||||||
data["title"] = title
|
data["title"] = title
|
||||||
data["prevent_enter"] = prevent_enter
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
/datum/tgui_input_text/tgui_data(mob/user)
|
/datum/tgui_input_text/tgui_data(mob/user)
|
||||||
|
|||||||
@@ -15,9 +15,10 @@ export class TextArea extends Component {
|
|||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
this.textareaRef = props.innerRef || createRef();
|
this.textareaRef = props.innerRef || createRef();
|
||||||
this.fillerRef = createRef();
|
// CHOMPedit
|
||||||
this.state = {
|
this.state = {
|
||||||
editing: false,
|
editing: false,
|
||||||
|
scrolledAmount: 0,
|
||||||
};
|
};
|
||||||
const { dontUseTabForIndent = false } = props;
|
const { dontUseTabForIndent = false } = props;
|
||||||
this.handleOnInput = (e) => {
|
this.handleOnInput = (e) => {
|
||||||
@@ -52,7 +53,8 @@ export class TextArea extends Component {
|
|||||||
};
|
};
|
||||||
this.handleKeyDown = (e) => {
|
this.handleKeyDown = (e) => {
|
||||||
const { editing } = this.state;
|
const { editing } = this.state;
|
||||||
const { onChange, onInput, onEnter, onKeyDown } = this.props;
|
// CHOMPedit
|
||||||
|
const { onChange, onInput, onEnter, onKey } = this.props;
|
||||||
if (e.keyCode === KEY_ENTER) {
|
if (e.keyCode === KEY_ENTER) {
|
||||||
this.setEditing(false);
|
this.setEditing(false);
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
@@ -86,8 +88,10 @@ export class TextArea extends Component {
|
|||||||
if (!editing) {
|
if (!editing) {
|
||||||
this.setEditing(true);
|
this.setEditing(true);
|
||||||
}
|
}
|
||||||
if (onKeyDown) {
|
// CHOMPedit
|
||||||
onKeyDown(e, e.target.value);
|
// Custom key handler
|
||||||
|
if (onKey) {
|
||||||
|
onKey(e, e.target.value);
|
||||||
}
|
}
|
||||||
if (!dontUseTabForIndent) {
|
if (!dontUseTabForIndent) {
|
||||||
const keyCode = e.keyCode || e.which;
|
const keyCode = e.keyCode || e.which;
|
||||||
@@ -96,6 +100,10 @@ export class TextArea extends Component {
|
|||||||
const { value, selectionStart, selectionEnd } = e.target;
|
const { value, selectionStart, selectionEnd } = e.target;
|
||||||
e.target.value = value.substring(0, selectionStart) + '\t' + value.substring(selectionEnd);
|
e.target.value = value.substring(0, selectionStart) + '\t' + value.substring(selectionEnd);
|
||||||
e.target.selectionEnd = selectionStart + 1;
|
e.target.selectionEnd = selectionStart + 1;
|
||||||
|
// CHOMPedit
|
||||||
|
if (onInput) {
|
||||||
|
onInput(e, e.target.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -115,6 +123,16 @@ export class TextArea extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// CHOMPedit Start
|
||||||
|
this.handleScroll = (e) => {
|
||||||
|
const { displayedValue } = this.props;
|
||||||
|
const input = this.textareaRef.current;
|
||||||
|
if (displayedValue && input) {
|
||||||
|
this.setState({
|
||||||
|
scrolledAmount: input.scrollTop,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -123,8 +141,15 @@ export class TextArea extends Component {
|
|||||||
if (input) {
|
if (input) {
|
||||||
input.value = toInputValue(nextValue);
|
input.value = toInputValue(nextValue);
|
||||||
}
|
}
|
||||||
if (this.props.autoFocus) {
|
if (this.props.autoFocus || this.props.autoSelect) {
|
||||||
setTimeout(() => input.focus(), 1);
|
setTimeout(() => {
|
||||||
|
input.focus();
|
||||||
|
|
||||||
|
if (this.props.autoSelect) {
|
||||||
|
input.select();
|
||||||
|
}
|
||||||
|
}, 1);
|
||||||
|
// CHOMPedit End
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,15 +183,37 @@ export class TextArea extends Component {
|
|||||||
value,
|
value,
|
||||||
maxLength,
|
maxLength,
|
||||||
placeholder,
|
placeholder,
|
||||||
|
// CHOMPedit Start
|
||||||
|
scrollbar,
|
||||||
|
noborder,
|
||||||
|
displayedValue,
|
||||||
...boxProps
|
...boxProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
// Box props
|
// Box props
|
||||||
const { className, fluid, ...rest } = boxProps;
|
const { className, fluid, nowrap, ...rest } = boxProps;
|
||||||
|
const { scrolledAmount } = this.state;
|
||||||
return (
|
return (
|
||||||
<Box className={classes(['TextArea', fluid && 'TextArea--fluid', className])} {...rest}>
|
<Box
|
||||||
|
className={classes(['TextArea', fluid && 'TextArea--fluid', noborder && 'TextArea--noborder', className])}
|
||||||
|
{...rest}>
|
||||||
|
{!!displayedValue && (
|
||||||
|
<Box position="absolute" width="100%" height="100%" overflow="hidden">
|
||||||
|
<div
|
||||||
|
className={classes(['TextArea__textarea', 'TextArea__textarea_custom'])}
|
||||||
|
style={{
|
||||||
|
'transform': `translateY(-${scrolledAmount}px)`,
|
||||||
|
}}>
|
||||||
|
{displayedValue}
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
<textarea
|
<textarea
|
||||||
ref={this.textareaRef}
|
ref={this.textareaRef}
|
||||||
className="TextArea__textarea"
|
className={classes([
|
||||||
|
'TextArea__textarea',
|
||||||
|
scrollbar && 'TextArea__textarea--scrollable',
|
||||||
|
nowrap && 'TextArea__nowrap',
|
||||||
|
])}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
onChange={this.handleOnChange}
|
onChange={this.handleOnChange}
|
||||||
onKeyDown={this.handleKeyDown}
|
onKeyDown={this.handleKeyDown}
|
||||||
@@ -174,8 +221,13 @@ export class TextArea extends Component {
|
|||||||
onInput={this.handleOnInput}
|
onInput={this.handleOnInput}
|
||||||
onFocus={this.handleFocus}
|
onFocus={this.handleFocus}
|
||||||
onBlur={this.handleBlur}
|
onBlur={this.handleBlur}
|
||||||
|
onScroll={this.handleScroll}
|
||||||
maxLength={maxLength}
|
maxLength={maxLength}
|
||||||
|
style={{
|
||||||
|
'color': displayedValue ? 'rgba(0, 0, 0, 0)' : 'inherit',
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
{/* CHOMPedit End */}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Loader } from './common/Loader';
|
import { Loader } from './common/Loader';
|
||||||
import { InputButtons } from './common/InputButtons';
|
import { InputButtons } from './common/InputButtons';
|
||||||
import { useBackend, useLocalState } from '../backend';
|
import { useBackend, useLocalState } from '../backend';
|
||||||
|
import { KEY_ENTER, KEY_ESCAPE } from '../../common/keycodes'; // CHOMPedit
|
||||||
import { Box, Section, Stack, TextArea } from '../components';
|
import { Box, Section, Stack, TextArea } from '../components';
|
||||||
import { Window } from '../layouts';
|
import { Window } from '../layouts';
|
||||||
|
|
||||||
@@ -12,37 +13,51 @@ type TextInputData = {
|
|||||||
placeholder: string;
|
placeholder: string;
|
||||||
timeout: number;
|
timeout: number;
|
||||||
title: string;
|
title: string;
|
||||||
prevent_enter: boolean;
|
// CHOMPedit Start
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sanitizeMultiline = (toSanitize: string) => {
|
||||||
|
return toSanitize.replace(/(\n|\r\n){3,}/, '\n\n');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeAllSkiplines = (toSanitize: string) => {
|
||||||
|
return toSanitize.replace(/[\r\n]+/, '');
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TextInputModal = (props, context) => {
|
export const TextInputModal = (props, context) => {
|
||||||
const { act, data } = useBackend<TextInputData>(context);
|
const { act, data } = useBackend<TextInputData>(context);
|
||||||
const { large_buttons, max_length, message = '', multiline, placeholder, timeout, title, prevent_enter } = data;
|
const { large_buttons, max_length, message = '', multiline, placeholder, timeout, title } = data;
|
||||||
const [input, setInput] = useLocalState<string>(context, 'input', placeholder || '');
|
const [input, setInput] = useLocalState<string>(context, 'input', placeholder || '');
|
||||||
const onType = (value: string) => {
|
const onType = (value: string) => {
|
||||||
if (value === input) {
|
if (value === input) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setInput(value);
|
const sanitizedInput = multiline ? sanitizeMultiline(value) : removeAllSkiplines(value);
|
||||||
|
setInput(sanitizedInput);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const visualMultiline = multiline || input.length >= 30;
|
||||||
// Dynamically changes the window height based on the message.
|
// Dynamically changes the window height based on the message.
|
||||||
const windowHeight =
|
const windowHeight =
|
||||||
135 +
|
135 +
|
||||||
(message.length > 30 ? Math.ceil(message.length / 4) : 0) +
|
(message.length > 30 ? Math.ceil(message.length / 4) : 0) +
|
||||||
(multiline || input.length >= 30 ? 75 : 0) +
|
(visualMultiline ? 75 : 0) +
|
||||||
(message.length && large_buttons ? 5 : 0);
|
(message.length && large_buttons ? 5 : 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Window title={title} width={325} height={windowHeight}>
|
<Window title={title} width={325} height={windowHeight}>
|
||||||
{timeout && <Loader value={timeout} />}
|
{timeout && <Loader value={timeout} />}
|
||||||
<Window.Content
|
<Window.Content
|
||||||
onEscape={() => act('cancel')}
|
onKeyDown={(event) => {
|
||||||
onEnter={(event) => {
|
const keyCode = window.event ? event.which : event.keyCode;
|
||||||
if (!prevent_enter) {
|
if (keyCode === KEY_ENTER && (!visualMultiline || !event.shiftKey)) {
|
||||||
act('submit', { entry: input });
|
act('submit', { entry: input });
|
||||||
event.preventDefault();
|
}
|
||||||
|
if (keyCode === KEY_ESCAPE) {
|
||||||
|
act('cancel');
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
|
{/* CHOMPedit End */}
|
||||||
<Section fill>
|
<Section fill>
|
||||||
<Stack fill vertical>
|
<Stack fill vertical>
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
@@ -64,9 +79,11 @@ export const TextInputModal = (props, context) => {
|
|||||||
/** Gets the user input and invalidates if there's a constraint. */
|
/** Gets the user input and invalidates if there's a constraint. */
|
||||||
const InputArea = (props, context) => {
|
const InputArea = (props, context) => {
|
||||||
const { act, data } = useBackend<TextInputData>(context);
|
const { act, data } = useBackend<TextInputData>(context);
|
||||||
const { max_length, multiline, prevent_enter } = data;
|
const { max_length, multiline } = data; // CHOMPedit
|
||||||
const { input, onType } = props;
|
const { input, onType } = props;
|
||||||
|
|
||||||
|
const visualMultiline = multiline || input.length >= 30; // CHOMPedit
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TextArea
|
<TextArea
|
||||||
autoFocus
|
autoFocus
|
||||||
@@ -75,10 +92,13 @@ const InputArea = (props, context) => {
|
|||||||
maxLength={max_length}
|
maxLength={max_length}
|
||||||
onEscape={() => act('cancel')}
|
onEscape={() => act('cancel')}
|
||||||
onEnter={(event) => {
|
onEnter={(event) => {
|
||||||
if (!prevent_enter) {
|
// CHOMPedit Start
|
||||||
act('submit', { entry: input });
|
if (visualMultiline && event.shiftKey) {
|
||||||
event.preventDefault();
|
return;
|
||||||
}
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
act('submit', { entry: input });
|
||||||
|
// CHOMPedit End
|
||||||
}}
|
}}
|
||||||
onInput={(_, value) => onType(value)}
|
onInput={(_, value) => onType(value)}
|
||||||
placeholder="Type something..."
|
placeholder="Type something..."
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user