Some input updates

This commit is contained in:
Selis
2024-02-12 23:33:37 +01:00
parent b5570de419
commit 2fdb69ab5c
15 changed files with 389 additions and 568 deletions
@@ -14,7 +14,6 @@
S["tgui_fancy"] >> pref.tgui_fancy
S["tgui_lock"] >> pref.tgui_lock
S["tgui_input_mode"] >> pref.tgui_input_mode
S["tgui_input_lock"] >> pref.tgui_input_lock
S["tgui_large_buttons"] >> pref.tgui_large_buttons
S["tgui_swapped_buttons"] >> pref.tgui_swapped_buttons
S["obfuscate_key"] >> pref.obfuscate_key
@@ -34,7 +33,6 @@
S["tgui_fancy"] << pref.tgui_fancy
S["tgui_lock"] << pref.tgui_lock
S["tgui_input_mode"] << pref.tgui_input_mode
S["tgui_input_lock"] << pref.tgui_input_lock
S["tgui_large_buttons"] << pref.tgui_large_buttons
S["tgui_swapped_buttons"] << pref.tgui_swapped_buttons
S["obfuscate_key"] << pref.obfuscate_key
@@ -54,7 +52,6 @@
pref.tgui_fancy = sanitize_integer(pref.tgui_fancy, 0, 1, initial(pref.tgui_fancy))
pref.tgui_lock = sanitize_integer(pref.tgui_lock, 0, 1, initial(pref.tgui_lock))
pref.tgui_input_mode = sanitize_integer(pref.tgui_input_mode, 0, 1, initial(pref.tgui_input_mode))
pref.tgui_input_lock = sanitize_integer(pref.tgui_input_lock, 0, 1, initial(pref.tgui_input_lock))
pref.tgui_large_buttons = sanitize_integer(pref.tgui_large_buttons, 0, 1, initial(pref.tgui_large_buttons))
pref.tgui_swapped_buttons = sanitize_integer(pref.tgui_swapped_buttons, 0, 1, initial(pref.tgui_swapped_buttons))
pref.obfuscate_key = sanitize_integer(pref.obfuscate_key, 0, 1, initial(pref.obfuscate_key))
@@ -74,7 +71,6 @@
. += "<b>TGUI Window Mode:</b> <a href='?src=\ref[src];tgui_fancy=1'><b>[(pref.tgui_fancy) ? "Fancy (default)" : "Compatible (slower)"]</b></a><br>"
. += "<b>TGUI Window Placement:</b> <a href='?src=\ref[src];tgui_lock=1'><b>[(pref.tgui_lock) ? "Primary Monitor" : "Free (default)"]</b></a><br>"
. += "<b>TGUI Input Framework:</b> <a href='?src=\ref[src];tgui_input_mode=1'><b>[(pref.tgui_input_mode) ? "Enabled" : "Disabled (default)"]</b></a><br>"
. += "<b>TGUI Input Lock:</b> <a href='?src=\ref[src];tgui_input_lock=1'><b>[(pref.tgui_input_lock) ? "Enabled" : "Disabled (default)"]</b></a><br>"
. += "<b>TGUI Large Buttons:</b> <a href='?src=\ref[src];tgui_large_buttons=1'><b>[(pref.tgui_large_buttons) ? "Enabled (default)" : "Disabled"]</b></a><br>"
. += "<b>TGUI Swapped Buttons:</b> <a href='?src=\ref[src];tgui_swapped_buttons=1'><b>[(pref.tgui_swapped_buttons) ? "Enabled" : "Disabled (default)"]</b></a><br>"
. += "<b>Obfuscate Ckey:</b> <a href='?src=\ref[src];obfuscate_key=1'><b>[(pref.obfuscate_key) ? "Enabled" : "Disabled (default)"]</b></a><br>"
@@ -154,10 +150,6 @@
pref.tgui_input_mode = !pref.tgui_input_mode
return TOPIC_REFRESH
else if(href_list["tgui_input_lock"])
pref.tgui_input_lock = !pref.tgui_input_lock
return TOPIC_REFRESH
else if(href_list["tgui_large_buttons"])
pref.tgui_large_buttons = !pref.tgui_large_buttons
return TOPIC_REFRESH
-1
View File
@@ -29,7 +29,6 @@ var/list/preferences_datums = list()
var/tgui_fancy = TRUE
var/tgui_lock = FALSE
var/tgui_input_mode = FALSE // All the Input Boxes (Text,Number,List,Alert)
var/tgui_input_lock = FALSE
var/tgui_large_buttons = TRUE
var/tgui_swapped_buttons = FALSE
var/obfuscate_key = FALSE
@@ -379,17 +379,6 @@
You will have to reload TGChat and/or reconnect to the server for these changes to take place. \
TGChat message persistence is not guaranteed if you change this again before the start of the next round.")
/client/verb/toggle_tgui_inputlock()
set name = "Toggle TGUI Input Lock"
set category = "Preferences"
set desc = "Toggles whether or not pressing the 'Enter' key in TGUI input sends the message or creates a new line."
prefs.tgui_input_lock = !prefs.tgui_input_lock //There is no preference datum for tgui input lock, nor for any TGUI prefs.
SScharacter_setup.queue_preferences_save(prefs)
to_chat(src, span_notice("You have toggled TGUI input lock: [prefs.tgui_input_lock ? "ON" : "OFF"] \n \
This setting determines whether pressing enter on TGUI input sends the input, or creates a newline."))
/client/verb/toggle_chat_timestamps()
set name = "Toggle Chat Timestamps"
set category = "Preferences"
+7 -3
View File
@@ -14,13 +14,17 @@
if (!user)
user = usr
if(!length(items))
return
return null
if (!istype(user))
if (istype(user, /client))
var/client/client = user
user = client.mob
else
return
return null
if(isnull(user.client))
return null
if(!user.client.prefs.tgui_input_mode)
return input(user, message, title) as null|anything in items
var/datum/tgui_checkbox_input/input = new(user, message, title, items, min_checked, max_checked, timeout, ui_state)
@@ -66,7 +70,7 @@
start_time = world.time
QDEL_IN(src, timeout)
/datum/tgui_checkbox_input/Destroy(force, ...)
/datum/tgui_checkbox_input/Destroy(force)
SStgui.close_uis(src)
state = null
QDEL_NULL(items)
+29 -84
View File
@@ -7,28 +7,31 @@
* * message - The content of the input box, shown in the body of the TGUI window.
* * title - The title of the input box, shown on the top of the TGUI window.
* * items - The options that can be chosen by the user, each string is assigned a button on the UI.
* * default - The option with this value will be selected on first paint of the TGUI window.
* * timeout - The timeout of the input box, after which the input box will close and qdel itself. Set to zero for no timeout.
* * strict_modern - Disabled the preference check of the input box, only allowing the TGUI window to show.
* * default - If an option is already preselected on the UI. Current values, etc.
* * timeout - The timeout of the input box, after which the menu will close and qdel itself. Set to zero for no timeout.
*/
/proc/tgui_input_list(mob/user, message, title = "Select", list/items, default, timeout = 0, strict_modern = FALSE)
if (istext(user))
stack_trace("tgui_alert() received text for user instead of mob")
return
/proc/tgui_input_list(mob/user, message, title = "Select", list/items, default, timeout = 0, strict_modern = FALSE, ui_state = GLOB.tgui_always_state)
if (!user)
user = usr
if(!length(items))
return
return null
if (!istype(user))
if (istype(user, /client))
var/client/client = user
user = client.mob
else
return
return null
if(isnull(user.client))
return null
/// Client does NOT have tgui_input on: Returns regular input
if(!user.client.prefs.tgui_input_mode && !strict_modern)
return input(user, message, title, default) as null|anything in items
var/datum/tgui_list_input/input = new(user, message, title, items, default, timeout)
var/datum/tgui_list_input/input = new(user, message, title, items, default, timeout, ui_state)
if(input.invalid)
qdel(input)
return
input.tgui_interact(user)
input.wait()
if (input)
@@ -48,11 +51,11 @@
var/message
/// The list of items (responses) provided on the TGUI window
var/list/items
/// Items (strings specifically) mapped to the actual value (e.g. a mob or a verb)
/// Buttons (strings specifically) mapped to the actual value (e.g. a mob or a verb)
var/list/items_map
/// The button that the user has pressed, null if no selection has been made
var/choice
/// The default item to be selected
/// The default button to be selected
var/default
/// The time at which the tgui_list_input was created, for displaying timeout progress.
var/start_time
@@ -60,41 +63,42 @@
var/timeout
/// Boolean field describing if the tgui_list_input was closed by the user.
var/closed
/// The TGUI UI state that will be returned in ui_state(). Default: always_state
var/datum/tgui_state/state
/// Whether the tgui list input is invalid or not (i.e. due to all list entries being null)
var/invalid = FALSE
/datum/tgui_list_input/New(mob/user, message, title, list/items, default, timeout)
/datum/tgui_list_input/New(mob/user, message, title, list/items, default, timeout, ui_state)
src.title = title
src.message = message
src.items = list()
src.items_map = list()
src.default = default
src.state = ui_state
var/list/repeat_items = list()
// Gets rid of illegal characters
var/static/regex/whitelistedWords = regex(@{"([^\u0020-\u8000]+)"})
for(var/i in items)
if(isnull(i))
stack_trace("Null in a tgui_input_list() items")
if(!i)
continue
var/string_key = whitelistedWords.Replace("[i]", "")
//avoids duplicated keys E.g: when areas have the same name
string_key = avoid_assoc_duplicate_keys(string_key, repeat_items)
src.items += string_key
src.items_map[string_key] = i
if(length(src.items) == 0)
invalid = TRUE
if (timeout)
src.timeout = timeout
start_time = world.time
QDEL_IN(src, timeout)
/datum/tgui_list_input/Destroy(force, ...)
/datum/tgui_list_input/Destroy(force)
SStgui.close_uis(src)
state = null
QDEL_NULL(items)
. = ..()
return ..()
/**
* Waits for a user's response to the tgui_list_input's prompt before returning. Returns early if
@@ -115,7 +119,7 @@
closed = TRUE
/datum/tgui_list_input/tgui_state(mob/user)
return GLOB.tgui_always_state
return state
/datum/tgui_list_input/tgui_static_data(mob/user)
var/list/data = list()
@@ -146,68 +150,9 @@
SStgui.close_uis(src)
return TRUE
if("cancel")
SStgui.close_uis(src)
closed = TRUE
SStgui.close_uis(src)
return TRUE
/datum/tgui_list_input/proc/set_choice(choice)
src.choice = choice
/**
* Creates an asynchronous TGUI input list window with an associated callback.
*
* This proc should be used to create inputs that invoke a callback with the user's chosen option.
* Arguments:
* * user - The user to show the input box to.
* * message - The content of the input box, shown in the body of the TGUI window.
* * title - The title of the input box, shown on the top of the TGUI window.
* * items - The options that can be chosen by the user, each string is assigned a button on the UI.
* * default - The option with this value will be selected on first paint of the TGUI window.
* * callback - The callback to be invoked when a choice is made.
* * timeout - The timeout of the input box, after which the menu will close and qdel itself. Set to zero for no timeout.
*/
/proc/tgui_input_list_async(mob/user, message, title, list/items, default, datum/callback/callback, timeout = 60 SECONDS)
if (istext(user))
stack_trace("tgui_alert() received text for user instead of mob")
return
if (!user)
user = usr
if(!length(items))
return
if (!istype(user))
if (istype(user, /client))
var/client/client = user
user = client.mob
else
return
var/datum/tgui_list_input/async/input = new(user, message, title, items, default, callback, timeout)
input.tgui_interact(user)
/**
* # async tgui_list_input
*
* An asynchronous version of tgui_list_input to be used with callbacks instead of waiting on user responses.
*/
/datum/tgui_list_input/async
/// The callback to be invoked by the tgui_list_input upon having a choice made.
var/datum/callback/callback
/datum/tgui_list_input/async/New(mob/user, message, title, list/items, default, callback, timeout)
..(user, title, message, items, default, timeout)
src.callback = callback
/datum/tgui_list_input/async/Destroy(force, ...)
QDEL_NULL(callback)
. = ..()
/datum/tgui_list_input/async/tgui_close(mob/user)
. = ..()
qdel(src)
/datum/tgui_list_input/async/set_choice(choice)
. = ..()
if(!isnull(src.choice))
callback?.InvokeAsync(src.choice)
/datum/tgui_list_input/async/wait()
return
-57
View File
@@ -152,60 +152,3 @@
/datum/tgui_input_number/proc/set_entry(entry)
src.entry = entry
/**
* Creates an asynchronous TGUI input num window with an associated callback.
*
* This proc should be used to create inputs that invoke a callback with the user's chosen option.
* Arguments:
* * user - The user to show the input box to.
* * message - The content of the input box, shown in the body of the TGUI window.
* * title - The title of the input box, shown on the top of the TGUI window.
* * default - The default value pre-populated in the input box.
* * callback - The callback to be invoked when a choice is made.
* * timeout - The timeout of the input box, after which the menu will close and qdel itself. Set to zero for no timeout.
* * round_value - whether the inputted number is rounded down into an integer.
*/
/proc/tgui_input_number_async(mob/user, message, title, default, datum/callback/callback, timeout = 60 SECONDS, round_value = FALSE)
if (istext(user))
stack_trace("tgui_input_num_async() received text for user instead of mob")
return
if (!user)
user = usr
if (!istype(user))
if (istype(user, /client))
var/client/client = user
user = client.mob
else
return
var/datum/tgui_input_number/async/input = new(user, message, title, default, callback, timeout, round_value)
input.tgui_interact(user)
/**
* # async tgui_text_input
*
* An asynchronous version of tgui_text_input to be used with callbacks instead of waiting on user responses.
*/
/datum/tgui_input_number/async
/// The callback to be invoked by the tgui_text_input upon having a choice made.
var/datum/callback/callback
/datum/tgui_input_number/async/New(mob/user, message, title, default, callback, timeout, round_value)
..(user, title, message, default, timeout, round_value)
src.callback = callback
/datum/tgui_input_number/async/Destroy(force, ...)
QDEL_NULL(callback)
. = ..()
/datum/tgui_input_number/async/tgui_close(mob/user)
. = ..()
qdel(src)
/datum/tgui_input_number/async/set_entry(entry)
. = ..()
if(!isnull(src.entry))
callback?.InvokeAsync(src.entry)
/datum/tgui_input_number/async/wait()
return
+21 -78
View File
@@ -16,9 +16,6 @@
* * timeout - The timeout of the textbox, after which the modal will close and qdel itself. Set to zero for no timeout.
*/
/proc/tgui_input_text(mob/user, message = "", title = "Text Input", default, max_length = INFINITY, multiline = FALSE, encode = FALSE, timeout = 0, prevent_enter = FALSE)
if (istext(user))
stack_trace("tgui_input_text() received text for user instead of mob")
return
if (!user)
user = usr
if (!istype(user))
@@ -27,6 +24,10 @@
user = client.mob
else
return
if(isnull(user.client))
return null
// Client does NOT have tgui_input on: Returns regular input
if(!user.client.prefs.tgui_input_mode)
if(encode)
@@ -40,11 +41,7 @@
else
return input(user, message, title, default) as text|null
//Client has TGUI input lock on; override whatever prevent_enter was specified beforehand
if(user.client.prefs.tgui_input_lock)
prevent_enter = TRUE
var/datum/tgui_input_text/text_input = new(user, message, title, default, max_length, multiline, encode, timeout, prevent_enter)
var/datum/tgui_input_text/text_input = new(user, message, title, default, max_length, multiline, encode, timeout)
text_input.tgui_interact(user)
text_input.wait()
if (text_input)
@@ -79,9 +76,7 @@
/// The title of the TGUI window
var/title
var/prevent_enter
/datum/tgui_input_text/New(mob/user, message, title, default, max_length, multiline, encode, timeout, prevent_enter)
/datum/tgui_input_text/New(mob/user, message, title, default, max_length, multiline, encode, timeout)
src.default = default
src.encode = encode
src.max_length = max_length
@@ -92,18 +87,17 @@
src.timeout = timeout
start_time = world.time
QDEL_IN(src, timeout)
src.prevent_enter = prevent_enter
/datum/tgui_input_text/Destroy(force, ...)
/datum/tgui_input_text/Destroy(force)
SStgui.close_uis(src)
. = ..()
return ..()
/**
* Waits for a user's response to the tgui_text_input's prompt before returning. Returns early if
* the window was closed by the user.
*/
/datum/tgui_input_text/proc/wait()
while (!entry && !closed)
while (!entry && !closed && !QDELETED(src))
stoplag(1)
/datum/tgui_input_text/tgui_interact(mob/user, datum/tgui/ui)
@@ -128,7 +122,6 @@
data["placeholder"] = default // Default is a reserved keyword
data["swapped_buttons"] = !user.client.prefs.tgui_swapped_buttons
data["title"] = title
data["prevent_enter"] = prevent_enter
return data
/datum/tgui_input_text/tgui_data(mob/user)
@@ -143,77 +136,27 @@
return
switch(action)
if("submit")
if(length(params["entry"]) > max_length)
return
if(encode && (length(html_encode(params["entry"])) > max_length))
to_chat(usr, span_notice("Your message was clipped due to special character usage."))
if(max_length)
if(length(params["entry"]) > max_length)
CRASH("[usr] typed a text string longer than the max length")
if(encode && (length(html_encode(params["entry"])) > max_length))
to_chat(usr, span_notice("Your message was clipped due to special character usage."))
set_entry(params["entry"])
closed = TRUE
SStgui.close_uis(src)
return TRUE
if("cancel")
SStgui.close_uis(src)
closed = TRUE
SStgui.close_uis(src)
return TRUE
/**
* Sets the return value for the tgui text proc.
* If html encoding is enabled, the text will be encoded.
* This can sometimes result in a string that is longer than the max length.
* If the string is longer than the max length, it will be clipped.
*/
/datum/tgui_input_text/proc/set_entry(entry)
if(!isnull(entry))
var/converted_entry = encode ? html_encode(entry) : entry
//converted_entry = readd_quotes(converted_entry)
src.entry = trim(converted_entry, max_length)
/**
* Creates an asynchronous TGUI input text window with an associated callback.
*
* This proc should be used to create inputs that invoke a callback with the user's chosen option.
* Arguments:
* * user - The user to show the input box to.
* * message - The content of the input box, shown in the body of the TGUI window.
* * title - The title of the input box, shown on the top of the TGUI window.
* * default - The default value pre-populated in the input box.
* * callback - The callback to be invoked when a choice is made.
* * timeout - The timeout of the input box, after which the menu will close and qdel itself. Set to zero for no timeout.
*/
/proc/tgui_input_text_async(mob/user, message, title, default, datum/callback/callback, max_length, multiline, encode, timeout = 60 SECONDS)
if (istext(user))
stack_trace("tgui_input_text_async() received text for user instead of mob")
return
if (!user)
user = usr
if (!istype(user))
if (istype(user, /client))
var/client/client = user
user = client.mob
else
return
var/datum/tgui_input_text/async/input = new(user, message, title, default, callback, max_length, multiline, encode, timeout)
input.tgui_interact(user)
/**
* # async tgui_text_input
*
* An asynchronous version of tgui_text_input to be used with callbacks instead of waiting on user responses.
*/
/datum/tgui_input_text/async
/// The callback to be invoked by the tgui_text_input upon having a choice made.
var/datum/callback/callback
/datum/tgui_input_text/async/New(mob/user, message, title, default, callback, max_length, multiline, encode, timeout)
..(user, title, message, default, max_length, multiline, encode, timeout)
src.callback = callback
/datum/tgui_input_text/async/Destroy(force, ...)
QDEL_NULL(callback)
. = ..()
/datum/tgui_input_text/async/tgui_close(mob/user)
. = ..()
qdel(src)
/datum/tgui_input_text/async/set_entry(entry)
. = ..()
if(!isnull(src.entry))
callback?.InvokeAsync(src.entry)
/datum/tgui_input_text/async/wait()
return
+63 -78
View File
@@ -5,72 +5,56 @@
*/
import { BooleanLike, classes } from 'common/react';
import { createElement, ReactNode } from 'react';
import {
createElement,
KeyboardEventHandler,
MouseEventHandler,
ReactNode,
UIEventHandler,
} from 'react';
import { CSS_COLORS } from '../constants';
import { logger } from '../logging';
export type BoxProps = {
[key: string]: any;
as?: string;
className?: string | BooleanLike;
children?: ReactNode;
position?: string | BooleanLike;
overflow?: string | BooleanLike;
overflowX?: string | BooleanLike;
overflowY?: string | BooleanLike;
top?: string | BooleanLike;
bottom?: string | BooleanLike;
left?: string | BooleanLike;
right?: string | BooleanLike;
width?: string | BooleanLike;
minWidth?: string | BooleanLike;
maxWidth?: string | BooleanLike;
height?: string | BooleanLike;
minHeight?: string | BooleanLike;
maxHeight?: string | BooleanLike;
fontSize?: string | BooleanLike;
fontFamily?: string;
lineHeight?: string | BooleanLike;
opacity?: number;
textAlign?: string | BooleanLike;
verticalAlign?: string | BooleanLike;
textTransform?: string | BooleanLike; // VOREStation Addition
inline?: BooleanLike;
bold?: BooleanLike;
italic?: BooleanLike;
nowrap?: BooleanLike;
preserveWhitespace?: BooleanLike;
m?: string | BooleanLike;
mx?: string | BooleanLike;
my?: string | BooleanLike;
mt?: string | BooleanLike;
mb?: string | BooleanLike;
ml?: string | BooleanLike;
mr?: string | BooleanLike;
p?: string | BooleanLike;
px?: string | BooleanLike;
py?: string | BooleanLike;
pt?: string | BooleanLike;
pb?: string | BooleanLike;
pl?: string | BooleanLike;
pr?: string | BooleanLike;
color?: string | BooleanLike;
textColor?: string | BooleanLike;
backgroundColor?: string | BooleanLike;
// VOREStation Addition Start
// Flex props
flexGrow?: string | BooleanLike;
flexWrap?: string | BooleanLike;
flexBasis?: string | BooleanLike;
flex?: string | BooleanLike;
// VOREStation Addition End
fillPositionedParent?: boolean;
type BooleanProps = Partial<Record<keyof typeof booleanStyleMap, boolean>>;
type StringProps = Partial<
Record<keyof typeof stringStyleMap, string | BooleanLike>
>;
export type EventHandlers = Partial<{
onClick: MouseEventHandler<HTMLDivElement>;
onContextMenu: MouseEventHandler<HTMLDivElement>;
onDoubleClick: MouseEventHandler<HTMLDivElement>;
onKeyDown: KeyboardEventHandler<HTMLDivElement>;
onKeyUp: KeyboardEventHandler<HTMLDivElement>;
onMouseDown: MouseEventHandler<HTMLDivElement>;
onMouseMove: MouseEventHandler<HTMLDivElement>;
onMouseOver: MouseEventHandler<HTMLDivElement>;
onMouseUp: MouseEventHandler<HTMLDivElement>;
onScroll: UIEventHandler<HTMLDivElement>;
}>;
export type BoxProps = Partial<{
as: string;
children: ReactNode;
className: string | BooleanLike;
style: Partial<CSSStyleDeclaration>;
}> &
BooleanProps &
StringProps &
EventHandlers;
// Don't you dare put this elsewhere
type DangerDoNotUse = {
dangerouslySetInnerHTML?: {
__html: any;
};
};
/**
* Coverts our rem-like spacing unit into a CSS unit.
*/
export const unit = (value: unknown): string | undefined => {
export const unit = (value: unknown) => {
if (typeof value === 'string') {
// Transparently convert pixels into rem units
if (value.endsWith('px')) {
@@ -86,7 +70,7 @@ export const unit = (value: unknown): string | undefined => {
/**
* Same as `unit`, but half the size for integers numbers.
*/
export const halfUnit = (value: unknown): string | undefined => {
export const halfUnit = (value: unknown) => {
if (typeof value === 'string') {
return unit(value);
}
@@ -98,7 +82,7 @@ export const halfUnit = (value: unknown): string | undefined => {
const isColorCode = (str: unknown) => !isColorClass(str);
const isColorClass = (str: unknown): boolean => {
return typeof str === 'string' && CSS_COLORS.includes(str);
return typeof str === 'string' && CSS_COLORS.includes(str as any);
};
const mapRawPropTo = (attrName) => (style, value) => {
@@ -135,9 +119,12 @@ const mapColorPropTo = (attrName) => (style, value) => {
// String / number props
const stringStyleMap = {
align: mapRawPropTo('textAlign'),
bottom: mapUnitPropTo('bottom', unit),
colSpan: mapRawPropTo('colSpan'),
fontFamily: mapRawPropTo('fontFamily'),
fontSize: mapUnitPropTo('fontSize', unit),
fontWeight: mapRawPropTo('fontWeight'),
height: mapUnitPropTo('height', unit),
left: mapUnitPropTo('left', unit),
maxHeight: mapUnitPropTo('maxHeight', unit),
@@ -162,22 +149,19 @@ const stringStyleMap = {
style['lineHeight'] = unit(value);
}
},
textTransform: mapRawPropTo('text-transform'), // VOREStation Addition
// Margins
// Margin
m: mapDirectionalUnitPropTo('margin', halfUnit, [
'Top',
'Bottom',
'Left',
'Right',
]),
mx: mapDirectionalUnitPropTo('margin', halfUnit, ['Left', 'Right']),
my: mapDirectionalUnitPropTo('margin', halfUnit, ['Top', 'Bottom']),
mt: mapUnitPropTo('marginTop', halfUnit),
mb: mapUnitPropTo('marginBottom', halfUnit),
ml: mapUnitPropTo('marginLeft', halfUnit),
mr: mapUnitPropTo('marginRight', halfUnit),
mt: mapUnitPropTo('marginTop', halfUnit),
mx: mapDirectionalUnitPropTo('margin', halfUnit, ['Left', 'Right']),
my: mapDirectionalUnitPropTo('margin', halfUnit, ['Top', 'Bottom']),
// Padding
p: mapDirectionalUnitPropTo('padding', halfUnit, [
'Top',
@@ -185,12 +169,12 @@ const stringStyleMap = {
'Left',
'Right',
]),
px: mapDirectionalUnitPropTo('padding', halfUnit, ['Left', 'Right']),
py: mapDirectionalUnitPropTo('padding', halfUnit, ['Top', 'Bottom']),
pt: mapUnitPropTo('paddingTop', halfUnit),
pb: mapUnitPropTo('paddingBottom', halfUnit),
pl: mapUnitPropTo('paddingLeft', halfUnit),
pr: mapUnitPropTo('paddingRight', halfUnit),
pt: mapUnitPropTo('paddingTop', halfUnit),
px: mapDirectionalUnitPropTo('padding', halfUnit, ['Left', 'Right']),
py: mapDirectionalUnitPropTo('padding', halfUnit, ['Top', 'Bottom']),
// Color props
color: mapColorPropTo('color'),
textColor: mapColorPropTo('color'),
@@ -203,8 +187,11 @@ const stringStyleMap = {
flexBasis: mapRawPropTo('flex-basis'),
flex: mapRawPropTo('flex'),
// VOREStation Addition End
} as const;
// Utility props
// Boolean props
const booleanStyleMap = {
bold: mapBooleanPropTo('fontWeight', 'bold'),
fillPositionedParent: (style, value) => {
if (value) {
style['position'] = 'absolute';
@@ -214,11 +201,6 @@ const stringStyleMap = {
style['right'] = 0;
}
},
} as const;
// Boolean props
const booleanStyleMap = {
bold: mapBooleanPropTo('fontWeight', 'bold'),
inline: mapBooleanPropTo('display', 'inline-block'),
italic: mapBooleanPropTo('fontStyle', 'italic'),
nowrap: mapBooleanPropTo('whiteSpace', 'nowrap'),
@@ -262,7 +244,7 @@ export const computeBoxClassName = (props: BoxProps) => {
]);
};
export const Box = (props: BoxProps) => {
export const Box = (props: BoxProps & DangerDoNotUse) => {
const { as = 'div', className, children, ...rest } = props;
// Compute class name and styles
@@ -270,8 +252,11 @@ export const Box = (props: BoxProps) => {
? `${className} ${computeBoxClassName(rest)}`
: computeBoxClassName(rest);
const computedProps = computeBoxProps(rest);
if (as === 'img') {
computedProps.style['-ms-interpolation-mode'] = 'nearest-neighbor';
logger.error(
'Box component cannot be used as an image. Use Image component instead.',
);
}
// Render the component
@@ -373,7 +373,7 @@ const BodyScannerMainOrgansExternal = (props) => {
<Table.Cell textAlign="right">Injuries</Table.Cell>
</Table.Row>
{props.organs.map((o, i) => (
<Table.Row key={i} textTransform="capitalize">
<Table.Row key={i} style={{ textTransform: 'capitalize' }}>
<Table.Cell width="33%">{o.name}</Table.Cell>
<Table.Cell textAlign="center" q>
<ProgressBar
@@ -450,7 +450,7 @@ const BodyScannerMainOrgansInternal = (props) => {
<Table.Cell textAlign="right">Injuries</Table.Cell>
</Table.Row>
{props.organs.map((o, i) => (
<Table.Row key={i} textTransform="capitalize">
<Table.Row key={i} style={{ textTransform: 'capitalize' }}>
<Table.Cell width="33%">{o.name}</Table.Cell>
<Table.Cell textAlign="center">
<ProgressBar
+10 -10
View File
@@ -80,7 +80,7 @@ export const Communicator = (props) => {
height="88%"
mb={1}
style={{
'overflow-y': 'auto',
overflowY: 'auto',
}}
>
{TabToTemplate[currentTab] || <TemplateError />}
@@ -154,7 +154,7 @@ const VideoComm = (props) => {
position: 'absolute',
right: '5px',
bottom: '50px',
'z-index': 1,
zIndex: '1',
}}
>
<Section p={0} m={0}>
@@ -843,8 +843,8 @@ const MessagingThreadTab = (props) => {
<Box
inline
style={{
'white-space': 'nowrap',
'overflow-x': 'hidden',
whiteSpace: 'nowrap',
overflowX: 'hidden',
}}
width="90%"
>
@@ -870,7 +870,7 @@ const MessagingThreadTab = (props) => {
<Section
style={{
height: '95%',
'overflow-y': 'auto',
overflowY: 'auto',
}}
>
{imList.map(
@@ -905,8 +905,8 @@ const MessagingThreadTab = (props) => {
<Box
inline
style={{
'white-space': 'nowrap',
'overflow-x': 'hidden',
whiteSpace: 'nowrap',
overflowX: 'hidden',
}}
width="100%"
>
@@ -932,7 +932,7 @@ const MessagingThreadTab = (props) => {
<Section
style={{
height: '95%',
'overflow-y': 'auto',
overflowY: 'auto',
}}
>
{imList.map(
@@ -1126,8 +1126,8 @@ const NoteTab = (props) => {
width="100%"
height="100%"
style={{
'word-break': 'break-all',
'overflow-y': 'auto',
wordBreak: 'break-all',
overflowY: 'auto',
}}
>
{note}
@@ -9,7 +9,7 @@ import {
KEY_Z,
} from '../../common/keycodes';
import { useBackend } from '../backend';
import { Button, Input, Section, Stack } from '../components';
import { Autofocus, Button, Input, Section, Stack } from '../components';
import { Window } from '../layouts';
import { InputButtons } from './common/InputButtons';
import { Loader } from './common/Loader';
@@ -188,16 +188,16 @@ const ListDisplay = (props) => {
props;
return (
<Section fill scrollable tabIndex={0}>
<Section fill scrollable>
<Autofocus />
{filteredItems.map((item, index) => {
return (
<Button
color="transparent"
fluid
id={index}
key={index}
onClick={() => onClick(index)}
onDblClick={(event) => {
onDoubleClick={(event) => {
event.preventDefault();
act('submit', { entry: filteredItems[selected] });
}}
@@ -1,4 +1,5 @@
import { useState } from 'react';
import { KEY } from 'common/keys';
import { KeyboardEvent, useState } from 'react';
import { useBackend } from '../backend';
import { Box, Section, Stack, TextArea } from '../components';
@@ -14,7 +15,14 @@ type TextInputData = {
placeholder: string;
timeout: number;
title: string;
prevent_enter: boolean;
};
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) => {
@@ -27,31 +35,40 @@ export const TextInputModal = (props) => {
placeholder = '',
timeout,
title,
prevent_enter,
} = data;
const [input, setInput] = useState(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) => {
if (
event.key === KEY.Enter &&
(!visualMultiline || !event.shiftKey)
) {
act('submit', { entry: input });
event.preventDefault();
}
if (event.key === KEY.Escape) {
act('cancel');
}
}}
>
@@ -82,9 +99,11 @@ const InputArea = (props: {
onType: (value: string) => void;
}) => {
const { act, data } = useBackend<TextInputData>();
const { max_length, multiline, prevent_enter } = data;
const { max_length, multiline } = data;
const { input, onType } = props;
const visualMultiline = multiline || input.length >= 30;
return (
<TextArea
autoFocus
@@ -92,12 +111,14 @@ const InputArea = (props: {
height={multiline || input.length >= 30 ? '100%' : '1.8rem'}
maxLength={max_length}
onEscape={() => act('cancel')}
onEnter={(event) => {
if (!prevent_enter) {
act('submit', { entry: input });
event.preventDefault();
onEnter={(event: KeyboardEvent<HTMLTextAreaElement>) => {
if (visualMultiline && event.shiftKey) {
return;
}
event.preventDefault();
act('submit', { entry: input });
}}
onChange={(_, value) => onType(value)}
onInput={(_, value) => onType(value)}
placeholder="Type something..."
value={input}
@@ -65,7 +65,7 @@ export const TraitDescription = (props) => {
const { descriptions, categories, tutorials } = data;
return (
<Section StackWrap>
<Section>
<b>Name:</b> {name}
<br />
<b>Category:</b> {categories[name]}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long