mirror of
https://github.com/VOREStation/VOREStation.git
synced 2026-08-23 03:57:48 +01:00
Some input updates
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]}
|
||||
|
||||
+127
-127
File diff suppressed because one or more lines are too long
+90
-90
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user