[MIRROR] refactors statpanel to use tgui API [MDB IGNORE] (#13646)

* refactors statpanel to use tgui API

* fix

Co-authored-by: magatsuchi <88991542+magatsuchi@users.noreply.github.com>
Co-authored-by: Tom <8881105+tf-4@users.noreply.github.com>
This commit is contained in:
SkyratBot
2022-05-20 16:02:16 +02:00
committed by GitHub
parent 2856b73bc9
commit a079903ecb
20 changed files with 1418 additions and 1420 deletions

View File

@@ -1,8 +0,0 @@
/// Bare minimum required verbs for stat panel operation
GLOBAL_LIST_INIT(stat_panel_verbs, list(
/client/verb/set_tab,
/client/verb/send_tabs,
/client/verb/remove_tabs,
/client/verb/reset_tabs,
/client/verb/panel_ready
))

View File

@@ -43,9 +43,8 @@
for(var/thing in verbs_list)
var/procpath/verb_to_add = thing
output_list[++output_list.len] = list(verb_to_add.category, verb_to_add.name)
output_list = url_encode(json_encode(output_list))
target << output("[output_list];", "statbrowser:add_verb_list")
target.stat_panel.send_message("add_verb_list", output_list)
/**
* handles removing verb and sending it to browser to update, use this for removing verbs
@@ -91,6 +90,5 @@
for(var/thing in verbs_list)
var/procpath/verb_to_remove = thing
output_list[++output_list.len] = list(verb_to_remove.category, verb_to_remove.name)
output_list = url_encode(json_encode(output_list))
target << output("[output_list];", "statbrowser:remove_verb_list")
target.stat_panel.send_message("remove_verb_list", output_list)

View File

@@ -412,7 +412,7 @@
var/turf/T = get_turf(src)
if(T && (isturf(loc) || isturf(src)) && user.TurfAdjacent(T))
user.listed_turf = T
user.client << output("[url_encode(json_encode(T.name))];", "statbrowser:create_listedturf")
user.client.stat_panel.send_message("create_listedturf", T.name)
///The base proc of when something is right clicked on when alt is held - generally use alt_click_secondary instead
/atom/proc/alt_click_on_secondary(atom/A)
@@ -436,7 +436,7 @@
var/turf/T = get_turf(A)
if(T && user.TurfAdjacent(T))
user.listed_turf = T
user.client << output("[url_encode(json_encode(T.name))];", "statbrowser:create_listedturf")
user.client.stat_panel.send_message("create_listedturf", T.name)
/mob/proc/TurfAdjacent(turf/T)
return T.Adjacent(src)

View File

@@ -6,14 +6,14 @@ SUBSYSTEM_DEF(statpanels)
priority = FIRE_PRIORITY_STATPANEL
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
var/list/currentrun = list()
var/encoded_global_data
var/mc_data_encoded
var/list/global_data
var/list/mc_data
var/list/cached_images = list()
///how many subsystem fires between most tab updates
var/default_wait = 10
///how many subsystem fires between updates of the status tab
var/status_wait = 12
var/status_wait = 6
///how many subsystem fires between updates of the MC tab
var/mc_wait = 5
///how many full runs this subsystem has completed. used for variable rate refreshes.
@@ -24,7 +24,7 @@ SUBSYSTEM_DEF(statpanels)
num_fires++
var/datum/map_config/cached = SSmapping.next_map_config
/* SKYRAT EDIT CHANGE
var/list/global_data = list(
global_data = list(
"Map: [SSmapping.config?.map_name || "Loading..."]",
cached ? "Next Map: [cached.map_name]" : null,
"Round ID: [GLOB.round_id ? GLOB.round_id : "NULL"]",
@@ -56,28 +56,27 @@ SUBSYSTEM_DEF(statpanels)
var/ETA = SSshuttle.emergency.getModeStr()
if(ETA)
global_data += "[ETA] [SSshuttle.emergency.getTimerStr()]"
encoded_global_data = url_encode(json_encode(global_data))
src.currentrun = GLOB.clients.Copy()
mc_data_encoded = null
mc_data = null
var/list/currentrun = src.currentrun
while(length(currentrun))
var/client/target = currentrun[length(currentrun)]
currentrun.len--
if(!target.statbrowser_ready)
if(!target.stat_panel.is_ready())
continue
if(target.stat_tab == "Status" && num_fires % status_wait == 0)
set_status_tab(target)
if(!target.holder)
target << output("", "statbrowser:remove_admin_tabs")
target.stat_panel.send_message("remove_admin_tabs")
else
target << output("[!!(target.prefs.toggles & SPLIT_ADMIN_TABS)]", "statbrowser:update_split_admin_tabs")
target.stat_panel.send_message("update_split_admin_tabs", !!(target.prefs.toggles & SPLIT_ADMIN_TABS))
if(!("MC" in target.panel_tabs) || !("Tickets" in target.panel_tabs))
target << output("[url_encode(target.holder.href_token)]", "statbrowser:add_admin_tabs")
target.stat_panel.send_message("add_admin_tabs", target.holder.href_token)
if(target.stat_tab == "MC" && ((num_fires % mc_wait == 0) || target?.prefs.read_preference(/datum/preference/toggle/fast_mc_refresh)))
set_MC_tab(target)
@@ -86,7 +85,7 @@ SUBSYSTEM_DEF(statpanels)
set_tickets_tab(target)
if(!length(GLOB.sdql2_queries) && ("SDQL2" in target.panel_tabs))
target << output("", "statbrowser:remove_sdql2")
target.stat_panel.send_message("remove_sdql2")
else if(length(GLOB.sdql2_queries) && (target.stat_tab == "SDQL2" || !("SDQL2" in target.panel_tabs)) && num_fires % default_wait == 0)
set_SDQL2_tab(target)
@@ -97,9 +96,10 @@ SUBSYSTEM_DEF(statpanels)
if(num_fires % default_wait == 0)
set_spells_tab(target, target_mob)
if(target_mob?.listed_turf && num_fires % default_wait == 0)
if(!target_mob.TurfAdjacent(target_mob.listed_turf))
target << output("", "statbrowser:remove_listedturf")
if(!target_mob.TurfAdjacent(target_mob.listed_turf) || isnull(target_mob.listed_turf))
target.stat_panel.send_message("remove_listedturf")
target_mob.listed_turf = null
else if(target.stat_tab == target_mob?.listed_turf.name || !(target_mob?.listed_turf.name in target.panel_tabs))
@@ -109,23 +109,25 @@ SUBSYSTEM_DEF(statpanels)
return
/datum/controller/subsystem/statpanels/proc/set_status_tab(client/target)
if(!encoded_global_data)//statbrowser hasnt fired yet and we were called from immediate_send_stat_data()
if(!global_data)//statbrowser hasnt fired yet and we were called from immediate_send_stat_data()
return
var/ping_str = url_encode("Ping: [round(target.lastping, 1)]ms (Average: [round(target.avgping, 1)]ms)")
var/other_str = url_encode(json_encode(target.mob?.get_status_tab_items()))
target << output("[encoded_global_data];[ping_str];[other_str]", "statbrowser:update")
target.stat_panel.send_message("update_stat", list(
global_data = global_data,
ping_str = "Ping: [round(target.lastping, 1)]ms (Average: [round(target.avgping, 1)]ms)",
other_str = target.mob?.get_status_tab_items(),
))
/datum/controller/subsystem/statpanels/proc/set_MC_tab(client/target)
var/turf/eye_turf = get_turf(target.eye)
var/coord_entry = url_encode(COORD(eye_turf))
if(!mc_data_encoded)
var/coord_entry = COORD(eye_turf)
if(!mc_data)
generate_mc_data()
target << output("[mc_data_encoded];[coord_entry]", "statbrowser:update_mc")
target.stat_panel.send_message("update_mc", list(mc_data = mc_data, coord_entry = coord_entry))
/datum/controller/subsystem/statpanels/proc/set_tickets_tab(client/target)
var/list/ahelp_tickets = GLOB.ahelp_tickets.stat_entry()
target << output("[url_encode(json_encode(ahelp_tickets))];", "statbrowser:update_tickets")
target.stat_panel.send_message("update_tickets", ahelp_tickets)
var/datum/interview_manager/m = GLOB.interviews
// get open interview count
@@ -153,7 +155,7 @@ SUBSYSTEM_DEF(statpanels)
)
// Push update
target << output("[url_encode(json_encode(data))];", "statbrowser:update_interviews")
target.stat_panel.send_message("update_interviews", data)
/datum/controller/subsystem/statpanels/proc/set_SDQL2_tab(client/target)
var/list/sdql2A = list()
@@ -163,7 +165,7 @@ SUBSYSTEM_DEF(statpanels)
sdql2B = query.generate_stat()
sdql2A += sdql2B
target << output(url_encode(json_encode(sdql2A)), "statbrowser:update_sdql2")
target.stat_panel.send_message("update_sdql2", sdql2A)
/datum/controller/subsystem/statpanels/proc/set_spells_tab(client/target, mob/target_mob)
var/list/proc_holders = target_mob.get_proc_holders()
@@ -172,11 +174,7 @@ SUBSYSTEM_DEF(statpanels)
for(var/proc_holder_list as anything in proc_holders)
target.spell_tabs |= proc_holder_list[1]
var/proc_holders_encoded = ""
if(length(proc_holders))
proc_holders_encoded = url_encode(json_encode(proc_holders))
target << output("[url_encode(json_encode(target.spell_tabs))];[proc_holders_encoded]", "statbrowser:update_spells")
target.stat_panel.send_message("update_spells", list(spell_tabs = target.spell_tabs, proc_holders_encoded = proc_holders))
/datum/controller/subsystem/statpanels/proc/set_turf_examine_tab(client/target, mob/target_mob)
var/list/overrides = list()
@@ -213,11 +211,11 @@ SUBSYSTEM_DEF(statpanels)
else
turfitems[++turfitems.len] = list("[turf_content.name]", REF(turf_content))
turfitems = url_encode(json_encode(turfitems))
target << output("[turfitems];", "statbrowser:update_listedturf")
turfitems = turfitems
target.stat_panel.send_message("update_listedturf", turfitems)
/datum/controller/subsystem/statpanels/proc/generate_mc_data()
var/list/mc_data = list(
mc_data = list(
list("CPU:", world.cpu),
list("Instances:", "[num2text(world.contents.len, 10)]"),
list("World Time:", "[world.time]"),
@@ -231,11 +229,10 @@ SUBSYSTEM_DEF(statpanels)
for(var/datum/controller/subsystem/sub_system as anything in Master.subsystems)
mc_data[++mc_data.len] = list("\[[sub_system.state_letter()]][sub_system.name]", sub_system.stat_entry(), "\ref[sub_system]")
mc_data[++mc_data.len] = list("Camera Net", "Cameras: [GLOB.cameranet.cameras.len] | Chunks: [GLOB.cameranet.chunks.len]", "\ref[GLOB.cameranet]")
mc_data_encoded = url_encode(json_encode(mc_data))
///immediately update the active statpanel tab of the target client
/datum/controller/subsystem/statpanels/proc/immediate_send_stat_data(client/target)
if(!target.statbrowser_ready)
if(!target.stat_panel.is_ready())
return FALSE
if(target.stat_tab == "Status")
@@ -249,7 +246,7 @@ SUBSYSTEM_DEF(statpanels)
if(target_mob?.listed_turf)
if(!target_mob.TurfAdjacent(target_mob.listed_turf))
target << output("", "statbrowser:remove_listedturf")
target.stat_panel.send_message("removed_listedturf")
target_mob.listed_turf = null
else if(target.stat_tab == target_mob?.listed_turf.name || !(target_mob?.listed_turf.name in target.panel_tabs))
@@ -268,7 +265,7 @@ SUBSYSTEM_DEF(statpanels)
return TRUE
if(!length(GLOB.sdql2_queries) && ("SDQL2" in target.panel_tabs))
target << output("", "statbrowser:remove_sdql2")
target.stat_panel.send_message("remove_sdql2")
else if(length(GLOB.sdql2_queries) && target.stat_tab == "SDQL2")
set_SDQL2_tab(target)
@@ -277,41 +274,5 @@ SUBSYSTEM_DEF(statpanels)
SIGNAL_HANDLER
SSstatpanels.cached_images -= REF(src)
/// verbs that send information from the browser UI
/client/verb/set_tab(tab as text|null)
set name = "Set Tab"
set hidden = TRUE
stat_tab = tab
SSstatpanels.immediate_send_stat_data(src)
/client/verb/send_tabs(tabs as text|null)
set name = "Send Tabs"
set hidden = TRUE
panel_tabs |= tabs
/client/verb/remove_tabs(tabs as text|null)
set name = "Remove Tabs"
set hidden = TRUE
panel_tabs -= tabs
/client/verb/reset_tabs()
set name = "Reset Tabs"
set hidden = TRUE
panel_tabs = list()
/client/verb/panel_ready()
set name = "Panel Ready"
set hidden = TRUE
statbrowser_ready = TRUE
init_verbs()
/client/verb/update_verbs()
set name = "Update Verbs"
set hidden = TRUE
init_verbs()
/// Stat panel window declaration
/client/var/datum/tgui_window/stat_panel

View File

@@ -819,7 +819,7 @@
if(istype(S, spell))
spell_list -= S
qdel(S)
current?.client << output(null, "statbrowser:check_spells")
current?.client.stat_panel.send_message("check_spells")
/datum/mind/proc/RemoveAllSpells()
for(var/obj/effect/proc_holder/S in spell_list)

View File

@@ -862,7 +862,7 @@ GLOBAL_PROTECT(admin_verbs_hideable)
set name = "Debug Stat Panel"
set category = "Debug"
src << output("", "statbrowser:create_debug")
src.stat_panel.send_message("create_debug")
/client/proc/admin_2fa_verify()
set name = "Verify Admin"

View File

@@ -181,9 +181,6 @@
/// our current tab
var/stat_tab
/// whether our browser is ready or not yet
var/statbrowser_ready = FALSE
/// list of all tabs
var/list/panel_tabs = list()
/// list of tabs containing spells and abilities

View File

@@ -87,7 +87,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
if(href_list["reload_tguipanel"])
nuke_chat()
if(href_list["reload_statbrowser"])
src << browse(file('html/statbrowser.html'), "window=statbrowser")
stat_panel.reinitialize()
// Log all hrefs
log_href("[src] (usr:[usr]\[[COORD(usr)]\]) : [hsrc ? "[hsrc] " : ""][href]")
@@ -213,8 +213,12 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
GLOB.clients += src
GLOB.directory[ckey] = src
// Instantiate stat panel
stat_panel = new(src, "statbrowser")
stat_panel.subscribe(src, .proc/on_stat_panel_message)
// Instantiate tgui panel
tgui_panel = new(src)
tgui_panel = new(src, "browseroutput")
set_right_click_menu_mode(TRUE)
@@ -344,9 +348,15 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
if(SSinput.initialized)
set_macros()
// Initialize tgui panel
src << browse(file('html/statbrowser.html'), "window=statbrowser")
// Initialize stat panel
stat_panel.initialize(
inline_html = file2text('html/statbrowser.html'),
inline_js = file2text('html/statbrowser.js'),
inline_css = file2text('html/statbrowser.css'),
)
addtimer(CALLBACK(src, .proc/check_panel_loaded), 30 SECONDS)
// Initialize tgui panel
tgui_panel.initialize()
if(alert_mob_dupe_login && !holder)
@@ -1129,10 +1139,10 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
continue
panel_tabs |= verb_to_init.category
verblist[++verblist.len] = list(verb_to_init.category, verb_to_init.name)
src << output("[url_encode(json_encode(panel_tabs))];[url_encode(json_encode(verblist))]", "statbrowser:init_verbs")
src.stat_panel.send_message("init_verbs", list(panel_tabs = panel_tabs, verblist = verblist))
/client/proc/check_panel_loaded()
if(statbrowser_ready)
if(stat_panel.is_ready())
return
to_chat(src, span_userdanger("Statpanel failed to load, click <a href='?src=[REF(src)];reload_statbrowser=1'>here</a> to reload the panel "))
@@ -1182,6 +1192,23 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
else
SSambience.remove_ambience_client(src)
/**
* Handles incoming messages from the stat-panel TGUI.
*/
/client/proc/on_stat_panel_message(type, payload)
switch(type)
if("Update-Verbs")
init_verbs()
if("Remove-Tabs")
panel_tabs -= payload["tab"]
if("Send-Tabs")
panel_tabs |= payload["tab"]
if("Reset-Tabs")
panel_tabs = list()
if("Set-Tab")
stat_tab = payload["tab"]
SSstatpanels.immediate_send_stat_data(src)
/// Checks if this client has met the days requirement passed in, or if
/// they are exempt from it.
/// Returns the number of days left, or 0.

View File

@@ -472,14 +472,12 @@
// First we detain them by removing all the verbs they have on client
for (var/v in client.verbs)
var/procpath/verb_path = v
if (!(verb_path in GLOB.stat_panel_verbs))
remove_verb(client, verb_path)
remove_verb(client, verb_path)
// Then remove those on their mob as well
for (var/v in verbs)
var/procpath/verb_path = v
if (!(verb_path in GLOB.stat_panel_verbs))
remove_verb(src, verb_path)
remove_verb(src, verb_path)
// Then we create the interview form and show it to the client
var/datum/interview/I = GLOB.interviews.interview_for_client(client)

View File

@@ -924,7 +924,7 @@
LAZYREMOVE(mob_spell_list, S)
qdel(S)
if(client)
client << output(null, "statbrowser:check_spells")
client.stat_panel.send_message("check_spells")
/**
* Checks to see if the mob can cast normal magic spells.

View File

@@ -94,6 +94,7 @@
window.acquire_lock(src)
if(!window.is_ready())
window.initialize(
strict_mode = TRUE,
fancy = user.client.prefs.read_preference(/datum/preference/toggle/tgui_fancy),
assets = list(
get_asset_datum(/datum/asset/simple/tgui),

View File

@@ -18,6 +18,7 @@
var/message_queue
var/sent_assets = list()
// Vars passed to initialize proc (and saved for later)
var/initial_strict_mode
var/initial_fancy
var/initial_assets
var/initial_inline_html
@@ -47,11 +48,15 @@
* state. You can begin sending messages right after initializing. Messages
* will be put into the queue until the window finishes loading.
*
* optional assets list List of assets to inline into the html.
* optional inline_html string Custom HTML to inject.
* optional fancy bool If TRUE, will hide the window titlebar.
* optional strict_mode bool - Enables strict error handling and BSOD.
* optional fancy bool - If TRUE and if this is NOT a panel, will hide the window titlebar.
* optional assets list - List of assets to load during initialization.
* optional inline_html string - Custom HTML to inject.
* optional inline_js string - Custom JS to inject.
* optional inline_css string - Custom CSS to inject.
*/
/datum/tgui_window/proc/initialize(
strict_mode = FALSE,
fancy = FALSE,
assets = list(),
inline_html = "",
@@ -79,6 +84,7 @@
// Generate page html
var/html = SStgui.basehtml
html = replacetextEx(html, "\[tgui:windowId]", id)
html = replacetextEx(html, "\[tgui:strictMode]", strict_mode)
// Inject assets
var/inline_assets_str = ""
for(var/datum/asset/asset in assets)
@@ -99,7 +105,7 @@
html = replacetextEx(html, "<!-- tgui:inline-html -->", inline_html)
// Inject inline JS
if (inline_js)
inline_js = "<script>\n[inline_js]\n</script>"
inline_js = "<script>\n'use strict';\n[inline_js]\n</script>"
html = replacetextEx(html, "<!-- tgui:inline-js -->", inline_js)
// Inject inline CSS
if (inline_css)
@@ -113,6 +119,20 @@
if(!is_browser)
winset(client, id, "on-close=\"uiclose [id]\"")
/**
* public
*
* Reinitializes the panel with previous data used for initialization.
*/
/datum/tgui_window/proc/reinitialize()
initialize(
strict_mode = initial_strict_mode,
fancy = initial_fancy,
assets = initial_assets,
inline_html = initial_inline_html,
inline_js = initial_inline_js,
inline_css = initial_inline_css)
/**
* public
*
@@ -346,12 +366,7 @@
client << link(href_list["url"])
if("cacheReloaded")
// Reinitialize
initialize(
fancy = initial_fancy,
assets = initial_assets,
inline_html = initial_inline_html,
inline_js = initial_inline_js,
inline_css = initial_inline_css)
reinitialize()
// Resend the assets
for(var/asset in sent_assets)
send_asset(asset)

View File

@@ -13,9 +13,9 @@
var/broken = FALSE
var/initialized_at
/datum/tgui_panel/New(client/client)
/datum/tgui_panel/New(client/client, id)
src.client = client
window = new(client, "browseroutput")
window = new(client, id)
window.subscribe(src, .proc/on_message)
/datum/tgui_panel/Del()
@@ -42,9 +42,11 @@
sleep(1)
initialized_at = world.time
// Perform a clean initialization
window.initialize(assets = list(
get_asset_datum(/datum/asset/simple/tgui_panel),
))
window.initialize(
strict_mode = TRUE,
assets = list(
get_asset_datum(/datum/asset/simple/tgui_panel),
))
window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/fontawesome))
window.send_asset(get_asset_datum(/datum/asset/spritesheet/chat))
// Other setup

227
html/statbrowser.css Normal file
View File

@@ -0,0 +1,227 @@
body {
font-family: Verdana, Geneva, Tahoma, sans-serif;
font-size: 12px !important;
margin: 0 !important;
padding: 0 !important;
overflow-x: hidden;
overflow-y: scroll;
}
body.dark {
background-color: #131313;
color: #b2c4dd;
scrollbar-base-color: #1c1c1c;
scrollbar-face-color: #3b3b3b;
scrollbar-3dlight-color: #252525;
scrollbar-highlight-color: #252525;
scrollbar-track-color: #1c1c1c;
scrollbar-arrow-color: #929292;
scrollbar-shadow-color: #3b3b3b;
}
#menu {
background-color: #F0F0F0;
position: fixed;
width: 100%;
z-index: 100;
}
.dark #menu {
background-color: #202020;
}
#statcontent {
padding: 7px 7px 7px 7px;
}
a {
color: black;
text-decoration: none
}
.dark a {
color: #b2c4dd;
}
a:hover,
.dark a:hover {
text-decoration: underline;
}
ul {
list-style-type: none;
margin: 0;
padding: 0;
background-color: #333;
}
li {
float: left;
}
li a {
display: block;
color: white;
text-align: center;
padding: 14px 16px;
text-decoration: none;
}
li a:hover:not(.active) {
background-color: #111;
}
.button-container {
display: inline-flex;
flex-wrap: wrap-reverse;
flex-direction: row;
align-items: flex-start;
overflow-x: hidden;
white-space: pre-wrap;
padding: 0 4px;
}
.button {
background-color: #dfdfdf;
border: 1px solid #cecece;
border-bottom-width: 2px;
color: rgba(0, 0, 0, 0.7);
padding: 6px 4px 4px;
text-align: center;
text-decoration: none;
font-size: 12px;
margin: 0;
cursor: pointer;
transition-duration: 100ms;
order: 3;
min-width: 40px;
}
.dark button {
background-color: #222222;
border-color: #343434;
color: rgba(255, 255, 255, 0.5);
}
.button:hover {
background-color: #ececec;
transition-duration: 0;
}
.dark button:hover {
background-color: #2e2e2e;
}
.button:active,
.button.active {
background-color: #ffffff;
color: black;
border-top-color: #cecece;
border-left-color: #cecece;
border-right-color: #cecece;
border-bottom-color: #ffffff;
}
.dark .button:active,
.dark .button.active {
background-color: #444444;
color: white;
border-top-color: #343434;
border-left-color: #343434;
border-right-color: #343434;
border-bottom-color: #ffffff;
}
.grid-container {
margin: -2px;
margin-right: -15px;
}
.grid-item {
position: relative;
display: inline-block;
width: 100%;
box-sizing: border-box;
overflow: visible;
padding: 3px 2px;
text-decoration: none;
}
@media only screen and (min-width: 300px) {
.grid-item {
width: 50%;
}
}
@media only screen and (min-width: 430px) {
.grid-item {
width: 33%;
}
}
@media only screen and (min-width: 560px) {
.grid-item {
width: 25%;
}
}
@media only screen and (min-width: 770px) {
.grid-item {
width: 20%;
}
}
.grid-item:hover {
z-index: 1;
}
.grid-item:hover .grid-item-text {
width: auto;
text-decoration: underline;
}
.grid-item-text {
display: inline-block;
width: 100%;
background-color: #ffffff;
margin: 0 -6px;
padding: 0 6px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
pointer-events: none;
}
.dark .grid-item-text {
background-color: #131313;
}
.link {
display: inline;
background: none;
border: none;
padding: 7px 14px;
color: black;
text-decoration: none;
cursor: pointer;
font-size: 13px;
margin: 2px 2px;
}
.dark .link {
color: #abc6ec;
}
.link:hover {
text-decoration: underline;
}
img {
-ms-interpolation-mode: nearest-neighbor;
image-rendering: pixelated;
}
.interview_panel_controls,
.interview_panel_stats {
margin-bottom: 10px;
}

File diff suppressed because it is too large Load Diff

1003
html/statbrowser.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -165,7 +165,6 @@
#include "code\__DEFINES\stat.dm"
#include "code\__DEFINES\stat_tracking.dm"
#include "code\__DEFINES\station.dm"
#include "code\__DEFINES\statpanel.dm"
#include "code\__DEFINES\status_effects.dm"
#include "code\__DEFINES\storage.dm"
#include "code\__DEFINES\strippable.dm"

View File

@@ -261,3 +261,21 @@ Byond.winget(null, 'url').then((serverUrl) => {
Byond.command('.quit');
});
```
## Strict Mode
Strict mode is a flag that you can set on tgui window.
```dm
window.initialize(strict_mode = TRUE)
```
If `TRUE`, unhandled errors and common mistakes result in a blue screen of death with a stack trace of the error, which you can use to debug it. Bluescreened window stops handling incoming messages and closes the active instance of tgui datum if there was one, to avoid a massive spam of errors and help to deal with them one by one.
It can be defined in `window.initialize()` in DM, as shown above, or changed in runtime at runtime via `Byond.strictMode` to `true` or `false`.
```js
Byond.strictMode = true;
```
It is recommended that you keep this **ON** to detect hard to find bugs.

12
tgui/global.d.ts vendored
View File

@@ -71,6 +71,18 @@ type ByondType = {
*/
IS_LTE_IE11: boolean;
/**
* If `true`, unhandled errors and common mistakes result in a blue screen
* of death, which stops this window from handling incoming messages and
* closes the active instance of tgui datum if there was one.
*
* It can be defined in window.initialize() in DM, or changed in runtime
* here via this property to `true` or `false`.
*
* It is recommended that you keep this ON to detect hard to find bugs.
*/
strictMode: boolean;
/**
* Makes a BYOND call.
*

View File

@@ -6,6 +6,7 @@
<!-- Inlined metadata -->
<meta id="tgui:windowId" content="[tgui:windowId]">
<meta id="tgui:strictMode" content="[tgui:strictMode]">
<!-- Early setup -->
<script type="text/javascript">
@@ -64,6 +65,9 @@
Byond.IS_LTE_IE10 = Byond.TRIDENT !== null && Byond.TRIDENT <= 6;
Byond.IS_LTE_IE11 = Byond.TRIDENT !== null && Byond.TRIDENT <= 7;
// Strict mode flag
Byond.strictMode = Boolean(Number(parseMetaTag('tgui:strictMode')));
// Callbacks for asynchronous calls
Byond.__callbacks__ = [];
@@ -220,13 +224,13 @@
};
Byond.subscribeTo = function (type, listener) {
listener = function (_type, payload) {
var _listener = function (_type, payload) {
if (_type === type) {
listener(payload);
}
};
window.update.listeners.push(listener);
window.update.flushQueue(listener);
window.update.flushQueue(_listener);
window.update.listeners.push(_listener);
};
// Asset loaders
@@ -363,6 +367,7 @@
// ------------------------------------------------------
window.onerror = function (msg, url, line, col, error) {
window.onerror.errorCount = (window.onerror.errorCount || 0) + 1;
// Proper stacktrace
var stack = error && error.stack;
// Ghetto stacktrace
@@ -375,39 +380,51 @@ window.onerror = function (msg, url, line, col, error) {
// Augment the stack
stack = window.__augmentStack__(stack, error);
// Print error to the page
var errorRoot = document.getElementById('FatalError');
var errorStack = document.getElementById('FatalError__stack');
if (errorRoot) {
errorRoot.className = 'FatalError FatalError--visible';
if (window.onerror.__stack__) {
window.onerror.__stack__ += '\n\n' + stack;
if (Byond.strictMode) {
var errorRoot = document.getElementById('FatalError');
var errorStack = document.getElementById('FatalError__stack');
if (errorRoot) {
errorRoot.className = 'FatalError FatalError--visible';
if (window.onerror.__stack__) {
window.onerror.__stack__ += '\n\n' + stack;
}
else {
window.onerror.__stack__ = stack;
}
var textProp = Byond.IS_LTE_IE8 ? 'innerText' : 'textContent';
errorStack[textProp] = window.onerror.__stack__;
}
else {
window.onerror.__stack__ = stack;
}
var textProp = Byond.IS_LTE_IE8 ? 'innerText' : 'textContent';
errorStack[textProp] = window.onerror.__stack__;
// Set window geometry
var setFatalErrorGeometry = function () {
Byond.winset(Byond.windowId, {
titlebar: true,
'is-visible': true,
'can-resize': true,
});
};
setFatalErrorGeometry();
setInterval(setFatalErrorGeometry, 1000);
}
// Set window geometry
var setFatalErrorGeometry = function () {
Byond.winset(Byond.windowId, {
titlebar: true,
size: '600x600',
'is-visible': true,
'can-resize': true,
});
};
setFatalErrorGeometry();
setInterval(setFatalErrorGeometry, 1000);
// Send logs to the game server
Byond.sendMessage({
type: 'log',
fatal: 1,
message: stack,
});
if (Byond.strictMode) {
Byond.sendMessage({
type: 'log',
fatal: 1,
message: stack,
});
}
else if (window.onerror.errorCount <= 1) {
stack += '\nWindow is in non-strict mode, future errors are suppressed.';
Byond.sendMessage({
type: 'log',
message: stack,
});
}
// Short-circuit further updates
window.__updateQueue__ = [];
window.update = function () {};
if (Byond.strictMode) {
window.update = function () {};
window.update.queue = [];
}
// Prevent default action
return true;
};