mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-27 23:27:34 +01:00
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:
@@ -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)
|
||||
|
||||
+79
-93
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user