[MIRROR] up ports a bunch of TGS commands (#11173)

Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
CHOMPStation2StaffMirrorBot
2025-07-13 05:34:17 -07:00
committed by GitHub
parent 42b68b856b
commit 0160eb3e68
29 changed files with 639 additions and 366 deletions

View File

@@ -46,7 +46,7 @@
#define R_DEFAULT R_NONE
#define R_EVERYTHING (1<<17)-1 //the sum of all other rank permissions, used for +EVERYTHING
#define R_HOLDER ((R_EVERYTHING) & (~R_MENTOR))
#define R_HOLDER ((R_EVERYTHING) & (~(R_MENTOR | R_STEALTH)))
#define SMITE_BREAKLEGS "Break Legs"
#define SMITE_BLUESPACEARTILLERY "Bluespace Artillery"

View File

@@ -432,7 +432,7 @@ so as to remain in compliance with the most up-to-date laws."
name = "Admin Chat Request"
desc = "A Administrator would like to chat with you. \
Click here to begin."
icon_state = "32x32"
icon_state = "nt_logo"
/obj/screen/alert/open_ticket/Click()
if(!usr || !usr.client) return

View File

@@ -744,6 +744,45 @@
/datum/config_entry/flag/discord_ahelps_disabled
default = FALSE
/// So, nodebot is a supplement to the TGS discord bot pretty much. For things likes faxes and the manifest it's very helpful because it's able to render html into an image and post it.
/datum/config_entry/flag/nodebot_enabled
default = FALSE
/// We need to print the manifest to this location so nodebot can render it to chat.
/// NOTE: TO BE REPLACED BY BETTER CODE FOR FETCHING MANIFEST
/datum/config_entry/string/nodebot_location
/datum/config_entry/string/fax_channel_tag
/datum/config_entry/string/role_request_channel_tag
/// These are for the role request TGS discord bot. Role IDs to ping.
/datum/config_entry/string/role_request_id_command
/datum/config_entry/string/role_request_id_security
/datum/config_entry/string/role_request_id_engineering
/datum/config_entry/string/role_request_id_medical
/datum/config_entry/string/role_request_id_research
/datum/config_entry/string/role_request_id_supply
/datum/config_entry/string/role_request_id_service
/datum/config_entry/string/role_request_id_expedition
/datum/config_entry/string/role_request_id_silicon
/// Only turn this on if you're not using the nodebot.
/datum/config_entry/flag/discord_faxes_autoprint
default = FALSE
/// Turn this off if you don't want the TGS bot sending you messages whenever a fax is sent to central.
/datum/config_entry/flag/discord_faxes_disabled
default = FALSE
/// Turn this on if you want all admin-PMs to go to be sent to discord, and not only the first message of a ticket.
/datum/config_entry/flag/discord_ahelps_all
default = FALSE

View File

@@ -61,5 +61,5 @@ SUBSYSTEM_DEF(inactivity)
return ..()
/datum/controller/subsystem/inactivity/proc/can_kick(var/client/C)
if(C.holder) return FALSE //VOREStation Add - Don't kick admins.
if(check_rights_for(C, R_HOLDER|R_MENTOR)) return FALSE //VOREStation Add - Don't kick admins.
return TRUE

View File

@@ -199,8 +199,11 @@ var/list/runechat_image_cache = list()
// Translate any existing messages upwards, apply exponential decay factors to timers
message_loc = target.runechat_holder(src)
if(!owned_by)
qdel(src)
return
RegisterSignal(message_loc, COMSIG_PARENT_QDELETING, PROC_REF(qdel_self))
if(owned_by && owned_by.seen_messages)
if(owned_by.seen_messages)
var/idx = 1
var/combined_height = approx_lines
for(var/datum/chatmessage/m as anything in owned_by.seen_messages[message_loc])

View File

@@ -17,7 +17,7 @@
var/static/list/locked = list()
/datum/datacore/proc/get_manifest(monochrome, OOC,var/snowflake = FALSE) //CHOMPStation Edit
/datum/datacore/proc/get_manifest(monochrome, OOC,var/snowflake = FALSE)
var/list/heads = new()
var/list/sec = new()
var/list/eng = new()

View File

@@ -116,5 +116,5 @@
peeb += dat
peeb += span_notice("For more detailed information on the patient's condition, utilize a body scanner at the closest medical bay.")
user.show_message(peeb, 1)
user.show_message(peeb, 1)
//CHOMPedit end.

View File

@@ -867,7 +867,7 @@
message_admins(span_blue("[usr.client.ckey] has banned [M.ckey].\nReason: [reason]\nThis will be removed in [mins] minutes."))
var/datum/ticket/T = M.client ? M.client.current_ticket : null
if(T)
T.Resolve()
T.Resolve(usr)
qdel(M.client)
// CHOMPedit End
//qdel(M) // See no reason why to delete mob. Important stuff can be lost. And ban can be lifted before round ends.
@@ -896,7 +896,7 @@
DB_ban_record(BANTYPE_PERMA, M, -1, reason)
var/datum/ticket/T = M.client ? M.client.current_ticket : null
if(T)
T.Resolve()
T.Resolve(usr)
qdel(M.client)
//qdel(M)
if("Cancel")

View File

@@ -213,8 +213,11 @@
if(!istype(target))
return
var/real_user = user ? user : usr
var/user_name = real_user ? key_name(real_user) : "Remotely (Discord)"
to_chat(target,"You've been hit by bluespace artillery!")
log_and_message_admins("has been hit by Bluespace Artillery fired by [key_name(user ? user : usr)]", target)
log_and_message_admins("has been hit by Bluespace Artillery fired by [user_name]", target)
target.setMoveCooldown(2 SECONDS)

View File

@@ -213,7 +213,7 @@ var/list/gear_datums = list()
return TOPIC_REFRESH_UPDATE_PREVIEW
if("clear_loadout")
active_gear_list.Cut()
active_gear_list?.Cut()
return TOPIC_REFRESH_UPDATE_PREVIEW
if("copy_loadout")

View File

@@ -153,9 +153,12 @@ vorestation edit end */
return
to_chat(user, span_notice("The crate is locked with a Deca-code lock."))
var/input = tgui_input_text(user, "Enter [codelen] digits. All digits must be unique.", "Deca-Code Lock", "")
var/input = tgui_input_text(user, "Enter [codelen] digits. All digits must be unique.", "Deca-Code Lock", "", codelen)
if(!Adjacent(user))
return
if(input == null)
to_chat(user, span_notice("You leave the crate alone."))
return
var/list/sanitised = list()
var/sanitycheck = 1
for(var/i=1,i<=length(input),i++) //put the guess into a list
@@ -165,9 +168,11 @@ vorestation edit end */
if(sanitised[i] == sanitised[j])
sanitycheck = null //if a digit is repeated, reject the input
if(input == null || sanitycheck == null || length(input) != codelen)
to_chat(user, span_notice("You leave the crate alone."))
else if(check_input(input))
if(sanitycheck == null || length(input) != codelen)
to_chat(user, span_notice("You aren't sure this input is a good idea."))
return
if(check_input(input))
to_chat(user, span_notice("The crate unlocks!"))
playsound(src, 'sound/machines/lockreset.ogg', 50, 1)
set_locked(0)

View File

@@ -6,7 +6,8 @@
update_client_z(null)
log_access_out(src)
unset_machine()
if(GLOB.admin_datums[src.ckey] && check_rights(R_HOLDER, FALSE))
var/datum/admins/is_admin = GLOB.admin_datums[src.ckey]
if(is_admin && is_admin.check_for_rights(R_HOLDER))
message_admins("Staff logout: [key_name(src)]") // Staff logout notice displays no matter what
if (ticker && ticker.current_state == GAME_STATE_PLAYING) //Only report this stuff if we are currently playing.
var/admins_number = GLOB.admins.len

View File

@@ -1,130 +0,0 @@
/datum/tgs_chat_command/sharktest
name = "flip"
help_text = "babies first TGS command"
admin_only = FALSE
/datum/tgs_chat_command/sharktest/Run(datum/tgs_chat_user/sender, params)
var/x
if(params != "tails" && params != "heads")
return "```You need to guess!\n either heads or tails!```"
if(prob(50))
if(params == "tails")
x = "correct"
else
x = "wrong"
return "```Tails. [x]!```"
if(params == "tails")
x = "wrong"
else
x = "correct"
return "```Heads. [x]!```"
/datum/tgs_chat_command/sharktest/alias
name = "coin"
/datum/tgs_chat_command/manifest
name = "manifest"
help_text = "Shows the current crew manifest"
admin_only = FALSE
/proc/ManifestToHtml()
var/html = ""
if(GLOB.data_core)
html = GLOB.data_core.get_manifest(FALSE,TRUE,snowflake = TRUE)
else
html = span_bold("ERROR: NO DATACORE") //Could make the error more fancy later
rustg_file_write(html,"[CONFIG_GET(string/nodebot_location)]\\html.html")
/datum/tgs_chat_command/manifest/Run(datum/tgs_chat_user/sender, params)
if(CONFIG_GET(flag/nodebot_enabled))
ManifestToHtml()
return "http://manifest.chompstation13.net/"
else
var/outp = "Crew Manifest:"
var/list/total = list()
if(GLOB.data_core)
GLOB.data_core.get_manifest_list()
for(var/list/item in GLOB.PDA_Manifest)
outp += "\n__**[item["cat"]]:**__"
for(var/list/person in item["elems"])
total |= person
outp += "\n[person["name"]] -:- [person["rank"]]"
return "**Total crew members:** [total.len]\n" + outp
/datum/tgs_chat_command/discordping
name = "discordping"
help_text = "Pings the discord associated with the associated ckey"
admin_only = TRUE
/datum/tgs_chat_command/discordping/Run(datum/tgs_chat_user/sender, params)
var/key_to_find = "[ckey(params)]"
// They didn't provide anything worth looking up.
if(!length(key_to_find))
return "[sender.friendly_name], you need to provide a Byond username at the end of the command. It can be in 'key' format (with spaces and characters) or 'ckey' format (without spaces or special characters)."
var/datum/db_query/query = SSdbcore.NewQuery("SELECT discord_id FROM erro_player WHERE ckey = :t_ckey",list("t_ckey" = key_to_find))
query.Execute()
if(!query.NextRow())
qdel(query)
return "[sender.friendly_name], the server's database is either not responding or there's no such ckey in the database."
if(!query.item[1])
qdel(query)
return "[sender.friendly_name], [key_to_find] is in the database, but has no discord ID associated with them."
var/discord_id = query.item[1]
qdel(query)
return "[key_to_find]'s discord is <@[discord_id]>"
/datum/tgs_chat_command/getkey
name = "getkey"
help_text = "Finds the key associated with a discord id"
admin_only = TRUE
/datum/tgs_chat_command/getkey/Run(datum/tgs_chat_user/sender, params)
if(!params)
return "[sender.friendly_name], you need to provide a Discord ID at the end of the command. To obtain someone's Discord ID, you need to enable developer mode on discord, and then right click on their name and click Copy ID."
var/datum/db_query/query = SSdbcore.NewQuery("SELECT ckey FROM erro_player WHERE discord_id = :t_discord", list("t_discord"=params))
query.Execute()
if(!query.NextRow())
qdel(query)
return "[sender.friendly_name], the server's database is either not responding or there's no such Discord ID in the database."
var/user_key = query.item[1]
qdel(query)
return "<@[params]>'s ckey is [user_key]"
//modded fax code to properly handle non existing files before accessing the void
/datum/tgs_chat_command/readfax/Run(sender, params)
var/list/all_params = splittext(params, " ")
var/faxid = all_params[1]
if(!all_params[1] || !fexists("[CONFIG_GET(string/fax_export_dir)]/fax_[faxid].html"))
return "Im sorry Dave, Im afraid I cant do that"
var/faxmsg = return_file_text("[CONFIG_GET(string/fax_export_dir)]/fax_[faxid].html")
return "FAX: ```[strip_html_properly(faxmsg)]```"
/datum/tgs_chat_command/vore
name = "vore"
help_text = "vore"
admin_only = FALSE
/datum/tgs_chat_command/vore/Run(datum/tgs_chat_user/sender, params)
return "vore"
// - FAX
/datum/tgs_chat_command/readfax
name = "readfax"
help_text = "Reads a fax with specified faxid"
admin_only = TRUE
/datum/tgs_chat_command/readfax/Run(sender, params)
var/list/all_params = splittext(params, " ")
var/faxid = all_params[1]
var/faxmsg = return_file_text("[CONFIG_GET(string/fax_export_dir)]/fax_[faxid].html")
return "FAX: ```[strip_html_properly(faxmsg)]```"

View File

@@ -138,3 +138,243 @@ GLOBAL_LIST_EMPTY(pending_discord_registrations)
GLOB.pending_discord_registrations[GLOB.pending_discord_registrations.len] = list("ckey" = key_to_find, "id" = sender.id, "time" = world.realtime)
return "[sender.friendly_name], I've sent you a message in-game. Please verify your username there to complete your registration within 10 minutes."
// Coin flip
/datum/tgs_chat_command/coinflip
name = "flip"
help_text = "babies first TGS command"
admin_only = FALSE
/datum/tgs_chat_command/coinflip/Run(datum/tgs_chat_user/sender, params)
var/x
if(params != "tails" && params != "heads")
return "```You need to guess!\n either heads or tails!```"
if(prob(50))
if(params == "tails")
x = "correct"
else
x = "wrong"
return "```Tails. [x]!```"
if(params == "tails")
x = "wrong"
else
x = "correct"
return "```Heads. [x]!```"
/datum/tgs_chat_command/coinflip/alias
name = "coin"
// Manifest
/datum/tgs_chat_command/manifest
name = "manifest"
help_text = "Shows the current crew manifest"
admin_only = FALSE
/proc/ManifestToHtml()
var/html = ""
if(GLOB.data_core)
html = GLOB.data_core.get_manifest(FALSE,TRUE,snowflake = TRUE)
else
html = span_bold("ERROR: NO DATACORE") //Could make the error more fancy later
rustg_file_write(html,"[CONFIG_GET(string/nodebot_location)]\\html.html")
/datum/tgs_chat_command/manifest/Run(datum/tgs_chat_user/sender, params)
if(CONFIG_GET(flag/nodebot_enabled))
ManifestToHtml()
return "http://manifest.chompstation13.net/"
else
var/outp = "Crew Manifest:"
var/list/total = list()
if(GLOB.data_core)
GLOB.data_core.get_manifest_list()
for(var/list/item in GLOB.PDA_Manifest)
outp += "\n__**[item["cat"]]:**__"
for(var/list/person in item["elems"])
total |= person
outp += "\n[person["name"]] -:- [person["rank"]]"
return "**Total crew members:** [total.len]\n" + outp
// Discord ping
/datum/tgs_chat_command/discordping
name = "discordping"
help_text = "Pings the discord associated with the associated ckey"
admin_only = TRUE
/datum/tgs_chat_command/discordping/Run(datum/tgs_chat_user/sender, params)
var/key_to_find = "[ckey(params)]"
// They didn't provide anything worth looking up.
if(!length(key_to_find))
return "[sender.friendly_name], you need to provide a Byond username at the end of the command. It can be in 'key' format (with spaces and characters) or 'ckey' format (without spaces or special characters)."
var/datum/db_query/query = SSdbcore.NewQuery("SELECT discord_id FROM erro_player WHERE ckey = :t_ckey",list("t_ckey" = key_to_find))
query.Execute()
if(!query.NextRow())
qdel(query)
return "[sender.friendly_name], the server's database is either not responding or there's no such ckey in the database."
if(!query.item[1])
qdel(query)
return "[sender.friendly_name], [key_to_find] is in the database, but has no discord ID associated with them."
var/discord_id = query.item[1]
qdel(query)
return "[key_to_find]'s discord is <@[discord_id]>"
/datum/tgs_chat_command/getkey
name = "getkey"
help_text = "Finds the key associated with a discord id"
admin_only = TRUE
/datum/tgs_chat_command/getkey/Run(datum/tgs_chat_user/sender, params)
if(!params)
return "[sender.friendly_name], you need to provide a Discord ID at the end of the command. To obtain someone's Discord ID, you need to enable developer mode on discord, and then right click on their name and click Copy ID."
var/datum/db_query/query = SSdbcore.NewQuery("SELECT ckey FROM erro_player WHERE discord_id = :t_discord", list("t_discord"=params))
query.Execute()
if(!query.NextRow())
qdel(query)
return "[sender.friendly_name], the server's database is either not responding or there's no such Discord ID in the database."
var/user_key = query.item[1]
qdel(query)
return "<@[params]>'s ckey is [user_key]"
/datum/tgs_chat_command/vore
name = "vore"
help_text = "vore"
admin_only = FALSE
/datum/tgs_chat_command/vore/Run(datum/tgs_chat_user/sender, params)
return "vore"
// - FAX
/datum/tgs_chat_command/readfax
name = "readfax"
help_text = "Reads a fax with specified faxid"
admin_only = TRUE
/datum/tgs_chat_command/readfax/Run(sender, params)
var/list/all_params = splittext(params, " ")
if(!LAZYLEN(all_params))
return "```Invalid command, missing fax id```"
var/faxid = all_params[1]
if(!all_params[1] || !fexists("[CONFIG_GET(string/fax_export_dir)]/fax_[faxid].html"))
return "Im sorry Dave, Im afraid I cant do that"
var/faxmsg = return_file_text("[CONFIG_GET(string/fax_export_dir)]/fax_[faxid].html")
return "FAX: ```[strip_html_properly(faxmsg)]```"
// Reply to admin tickets
/datum/tgs_chat_command/ticketreply
name = "ticket"
help_text = "allows admins to reply to open tickets. Usage: ticket id \[reply, reject, icissue, close, resolve, handle, reopen\] message"
admin_only = TRUE
/datum/tgs_chat_command/ticketreply/Run(datum/tgs_chat_user/sender, params)
var/list/message_as_list = splittext(params, " ")
if(!LAZYLEN(message_as_list))
return "```Invalid command usage: ticket id \[reply, reject, icissue, close, resolve, handle, reopen\] message```"
var/id = text2num(message_as_list[1])
if(!isnum(id))
return "```First param must be the ticket ID.```"
message_as_list.Cut(1, 2)
if(!LAZYLEN(message_as_list))
return "```Invalid command usage: ticket id \[reply, reject, icissue, close, resolve, handle, reopen\] message```"
var/action = message_as_list[1]
if(isnum(action))
return "```Second param must be the action type.```"
message_as_list.Cut(1, 2)
if(!LAZYLEN(message_as_list) && action == "reply")
return "```Invalid command usage: ticket id \[reply, reject, icissue, close, resolve, handle, reopen\] message```"
if(!(action in list("reply", "reject", "icissue", "close", "resolve", "handle", "reopen")))
return "```Invalid command usage: ticket id \[reply, reject, icissue, close, resolve, handle, reopen\] message```"
var/text_message
if(LAZYLEN(message_as_list))
text_message = message_as_list.Join(" ")
var/datum/ticket/found
if(action == "reopen")
for(var/datum/ticket/ticket in GLOB.tickets.closed_tickets)
if(ticket.id == id)
found = ticket
if(!found)
return "```Ticket with id #[id] was not found in closed tickets!```"
else
for(var/datum/ticket/ticket in GLOB.tickets.active_tickets)
if(ticket.id == id)
found = ticket
if(!found)
return "```Ticket with id #[id] was not found in active tickets!```"
if(text_message)
to_chat(found.initiator, span_admin_pm_warning("Admin PM from-" + span_bold("Discord Relay") + ": [text_message]"))
found.AddInteraction("Discord Relay: [text_message]")
switch(action)
if("reject")
found.Reject("Remote (Discord)")
if("icissue")
found.ICIssue("Remote (Discord)")
if("close")
found.Close("Remote (Discord)")
if("resolve")
found.Resolve("Remote (Discord)")
if("handle")
found.HandleIssue("Remote (Discord)")
if("reopen")
found.Reopen("Remote (Discord)")
return "Ticket command ([action]) sent!"
// Remote smite
/datum/tgs_chat_command/remote_smite
name = "smite"
help_text = "allows admins to remotely smite players. Usage: smite \[bsa, lightning, pie, gib, dust\] name"
admin_only = TRUE
/datum/tgs_chat_command/remote_smite/Run(datum/tgs_chat_user/sender, params)
var/list/message_as_list = splittext(params, " ")
if(!LAZYLEN(message_as_list))
return "```Invalid command usage: smite \[bsa, lightning, pie, gib, dust\] name```"
var/smite_name = message_as_list[1]
if(!istext(smite_name))
return "```First param must be the smite name.```"
message_as_list.Cut(1, 2)
if(!LAZYLEN(message_as_list))
return "```Invalid command usage: smite \[bsa, lightning, pie, gib, dust\] name```"
var/player_name = message_as_list.Join(" ")
var/mob/living/real_target
for(var/mob/living/target in player_list)
if(target.real_name == player_name)
real_target = target
break
if(!real_target)
return "Smite [smite_name] failed to find player ([player_name]), validate their name and try again."
switch(smite_name)
if("bsa")
bluespace_artillery(real_target)
if("lightning")
var/turf/T = get_step(get_step(real_target, NORTH), NORTH)
T.Beam(real_target, icon_state="lightning[rand(1,12)]", time = 5)
real_target.electrocute_act(75,def_zone = BP_HEAD)
real_target.visible_message(span_danger("[real_target] is struck by lightning!"))
if("pie")
new/obj/effect/decal/cleanable/pie_smudge(get_turf(real_target))
playsound(real_target, 'sound/effects/slime_squish.ogg', 100, 1, get_rand_frequency(), falloff = 5)
real_target.Weaken(1)
real_target.visible_message(span_danger("[real_target] is struck by pie!"))
if("gib")
real_target.gib()
if("dust")
real_target.dust()
return "Smite [smite_name] sent!"

View File

@@ -7,7 +7,6 @@
GLOBAL_DATUM_INIT(tgui_ticket_state, /datum/tgui_state/ticket_state, new)
/datum/tgui_state/ticket_state/can_use_topic(src_object, mob/user)
//if (user.client.current_ticket)
// return STATUS_INTERACTIVE
//return STATUS_CLOSE
return STATUS_INTERACTIVE
if(user.client.current_ticket)
return STATUS_INTERACTIVE
return STATUS_CLOSE

View File

@@ -36,7 +36,7 @@
to_chat(src, span_warning("Ticket not found, creating new one..."))
else
current_ticket.AddInteraction("[usr.ckey] opened a new ticket.")
current_ticket.Resolve()
current_ticket.Resolve(usr)
new /datum/ticket(msg, src, FALSE, 0)
@@ -116,7 +116,7 @@ ADMIN_VERB(cmd_mentor_ticket_panel, (R_ADMIN|R_SERVER|R_MOD|R_MENTOR), "Mentor T
to_chat(src, span_warning("Ticket not found, creating new one..."))
else if(current_ticket)
current_ticket.AddInteraction("[key_name_admin(usr)] opened a new ticket.")
current_ticket.Close()
current_ticket.Close(usr)
new /datum/ticket(msg, src, FALSE, 1)

View File

@@ -162,14 +162,14 @@ GLOBAL_DATUM_INIT(tickets, /datum/tickets, new)
if(C.current_ticket)
C.current_ticket.AddInteraction("Client reconnected.")
C.current_ticket.initiator = C
// C.current_ticket.initiator.mob.throw_alert("open ticket", /obj/screen/alert/open_ticket) // Uncomment this line to enable player-side ticket ui
C.current_ticket.initiator.mob.throw_alert("open ticket", /obj/screen/alert/open_ticket)
//Dissasociate ticket
/datum/tickets/proc/ClientLogout(client/C)
if(C.current_ticket)
var/datum/ticket/T = C.current_ticket
T.AddInteraction("Client disconnected.")
// T.initiator.mob.clear_alert("open ticket") // Uncomment this line to enable player-side ticket ui
T.initiator.mob.clear_alert("open ticket")
T.initiator = null
T = null
@@ -227,6 +227,7 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick/ticket_list)
var/closed_at
var/client/initiator //semi-misnomer, it's the person who ahelped/was bwoinked
var/datum/weakref/handler_ref
var/handler = "/Unassigned\\" // The admin handling the ticket
var/initiator_ckey
var/initiator_key_name
@@ -287,7 +288,7 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick/ticket_list)
if(initiator.current_ticket) //This is a bug
log_debug("Multiple ahelp current_tickets")
initiator.current_ticket.AddInteraction("Ticket erroneously left open by code")
initiator.current_ticket.Close()
initiator.current_ticket.Close(usr)
initiator.current_ticket = src
var/parsed_message = keywords_lookup(msg)
@@ -340,12 +341,13 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick/ticket_list)
//TC.T = src
//TC.tgui_interact(C.mob)
// C.mob.throw_alert("open ticket", /obj/screen/alert/open_ticket) // Uncomment this line to enable player-side ticket ui
C.mob.throw_alert("open ticket", /obj/screen/alert/open_ticket)
/datum/ticket/Destroy()
RemoveActive()
GLOB.tickets.closed_tickets -= src
GLOB.tickets.resolved_tickets -= src
handler_ref = null
return ..()
/datum/ticket/proc/AddInteraction(formatted_message)
@@ -440,7 +442,7 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick/ticket_list)
*/
//Reopen a closed ticket
/datum/ticket/proc/Reopen()
/datum/ticket/proc/Reopen(user)
if(state == AHELP_ACTIVE)
to_chat(usr, span_warning("This ticket is already open."))
return
@@ -463,20 +465,22 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick/ticket_list)
if(initiator)
initiator.current_ticket = src
AddInteraction(span_purple("Reopened by [key_name_admin(usr)]"))
var/admin_reopener_name = ismob(user) ? key_name_admin(user) : user
AddInteraction(span_purple("Reopened by [admin_reopener_name]"))
if(initiator)
to_chat(initiator, span_filter_adminlog("[span_purple("Ticket [TicketHref("#[id]")] was reopened by [key_name(usr,FALSE,FALSE)].")]"))
var/msg = span_adminhelp("Ticket [TicketHref("#[id]")] reopened by [key_name_admin(usr)].")
to_chat(initiator, span_filter_adminlog("[span_purple("Ticket [TicketHref("#[id]")] was reopened by [ismob(user) ? key_name(usr,FALSE,FALSE) : user].")]"))
var/msg = span_adminhelp("Ticket [TicketHref("#[id]")] reopened by [admin_reopener_name].")
message_admins(msg)
log_admin(msg)
feedback_inc("ticket_reopen")
initiator.mob.throw_alert("open ticket", /obj/screen/alert/open_ticket)
//TicketPanel() //can only be done from here, so refresh it
SSwebhooks.send(
WEBHOOK_AHELP_SENT,
list(
"name" = "Ticket ([id]) (Round ID: [GLOB.round_id ? GLOB.round_id : "No database"]) reopened.",
"body" = "Reopened by [key_name(usr)]."
"body" = "Reopened by [ismob(user) ? key_name(user) : user]."
)
)
@@ -491,44 +495,46 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick/ticket_list)
initiator.current_ticket = null
//Mark open ticket as closed/meme
/datum/ticket/proc/Close(silent = FALSE)
/datum/ticket/proc/Close(user, silent = FALSE)
if(state != AHELP_ACTIVE)
return
RemoveActive()
state = AHELP_CLOSED
GLOB.tickets.ListInsert(src)
AddInteraction(span_filter_adminlog(span_red("Closed by [key_name_admin(usr)].")))
var/admin_closer_name = ismob(user) ? key_name_admin(user) : user
AddInteraction(span_filter_adminlog(span_red("Closed by [admin_closer_name].")))
if(initiator)
to_chat(initiator, span_filter_adminlog("[span_red("Ticket [TicketHref("#[id]")] was closed by [key_name(usr,FALSE,FALSE)].")]"))
to_chat(initiator, span_filter_adminlog("[span_red("Ticket [TicketHref("#[id]")] was closed by [ismob(user) ? key_name(usr,FALSE,FALSE) : user].")]"))
if(!silent)
feedback_inc("ahelp_close")
var/msg = "Ticket [TicketHref("#[id]")] closed by [key_name_admin(usr)]."
var/msg = "Ticket [TicketHref("#[id]")] closed by [admin_closer_name]."
message_admins(msg)
log_admin(msg)
SSwebhooks.send(
WEBHOOK_AHELP_SENT,
list(
"name" = "Ticket ([id]) (Round ID: [GLOB.round_id ? GLOB.round_id : "No database"]) closed.",
"body" = "Closed by [key_name(usr)].",
"body" = "Closed by [ismob(user) ? key_name(user) : user].",
"color" = COLOR_WEBHOOK_BAD
)
)
// initiator?.mob?.clear_alert("open ticket") // Uncomment this line to enable player-side ticket ui
initiator?.mob?.clear_alert("open ticket")
//Mark open ticket as resolved/legitimate, returns ahelp verb
/datum/ticket/proc/Resolve(silent = FALSE)
/datum/ticket/proc/Resolve(user, silent = FALSE)
if(state != AHELP_ACTIVE)
return
RemoveActive()
state = AHELP_RESOLVED
GLOB.tickets.ListInsert(src)
AddInteraction(span_filter_adminlog(span_green("Resolved by [key_name_admin(usr)].")))
var/admin_resolver_name = ismob(user) ? key_name_admin(user) : user
AddInteraction(span_filter_adminlog(span_green("Resolved by [admin_resolver_name].")))
if(initiator)
to_chat(initiator, span_filter_adminlog("[span_green("Ticket [TicketHref("#[id]")] was marked resolved by [key_name(usr,FALSE,FALSE)].")]"))
to_chat(initiator, span_filter_adminlog("[span_green("Ticket [TicketHref("#[id]")] was marked resolved by [ismob(user) ? key_name(usr,FALSE,FALSE) : user].")]"))
if(!silent)
feedback_inc("ticket_resolve")
var/msg = "Ticket [TicketHref("#[id]")] resolved by [key_name_admin(usr)]"
var/msg = "Ticket [TicketHref("#[id]")] resolved by [admin_resolver_name]"
if(type == 1)
message_mentors(msg)
else if (type == 0)
@@ -540,14 +546,14 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick/ticket_list)
WEBHOOK_AHELP_SENT,
list(
"name" = "Ticket ([id]) (Round ID: [GLOB.round_id ? GLOB.round_id : "No database"]) resolved.",
"body" = "Marked as Resolved by [key_name(usr)].",
"body" = "Marked as Resolved by [ismob(user) ? key_name(user) : user].",
"color" = COLOR_WEBHOOK_GOOD
)
)
// initiator?.mob?.clear_alert("open ticket") // Uncomment this line to enable player-side ticket ui
initiator?.mob?.clear_alert("open ticket")
//Close and return ahelp verb, use if ticket is incoherent
/datum/ticket/proc/Reject(key_name = key_name_admin(usr))
/datum/ticket/proc/Reject(mob/user)
if(state != AHELP_ACTIVE)
return
@@ -559,23 +565,24 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick/ticket_list)
[span_red(span_bold("Your admin help was rejected."))]<br>\
Please try to be calm, clear, and descriptive in admin helps, do not assume the admin has seen any related events, and clearly state the names of anybody you are reporting."))
var/admin_rejecter_name = ismob(user) ? key_name_admin(user) : user
feedback_inc("ahelp_reject")
var/msg = "Ticket [TicketHref("#[id]")] rejected by [key_name_admin(usr)]"
var/msg = "Ticket [TicketHref("#[id]")] rejected by [admin_rejecter_name]"
message_admins(msg)
log_admin(msg)
AddInteraction("Rejected by [key_name_admin(usr)].")
Close(silent = TRUE)
AddInteraction("Rejected by [admin_rejecter_name].")
Close(user, silent = TRUE)
SSwebhooks.send(
WEBHOOK_AHELP_SENT,
list(
"name" = "Ticket ([id]) (Round ID: [GLOB.round_id ? GLOB.round_id : "No database"]) rejected.",
"body" = "Rejected by [key_name(usr)].",
"body" = "Rejected by [ismob(user) ? key_name(user) : user].",
"color" = COLOR_WEBHOOK_BAD
)
)
//Resolve ticket with IC Issue message
/datum/ticket/proc/ICIssue(key_name = key_name_admin(usr))
/datum/ticket/proc/ICIssue(user)
if(state != AHELP_ACTIVE)
return
@@ -586,51 +593,57 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick/ticket_list)
if(initiator)
to_chat(initiator, span_filter_pm(msg))
var/admin_resolve_name = ismob(user) ? key_name_admin(user) : user
feedback_inc("ahelp_icissue")
msg = "Ticket [TicketHref("#[id]")] marked as IC by [key_name_admin(usr)]"
msg = "Ticket [TicketHref("#[id]")] marked as IC by [admin_resolve_name]"
message_admins(msg)
log_admin(msg)
AddInteraction("Marked as IC issue by [key_name_admin(usr)]")
Resolve(silent = TRUE)
AddInteraction("Marked as IC issue by [admin_resolve_name]")
Resolve(user, silent = TRUE)
SSwebhooks.send(
WEBHOOK_AHELP_SENT,
list(
"name" = "Ticket ([id]) (Round ID: [GLOB.round_id ? GLOB.round_id : "No database"]) marked as IC issue.",
"body" = "Marked as IC Issue by [key_name(usr)].",
"body" = "Marked as IC Issue by [ismob(user) ? key_name(user) : user].",
"color" = COLOR_WEBHOOK_BAD
)
)
//Resolve ticket with IC Issue message
/datum/ticket/proc/HandleIssue()
//Handle ticket
/datum/ticket/proc/HandleIssue(user)
if(state != AHELP_ACTIVE)
return
if(handler == key_name(usr, FALSE, TRUE))
to_chat(usr, span_red("You are already handling this ticket."))
var/handler_name = ismob(user) ? key_name(user, FALSE, TRUE) : user
if(handler == handler_name)
to_chat(user, span_red("You are already handling this ticket."))
return
var/handler_shown_name = ismob(user) ? key_name_admin(user) : user
var/msg
switch(level)
if(0)
msg = span_green("Your MentorHelp is being handled by [key_name(usr,FALSE,FALSE)] please be patient.")
msg = span_green("Your MentorHelp is being handled by [handler_shown_name] please be patient.")
if(1)
msg = span_red("Your AdminHelp is being handled by [key_name(usr,FALSE,FALSE)] please be patient.")
msg = span_red("Your AdminHelp is being handled by [handler_shown_name] please be patient.")
if(initiator)
to_chat(initiator, msg)
feedback_inc("ahelp_handling")
msg = "Ticket [TicketHref("#[id]")] being handled by [key_name(usr,FALSE,FALSE)]"
msg = "Ticket [TicketHref("#[id]")] being handled by [handler_shown_name]"
message_admins(msg)
log_admin(msg)
AddInteraction("[key_name_admin(usr)] is now handling this ticket.")
handler = key_name(usr, FALSE, TRUE)
AddInteraction("[handler_shown_name] is now handling this ticket.")
handler = handler_name
if(ismob(user))
var/mob/our_handler_mob = user
handler_ref = WEAKREF(our_handler_mob.client)
SSwebhooks.send(
WEBHOOK_AHELP_SENT,
list(
"name" = "Ticket ([id]) (Round ID: [GLOB.round_id ? GLOB.round_id : "No database"]) being handled.",
"body" = "[key_name(usr)] is now handling the ticket."
"body" = "[ismob(user) ? key_name(user) : user] is now handling the ticket."
)
)
@@ -681,7 +694,7 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick/ticket_list)
if("retitle")
Retitle()
if("reject")
Reject()
Reject(usr)
if("reply")
switch(level)
if(0)
@@ -689,15 +702,15 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick/ticket_list)
if(1)
usr.client.cmd_ahelp_reply(initiator)
if("icissue")
ICIssue()
ICIssue(usr)
if("close")
Close()
Close(usr)
if("resolve")
Resolve()
Resolve(usr)
if("handleissue")
HandleIssue()
HandleIssue(usr)
if("reopen")
Reopen()
Reopen(usr)
if("escalate")
Escalate()

