diff --git a/code/__DEFINES/statpanel.dm b/code/__DEFINES/statpanel.dm deleted file mode 100644 index 7988b9b5c83..00000000000 --- a/code/__DEFINES/statpanel.dm +++ /dev/null @@ -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 -)) diff --git a/code/__HELPERS/verbs.dm b/code/__HELPERS/verbs.dm index 5a3df642c7d..d042929f118 100644 --- a/code/__HELPERS/verbs.dm +++ b/code/__HELPERS/verbs.dm @@ -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) diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm index 005c8467053..97466b9c851 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -403,7 +403,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) @@ -425,7 +425,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) diff --git a/code/controllers/subsystem/statpanel.dm b/code/controllers/subsystem/statpanel.dm index a5aaad9f3a4..b57b383f067 100644 --- a/code/controllers/subsystem/statpanel.dm +++ b/code/controllers/subsystem/statpanel.dm @@ -5,14 +5,26 @@ 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 = 6 + ///how many subsystem fires between updates of the MC tab + var/mc_wait = 5 + /// how many subsystem fires between updates of the turf examine tab + var/turf_wait = 2 + ///how many full runs this subsystem has completed. used for variable rate refreshes. + var/num_fires = 0 + /datum/controller/subsystem/statpanels/fire(resumed = FALSE) if (!resumed) + num_fires++ var/datum/map_config/cached = SSmapping.next_map_config - 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"]", @@ -26,129 +38,166 @@ 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") - 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") + + 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") - if(target.stat_tab == "MC") - var/turf/eye_turf = get_turf(target.eye) - var/coord_entry = url_encode(COORD(eye_turf)) - if(!mc_data_encoded) - generate_mc_data() - target << output("[mc_data_encoded];[coord_entry]", "statbrowser:update_mc") - if(target.stat_tab == "Tickets") - var/list/ahelp_tickets = GLOB.ahelp_tickets.stat_entry() - target << output("[url_encode(json_encode(ahelp_tickets))];", "statbrowser:update_tickets") - var/datum/interview_manager/m = GLOB.interviews - - // get open interview count - var/dc = 0 - for (var/ckey in m.open_interviews) - var/datum/interview/I = m.open_interviews[ckey] - if (I && !I.owner) - dc++ - var/stat_string = "([m.open_interviews.len - dc] online / [dc] disconnected)" - - // Prepare each queued interview - var/list/queued = list() - for (var/datum/interview/I in m.interview_queue) - queued += list(list( - "ref" = REF(I), - "status" = "\[[I.pos_in_queue]\]: [I.owner_ckey][!I.owner ? " (DC)": ""] \[INT-[I.id]\]" - )) - - var/list/data = list( - "status" = list( - "Active:" = "[m.open_interviews.len] [stat_string]", - "Queued:" = "[m.interview_queue.len]", - "Closed:" = "[m.closed_interviews.len]"), - "interviews" = queued - ) - - // Push update - target << output("[url_encode(json_encode(data))];", "statbrowser:update_interviews") + target.stat_panel.send_message("add_admin_tabs", target.holder.href_token) + + if(target.stat_tab == "MC" && ((num_fires % mc_wait == 0))) + set_MC_tab(target) + + if(target.stat_tab == "Tickets" && num_fires % default_wait == 0) + set_tickets_tab(target) + if(!length(GLOB.sdql2_queries) && ("SDQL2" in target.panel_tabs)) - target << output("", "statbrowser:remove_sdql2") - else if(length(GLOB.sdql2_queries) && (target.stat_tab == "SDQL2" || !("SDQL2" in target.panel_tabs))) - var/list/sdql2A = list() - sdql2A[++sdql2A.len] = list("", "Access Global SDQL2 List", REF(GLOB.sdql2_vv_statobj)) - var/list/sdql2B = list() - for(var/i in GLOB.sdql2_queries) - var/datum/sdql2_query/Q = i - sdql2B = Q.generate_stat() - sdql2A += sdql2B - target << output(url_encode(json_encode(sdql2A)), "statbrowser:update_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) + if(target.mob) - var/mob/M = target.mob - if((target.stat_tab in target.spell_tabs) || !length(target.spell_tabs) && (length(M.mob_spell_list) || length(M.mind?.spell_list))) - var/list/proc_holders = M.get_proc_holders() - target.spell_tabs.Cut() - for(var/phl in proc_holders) - var/list/proc_holder_list = phl - 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") - if(M?.listed_turf) - var/mob/target_mob = M - if(!target_mob.TurfAdjacent(target_mob.listed_turf)) - target << output("", "statbrowser:remove_listedturf") + var/mob/target_mob = target.mob + if((target.stat_tab in target.spell_tabs) || !length(target.spell_tabs) && (length(target_mob.mob_spell_list) || length(target_mob.mind?.spell_list))) + if(num_fires % default_wait == 0) + set_spells_tab(target, target_mob) + + + if(target_mob?.listed_turf && num_fires % turf_wait == 0) + 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 == M?.listed_turf.name || !(M?.listed_turf.name in target.panel_tabs)) - var/list/overrides = list() - var/list/turfitems = list() - for(var/img in target.images) - var/image/target_image = img - if(!target_image.loc || target_image.loc.loc != target_mob.listed_turf || !target_image.override) - continue - overrides += target_image.loc - turfitems[++turfitems.len] = list("[target_mob.listed_turf]", REF(target_mob.listed_turf), icon2html(target_mob.listed_turf, target, sourceonly=TRUE)) - for(var/tc in target_mob.listed_turf) - var/atom/movable/turf_content = tc - if(turf_content.mouse_opacity == MOUSE_OPACITY_TRANSPARENT) - continue - if(turf_content.invisibility > target_mob.see_invisible) - continue - if(turf_content in overrides) - continue - if(turf_content.IsObscured()) - continue - if(length(turfitems) < 30) // only create images for the first 30 items on the turf, for performance reasons - if(!(REF(turf_content) in cached_images)) - cached_images += REF(turf_content) - turf_content.RegisterSignal(turf_content, COMSIG_PARENT_QDELETING, TYPE_PROC_REF(/atom, remove_from_cache)) // we reset cache if anything in it gets deleted - if(ismob(turf_content) || length(turf_content.overlays) > 2) - turfitems[++turfitems.len] = list("[turf_content.name]", REF(turf_content), costly_icon2html(turf_content, target, sourceonly=TRUE)) - else - turfitems[++turfitems.len] = list("[turf_content.name]", REF(turf_content), icon2html(turf_content, target, sourceonly=TRUE)) - else - turfitems[++turfitems.len] = list("[turf_content.name]", REF(turf_content)) - else - turfitems[++turfitems.len] = list("[turf_content.name]", REF(turf_content)) - turfitems = url_encode(json_encode(turfitems)) - target << output("[turfitems];", "statbrowser:update_listedturf") + + else if(target.stat_tab == target_mob?.listed_turf.name || !(target_mob?.listed_turf.name in target.panel_tabs)) + set_turf_examine_tab(target, target_mob) + if(MC_TICK_CHECK) return +/datum/controller/subsystem/statpanels/proc/set_status_tab(client/target) + if(!global_data)//statbrowser hasnt fired yet and we were called from immediate_send_stat_data() + return + + 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 = COORD(eye_turf) + if(!mc_data) + generate_mc_data() + 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.stat_panel.send_message("update_tickets", ahelp_tickets) + var/datum/interview_manager/m = GLOB.interviews + + // get open interview count + var/dc = 0 + for (var/ckey in m.open_interviews) + var/datum/interview/current_interview = m.open_interviews[ckey] + if (current_interview && !current_interview.owner) + dc++ + var/stat_string = "([m.open_interviews.len - dc] online / [dc] disconnected)" + + // Prepare each queued interview + var/list/queued = list() + for (var/datum/interview/queued_interview in m.interview_queue) + queued += list(list( + "ref" = REF(queued_interview), + "status" = "\[[queued_interview.pos_in_queue]\]: [queued_interview.owner_ckey][!queued_interview.owner ? " (DC)": ""] \[INT-[queued_interview.id]\]" + )) + + var/list/data = list( + "status" = list( + "Active:" = "[m.open_interviews.len] [stat_string]", + "Queued:" = "[m.interview_queue.len]", + "Closed:" = "[m.closed_interviews.len]"), + "interviews" = queued + ) + + // Push update + target.stat_panel.send_message("update_interviews", data) + +/datum/controller/subsystem/statpanels/proc/set_SDQL2_tab(client/target) + var/list/sdql2A = list() + sdql2A[++sdql2A.len] = list("", "Access Global SDQL2 List", REF(GLOB.sdql2_vv_statobj)) + var/list/sdql2B = list() + for(var/datum/sdql2_query/query as anything in GLOB.sdql2_queries) + sdql2B = query.generate_stat() + + sdql2A += sdql2B + 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() + target.spell_tabs.Cut() + + for(var/proc_holder_list as anything in proc_holders) + target.spell_tabs |= proc_holder_list[1] + + 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() + var/list/turfitems = list() + for(var/image/target_image as anything in target.images) + if(!target_image.loc || target_image.loc.loc != target_mob.listed_turf || !target_image.override) + continue + overrides += target_image.loc + + turfitems[++turfitems.len] = list("[target_mob.listed_turf]", REF(target_mob.listed_turf), icon2html(target_mob.listed_turf, target, sourceonly=TRUE)) + + for(var/atom/movable/turf_content as anything in target_mob.listed_turf) + if(turf_content.mouse_opacity == MOUSE_OPACITY_TRANSPARENT) + continue + if(turf_content.invisibility > target_mob.see_invisible) + continue + if(turf_content in overrides) + continue + if(turf_content.IsObscured()) + continue + + if(length(turfitems) < 10) // only create images for the first 10 items on the turf, for performance reasons + var/turf_content_ref = REF(turf_content) + if(!(turf_content_ref in cached_images)) + cached_images += turf_content_ref + turf_content.RegisterSignal(turf_content, COMSIG_PARENT_QDELETING, /atom/.proc/remove_from_cache) // we reset cache if anything in it gets deleted + + if(ismob(turf_content) || length(turf_content.overlays) > 2) + turfitems[++turfitems.len] = list("[turf_content.name]", turf_content_ref, costly_icon2html(turf_content, target, sourceonly=TRUE)) + else + turfitems[++turfitems.len] = list("[turf_content.name]", turf_content_ref, icon2html(turf_content, target, sourceonly=TRUE)) + else + turfitems[++turfitems.len] = list("[turf_content.name]", turf_content_ref) + else + turfitems[++turfitems.len] = list("[turf_content.name]", REF(turf_content)) + + 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]"), @@ -159,50 +208,53 @@ SUBSYSTEM_DEF(statpanels) list("Failsafe Controller:", Failsafe.stat_entry(), "\ref[Failsafe]"), list("","") ) - for(var/ss in Master.subsystems) - var/datum/controller/subsystem/sub_system = ss + 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)) -/atom/proc/remove_from_cache() - 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 +///immediately update the active statpanel tab of the target client +/datum/controller/subsystem/statpanels/proc/immediate_send_stat_data(client/target) + if(!target.stat_panel.is_ready()) + return FALSE - stat_tab = tab + if(target.stat_tab == "Status") + set_status_tab(target) + return TRUE -/client/verb/send_tabs(tabs as text|null) - set name = "Send Tabs" - set hidden = TRUE + var/mob/target_mob = target.mob + if((target.stat_tab in target.spell_tabs) || !length(target.spell_tabs) && (length(target_mob.mob_spell_list) || length(target_mob.mind?.spell_list))) + set_spells_tab(target, target_mob) + return TRUE - panel_tabs |= tabs + if(target_mob?.listed_turf) + if(!target_mob.TurfAdjacent(target_mob.listed_turf)) + target.stat_panel.send_message("removed_listedturf") + target_mob.listed_turf = null -/client/verb/remove_tabs(tabs as text|null) - set name = "Remove Tabs" - set hidden = TRUE + else if(target.stat_tab == target_mob?.listed_turf.name || !(target_mob?.listed_turf.name in target.panel_tabs)) + set_turf_examine_tab(target, target_mob) + return TRUE - panel_tabs -= tabs + if(!target.holder) + return FALSE -/client/verb/reset_tabs() - set name = "Reset Tabs" - set hidden = TRUE + if(target.stat_tab == "MC") + set_MC_tab(target) + return TRUE - panel_tabs = list() + if(target.stat_tab == "Tickets") + set_tickets_tab(target) + return TRUE -/client/verb/panel_ready() - set name = "Panel Ready" - set hidden = TRUE + if(!length(GLOB.sdql2_queries) && ("SDQL2" in target.panel_tabs)) + target.stat_panel.send_message("remove_sdql2") - statbrowser_ready = TRUE - init_verbs() + else if(length(GLOB.sdql2_queries) && target.stat_tab == "SDQL2") + set_SDQL2_tab(target) -/client/verb/update_verbs() - set name = "Update Verbs" - set hidden = TRUE +/atom/proc/remove_from_cache() + SIGNAL_HANDLER + SSstatpanels.cached_images -= REF(src) - init_verbs() +/// Stat panel window declaration +/client/var/datum/tgui_window/stat_panel diff --git a/code/datums/mind.dm b/code/datums/mind.dm index e3e3cde53fd..37f0596f2af 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -739,7 +739,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) diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index 41413dd2771..17e3bcf7583 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -19,29 +19,33 @@ if(!check_rights(0)) return - var/dat = "
Game Panel

