Files
Letter NandGitHub 00045a5961 update vv (#7478)
<!-- 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. -->
2026-01-13 22:03:31 -05:00

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>