View File

@@ -6,22 +6,19 @@
var/datum/ticket/T
/datum/ticket_chat/tgui_interact(mob/user, datum/tgui/ui)
return // Remove this line to enable player-side ticket ui
//ui = SStgui.try_update_ui(user, src, ui)
//if(!ui)
// ui = new(user, src, "TicketChat", "Ticket #[T.id] - [T.LinkedReplyName("\ref[T]")]")
// ui.open()
// user.clear_alert("open ticket")
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "TicketChat", "Ticket #[T.id] - [T.LinkedReplyName("\ref[T]")]")
ui.open()
user.clear_alert("open ticket")
/datum/ticket_chat/tgui_close(mob/user)
. = ..()
return // Remove this line to enable player-side ticket ui
//if(user.client.current_ticket)
// user.throw_alert("open ticket", /obj/screen/alert/open_ticket)
if(user.client.current_ticket)
user.throw_alert("open ticket", /obj/screen/alert/open_ticket)
/datum/ticket_chat/tgui_state(mob/user)
return ADMIN_STATE(R_ADMIN|R_EVENT|R_DEBUG) // Remove this line to enable player-side ticket ui
//return GLOB.tgui_ticket_state
return GLOB.tgui_ticket_state
/datum/ticket_chat/tgui_data(mob/user)
var/list/data = list()
@@ -29,9 +26,9 @@
data["id"] = T.id
data["level"] = T.level
// data["handler"] = T.handler // Uncomment this line to enable player-side ticket ui
data["handler"] = T.handler
// data["log"] = T._interactions // Uncomment this line to enable player-side ticket ui
data["log"] = T._interactions
return data
@@ -43,10 +40,27 @@
if(!params["msg"])
return
var/sane_message = sanitize(params["msg"])
switch(T.level)
if (0)
ui.user.client.cmd_mentor_pm(ui.user.client, sanitize(params["msg"]), T)
if(T.initiator == ui.user.client)
var/client/handler = T.handler_ref?.resolve()
if(handler)
ui.user.client.cmd_mentor_pm(handler, sane_message, T)
return TRUE
T.AddInteraction("[T.initiator_key_name]: [sane_message]")
return TRUE
ui.user.client.cmd_mentor_pm(T.initiator, sane_message, T)
return TRUE
if (1)
ui.user.client.cmd_admin_pm(ui.user.client, sanitize(params["msg"]), T)
if(T.initiator == ui.user.client)
var/client/handler = T.handler_ref?.resolve()
if(handler)
ui.user.client.cmd_admin_pm(handler, sane_message, T)
return TRUE
T.AddInteraction("[key_name_admin(ui.user)]: [sane_message]")
return TRUE
ui.user.client.cmd_admin_pm(T.initiator, sane_message, T)
return TRUE
. = TRUE

