mirror of
https://github.com/Citadel-Station-13/Citadel-Station-13-RP.git
synced 2026-08-23 08:06:41 +01:00
<!-- Write **BELOW** The Headers and **ABOVE** The comments else it may not be viewable. --> <!-- You can view Contributing.MD for a detailed description of the pull request process. --> ## About The Pull Request title <!-- Describe The Pull Request. Please be sure every change is documented or this can delay review and even discourage maintainers from merging your PR! --> ## Why It's Good For The Game <!-- Argue for the merits of your changes and how they benefit the game, especially if they are controversial and/or far reaching. If you can't actually explain WHY what you are doing will improve the game, then it probably isn't good for the game in the first place. --> ## Changelog <!-- If your PR modifies aspects of the game that can be concretely observed by players or admins you should add a changelog. If your change does NOT meet this description, remove this section. Please note that maintainers freely reserve the right to remove and add tags should they deem it appropriate. You can attempt to finagle the system all you want, but it's best to shoot for clear communication right off the bat. --> 🆑 add: new adminbuse view matrix editor code: cleanup legacy stuff & moved them into their own respective dropdown option code: use tg display-mutable-appearance /🆑 <!-- Both 🆑's are required for the changelog to work! You can put your name to the right of the first 🆑 if you want to overwrite your GitHub username as author ingame. --> <!-- You can use multiple of the same prefix (they're only used for the icon ingame) and delete the unneeded ones. Despite some of the tags, changelogs should generally represent how a player might be affected by the changes rather than a summary of the PR's contents. -->
1166 lines
26 KiB
HTML
1166 lines
26 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
|
|
<!--
|
|
STATPANEL - In House Citadel Edition (tm)
|
|
|
|
Known limitations:
|
|
- does not handle mouse drag events
|
|
- does not handle holding down keys by BYOND refspec: we instead just pass in "button"
|
|
- does not handle mouse pixel location on listed turf (atom) clicks
|
|
- in fact, most BYOND mouse handling stuff isn't passed in.
|
|
- does not allow right clicking atoms for verbs.
|
|
- does NOT play nicely with tabs with spaces
|
|
-->
|
|
|
|
<head>
|
|
<title>Stat Browser</title>
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
|
<meta http-equiv="Cache-Control" content="max-age=15; must-revalidate" />
|
|
<link id="goonStyle" rel="stylesheet" type="text/css" href="browserOutput_white.css" media="all" />
|
|
|
|
<!-- FIRST SCRIPT - TGUI NEEDS TO BE ABLE TO CALL THIS FROM OTHER WINDOWS -->
|
|
|
|
<script>
|
|
|
|
// MAGICAL SHIT FROM TG IM NOT TOUCHING
|
|
// fun fact this doesn't even work
|
|
// because some idiot removed the .css entirely!
|
|
|
|
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);
|
|
}
|
|
|
|
</script>
|
|
|
|
<!-- MAIN CSS -->
|
|
|
|
<style>
|
|
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;
|
|
}
|
|
|
|
#stat_menu {
|
|
background-color: #F0F0F0;
|
|
position: fixed;
|
|
width: 100%;
|
|
z-index: 100;
|
|
}
|
|
|
|
.dark #stat_menu {
|
|
background-color: #202020;
|
|
}
|
|
|
|
#stat_content {
|
|
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;
|
|
background-color: #ffffff;
|
|
}
|
|
|
|
.dark .grid-item:hover .grid-item-text {
|
|
background-color: #131313;
|
|
}
|
|
|
|
.grid-item-text {
|
|
display: inline-block;
|
|
width: calc(100%);
|
|
background-color: #F0F0F0;
|
|
margin: 0 -6px;
|
|
padding: 0px;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.grid-item-text:hover {
|
|
padding: 0 3px;
|
|
}
|
|
|
|
.dark .grid-item-text {
|
|
background-color: #222222;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- HTML BODY -->
|
|
|
|
<ul id="stat_menu" class="button-container"></ul>
|
|
<div id="stat_submenu"></div>
|
|
<div id="stat_content"></div>
|
|
|
|
<!-- MAIN SCRIPT -->
|
|
|
|
<script>
|
|
// Polyfills and compatibility ------------------------------------------------
|
|
var decoder = decodeURIComponent || unescape;
|
|
var PROP_ADD_EVENT_LISTENER = (document.addEventListener ? 'addEventListener' : 'attachEvent'); // IE8 handling for Wine users
|
|
var PROP_TEXT_CONTENT = (typeof document.body.textContent != 'undefined') ? 'textContent' : 'innerText';
|
|
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, '');
|
|
};
|
|
}
|
|
/*\
|
|
|*|
|
|
|*| Polyfill which enables the passage of arbitrary arguments to the
|
|
|*| callback functions of JavaScript timers (HTML5 standard syntax).
|
|
|*|
|
|
|*| https://developer.mozilla.org/en-US/docs/DOM/window.setInterval
|
|
|*|
|
|
|*| Syntax:
|
|
|*| var timeoutID = window.setTimeout(func, delay[, arg1, arg2, ...]);
|
|
|*| var timeoutID = window.setTimeout(code, delay);
|
|
|*| var intervalID = window.setInterval(func, delay[, arg1, arg2, ...]);
|
|
|*| var intervalID = window.setInterval(code, delay);
|
|
|*|
|
|
\*/
|
|
(function() {
|
|
setTimeout(function(arg1) {
|
|
if (arg1 === 'test') {
|
|
// feature test is passed, no need for polyfill
|
|
return;
|
|
}
|
|
var __nativeST__ = window.setTimeout;
|
|
window.setTimeout = function(vCallback, nDelay /*, argumentToPass1, argumentToPass2, etc. */ ) {
|
|
var aArgs = Array.prototype.slice.call(arguments, 2);
|
|
return __nativeST__(vCallback instanceof Function ? function() {
|
|
vCallback.apply(null, aArgs);
|
|
} : vCallback, nDelay);
|
|
};
|
|
}, 0, 'test');
|
|
|
|
var interval = setInterval(function(arg1) {
|
|
clearInterval(interval);
|
|
if (arg1 === 'test') {
|
|
// feature test is passed, no need for polyfill
|
|
return;
|
|
}
|
|
var __nativeSI__ = window.setInterval;
|
|
window.setInterval = function(vCallback, nDelay /*, argumentToPass1, argumentToPass2, etc. */ ) {
|
|
var aArgs = Array.prototype.slice.call(arguments, 2);
|
|
return __nativeSI__(vCallback instanceof Function ? function() {
|
|
vCallback.apply(null, aArgs);
|
|
} : vCallback, nDelay);
|
|
};
|
|
}, 0, 'test');
|
|
}())
|
|
|
|
///////////////////////// CONSTANTS /////////////////////
|
|
|
|
const ELEMENT_ID_MENU = "stat_menu"
|
|
const ELEMENT_ID_SUBMENU = "stat_submenu"
|
|
const ELEMENT_ID_CONTENT = "stat_content"
|
|
const SKIN_ID_MAP = "map";
|
|
const ELEMENT_ID_ATOM_TABLE = "atom_elem"
|
|
|
|
/// how long to wait before re-loading missing images
|
|
var IMAGE_RETRY_DELAY = 1000;
|
|
/// how many times before we give up
|
|
var IMAGE_RETRY_LIMIT = 5;
|
|
|
|
/////////////////////// ELEMENTS ////////////////////////
|
|
|
|
var stat_menu = document.getElementById(ELEMENT_ID_MENU);
|
|
var stat_submenu = document.getElementById(ELEMENT_ID_SUBMENU);
|
|
var stat_content = document.getElementById(ELEMENT_ID_CONTENT);
|
|
|
|
///////////////////// BOOT //////////////////////////////
|
|
|
|
window.onload = function() {
|
|
window_boot();
|
|
};
|
|
|
|
function window_boot() {
|
|
register_events()
|
|
transmit_ready()
|
|
}
|
|
|
|
function register_events() {
|
|
// restore focus after press
|
|
document[PROP_ADD_EVENT_LISTENER]("mouseup", winset_focus_map);
|
|
// restore focus after press
|
|
document[PROP_ADD_EVENT_LISTENER]("keyup", winset_focus_map);
|
|
window.onresize = function() {
|
|
stat_submenu.style.height = stat_menu.clientHeight + 'px';
|
|
}
|
|
}
|
|
|
|
/////////////////////// DATA ////////////////////////////
|
|
|
|
// for verbs ; tab name = list ( verb1, verb2, ... );
|
|
var verbs = {};
|
|
// for stat ; list(entry, ...)
|
|
// entry format:
|
|
// 1. text: displayed verbatim, html allowed
|
|
// 2. text = text: displayed as key --> value with the obligatory tab/spacing
|
|
// 3. array [key, value, act]: clicking on the entry will trigger act with that act.
|
|
var data = []
|
|
// admin href token
|
|
var admin_token = null;
|
|
|
|
// for listed turf
|
|
var listed_turf_contents = null;
|
|
var listed_turf_name = null;
|
|
var listed_turf_icon = null;
|
|
var listed_turf_ref = null;
|
|
|
|
/// wipes state
|
|
function wipe_state(redraw) {
|
|
data = [];
|
|
verbs = {};
|
|
admin_token = null;
|
|
byond_turf_unset();
|
|
active_tab = null;
|
|
if(redraw) {
|
|
redraw_menu();
|
|
redraw_content();
|
|
}
|
|
}
|
|
|
|
//////////////// CALLED FROM BYOND //////////////////////
|
|
|
|
/// called for shutdown or reconnect - this should reset state
|
|
function byond_cleanup() {
|
|
// we just don't do that for now
|
|
// this way you can still see your stat data and it's smoother during restarts
|
|
// todo: connection state tracking?
|
|
}
|
|
|
|
/// called for init - this is not what initializes us, this is sent after we tell BYOND we are ready.
|
|
function byond_init() {
|
|
// nothing is done now, this is just a placeholder
|
|
// /client/proc/statpanel_init() needs to send all data
|
|
// in one package to this function instead of init_verbs()'ing
|
|
// if we want to do anything more
|
|
// as we cannot trust JS function call order or winset/winget order
|
|
// at all.
|
|
}
|
|
|
|
/// called for reconnect - this should reset state and transmit ready
|
|
function byond_reconnect() {
|
|
// wipe state
|
|
wipe_state();
|
|
// signal ready so BYOND re-initializes us.
|
|
transmit_ready();
|
|
}
|
|
|
|
/// called on data update
|
|
function byond_push(_data) {
|
|
data = [];
|
|
_push_data(_data);
|
|
redraw_content();
|
|
}
|
|
|
|
/// called on data update to append
|
|
function byond_append(_data) {
|
|
_push_data(_data);
|
|
redraw_content();
|
|
}
|
|
|
|
/// special 2 part push
|
|
function byond_update(cdata, ddata) {
|
|
// push both c(ached) data and d(ynamic) data at once.
|
|
data = [];
|
|
_push_data(cdata);
|
|
_push_data(ddata);
|
|
redraw_content();
|
|
}
|
|
|
|
/// pushes data
|
|
function _push_data(_data) {
|
|
if(!_data){
|
|
return;
|
|
}
|
|
var parsed = JSON.parse(_data);
|
|
for(var i = 0; i < parsed.length; i++){
|
|
if(parsed[i] != null){
|
|
data.push(parsed[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// called to add tab
|
|
function byond_add_tab(tab) {
|
|
ensure_tab(tab);
|
|
}
|
|
|
|
/// called to remove tab
|
|
function byond_remove_tab(tab) {
|
|
erase_tab(tab);
|
|
}
|
|
|
|
/// called to clear tabs
|
|
function byond_clear_tabs() {
|
|
erase_tabs();
|
|
}
|
|
|
|
/// called to add verb
|
|
function byond_add_verb(name, category) {
|
|
__byond_add_verbs({name: category});
|
|
}
|
|
|
|
/// called to remove verb
|
|
function byond_remove_verb(name, category) {
|
|
__byond_remove_verbs({name: category});
|
|
}
|
|
|
|
/// called to add multiple verbs: same format as init verbs
|
|
function byond_add_verbs(_data, force_draw, resetting) {
|
|
_data = JSON.parse(_data);
|
|
return __byond_add_verbs(_data, force_draw);
|
|
}
|
|
|
|
function __byond_add_verbs(_data, force_draw, resetting) {
|
|
var had = force_draw || false;
|
|
var tabs_changed = false;
|
|
for(var cat in _data) {
|
|
if(cat == active_tab) {
|
|
had = true;
|
|
}
|
|
if(!resetting && !(all_tabs.includes(cat)))
|
|
all_tabs.push(cat);
|
|
tabs_changed = true;
|
|
var arr = verbs[cat];
|
|
if(arr == undefined) {
|
|
arr = [];
|
|
verbs[cat] = arr;
|
|
}
|
|
for(var i = 0; i < _data[cat].length; ++i) {
|
|
var verb = _data[cat][i];
|
|
if(arr.includes(verb)) {
|
|
continue;
|
|
}
|
|
arr.push(verb);
|
|
}
|
|
arr.sort(function(a, b) {
|
|
var a_up = a.toUpperCase();
|
|
var b_up = b.toUpperCase();
|
|
if(a_up < b_up) {
|
|
return -1;
|
|
}
|
|
if(a_up > b_up) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
})
|
|
}
|
|
if((had || tabs_changed) && !resetting){
|
|
redraw_menu();
|
|
}
|
|
}
|
|
|
|
/// called to remove multiple verbs: same format as init verbs
|
|
function byond_remove_verbs(_data) {
|
|
_data = JSON.parse(_data);
|
|
return __byond_remove_verbs(_data);
|
|
}
|
|
|
|
function __byond_remove_verbs(_data) {
|
|
var had = false;
|
|
var tabs_changed = false;
|
|
for(var cat in _data) {
|
|
if(cat == active_tab) {
|
|
had = true;
|
|
}
|
|
var arr = verbs[cat];
|
|
if(arr == undefined) {
|
|
continue;
|
|
}
|
|
for(var i = 0; i < _data[cat].length; ++i) {
|
|
var verb = _data[cat][i];
|
|
for(var j = arr.length - 1; j >= 0; --j) {
|
|
if(arr[j] != verb) {
|
|
continue;
|
|
}
|
|
arr.splice(j, 1);
|
|
break;
|
|
}
|
|
}
|
|
// empty ; dispose.
|
|
if(!arr.length){
|
|
if(all_tabs.includes(cat) && !(stat_tabs.includes(cat))){
|
|
for(var i = all_tabs.length - 1; i >= 0; --i){
|
|
if(all_tabs[i] == cat){
|
|
all_tabs.splice(i, 1);
|
|
}
|
|
}
|
|
tabs_changed = true;
|
|
}
|
|
verbs[cat] = undefined
|
|
}
|
|
}
|
|
if(tabs_changed){
|
|
redraw_menu();
|
|
}
|
|
}
|
|
|
|
/// called to init verbs: takes object of categories to lists of names. throws out any verbs not in this.
|
|
function byond_init_verbs(_data, reset) {
|
|
verbs = [];
|
|
reset = (reset == "1")? true : false;
|
|
byond_add_verbs(_data, true, reset);
|
|
if(reset){
|
|
byond_clear_tabs();
|
|
}
|
|
}
|
|
|
|
/// called to remove listed turf
|
|
function byond_turf_unset(){
|
|
listed_turf_name = null;
|
|
listed_turf_icon = null;
|
|
listed_turf_contents = null;
|
|
erase_turf_tab();
|
|
}
|
|
|
|
/// called to set listed turf
|
|
function byond_turf_set(name, ref, icon, contents){
|
|
listed_turf_name = name;
|
|
listed_turf_icon = icon;
|
|
listed_turf_ref = ref;
|
|
listed_turf_contents = JSON.parse(contents);
|
|
ensure_turf_tab();
|
|
// switch to it
|
|
change_tab("Turf");
|
|
}
|
|
|
|
/// called to add an atom from listed turf
|
|
function byond_turf_add(_data){
|
|
_data = JSON.parse(_data);
|
|
draw_atom(_data[0], _data[1], _data[2]);
|
|
}
|
|
|
|
/// called to remove an atom from listed turf
|
|
function byond_turf_del(ref){
|
|
dispose_atom(ref);
|
|
}
|
|
|
|
/// called to give us a href token
|
|
function byond_grant_token(token){
|
|
href_token = token;
|
|
}
|
|
|
|
/// called ot dispose of our href token
|
|
function byond_dispose_token(token){
|
|
href_token = null;
|
|
}
|
|
|
|
|
|
//////////////// TRANSMIT TO BYOND //////////////////////
|
|
|
|
/// command queue
|
|
// todo: this will actually have a use if we make statpanel actually
|
|
// able to hold state for connection
|
|
// as of right now this *WAS* called (before I removed it) in init_verbs
|
|
// but why the hell are you even able to click verbs before they're init'd?
|
|
var command_queue = [];
|
|
// var connected = false;
|
|
|
|
/// direct send, do not use
|
|
function __transmit_verb(cmd) {
|
|
var href = "byond://winset?command=" + cmd;
|
|
window.location.href = href;
|
|
}
|
|
|
|
/// queues or sends
|
|
function transmit_verb(cmd) {
|
|
__transmit_verb(cmd);
|
|
/*
|
|
if(connected){
|
|
__transmit_verb(cmd);
|
|
}
|
|
else{
|
|
command_queue.push(cm)
|
|
}
|
|
*/
|
|
}
|
|
|
|
/// immediate sends
|
|
function transmit_href(action, _data) {
|
|
var assembled = "";
|
|
if(_data != undefined){
|
|
for(var key in _data){
|
|
assembled += key + "=" + ((_data[key] == undefined)? "1" : _data[key]) + ";"
|
|
}
|
|
}
|
|
window.location.href = "byond://?_src_=stat;act=" + action + ";" + assembled;
|
|
}
|
|
|
|
/// flushes queue
|
|
function transmit_flush_queue() {
|
|
for(var i = 0; i < command_queue.length; i++){
|
|
__transmit_verb(command_queue[i]);
|
|
}
|
|
command_queue.length = 0
|
|
}
|
|
|
|
/// informs byond we are ready
|
|
function transmit_ready() {
|
|
transmit_verb(".statpanel_ready");
|
|
}
|
|
|
|
/// quotes a text arg
|
|
function quote_verb_text(text) {
|
|
return "\"" + text.replace(/"/g, "\\\"") + "\"";
|
|
}
|
|
|
|
//////////////////// WINSET / WINGET / SKIN /////////////
|
|
|
|
function winset_focus_map() {
|
|
current_mouse_target = null;
|
|
yield_invoke(function() {window.location.href = "byond://winset?" + SKIN_ID_MAP + ".focus=true";});
|
|
}
|
|
|
|
/////////////////////// STARTUP /////////////////////////
|
|
|
|
//////////////////////// SHUTDOWN ///////////////////////
|
|
|
|
/////////////////////// CONNECTION //////////////////////
|
|
|
|
//////////////////////// MAIN ///////////////////////////
|
|
|
|
function redraw_menu(){
|
|
// wipe
|
|
stat_menu[PROP_TEXT_CONTENT] = ""
|
|
// redraw
|
|
for(var i = 0; i < all_tabs.length; ++i){
|
|
var tab = all_tabs[i];
|
|
__create_tab_element(tab);
|
|
}
|
|
// empty? shouldn't happen but..
|
|
if(!all_tabs.length) {
|
|
return;
|
|
}
|
|
// change if needed
|
|
if(!(all_tabs.includes(active_tab)) && all_tabs.includes("Status")) {
|
|
change_tab("Status");
|
|
}
|
|
}
|
|
|
|
function redraw_content(){
|
|
var tab = active_tab;
|
|
if(!tab) {
|
|
draw_loading();
|
|
return;
|
|
}
|
|
if(tab == "Turf"){
|
|
draw_turf();
|
|
}
|
|
else if(all_tabs.includes(tab)){
|
|
draw_tab(tab);
|
|
return;
|
|
}
|
|
else{
|
|
draw_error();
|
|
return;
|
|
}
|
|
}
|
|
|
|
//////////////////////// TABS ///////////////////////////
|
|
|
|
/// tabs
|
|
var all_tabs = [];
|
|
/// stat tabs
|
|
var stat_tabs = [];
|
|
/// active tab
|
|
var active_tab;
|
|
|
|
/// id for tab
|
|
function id_tab(name) {
|
|
return "t_" + name;
|
|
}
|
|
|
|
/// ensure a tab is there
|
|
function ensure_tab(name) {
|
|
var tabs_changed = false;
|
|
if(!(stat_tabs.includes(name))){
|
|
stat_tabs.push(name);
|
|
transmit_verb(".statpanel_tab_add " + name);
|
|
}
|
|
if(!(all_tabs.includes(name))){
|
|
all_tabs.push(name);
|
|
tabs_changed = true;
|
|
}
|
|
if(tabs_changed){
|
|
redraw_menu();
|
|
}
|
|
if(name == active_tab){
|
|
redraw_content();
|
|
}
|
|
}
|
|
|
|
/// ensure a tab *isn't* there
|
|
function erase_tab(name) {
|
|
var tabs_changed = false;
|
|
if(stat_tabs.includes(name)){
|
|
for(var i = stat_tabs.length - 1; i >= 0; --i){
|
|
if(stat_tabs[i] == name){
|
|
stat_tabs.splice(i, 1);
|
|
}
|
|
}
|
|
transmit_verb(".statpanel_tab_remove " + name);
|
|
}
|
|
if(all_tabs.includes(name) && !(name in verbs)){
|
|
tabs_changed = true;
|
|
for(var i = all_tabs.length - 1; i >= 0; --i){
|
|
if(all_tabs[i] == name){
|
|
all_tabs.splice(i, 1);
|
|
}
|
|
}
|
|
}
|
|
if(tabs_changed){
|
|
redraw_menu();
|
|
}
|
|
if(name == active_tab){
|
|
redraw_content();
|
|
}
|
|
}
|
|
|
|
/// clear tabs that don't have verbs
|
|
function erase_tabs() {
|
|
stat_tabs = [];
|
|
all_tabs = [];
|
|
for(var tab in verbs){
|
|
if(!verbs.hasOwnProperty(tab)){
|
|
continue;
|
|
}
|
|
if(!(all_tabs.includes(tab))){
|
|
all_tabs.push(tab);
|
|
}
|
|
}
|
|
// tell byond what's left
|
|
transmit_verb(".statpanel_tab_reset");
|
|
redraw_menu();
|
|
}
|
|
|
|
/// unchecked add tab element
|
|
function __create_tab_element(tab) {
|
|
var id = id_tab(tab);
|
|
if(document.getElementById(id)){
|
|
return;
|
|
}
|
|
var button = document.createElement("button");
|
|
button.onclick = function() {
|
|
change_tab(tab);
|
|
this.blur;
|
|
};
|
|
button.id = id;
|
|
button[PROP_TEXT_CONTENT] = tab;
|
|
button.className = (tab == active_tab)? "button active" : "button";
|
|
button.style.order = tab_order(tab);
|
|
stat_menu.appendChild(button);
|
|
// don't know what this does
|
|
stat_submenu.style.height = stat_menu.clientHeight + 'px';
|
|
}
|
|
|
|
/// get order for tab element
|
|
function tab_order(tab) {
|
|
if(tab == "Status") {
|
|
return 1;
|
|
}
|
|
else if(tab == "Turf") {
|
|
return 2;
|
|
}
|
|
else if(tab == "Examine") {
|
|
return 3;
|
|
}
|
|
else if(tab == "Species") {
|
|
return 4;
|
|
}
|
|
else if(tab == "MC") {
|
|
return 5;
|
|
}
|
|
else if(tab == "Tickets") {
|
|
return 6;
|
|
}
|
|
else {
|
|
return tab.charCodeAt(0) + 6;
|
|
}
|
|
}
|
|
|
|
/// unchecked del tab element
|
|
function __delete_tab_element(tab) {
|
|
var elem = document.getElementById(id_tab(tab));
|
|
if(!elem){
|
|
return;
|
|
}
|
|
stat_menu.removeChild(elem);
|
|
}
|
|
|
|
/// swap tabs - input is tab ID, NOT NAME.
|
|
function change_tab(tab){
|
|
// grab last
|
|
var last_tab = document.getElementById(id_tab(active_tab));
|
|
// grab current
|
|
var next_tab = document.getElementById(id_tab(tab));
|
|
// set active
|
|
active_tab = tab;
|
|
// transmit to byond
|
|
transmit_verb(".statpanel_tab " + tab);
|
|
// so UNFORTUNATELY we're going to wipe data
|
|
// in the future we want to have a system where we can cache last
|
|
// for now, well, the subsystem does fire every 0.4 seconds.
|
|
data = [];
|
|
|
|
// change ui
|
|
if(last_tab && (last_tab != next_tab)){
|
|
last_tab.className = "button";
|
|
}
|
|
if(next_tab){
|
|
next_tab.className = "button active";
|
|
}
|
|
// redraw
|
|
redraw_content();
|
|
}
|
|
|
|
// wrappers for turf tabs
|
|
function ensure_turf_tab() { ensure_tab("Turf"); }
|
|
function erase_turf_tab() { erase_tab("Turf"); }
|
|
|
|
///////////////////////// VERBS /////////////////////////
|
|
|
|
var current_mouse_target = null;
|
|
|
|
///////////////////// DRAWING - MISC ////////////////////
|
|
|
|
function draw_loading(){
|
|
stat_content[PROP_TEXT_CONTENT] = "Loading..."
|
|
}
|
|
|
|
function draw_error() {
|
|
stat_content[PROP_TEXT_CONTENT] = "<center><h1>Statpanel errored. Contact an admin/coder, and reconnect.</h1></center>"
|
|
}
|
|
|
|
///////////////////// DRAWING - TABS ////////////////////
|
|
|
|
function draw_tab(tab) {
|
|
// wipe
|
|
stat_content[PROP_TEXT_CONTENT] = "";
|
|
// draw verbs
|
|
if(verbs[tab] != undefined) {
|
|
var verb_table = document.createElement("div");
|
|
verb_table.className = "grid-container";
|
|
var iterating = verbs[tab]
|
|
for(var i = 0; i < iterating.length; ++i) {
|
|
var verb = iterating[i];
|
|
var button = document.createElement("button");
|
|
button.onmousedown = lambda_onmousedown_verb(button, verb.replace(/\s/g, "-"));
|
|
button.onmouseup = lambda_onmouseup_verb(button, verb.replace(/\s/g, "-"))
|
|
button.className = "grid-item";
|
|
var t = document.createElement("span");
|
|
t[PROP_TEXT_CONTENT] = verb;
|
|
t.className = "grid-item-text";
|
|
button.appendChild(t);
|
|
verb_table.appendChild(button);
|
|
}
|
|
stat_content.appendChild(verb_table)
|
|
}
|
|
// draw data
|
|
if(data.length != 0) {
|
|
var stat_table = document.createElement("table");
|
|
for(var i = 0; i < data.length; ++i) {
|
|
var tr = document.createElement("tr");
|
|
var entry = data[i];
|
|
// shitcode central: decode data
|
|
var dtype = typeof entry;
|
|
var tdk = document.createElement("td");
|
|
var tdv = document.createElement("td");
|
|
if(dtype == "string") {
|
|
// just text; render normally
|
|
tdk.innerHTML = entry;
|
|
}
|
|
// it's not just string, so it's an array
|
|
else if(entry.length == 2) {
|
|
// simple k-v entry
|
|
tdk.innerHTML = entry[0];
|
|
tdv.innerHTML = entry[1];
|
|
}
|
|
else if(entry.length == 3) {
|
|
// k-v with value being clickable
|
|
tdk.innerHTML = entry[0];
|
|
var a = document.createElement("a");
|
|
a.innerHTML = entry[1];
|
|
// todo: token
|
|
a.href = "byond://?_src_=stat;act=stat_click;ref=" + entry[2];
|
|
tdv.appendChild(a);
|
|
}
|
|
tr.appendChild(tdk);
|
|
tr.appendChild(tdv);
|
|
stat_table.appendChild(tr);
|
|
}
|
|
stat_content.appendChild(stat_table);
|
|
}
|
|
}
|
|
|
|
///////////////////// DRAWING - TURFS ///////////////////
|
|
|
|
function draw_turf() {
|
|
// wipe
|
|
stat_content[PROP_TEXT_CONTENT] = "";
|
|
// create table
|
|
var table = document.createElement("table");
|
|
table.id = ELEMENT_ID_ATOM_TABLE;
|
|
|
|
// draw turf itself
|
|
draw_atom(listed_turf_name, listed_turf_icon, listed_turf_ref, table);
|
|
}
|
|
|
|
/// id for atom div
|
|
function id_atom(ref){
|
|
return "a_" + ref;
|
|
}
|
|
|
|
function draw_atom(name, icon, ref, table){
|
|
// if none passed in, probably an atom is being injected as opposed to a full redraw
|
|
if(!table) {
|
|
// resolve table
|
|
table = document.getElementById(ELEMENT_ID_ATOM_TABLE);
|
|
// if still not here we don't bother
|
|
if(!table) {
|
|
return;
|
|
}
|
|
// since it's not a full draw we want to de-dupe
|
|
var existing = id_atom(ref);
|
|
if(existing){
|
|
// don't make another
|
|
// todo: update existing instead of just returning
|
|
return;
|
|
}
|
|
}
|
|
// outer div
|
|
var adiv = document.createElement("div");
|
|
adiv.id = id_atom(ref);
|
|
// inner div
|
|
var ldiv = document.createElement("div");
|
|
ldiv.className = "link";
|
|
ldiv.onmousedown = lambda_onclick_atom(ref);
|
|
ldiv[PROP_TEXT_CONTENT] = name;
|
|
// image
|
|
var img = document.createElement("img");
|
|
img.onerror = lambda_reload_image(img);
|
|
img.src = icon;
|
|
// add
|
|
adiv.appendChild(img);
|
|
adiv.appendChild(ldiv);
|
|
// append
|
|
table.appendChild(adiv);
|
|
}
|
|
|
|
function dispose_atom(ref){
|
|
var table = document.getElementById(ELEMENT_ID_ATOM_TABLE);
|
|
if(!table){
|
|
return;
|
|
}
|
|
var child = document.getElementById(id_atom(ref));
|
|
if(!child){
|
|
return;
|
|
}
|
|
table.removeChild(child);
|
|
}
|
|
|
|
function atom_invoke(event, ref){
|
|
// transmit the click with params encoded as byond params so Click() needs to do
|
|
// minimal work
|
|
var params = "";
|
|
switch(event.button){
|
|
case 1:
|
|
// middle
|
|
params += "button=middle&";
|
|
break;
|
|
case 2:
|
|
// right
|
|
params += "button=right&";
|
|
break;
|
|
default:
|
|
// left
|
|
params += "button=left&";
|
|
}
|
|
if(e.shiftKey){
|
|
params += "shift=1&";
|
|
}
|
|
if(e.ctrlKey){
|
|
params += "ctrl=1&"
|
|
}
|
|
if(e.altKey){
|
|
params += "alt=1&"
|
|
}
|
|
transmit_href("atom_click", {"ref": ref, "params": params});
|
|
}
|
|
|
|
/////////// FUNCTION FUNCTIONS (IE IS TERRIBLE) /////////
|
|
|
|
/**
|
|
* creates the onclick for verbs
|
|
*/
|
|
function lambda_onmousedown_verb(element, verb) {
|
|
return function(e) {
|
|
current_mouse_target = element;
|
|
}
|
|
}
|
|
|
|
function lambda_onmouseup_verb(element, verb) {
|
|
return function(e) {
|
|
if(current_mouse_target === element){
|
|
yield_invoke(function() {transmit_verb(verb);});
|
|
}
|
|
current_mouse_target = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* creates the onclick for atoms
|
|
*/
|
|
function lambda_onclick_atom(ref) {
|
|
return function(e) {
|
|
e.preventDefault();
|
|
atom_invoke(e, ref);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* creates the onerror function used to load things
|
|
*/
|
|
function lambda_reload_image(node) {
|
|
var re_ref = node;
|
|
return setTimeout(function() {
|
|
var attempts = parseInt(node.getAttribute('img-reload-n'), 10) || 0;
|
|
if(attempts >= IMAGE_RETRY_LIMIT){
|
|
return;
|
|
}
|
|
var old_src = re_ref.src
|
|
re_ref.src = null;
|
|
re_ref.src = old_src + '#' + (Math.random() * 1000);
|
|
re_ref.setAttribute('img-reload-n', attempts + 1);
|
|
}, IMAGE_RETRY_DELAY);
|
|
}
|
|
|
|
/**
|
|
* fires a function after the current stack/whatever
|
|
* used to yield focus and other stuff
|
|
*/
|
|
function yield_invoke(f) {
|
|
setTimeout(f, 0);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////
|
|
|
|
</script>
|
|
</body>
|
|
</html>
|