Converts ListInputModal to actually be a Modal | Adds ListInputWindow which uses it (#82792)

## About The Pull Request

If we say something is a Modal it should actually be a Modal

## Why It's Good For The Game

You can now use this system in other windows if you want.
Fixed the misnomer.

---------

Co-authored-by: Jeremiah <42397676+jlsnow301@users.noreply.github.com>
This commit is contained in:
Zephyr
2024-04-22 18:02:24 -07:00
committed by GitHub
co-authored by Jeremiah
parent 176253ce6a
commit b218130a2c
4 changed files with 144 additions and 97 deletions
+1 -1
View File
@@ -111,7 +111,7 @@
/datum/tgui_list_input/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "ListInputModal")
ui = new(user, src, "ListInputWindow")
ui.open()
/datum/tgui_list_input/ui_close(mob/user)
@@ -7,35 +7,26 @@ import {
KEY_ESCAPE,
KEY_UP,
KEY_Z,
} from '../../common/keycodes';
import { useBackend } from '../backend';
import { Autofocus, Button, Input, Section, Stack } from '../components';
import { Window } from '../layouts';
import { InputButtons } from './common/InputButtons';
import { Loader } from './common/Loader';
} from '../../../common/keycodes';
import { useBackend } from '../../backend';
import { Autofocus, Button, Input, Section, Stack } from '../../components';
import { InputButtons } from '../common/InputButtons';
type ListInputData = {
init_value: string;
type ListInputModalProps = {
items: string[];
large_buttons: boolean;
default_item: string;
message: string;
timeout: number;
title: string;
on_selected: (entry: string) => void;
on_cancel: () => void;
};
export const ListInputModal = (props) => {
const { act, data } = useBackend<ListInputData>();
const {
items = [],
message = '',
init_value,
large_buttons,
timeout,
title,
} = data;
const [selected, setSelected] = useState(items.indexOf(init_value));
export const ListInputModal = (props: ListInputModalProps) => {
const { items = [], default_item, message, on_selected, on_cancel } = props;
const [selected, setSelected] = useState(items.indexOf(default_item));
const [searchBarVisible, setSearchBarVisible] = useState(items.length > 9);
const [searchQuery, setSearchQuery] = useState('');
// User presses up or down on keyboard
// Simulates clicking an item
const onArrowKey = (key: number) => {
@@ -99,82 +90,77 @@ export const ListInputModal = (props) => {
const filteredItems = items.filter((item) =>
item?.toLowerCase().includes(searchQuery.toLowerCase()),
);
// Dynamically changes the window height based on the message.
const windowHeight =
325 + Math.ceil(message.length / 3) + (large_buttons ? 5 : 0);
// Grabs the cursor when no search bar is visible.
if (!searchBarVisible) {
setTimeout(() => document!.getElementById(selected.toString())?.focus(), 1);
}
return (
<Window title={title} width={325} height={windowHeight}>
{timeout && <Loader value={timeout} />}
<Window.Content
onKeyDown={(event) => {
const keyCode = window.event ? event.which : event.keyCode;
if (keyCode === KEY_DOWN || keyCode === KEY_UP) {
event.preventDefault();
onArrowKey(keyCode);
<Section
onKeyDown={(event) => {
const keyCode = window.event ? event.which : event.keyCode;
if (keyCode === KEY_DOWN || keyCode === KEY_UP) {
event.preventDefault();
onArrowKey(keyCode);
}
if (keyCode === KEY_ENTER) {
event.preventDefault();
on_selected(filteredItems[selected]);
}
if (!searchBarVisible && keyCode >= KEY_A && keyCode <= KEY_Z) {
event.preventDefault();
onLetterSearch(keyCode);
}
if (keyCode === KEY_ESCAPE) {
event.preventDefault();
on_cancel();
}
}}
buttons={
<Button
compact
icon={searchBarVisible ? 'search' : 'font'}
selected
tooltip={
searchBarVisible
? 'Search Mode. Type to search or use arrow keys to select manually.'
: 'Hotkey Mode. Type a letter to jump to the first match. Enter to select.'
}
if (keyCode === KEY_ENTER) {
event.preventDefault();
act('submit', { entry: filteredItems[selected] });
}
if (!searchBarVisible && keyCode >= KEY_A && keyCode <= KEY_Z) {
event.preventDefault();
onLetterSearch(keyCode);
}
if (keyCode === KEY_ESCAPE) {
event.preventDefault();
act('cancel');
}
}}
>
<Section
buttons={
<Button
compact
icon={searchBarVisible ? 'search' : 'font'}
selected
tooltip={
searchBarVisible
? 'Search Mode. Type to search or use arrow keys to select manually.'
: 'Hotkey Mode. Type a letter to jump to the first match. Enter to select.'
}
tooltipPosition="left"
onClick={() => onSearchBarToggle()}
/>
}
className="ListInput__Section"
fill
title={message}
>
<Stack fill vertical>
<Stack.Item grow>
<ListDisplay
filteredItems={filteredItems}
onClick={onClick}
onFocusSearch={onFocusSearch}
searchBarVisible={searchBarVisible}
selected={selected}
/>
</Stack.Item>
{searchBarVisible && (
<SearchBar
filteredItems={filteredItems}
onSearch={onSearch}
searchQuery={searchQuery}
selected={selected}
/>
)}
<Stack.Item>
<InputButtons input={filteredItems[selected]} />
</Stack.Item>
</Stack>
</Section>
</Window.Content>
</Window>
tooltipPosition="left"
onClick={() => onSearchBarToggle()}
/>
}
className="ListInput__Section"
fill
title={message}
>
<Stack fill vertical>
<Stack.Item grow>
<ListDisplay
filteredItems={filteredItems}
onClick={onClick}
onFocusSearch={onFocusSearch}
searchBarVisible={searchBarVisible}
selected={selected}
/>
</Stack.Item>
{searchBarVisible && (
<SearchBar
filteredItems={filteredItems}
onSearch={onSearch}
searchQuery={searchQuery}
selected={selected}
/>
)}
<Stack.Item>
<InputButtons
input={filteredItems[selected]}
on_submit={() => on_selected(filteredItems[selected])}
on_cancel={on_cancel}
/>
</Stack.Item>
</Stack>
</Section>
);
};
@@ -183,7 +169,7 @@ export const ListInputModal = (props) => {
* If a search query is provided, filters the items.
*/
const ListDisplay = (props) => {
const { act } = useBackend<ListInputData>();
const { act } = useBackend();
const { filteredItems, onClick, onFocusSearch, searchBarVisible, selected } =
props;
@@ -227,7 +213,7 @@ const ListDisplay = (props) => {
* Closing the bar defaults input to an empty string.
*/
const SearchBar = (props) => {
const { act } = useBackend<ListInputData>();
const { act } = useBackend();
const { filteredItems, onSearch, searchQuery, selected } = props;
return (
@@ -0,0 +1,44 @@
import { useBackend } from '../../backend';
import { Window } from '../../layouts';
import { Loader } from '../common/Loader';
import { ListInputModal } from './ListInputModal';
type ListInputData = {
init_value: string;
items: string[];
large_buttons: boolean;
message: string;
timeout: number;
title: string;
};
export const ListInputWindow = () => {
const { act, data } = useBackend<ListInputData>();
const {
items = [],
message = '',
init_value,
large_buttons,
timeout,
title,
} = data;
// Dynamically changes the window height based on the message.
const windowHeight =
325 + Math.ceil(message.length / 3) + (large_buttons ? 5 : 0);
return (
<Window title={title} width={325} height={windowHeight}>
{timeout && <Loader value={timeout} />}
<Window.Content>
<ListInputModal
items={items}
default_item={init_value}
message={message}
on_selected={(entry) => act('submit', { entry })}
on_cancel={() => act('cancel')}
/>
</Window.Content>
</Window>
);
};
@@ -8,19 +8,36 @@ type InputButtonsData = {
type InputButtonsProps = {
input: string | number | string[];
on_submit?: () => void;
on_cancel?: () => void;
message?: string;
};
export const InputButtons = (props: InputButtonsProps) => {
const { act, data } = useBackend<InputButtonsData>();
const { large_buttons, swapped_buttons } = data;
const { input, message } = props;
const { input, message, on_submit, on_cancel } = props;
let on_submit_actual = on_submit;
if (!on_submit_actual) {
on_submit_actual = () => {
act('submit', { entry: input });
};
}
let on_cancel_actual = on_cancel;
if (!on_cancel_actual) {
on_cancel_actual = () => {
act('cancel');
};
}
const submitButton = (
<Button
color="good"
fluid={!!large_buttons}
height={!!large_buttons && 2}
onClick={() => act('submit', { entry: input })}
onClick={on_submit_actual}
m={0.5}
pl={2}
pr={2}
@@ -37,7 +54,7 @@ export const InputButtons = (props: InputButtonsProps) => {
color="bad"
fluid={!!large_buttons}
height={!!large_buttons && 2}
onClick={() => act('cancel')}
onClick={on_cancel_actual}
m={0.5}
pl={2}
pr={2}