View File

@@ -57,6 +57,7 @@
"state" = get_ticket_state(T.state),
"level" = T.level,
"handler" = T.handler,
"ishandled" = !!T.handler_ref?.resolve(),
"opened_at" = (world.time - T.opened_at),
"closed_at" = (world.time - T.closed_at),
"opened_at_date" = gameTimestamp(wtime = T.opened_at),
@@ -71,6 +72,7 @@
"state" = get_ticket_state(T.state),
"level" = T.level,
"handler" = T.handler,
"ishandled" = !!T.handler_ref?.resolve(),
"opened_at" = (world.time - T.opened_at),
"closed_at" = (world.time - T.closed_at),
"opened_at_date" = gameTimestamp(wtime = T.opened_at),
@@ -85,6 +87,7 @@
"state" = get_ticket_state(T.state),
"level" = T.level,
"handler" = T.handler,
"ishandled" = !!T.handler_ref?.resolve(),
"opened_at" = (world.time - T.opened_at),
"closed_at" = (world.time - T.closed_at),
"opened_at_date" = gameTimestamp(wtime = T.opened_at),
@@ -145,7 +148,7 @@
to_chat(ui.user, span_warning("Ticket not found, creating new one..."))
else
player.current_ticket.AddInteraction("[key_name_admin(ui.user)] opened a new ticket.")
player.current_ticket.Close()
player.current_ticket.Close(ui.user)
// Create a new ticket and handle it. You created it afterall!
var/datum/ticket/T = new /datum/ticket(ticket_text, player, TRUE, level)
@@ -153,7 +156,7 @@
T.level = 1
else
T.level = 0
T.HandleIssue()
T.HandleIssue(ui.user)
switch(T.level)
if (0)
ui.user.client.cmd_mentor_pm(player, ticket_text, T)
@@ -168,7 +171,7 @@
ui.user.client.selected_ticket.Retitle()
. = TRUE
if("reopen_ticket")
ui.user.client.selected_ticket.Reopen()
ui.user.client.selected_ticket.Reopen(ui.user)
. = TRUE
if("undock_ticket")
ui.user.client.selected_ticket.tgui_interact(ui.user)
@@ -249,7 +252,7 @@
Retitle()
. = TRUE
if("reopen")
Reopen()
Reopen(ui.user)
. = TRUE
if("legacy")
TicketPanelLegacy(ui.user)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -792,22 +792,12 @@ macro "hotkeymode"
elem "ctrlshiftsubtract"
name = "CTRL+SHIFT+SUBTRACT"
command = "planedown"
<<<<<<< HEAD
// elem // CHOMPREMOVE Start
// name = "Space"
// command = ".throwon"
// elem
// name = "Space+UP"
// command = ".throwoff" // CHOMPREMOVE End
=======
elem "space"
name = "Space"
command = ".throwon"
elem "spaceup"
name = "Space+UP"
command = ".throwoff"
>>>>>>> b7969a971d (Replace the alt click menu with the RPG Lootpanel (#17938))
macro "borgmacro"
elem "tab"
name = "TAB"

View File

@@ -5,7 +5,6 @@ import { Window } from 'tgui/layouts';
import {
Box,
Button,
Divider,
Input,
LabeledList,
Section,
@@ -104,18 +103,34 @@ export const Ticket = (props) => {
<Section
title={'Ticket #' + id}
buttons={
<Box nowrap>
<Button icon="pen" onClick={() => act('retitle')}>
Rename Ticket
</Button>
<Button onClick={() => act('legacy')}>Legacy UI</Button>
<Button color={LevelColor[level]}>{Level[level]}</Button>
</Box>
<Stack>
<Stack.Item>
<Button icon="pen" onClick={() => act('retitle')}>
Rename Ticket
</Button>
</Stack.Item>
<Stack.Item>
<Button onClick={() => act('legacy')}>Legacy UI</Button>
</Stack.Item>
<Stack.Item>
<Box
className="TicketPanel__Label"
backgroundColor={LevelColor[level]}
>
{Level[level]}
</Box>
</Stack.Item>
</Stack>
}
>
<LabeledList>
<LabeledList.Item label="Ticket ID">
#{id}: <div dangerouslySetInnerHTML={{ __html: name }} />
<Stack>
<Stack.Item>#{id}:</Stack.Item>
<Stack.Item>
<div dangerouslySetInnerHTML={{ __html: name }} />
</Stack.Item>
</Stack>
</LabeledList.Item>
<LabeledList.Item label="Type">{Level[level]}</LabeledList.Item>
<LabeledList.Item label="State">
@@ -131,23 +146,28 @@ export const Ticket = (props) => {
</LabeledList.Item>
) : (
<LabeledList.Item label="Closed At">
{closed_at_date +
' (' +
toFixed(round((closed_at / 600) * 10, 0) / 10, 1) +
' minutes ago.)'}
<Button onClick={() => act('reopen')}>Reopen</Button>
<Stack>
<Stack.Item>
{closed_at_date +
' (' +
toFixed(round((closed_at / 600) * 10, 0) / 10, 1) +
' minutes ago.)'}
</Stack.Item>
<Stack.Item>
<Button onClick={() => act('reopen')}>Reopen</Button>
</Stack.Item>
</Stack>
</LabeledList.Item>
)}
<LabeledList.Item label="Actions">
<div dangerouslySetInnerHTML={{ __html: actions }} />
</LabeledList.Item>
<LabeledList.Item label="Log" />
</LabeledList>
</Section>
<Divider />
<Stack.Divider />
</Stack.Item>
<Stack.Item grow>
<Section scrollable ref={messagesEndRef} fill>
<Section scrollable ref={messagesEndRef} fill title="Log">
<Stack fill direction="column">
<Stack.Item grow>
{Object.keys(log)

View File

@@ -3,8 +3,8 @@ import { type RefObject, useEffect, useRef, useState } from 'react';
import { useBackend } from 'tgui/backend';
import { Window } from 'tgui/layouts';
import {
Box,
Button,
Divider,
Input,
LabeledList,
Section,
@@ -76,18 +76,22 @@ export const TicketChat = (props) => {
<Section
title={'Ticket #' + id}
buttons={
<Button color={LevelColor[level]}>{Level[level]}</Button>
<Box
className="TicketPanel__Label"
backgroundColor={LevelColor[level]}
>
{Level[level]}
</Box>
}
>
<LabeledList>
<LabeledList.Item label="Assignee">{handler}</LabeledList.Item>
<LabeledList.Item label="Log" />
</LabeledList>
</Section>
<Divider />
<Stack.Divider />
</Stack.Item>
<Stack.Item grow>
<Section fill ref={messagesEndRef} scrollable>
<Section fill ref={messagesEndRef} scrollable title="Log">
<Stack fill direction="column">
<Stack.Item grow>
{Object.keys(log)
@@ -101,6 +105,8 @@ export const TicketChat = (props) => {
</Stack.Item>
</Stack>
</Section>
</Stack.Item>
<Stack.Item>
<Section fill>
<Stack fill>
<Stack.Item grow>

View File

@@ -3,6 +3,7 @@ import { type RefObject, useEffect, useRef, useState } from 'react';
import { useBackend } from 'tgui/backend';
import { Window } from 'tgui/layouts';
import {
Blink,
Box,
Button,
Divider,
@@ -67,6 +68,7 @@ type Ticket = {
state: string;
level: number;
handler: string;
ishandled: BooleanLike;
opened_at: number;
closed_at: number;
opened_at_date: string;
@@ -138,65 +140,88 @@ export const TicketsPanel = (props) => {
<Window width={1000} height={600}>
<Window.Content>
<Stack fill>
<Stack.Item shrink>
<Section title="Filter">
<Dropdown
width="100%"
maxHeight="160px"
options={Object.values(State)}
selected={State[stateFilter]}
onSelected={(val) =>
setStateFilter(
Object.keys(State)[Object.values(State).indexOf(val)],
)
}
/>
<Divider />
<Dropdown
width="100%"
maxHeight="160px"
options={Object.values(availableLevel)}
selected={availableLevel[levelFilter]}
onSelected={(val) =>
setLevelFilter(Object.values(availableLevel).indexOf(val))
}
/>
</Section>
<Section
title="Tickets"
scrollable
fill
height="450px"
width="300px"
>
<Tabs vertical>
<Tabs.Tab onClick={() => act('new_ticket')}>
New Ticket
<Icon name="plus" ml={0.5} />
</Tabs.Tab>
<Divider />
{filtered_tickets.map((ticket) => (
<Tabs.Tab
key={ticket.id}
selected={ticket.id === selected_ticket?.id}
onClick={() => act('pick_ticket', { ticket_id: ticket.id })}
>
<Box inline>
<Box>
<Button color={LevelColor[ticket.level]}>
{availableLevel[ticket.level]}
</Button>
{ticket.name}
</Box>
<Box fontSize={0.9} textColor={StateColor[ticket.state]}>
State: {State[ticket.state]} | Assignee:
{ticket.handler}
</Box>
</Box>
</Tabs.Tab>
))}
</Tabs>
</Section>
<Stack.Item basis="25%">
<Stack vertical fill>
<Stack.Item>
<Section title="Filter">
<Dropdown
options={Object.values(State)}
selected={State[stateFilter]}
onSelected={(val) =>
setStateFilter(
Object.keys(State)[Object.values(State).indexOf(val)],
)
}
/>
<Divider />
<Dropdown
options={Object.values(availableLevel)}
selected={availableLevel[levelFilter]}
onSelected={(val) =>
setLevelFilter(Object.values(availableLevel).indexOf(val))
}
/>
</Section>
</Stack.Item>
<Stack.Item grow>
<Section title="Tickets" scrollable fill>
<Tabs vertical>
<Tabs.Tab onClick={() => act('new_ticket')}>
New Ticket
<Icon name="plus" ml={0.5} />
</Tabs.Tab>
<Divider />
{filtered_tickets.map((ticket) => (
<Tabs.Tab
key={ticket.id}
selected={ticket.id === selected_ticket?.id}
onClick={() =>
act('pick_ticket', { ticket_id: ticket.id })
}
>
<Stack vertical>
<Stack.Item>
<Stack align="center">
<Stack.Item>
{ticket.ishandled ? (
<Box
textColor="white"
className="TicketPanel__Label"
backgroundColor={LevelColor[ticket.level]}
>
{availableLevel[ticket.level]}
</Box>
) : (
<Blink>
<Box
textColor="white"
className="TicketPanel__Label"
backgroundColor={LevelColor[ticket.level]}
>
{availableLevel[ticket.level]}
</Box>
</Blink>
)}
</Stack.Item>
<Stack.Item>{ticket.name}</Stack.Item>
</Stack>
</Stack.Item>
<Stack.Item>
<Box
fontSize={0.9}
textColor={StateColor[ticket.state]}
>
State: {State[ticket.state]} | Assignee:
{ticket.handler}
</Box>
</Stack.Item>
</Stack>
</Tabs.Tab>
))}
</Tabs>
</Section>
</Stack.Item>
</Stack>
</Stack.Item>
<Stack.Item grow>
{(selected_ticket && (
@@ -205,34 +230,51 @@ export const TicketsPanel = (props) => {
<Section
title={'Ticket #' + selected_ticket.id}
buttons={
<Box nowrap>
<Button
icon="arrow-up"
onClick={() => act('undock_ticket')}
>
Undock
</Button>
<Button
icon="pen"
onClick={() => act('retitle_ticket')}
>
Rename Ticket
</Button>
<Button onClick={() => act('legacy')}>Legacy UI</Button>
<Button color={LevelColor[selected_ticket.level]}>
{availableLevel[selected_ticket.level]}
</Button>
</Box>
<Stack>
<Stack.Item>
<Button
icon="arrow-up"
onClick={() => act('undock_ticket')}
>
Undock
</Button>
</Stack.Item>
<Stack.Item>
<Button
icon="pen"
onClick={() => act('retitle_ticket')}
>
Rename Ticket
</Button>
</Stack.Item>
<Stack.Item>
<Button onClick={() => act('legacy')}>
Legacy UI
</Button>
</Stack.Item>
<Stack.Item>
<Box
className="TicketPanel__Label"
backgroundColor={LevelColor[selected_ticket.level]}
>
{availableLevel[selected_ticket.level]}
</Box>
</Stack.Item>
</Stack>
}
>
<LabeledList>
<LabeledList.Item label="Ticket ID">
#{selected_ticket.id}:
<div
dangerouslySetInnerHTML={{
__html: selected_ticket.name,
}}
/>
<Stack>
<Stack.Item>#{selected_ticket.id}:</Stack.Item>
<Stack.Item>
<div
dangerouslySetInnerHTML={{
__html: selected_ticket.name,
}}
/>
</Stack.Item>
</Stack>
</LabeledList.Item>
<LabeledList.Item label="Type">
{availableLevel[selected_ticket.level]}
@@ -256,17 +298,25 @@ export const TicketsPanel = (props) => {
</LabeledList.Item>
) : (
<LabeledList.Item label="Closed At">
{selected_ticket.closed_at_date +
' (' +
toFixed(
round((selected_ticket.closed_at / 600) * 10, 0) /
10,
1,
) +
' minutes ago.)'}
<Button onClick={() => act('reopen_ticket')}>
Reopen
</Button>
<Stack>
<Stack.Item>
{selected_ticket.closed_at_date +
' (' +
toFixed(
round(
(selected_ticket.closed_at / 600) * 10,
0,
) / 10,
1,
) +
' minutes ago.)'}
</Stack.Item>
<Stack.Item>
<Button onClick={() => act('reopen_ticket')}>
Reopen
</Button>
</Stack.Item>
</Stack>
</LabeledList.Item>
)}
<LabeledList.Item label="Actions">
@@ -276,13 +326,12 @@ export const TicketsPanel = (props) => {
}}
/>
</LabeledList.Item>
<LabeledList.Item label="Log" />
</LabeledList>
</Section>
<Divider />
<Stack.Divider />
</Stack.Item>
<Stack.Item grow>
<Section fill ref={messagesEndRef} scrollable>
<Section fill ref={messagesEndRef} scrollable title="Log">
<Stack fill direction="column">
<Stack.Item grow>
{Object.keys(selected_ticket.log)
@@ -336,23 +385,29 @@ export const TicketsPanel = (props) => {
<Section
title="No ticket selected"
buttons={
<Box nowrap>
<Button
disabled
icon="arrow-up"
onClick={() => act('undock_ticket')}
>
Undock
</Button>
<Button
disabled
icon="pen"
onClick={() => act('retitle_ticket')}
>
Rename Ticket
</Button>
<Button onClick={() => act('legacy')}>Legacy UI</Button>
</Box>
<Stack>
<Stack.Item>
<Button
disabled
icon="arrow-up"
onClick={() => act('undock_ticket')}
>
Undock
</Button>
</Stack.Item>
<Stack.Item>
<Button
disabled
icon="pen"
onClick={() => act('retitle_ticket')}
>
Rename Ticket
</Button>
</Stack.Item>
<Stack.Item>
<Button onClick={() => act('legacy')}>Legacy UI</Button>
</Stack.Item>
</Stack>
}
>
Please select a ticket on the left to view its details.

View File

@@ -112,7 +112,7 @@ const VoreSoulcatcherSection = (props: {
title={'Soulcatcher (' + name + ')'}
fill
buttons={
<Stack>
<Stack align="center">
<Stack.Item>
<VorePanelEditSwitch
action="soulcatcher_toggle"

View File

@@ -0,0 +1,12 @@
.TicketPanel__Label {
display: inline-block;
line-height: 1.667em;
padding: 0 var(--space-m);
outline: 0;
border-radius: var(--button-border-radius);
transition-property: background-color, color, opacity;
transition-duration: var(--button-transition);
transition-timing-function: var(--button-transition-timing);
user-select: none;
background-color: var(--button-background-default);
}

View File

@@ -52,6 +52,7 @@
@include meta.load-css('./interfaces/IDCard.scss');
@include meta.load-css('./interfaces/TinderMessaging.scss');
@include meta.load-css('./interfaces/Turbolift.scss');
@include meta.load-css('./interfaces/TicketPanel.scss');
@include meta.load-css('./interfaces/VorePanel.scss');
@include meta.load-css('./interfaces/Wires.scss');

View File

@@ -14,6 +14,6 @@
background-image: none;
}
.TitleBar__title {
left: 12px;
margin-left: 12px;
}
}

View File

@@ -4561,7 +4561,6 @@
#include "code\modules\tgchat\message.dm"
#include "code\modules\tgchat\to_chat.dm"
#include "code\modules\tgs\includes.dm"
#include "code\modules\tgs_commands\chompstation.dm"
#include "code\modules\tgs_commands\vorestation.dm"
#include "code\modules\tgui\external.dm"
#include "code\modules\tgui\modal.dm"