" + var/dat if(SSticker.current_state <= GAME_STATE_PREGAME) - dat += "(Force Roundstart Rulesets)
" + dat += "(Manage Dynamic Rulesets)
" + dat += "(Force Roundstart Rulesets)
" if (GLOB.dynamic_forced_roundstart_ruleset.len > 0) for(var/datum/dynamic_ruleset/roundstart/rule in GLOB.dynamic_forced_roundstart_ruleset) - dat += {"-> [rule.name] <-
"} - dat += "(Clear Rulesets)
" - dat += "(Dynamic mode options)
" - dat += "
" + dat += {"-> [rule.name] <-
"} + dat += "(Clear Rulesets)
" + dat += "(Dynamic mode options)
" + dat += "
" if(SSticker.IsRoundInProgress()) - dat += "(Game Mode Panel)
" + dat += "(Game Mode Panel)
" + dat += "(Manage Dynamic Rulesets)
" + dat += "
" dat += {" -
- Create Object
- Quick Create Object
- Create Turf
- Create Mob
+ Create Object
+ Quick Create Object
+ Create Turf
+ Create Mob
"} if(marked_datum && istype(marked_datum, /atom)) - dat += "Duplicate Marked Datum
" + dat += "Duplicate Marked Datum
" - usr << browse(dat, "window=admin2;size=240x280") + var/datum/browser/browser = new(usr, "admin2", "Game Panel", 240, 280) + browser.set_content(dat) + browser.open() return ////////////////////////////////////////////////////////////////////////////////////////////////ADMIN HELPER PROCS diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 863964ed3ef..61b0ede486e 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -807,7 +807,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" diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm index cc1177759d3..83988fc9f54 100644 --- a/code/modules/client/client_defines.dm +++ b/code/modules/client/client_defines.dm @@ -153,9 +153,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 diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 7fa8139f90e..844b53545be 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -83,7 +83,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]") @@ -208,6 +208,10 @@ 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) @@ -324,9 +328,15 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( if(SSinput.initialized) set_macros() + // 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 - src << browse(file('html/statbrowser.html'), "window=statbrowser") - addtimer(CALLBACK(src, PROC_REF(check_panel_loaded)), 30 SECONDS) tgui_panel.initialize() if(alert_mob_dupe_login && !holder) @@ -1065,12 +1075,10 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( var/list/verbstoprocess = verbs.Copy() if(mob) verbstoprocess += mob.verbs - for(var/AM in mob.contents) - var/atom/movable/thing = AM + for(var/atom/movable/thing as anything in mob.contents) verbstoprocess += thing.verbs panel_tabs.Cut() // panel_tabs get reset in init_verbs on JS side anyway - for(var/thing in verbstoprocess) - var/procpath/verb_to_init = thing + for(var/procpath/verb_to_init as anything in verbstoprocess) if(!verb_to_init) continue if(verb_to_init.hidden) @@ -1079,10 +1087,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 here to reload the panel ")) @@ -1135,6 +1143,23 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( else SSambience.ambience_listening_clients -= 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. diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm index 5b12f751b01..62d77d3e5d9 100644 --- a/code/modules/mob/dead/new_player/new_player.dm +++ b/code/modules/mob/dead/new_player/new_player.dm @@ -510,14 +510,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) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index d1ddce7b409..cb65108d854 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -954,7 +954,7 @@ LAZYREMOVE(mob_spell_list, S) qdel(S) if(client) - client << output(null, "statbrowser:check_spells") + client.stat_panel.send_message("check_spells") ///Return any anti magic atom on this mob that matches the magic type /mob/proc/anti_magic_check(magic = TRUE, holy = FALSE, tinfoil = FALSE, chargecost = 1, self = FALSE) diff --git a/html/statbrowser.css b/html/statbrowser.css new file mode 100644 index 00000000000..dc693f42f75 --- /dev/null +++ b/html/statbrowser.css @@ -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; +} diff --git a/html/statbrowser.html b/html/statbrowser.html index b32c657679d..1aea8811d58 100644 --- a/html/statbrowser.html +++ b/html/statbrowser.html @@ -1,1272 +1,3 @@ - - - -Stat Browser - - - - - - -
- - - diff --git a/html/statbrowser.js b/html/statbrowser.js new file mode 100644 index 00000000000..d024d50b8c3 --- /dev/null +++ b/html/statbrowser.js @@ -0,0 +1,1003 @@ +// Polyfills and compatibility ------------------------------------------------ +var decoder = decodeURIComponent || unescape; +if (!Array.prototype.includes) { + Array.prototype.includes = function (thing) { + for (var i = 0; i < this.length; i++) { + if (this[i] == thing) return true; + } + return false; + } +} +if (!String.prototype.trim) { + String.prototype.trim = function () { + return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); + }; +} + +// Status panel implementation ------------------------------------------------ +var status_tab_parts = ["Loading..."]; +var current_tab = null; +var mc_tab_parts = [["Loading...", ""]]; +var href_token = null; +var spells = []; +var spell_tabs = []; +var verb_tabs = []; +var verbs = [["", ""]]; // list with a list inside +var tickets = []; +var interviewManager = { status: "", interviews: [] }; +var sdql2 = []; +var permanent_tabs = []; // tabs that won't be cleared by wipes +var turfcontents = []; +var turfname = ""; +var imageRetryDelay = 500; +var imageRetryLimit = 50; +var menu = document.getElementById('menu'); +var under_menu = document.getElementById('under_menu'); +var statcontentdiv = document.getElementById('statcontent'); +var storedimages = []; +var split_admin_tabs = false; + +// Any BYOND commands that could result in the client's focus changing go through this +// to ensure that when we relinquish our focus, we don't do it after the result of +// a command has already taken focus for itself. +function run_after_focus(callback) { + setTimeout(callback, 0); +} + +function createStatusTab(name) { + if (name.indexOf(".") != -1) { + var splitName = name.split("."); + if (split_admin_tabs && splitName[0] === "Admin") + name = splitName[1]; + else + name = splitName[0]; + } + if (document.getElementById(name) || name.trim() == "") { + return; + } + if (!verb_tabs.includes(name) && !permanent_tabs.includes(name)) { + return; + } + var B = document.createElement("BUTTON"); + B.onclick = function () { + tab_change(name); + this.blur(); + }; + B.id = name; + B.textContent = name; + B.className = "button"; + //ORDERING ALPHABETICALLY + B.style.order = name.charCodeAt(0); + if (name == "Status" || name == "MC") { + B.style.order = name == "Status" ? 1 : 2; + } + //END ORDERING + menu.appendChild(B); + SendTabToByond(name); + under_menu.style.height = menu.clientHeight + 'px'; +} + +function removeStatusTab(name) { + if (!document.getElementById(name) || permanent_tabs.includes(name)) { + return; + } + for (var i = verb_tabs.length - 1; i >= 0; --i) { + if (verb_tabs[i] == name) { + verb_tabs.splice(i, 1); + } + } + menu.removeChild(document.getElementById(name)); + TakeTabFromByond(name); + under_menu.style.height = menu.clientHeight + 'px'; +} + +function sortVerbs() { + verbs.sort(function (a, b) { + var selector = a[0] == b[0] ? 1 : 0; + if (a[selector].toUpperCase() < b[selector].toUpperCase()) { + return 1; + } + else if (a[selector].toUpperCase() > b[selector].toUpperCase()) { + return -1; + } + return 0; + }) +} + +window.onresize = function () { + under_menu.style.height = menu.clientHeight + 'px'; +} + +function addPermanentTab(name) { + if (!permanent_tabs.includes(name)) { + permanent_tabs.push(name); + } + createStatusTab(name); +} + +function removePermanentTab(name) { + for (var i = permanent_tabs.length - 1; i >= 0; --i) { + if (permanent_tabs[i] == name) { + permanent_tabs.splice(i, 1); + } + } + removeStatusTab(name); +} + +function checkStatusTab() { + for (var i = 0; i < menu.children.length; i++) { + if (!verb_tabs.includes(menu.children[i].id) && !permanent_tabs.includes(menu.children[i].id)) { + menu.removeChild(menu.children[i]); + } + } +} + +function remove_verb(v) { + var verb_to_remove = v; // to_remove = [verb:category, verb:name] + for (var i = verbs.length - 1; i >= 0; i--) { + var part_to_remove = verbs[i]; + if (part_to_remove[1] == verb_to_remove[1]) { + verbs.splice(i, 1) + } + } +} + +function check_verbs() { + for (var v = verb_tabs.length - 1; v >= 0; v--) { + verbs_cat_check(verb_tabs[v]); + } +} + +function verbs_cat_check(cat) { + var tabCat = cat; + if (cat.indexOf(".") != -1) { + var splitName = cat.split("."); + if (split_admin_tabs && splitName[0] === "Admin") + tabCat = splitName[1]; + else + tabCat = splitName[0]; + } + var verbs_in_cat = 0; + var verbcat = ""; + if (!verb_tabs.includes(tabCat)) { + removeStatusTab(tabCat); + return; + } + for (var v = 0; v < verbs.length; v++) { + var part = verbs[v]; + verbcat = part[0]; + if (verbcat.indexOf(".") != -1) { + var splitName = verbcat.split("."); + if (split_admin_tabs && splitName[0] === "Admin") + verbcat = splitName[1]; + else + verbcat = splitName[0]; + } + if (verbcat != tabCat || verbcat.trim() == "") { + continue; + } + else { + verbs_in_cat = 1; + break; // we only need one + } + } + if (verbs_in_cat != 1) { + removeStatusTab(tabCat); + if (current_tab == tabCat) + tab_change("Status"); + } +} + +function findVerbindex(name, verblist) { + for (var i = 0; i < verblist.length; i++) { + var part = verblist[i]; + if (part[1] == name) + return i; + } +} +function wipe_verbs() { + verbs = [["", ""]]; + verb_tabs = []; + checkStatusTab(); // remove all empty verb tabs +} + +function update_verbs() { + wipe_verbs(); + Byond.sendMessage("Update-Verbs"); +} + +function SendTabsToByond() { + var tabstosend = []; + tabstosend = tabstosend.concat(permanent_tabs, verb_tabs); + for (var i = 0; i < tabstosend.length; i++) { + SendTabToByond(tabstosend[i]); + } +} + +function SendTabToByond(tab) { + Byond.sendMessage("Send-Tabs", {tab: tab}); +} + +//Byond can't have this tab anymore since we're removing it +function TakeTabFromByond(tab) { + Byond.sendMessage("Remove-Tabs", {tab: tab}); +} + +function spell_cat_check(cat) { + var spells_in_cat = 0; + var spellcat = ""; + for (var s = 0; s < spells.length; s++) { + var spell = spells[s]; + spellcat = spell[0]; + if (spellcat == cat) { + spells_in_cat++; + } + } + if (spells_in_cat < 1) { + removeStatusTab(cat); + } +} + +function tab_change(tab) { + if (tab == current_tab) return; + if (document.getElementById(current_tab)) + document.getElementById(current_tab).className = "button"; // disable active on last button + current_tab = tab; + set_byond_tab(tab); + if (document.getElementById(tab)) + document.getElementById(tab).className = "button active"; // make current button active + var spell_tabs_thingy = (spell_tabs.includes(tab)); + var verb_tabs_thingy = (verb_tabs.includes(tab)); + if (tab == "Status") { + draw_status(); + } else if (tab == "MC") { + draw_mc(); + } else if (spell_tabs_thingy) { + draw_spells(tab); + } else if (verb_tabs_thingy) { + draw_verbs(tab); + } else if (tab == "Debug Stat Panel") { + draw_debug(); + } else if (tab == "Tickets") { + draw_tickets(); + draw_interviews(); + } else if (tab == "SDQL2") { + draw_sdql2(); + } else if (tab == turfname) { + draw_listedturf(); + } else { + statcontentdiv.textContext = "Loading..."; + } + Byond.winset(Byond.windowId, { + 'is-visible': true, + }); +} + +function set_byond_tab(tab) { + Byond.sendMessage("Set-Tab", {tab: tab}); +} + +function draw_debug() { + statcontentdiv.textContent = ""; + var wipeverbstabs = document.createElement("div"); + var link = document.createElement("a"); + link.onclick = function () { wipe_verbs() }; + link.textContent = "Wipe All Verbs"; + wipeverbstabs.appendChild(link); + document.getElementById("statcontent").appendChild(wipeverbstabs); + var wipeUpdateVerbsTabs = document.createElement("div"); + var updateLink = document.createElement("a"); + updateLink.onclick = function () { update_verbs() }; + updateLink.textContent = "Wipe and Update All Verbs"; + wipeUpdateVerbsTabs.appendChild(updateLink); + document.getElementById("statcontent").appendChild(wipeUpdateVerbsTabs); + var text = document.createElement("div"); + text.textContent = "Verb Tabs:"; + document.getElementById("statcontent").appendChild(text); + var table1 = document.createElement("table"); + for (var i = 0; i < verb_tabs.length; i++) { + var part = verb_tabs[i]; + // Hide subgroups except admin subgroups if they are split + if (verb_tabs[i].lastIndexOf(".") != -1) { + var splitName = verb_tabs[i].split("."); + if (split_admin_tabs && splitName[0] === "Admin") + part = splitName[1]; + else + continue; + } + var tr = document.createElement("tr"); + var td1 = document.createElement("td"); + td1.textContent = part; + var a = document.createElement("a"); + a.onclick = function (part) { + return function () { removeStatusTab(part) }; + }(part); + a.textContent = " Delete Tab " + part; + td1.appendChild(a); + tr.appendChild(td1); + table1.appendChild(tr); + } + document.getElementById("statcontent").appendChild(table1); + var header2 = document.createElement("div"); + header2.textContent = "Verbs:"; + document.getElementById("statcontent").appendChild(header2); + var table2 = document.createElement("table"); + for (var v = 0; v < verbs.length; v++) { + var part2 = verbs[v]; + var trr = document.createElement("tr"); + var tdd1 = document.createElement("td"); + tdd1.textContent = part2[0]; + var tdd2 = document.createElement("td"); + tdd2.textContent = part2[1]; + trr.appendChild(tdd1); + trr.appendChild(tdd2); + table2.appendChild(trr); + } + document.getElementById("statcontent").appendChild(table2); + var text3 = document.createElement("div"); + text3.textContent = "Permanent Tabs:"; + document.getElementById("statcontent").appendChild(text3); + var table3 = document.createElement("table"); + for (var i = 0; i < permanent_tabs.length; i++) { + var part3 = permanent_tabs[i]; + var trrr = document.createElement("tr"); + var tddd1 = document.createElement("td"); + tddd1.textContent = part3; + trrr.appendChild(tddd1); + table3.appendChild(trrr); + } + document.getElementById("statcontent").appendChild(table3); + +} +function draw_status() { + if (!document.getElementById("Status")) { + createStatusTab("Status"); + current_tab = "Status"; + } + statcontentdiv.textContent = ''; + for (var i = 0; i < status_tab_parts.length; i++) { + if (status_tab_parts[i].trim() == "") { + document.getElementById("statcontent").appendChild(document.createElement("br")); + } else { + var div = document.createElement("div"); + div.textContent = status_tab_parts[i]; + document.getElementById("statcontent").appendChild(div); + } + } + if (verb_tabs.length == 0 || !verbs) { + Byond.command("Fix-Stat-Panel"); + } +} + +function draw_mc() { + statcontentdiv.textContent = ""; + var table = document.createElement("table"); + for (var i = 0; i < mc_tab_parts.length; i++) { + var part = mc_tab_parts[i]; + var tr = document.createElement("tr"); + var td1 = document.createElement("td"); + td1.textContent = part[0]; + var td2 = document.createElement("td"); + if (part[2]) { + var a = document.createElement("a"); + a.href = "?_src_=vars;admin_token=" + href_token + ";Vars=" + part[2]; + a.textContent = part[1]; + td2.appendChild(a); + } else { + td2.textContent = part[1]; + } + tr.appendChild(td1); + tr.appendChild(td2); + table.appendChild(tr); + } + document.getElementById("statcontent").appendChild(table); +} + +function remove_tickets() { + if (tickets) { + tickets = []; + removePermanentTab("Tickets"); + if (current_tab == "Tickets") + tab_change("Status"); + } + checkStatusTab(); +} + +function remove_sdql2() { + if (sdql2) { + sdql2 = []; + removePermanentTab("SDQL2"); + if (current_tab == "SDQL2") + tab_change("Status"); + } + checkStatusTab(); +} + +function remove_interviews() { + if (tickets) { + tickets = []; + } + checkStatusTab(); +} + +function iconError(e) { + if(current_tab != turfname) { + return; + } + setTimeout(function () { + var node = e.target; + var current_attempts = Number(node.getAttribute("data-attempts")) || 0 + if (current_attempts > imageRetryLimit) { + return; + } + var src = node.src; + node.src = null; + node.src = src + '#' + current_attempts; + node.setAttribute("data-attempts", current_attempts + 1) + draw_listedturf(); + }, imageRetryDelay); +} + +function draw_listedturf() { + statcontentdiv.textContent = ""; + var table = document.createElement("table"); + for (var i = 0; i < turfcontents.length; i++) { + var part = turfcontents[i]; + if (storedimages[part[1]] == null && part[2]) { + var img = document.createElement("img"); + img.src = part[2]; + img.id = part[1]; + storedimages[part[1]] = part[2]; + img.onerror = iconError; + table.appendChild(img); + } else { + var img = document.createElement("img"); + img.onerror = iconError; + img.src = storedimages[part[1]]; + img.id = part[1]; + table.appendChild(img); + } + var b = document.createElement("div"); + var clickcatcher = ""; + b.className = "link"; + b.onmousedown = function (part) { + // The outer function is used to close over a fresh "part" variable, + // rather than every onmousedown getting the "part" of the last entry. + return function (e) { + e.preventDefault(); + clickcatcher = "?src=" + part[1]; + switch (e.button) { + case 1: + clickcatcher += ";statpanel_item_click=middle" + break; + case 2: + clickcatcher += ";statpanel_item_click=right" + break; + default: + clickcatcher += ";statpanel_item_click=left" + } + if (e.shiftKey) { + clickcatcher += ";statpanel_item_shiftclick=1"; + } + if (e.ctrlKey) { + clickcatcher += ";statpanel_item_ctrlclick=1"; + } + if (e.altKey) { + clickcatcher += ";statpanel_item_altclick=1"; + } + window.location.href = clickcatcher; + } + }(part); + b.textContent = part[0]; + table.appendChild(b); + table.appendChild(document.createElement("br")); + } + document.getElementById("statcontent").appendChild(table); +} + +function remove_listedturf() { + removePermanentTab(turfname); + checkStatusTab(); + if (current_tab == turfname) { + tab_change("Status"); + } +} + +function remove_mc() { + removeStatusTab("MC"); + if (current_tab == "MC") { + tab_change("Status"); + } +}; + +function draw_sdql2() { + statcontentdiv.textContent = ""; + var table = document.createElement("table"); + for (var i = 0; i < sdql2.length; i++) { + var part = sdql2[i]; + var tr = document.createElement("tr"); + var td1 = document.createElement("td"); + td1.textContent = part[0]; + var td2 = document.createElement("td"); + if (part[2]) { + var a = document.createElement("a"); + a.href = "?src=" + part[2] + ";statpanel_item_click=left"; + a.textContent = part[1]; + td2.appendChild(a); + } else { + td2.textContent = part[1]; + } + tr.appendChild(td1); + tr.appendChild(td2); + table.appendChild(tr); + } + document.getElementById("statcontent").appendChild(table); +} + +function draw_tickets() { + statcontentdiv.textContent = ""; + var table = document.createElement("table"); + if (!tickets) { + return; + } + for (var i = 0; i < tickets.length; i++) { + var part = tickets[i]; + var tr = document.createElement("tr"); + var td1 = document.createElement("td"); + td1.textContent = part[0]; + var td2 = document.createElement("td"); + if (part[2]) { + var a = document.createElement("a"); + a.href = "?_src_=holder;admin_token=" + href_token + ";ahelp=" + part[2] + ";ahelp_action=ticket;statpanel_item_click=left;action=ticket"; + a.textContent = part[1]; + td2.appendChild(a); + } else if (part[3]) { + var a = document.createElement("a"); + a.href = "?src=" + part[3] + ";statpanel_item_click=left"; + a.textContent = part[1]; + td2.appendChild(a); + } else { + td2.textContent = part[1]; + } + tr.appendChild(td1); + tr.appendChild(td2); + table.appendChild(tr); + } + document.getElementById("statcontent").appendChild(table); +} + +function draw_interviews() { + var body = document.createElement("div"); + var header = document.createElement("h3"); + header.textContent = "Interviews"; + body.appendChild(header); + var manDiv = document.createElement("div"); + manDiv.className = "interview_panel_controls" + var manLink = document.createElement("a"); + manLink.textContent = "Open Interview Manager Panel"; + manLink.href = "?_src_=holder;admin_token=" + href_token + ";interview_man=1;statpanel_item_click=left"; + manDiv.appendChild(manLink); + body.appendChild(manDiv); + + // List interview stats + var statsDiv = document.createElement("table"); + statsDiv.className = "interview_panel_stats"; + for (var key in interviewManager.status) { + var d = document.createElement("div"); + var tr = document.createElement("tr"); + var stat_name = document.createElement("td"); + var stat_text = document.createElement("td"); + stat_name.textContent = key; + stat_text.textContent = interviewManager.status[key]; + tr.appendChild(stat_name); + tr.appendChild(stat_text); + statsDiv.appendChild(tr); + } + body.appendChild(statsDiv); + document.getElementById("statcontent").appendChild(body); + + // List interviews if any are open + var table = document.createElement("table"); + table.className = "interview_panel_table"; + if (!interviewManager) { + return; + } + for (var i = 0; i < interviewManager.interviews.length; i++) { + var part = interviewManager.interviews[i]; + var tr = document.createElement("tr"); + var td = document.createElement("td"); + var a = document.createElement("a"); + a.textContent = part["status"]; + a.href = "?_src_=holder;admin_token=" + href_token + ";interview=" + part["ref"] + ";statpanel_item_click=left"; + td.appendChild(a); + tr.appendChild(td); + table.appendChild(tr); + } + document.getElementById("statcontent").appendChild(table); +} + +function draw_spells(cat) { + statcontentdiv.textContent = ""; + var table = document.createElement("table"); + for (var i = 0; i < spells.length; i++) { + var part = spells[i]; + if (part[0] != cat) continue; + var tr = document.createElement("tr"); + var td1 = document.createElement("td"); + td1.textContent = part[1]; + var td2 = document.createElement("td"); + if (part[3]) { + var a = document.createElement("a"); + a.href = "?src=" + part[3] + ";statpanel_item_click=left"; + a.textContent = part[2]; + td2.appendChild(a); + } else { + td2.textContent = part[2]; + } + tr.appendChild(td1); + tr.appendChild(td2); + table.appendChild(tr); + } + document.getElementById("statcontent").appendChild(table); +} + +function make_verb_onclick(command) { + return function () { + run_after_focus(function () { + Byond.command(command); + }); + }; +} + +function draw_verbs(cat) { + statcontentdiv.textContent = ""; + var table = document.createElement("div"); + var additions = {}; // additional sub-categories to be rendered + table.className = "grid-container"; + sortVerbs(); + if (split_admin_tabs && cat.lastIndexOf(".") != -1) { + var splitName = cat.split("."); + if (splitName[0] === "Admin") + cat = splitName[1]; + } + verbs.reverse(); // sort verbs backwards before we draw + for (var i = 0; i < verbs.length; ++i) { + var part = verbs[i]; + var name = part[0]; + if (split_admin_tabs && name.lastIndexOf(".") != -1) { + var splitName = name.split("."); + if (splitName[0] === "Admin") + name = splitName[1]; + } + var command = part[1]; + + if (command && name.lastIndexOf(cat, 0) != -1 && (name.length == cat.length || name.charAt(cat.length) == ".")) { + var subCat = name.lastIndexOf(".") != -1 ? name.split(".")[1] : null; + if (subCat && !additions[subCat]) { + var newTable = document.createElement("div"); + newTable.className = "grid-container"; + additions[subCat] = newTable; + } + + var a = document.createElement("a"); + a.href = "#"; + a.onclick = make_verb_onclick(command.replace(/\s/g, "-")); + a.className = "grid-item"; + var t = document.createElement("span"); + t.textContent = command; + t.className = "grid-item-text"; + a.appendChild(t); + (subCat ? additions[subCat] : table).appendChild(a); + } + } + + // Append base table to view + var content = document.getElementById("statcontent"); + content.appendChild(table); + + // Append additional sub-categories if relevant + for (var cat in additions) { + if (additions.hasOwnProperty(cat)) { + // do addition here + var header = document.createElement("h3"); + header.textContent = cat; + content.appendChild(header); + content.appendChild(additions[cat]); + } + } +} + +function set_theme(which) { + if (which == "light") { + document.body.className = ""; + set_style_sheet("browserOutput_white"); + } else if (which == "dark") { + document.body.className = "dark"; + set_style_sheet("browserOutput"); + } +} + +function set_style_sheet(sheet) { + if (document.getElementById("goonStyle")) { + var currentSheet = document.getElementById("goonStyle"); + currentSheet.parentElement.removeChild(currentSheet); + } + var head = document.getElementsByTagName('head')[0]; + var sheetElement = document.createElement("link"); + sheetElement.id = "goonStyle"; + sheetElement.rel = "stylesheet"; + sheetElement.type = "text/css"; + sheetElement.href = sheet + ".css"; + sheetElement.media = 'all'; + head.appendChild(sheetElement); +} + +function restoreFocus() { + run_after_focus(function () { + Byond.winset('map', { + focus: true, + }); + }); +} + +function getCookie(cname) { + var name = cname + '='; + var ca = document.cookie.split(';'); + for (var i = 0; i < ca.length; i++) { + var c = ca[i]; + while (c.charAt(0) == ' ') c = c.substring(1); + if (c.indexOf(name) === 0) { + return decoder(c.substring(name.length, c.length)); + } + } + return ''; +} + +function add_verb_list(payload) { + var to_add = payload; // list of a list with category and verb inside it + to_add.sort(); // sort what we're adding + for (var i = 0; i < to_add.length; i++) { + var part = to_add[i]; + if (!part[0]) + continue; + var category = part[0]; + if (category.indexOf(".") != -1) { + var splitName = category.split("."); + if (split_admin_tabs && splitName[0] === "Admin") + category = splitName[1]; + else + category = splitName[0]; + } + if (findVerbindex(part[1], verbs)) + continue; + if (verb_tabs.includes(category)) { + verbs.push(part); + if (current_tab == category) { + draw_verbs(category); // redraw if we added a verb to the tab we're currently in + } + } else if (category) { + verb_tabs.push(category); + verbs.push(part); + createStatusTab(category); + } + } +}; + +function init_spells() { + var cat = ""; + for (var i = 0; i < spell_tabs.length; i++) { + cat = spell_tabs[i]; + if (cat.length > 0) { + verb_tabs.push(cat); + createStatusTab(cat); + } + } +} + +document.addEventListener("mouseup", restoreFocus); +document.addEventListener("keyup", restoreFocus); + +if (!current_tab) { + addPermanentTab("Status"); + tab_change("Status"); +} + +window.onload = function () { + Byond.command("Update-Verbs"); +}; + +Byond.subscribeTo('update_spells', function (payload) { + spell_tabs = payload.spell_tabs; + var do_update = false; + if (spell_tabs.includes(current_tab)) { + do_update = true; + } + init_spells(); + if (payload.verblist) { + spells = payload.verblist; + if (do_update) { + draw_spells(current_tab); + } + } else { + remove_spells(); + } +}); + +Byond.subscribeTo('remove_verb_list', function (v) { + var to_remove = v; + for (var i = 0; i < to_remove.length; i++) { + remove_verb(to_remove[i]); + } + check_verbs(); + sortVerbs(); + if (verb_tabs.includes(current_tab)) + draw_verbs(current_tab); +}); + +// passes a 2D list of (verbcategory, verbname) creates tabs and adds verbs to respective list +// example (IC, Say) +Byond.subscribeTo('init_verbs', function (payload) { + wipe_verbs(); // remove all verb categories so we can replace them + checkStatusTab(); // remove all status tabs + verb_tabs = payload.panel_tabs; + verb_tabs.sort(); // sort it + var do_update = false; + var cat = ""; + for (var i = 0; i < verb_tabs.length; i++) { + cat = verb_tabs[i]; + createStatusTab(cat); // create a category if the verb doesn't exist yet + } + if (verb_tabs.includes(current_tab)) { + do_update = true; + } + if (payload.verblist) { + add_verb_list(payload.verblist); + sortVerbs(); // sort them + if (do_update) { + draw_verbs(current_tab); + } + } + SendTabsToByond(); +}); + +Byond.subscribeTo('update_stat', function (payload) { + status_tab_parts = [payload.ping_str]; + var parsed = payload.global_data; + + for (var i = 0; i < parsed.length; i++) if (parsed[i] != null) status_tab_parts.push(parsed[i]); + + parsed = payload.other_str; + + for (var i = 0; i < parsed.length; i++) if (parsed[i] != null) status_tab_parts.push(parsed[i]); + + if (current_tab == "Status") { + draw_status(); + } else if (current_tab == "Debug Stat Panel") { + draw_debug(); + } +}); + +Byond.subscribeTo('update_mc', function (payload) { + mc_tab_parts = payload.mc_data; + mc_tab_parts.splice(0, 0, ["Location:", payload.coord_entry]); + + if (!verb_tabs.includes("MC")) { + verb_tabs.push("MC"); + } + + createStatusTab("MC"); + + if (current_tab == "MC") { + draw_mc(); + } +}); + +Byond.subscribeTo('remove_spells', function () { + for (var s = 0; s < spell_tabs.length; s++) { + removeStatusTab(spell_tabs[s]); + } +}); + +Byond.subscribeTo('init_spells', function () { + var cat = ""; + for (var i = 0; i < spell_tabs.length; i++) { + cat = spell_tabs[i]; + if (cat.length > 0) { + verb_tabs.push(cat); + createStatusTab(cat); + } + } +}); + +Byond.subscribeTo('check_spells', function () { + for (var v = 0; v < spell_tabs.length; v++) { + spell_cat_check(spell_tabs[v]); + } +}); + +Byond.subscribeTo('create_debug', function () { + if (!document.getElementById("Debug Stat Panel")) { + addPermanentTab("Debug Stat Panel"); + } else { + removePermanentTab("Debug Stat Panel"); + } +}); + +Byond.subscribeTo('create_listedturf', function (TN) { + remove_listedturf(); // remove the last one if we had one + turfname = TN; + addPermanentTab(turfname); + tab_change(turfname); +}); + +Byond.subscribeTo('remove_admin_tabs', function () { + href_token = null; + remove_mc(); + remove_tickets(); + remove_sdql2(); + remove_interviews(); +}); + +Byond.subscribeTo('update_listedturf', function (TC) { + turfcontents = TC; + if (current_tab == turfname) { + draw_listedturf(); + } +}); + +Byond.subscribeTo('update_interviews', function (I) { + interviewManager = I; + if (current_tab == "Tickets") { + draw_interviews(); + } +}); + +Byond.subscribeTo('update_split_admin_tabs', function (status) { + status = (status == true); + + if (split_admin_tabs !== status) { + if (split_admin_tabs === true) { + removeStatusTab("Events"); + removeStatusTab("Fun"); + removeStatusTab("Game"); + } + update_verbs(); + } + split_admin_tabs = status; +}); + +Byond.subscribeTo('add_admin_tabs', function (ht) { + href_token = ht; + addPermanentTab("MC"); + addPermanentTab("Tickets"); +}); + +Byond.subscribeTo('update_sdql2', function (S) { + sdql2 = S; + if (sdql2.length > 0 && !verb_tabs.includes("SDQL2")) { + verb_tabs.push("SDQL2"); + addPermanentTab("SDQL2"); + } + if (current_tab == "SDQL2") { + draw_sdql2(); + } +}); + +Byond.subscribeTo('update_tickets', function (T) { + tickets = T; + if (!verb_tabs.includes("Tickets")) { + verb_tabs.push("Tickets"); + addPermanentTab("Tickets"); + } + if (current_tab == "Tickets") { + draw_tickets(); + } +}); + +Byond.subscribeTo('remove_listedturf', remove_listedturf); + +Byond.subscribeTo('remove_sdql2', remove_sdql2); + +Byond.subscribeTo('remove_mc', remove_mc); + +Byond.subscribeTo('add_verb_list', add_verb_list); diff --git a/tgstation.dme b/tgstation.dme index 35de42d4377..d1393920ed9 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -138,7 +138,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"