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