Files
S.P.L.U.R.T-Station-13/tgui/public/tgui.html

527 lines
13 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
<!-- Inlined data -->
<meta id="tgui:windowId" content="[tgui:windowId]">
<!-- Early setup -->
<script type="text/javascript">
// Read window id into a global
window.__windowId__ = document
.getElementById('tgui:windowId')
.getAttribute('content');
if (window.__windowId__ === '[' + 'tgui:windowId' + ']') {
window.__windowId__ = null;
}
(function () {
// Utility functions
var hasOwn = Object.prototype.hasOwnProperty;
var assign = function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (hasOwn.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
// BYOND API object
// ------------------------------------------------------
var Byond = window.Byond = {};
// Trident engine version
var tridentVersion = (function () {
var groups = navigator.userAgent.match(/Trident\/(\d+).+?;/i);
var majorVersion = groups && groups[1];
return majorVersion
? parseInt(majorVersion, 10)
: null;
})();
// Basic checks to detect whether this page runs in BYOND
var isByond = tridentVersion !== null
&& location.hostname === '127.0.0.1'
&& location.pathname.indexOf('/tmp') === 0
&& location.search !== '?external';
// Version constants
Byond.IS_BYOND = isByond;
Byond.IS_LTE_IE8 = tridentVersion !== null && tridentVersion <= 4;
Byond.IS_LTE_IE9 = tridentVersion !== null && tridentVersion <= 5;
Byond.IS_LTE_IE10 = tridentVersion !== null && tridentVersion <= 6;
Byond.IS_LTE_IE11 = tridentVersion !== null && tridentVersion <= 7;
// Callbacks for asynchronous calls
Byond.__callbacks__ = [];
// Reviver for BYOND JSON
// IE8: No reviver for you!
// See: https://stackoverflow.com/questions/1288962
var byondJsonReviver;
if (!Byond.IS_LTE_IE8) {
byondJsonReviver = function (key, value) {
if (typeof value === 'object' && value !== null && value.__number__) {
return parseFloat(value.__number__);
}
return value;
};
}
// Makes a BYOND call.
// See: https://secure.byond.com/docs/ref/skinparams.html
Byond.call = function (path, params) {
// Not running in BYOND, abort.
if (!isByond) {
return;
}
// Build the URL
var url = (path || '') + '?';
var i = 0;
if (params) {
for (var key in params) {
if (hasOwn.call(params, key)) {
if (i++ > 0) {
url += '&';
}
var value = params[key];
if (value === null || value === undefined) {
value = '';
}
url += encodeURIComponent(key)
+ '=' + encodeURIComponent(value)
}
}
}
// Perform a standard call via location.href
if (url.length < 2048) {
location.href = 'byond://' + url;
return;
}
// Send an HTTP request to DreamSeeker's HTTP server.
// Allows sending much bigger payloads.
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.send();
};
Byond.callAsync = function (path, params) {
if (!window.Promise) {
throw new Error('Async calls require API level of ES2015 or later.');
}
var index = Byond.__callbacks__.length;
var promise = new window.Promise(function (resolve) {
Byond.__callbacks__.push(resolve);
});
Byond.call(path, assign({}, params, {
callback: 'Byond.__callbacks__[' + index + ']',
}));
return promise;
};
Byond.topic = function (params) {
return Byond.call('', params);
};
Byond.command = function (command) {
return Byond.call('winset', {
command: command,
});
};
Byond.winget = function (id, propName) {
var isArray = propName instanceof Array;
var isSpecific = propName && propName !== '*' && !isArray;
var promise = Byond.callAsync('winget', {
id: id,
property: isArray && propName.join(',') || propName || '*',
});
if (isSpecific) {
promise = promise.then(function (props) {
return props[propName];
});
}
return promise;
};
Byond.winset = function (id, propName, propValue) {
if (typeof id === 'object' && id !== null) {
return Byond.call('winset', id);
}
var props = {};
if (typeof propName === 'string') {
props[propName] = propValue;
}
else {
assign(props, propName);
}
props.id = id;
return Byond.call('winset', props);
};
Byond.parseJson = function (json) {
try {
return JSON.parse(json, byondJsonReviver);
}
catch (err) {
throw new Error('JSON parsing error: ' + (err && err.message));
}
};
// Asset loaders
// ------------------------------------------------------
var RETRY_ATTEMPTS = 5;
var RETRY_INTERVAL = 2000;
var loadedCssByUrl = {};
var loadedJsByUrl = {};
var isStyleSheetLoaded = function (node, url) {
// Method #1 (works on IE10+)
var styleSheet = node.sheet;
if (styleSheet) {
return styleSheet.rules.length > 0;
}
// Method #2
var styleSheets = document.styleSheets;
var len = styleSheets.length;
for (var i = 0; i < len; i++) {
var styleSheet = styleSheets[i];
if (styleSheet.href.indexOf(url) !== -1) {
return styleSheet.rules.length > 0;
}
}
// All methods failed
return false;
};
var onDocumentBodyReady = function (done) {
if (document.body) {
return done();
}
setTimeout(function () {
onDocumentBodyReady(done);
});
};
var getRefNode = function () {
var refs = (document.body || document.getElementsByTagName('head')[0])
.childNodes;
return refs[refs.length - 1];
};
// Based on fg-loadcss package
// See: https://github.com/filamentgroup/loadCSS
Byond.loadCss = function (url, sync, attempt) {
if (loadedCssByUrl[url]) {
return;
}
if (!attempt) {
attempt = 1;
}
loadedCssByUrl[url] = true;
// Inject the stylesheet
var ref = getRefNode();
/** @type {HTMLLinkElement} */
var node = document.createElement('link');
node.type = 'text/css';
node.rel = 'stylesheet';
node.href = url;
// Temporarily set media to something inapplicable
// to ensure it'll fetch without blocking render
if (!sync) {
node.media = 'only x';
}
onDocumentBodyReady(function () {
ref.parentNode.insertBefore(node, ref.nextSibling);
});
// Listen for the load event
node.onload = function () {
node.onload = null;
if (isStyleSheetLoaded(node, url)) {
// Render the stylesheet
node.media = 'all';
return;
}
// Try again
node.parentNode.removeChild(node);
node = null;
loadedCssByUrl[url] = null;
if (attempt >= RETRY_ATTEMPTS) {
throw new Error("Error: Failed to load the stylesheet "
+ "'" + url + "' after " + RETRY_ATTEMPTS + " attempts.\n"
+ "It was either not found, or you're trying to load "
+ "an empty stylesheet that has no CSS rules in it.");
}
setTimeout(function () {
Byond.loadCss(url, sync, attempt + 1);
}, RETRY_INTERVAL);
};
};
Byond.loadJs = function (url, sync, attempt) {
if (loadedJsByUrl[url]) {
return;
}
if (!attempt) {
attempt = 1;
}
loadedJsByUrl[url] = true;
// Inject the stylesheet
var ref = getRefNode();
var node = document.createElement('script');
node.type = 'text/javascript';
if (sync) {
node.defer = true;
}
else {
node.async = true;
}
node.src = url;
onDocumentBodyReady(function () {
ref.parentNode.insertBefore(node, ref.nextSibling);
});
node.onerror = function () {
node.onerror = null;
node.parentNode.removeChild(node);
node = null;
loadedJsByUrl[url] = null;
if (attempt >= RETRY_ATTEMPTS) {
throw new Error("Error: Failed to load the script "
+ "'" + url + "' after " + RETRY_ATTEMPTS + " attempts.");
}
setTimeout(function () {
Byond.loadJs(url, sync, attempt + 1);
}, RETRY_INTERVAL);
};
};
})();
// Global error handling
window.onerror = function (msg, url, line, col, error) {
// Proper stacktrace
var stack = error && error.stack;
// Ghetto stacktrace
if (!stack) {
stack = msg + '\n at ' + url + ':' + line;
if (col) {
stack += ':' + col;
}
}
// Augment the stack
stack = window.__augmentStack__(stack, error);
// Print error to the page
var errorRoot = document.getElementById('FatalError');
var errorStack = document.getElementById('FatalError__stack');
if (errorRoot) {
errorRoot.className = 'FatalError FatalError--visible';
if (errorStack.textContent) {
errorStack.textContent += '\n\n' + stack;
}
else {
errorStack.textContent = stack;
}
}
// Set window geometry
var setFatalErrorGeometry = function () {
Byond.winset(window.__windowId__, {
titlebar: true,
size: '600x600',
'is-visible': true,
'can-resize': true,
});
};
setFatalErrorGeometry();
setInterval(setFatalErrorGeometry, 1000);
// Send logs to the game server
Byond.topic({
tgui: 1,
window_id: window.__windowId__,
type: 'log',
fatal: 1,
message: stack,
});
// Short-circuit further updates
window.__updateQueue__ = [];
window.update = function () {};
// Prevent default action
return true;
};
// Catch unhandled rejections
window.onunhandledrejection = window.onerror;
// Helper for augmenting stack traces on fatal errors
window.__augmentStack__ = function (stack, error) {
return stack + '\nUser Agent: ' + navigator.userAgent;
};
// Early initialization
window.__updateQueue__ = [];
window.update = function (message) {
window.__updateQueue__.push(message);
};
Byond.topic({
tgui: 1,
window_id: window.__windowId__,
type: 'ready',
});
// Necessary polyfill to make Webpack code splitting work on IE8
if (!Function.prototype.bind) (function () {
var slice = Array.prototype.slice;
Function.prototype.bind = function () {
var thatFunc = this, thatArg = arguments[0];
var args = slice.call(arguments, 1);
if (typeof thatFunc !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - ' +
'what is trying to be bound is not callable');
}
return function () {
var funcArgs = args.concat(slice.call(arguments))
return thatFunc.apply(thatArg, funcArgs);
};
};
})();
</script>
<style>
.FatalError {
display: none;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
padding: 12px;
font-size: 12px;
font-family: Consolas, monospace;
color: #ffffff;
background-color: #0000dd;
z-index: 1000;
overflow: hidden;
text-align: center;
}
.FatalError--visible {
display: block !important;
}
.FatalError__logo {
display: inline-block;
text-align: left;
font-size: 10px;
line-height: 12px;
position: relative;
margin: 16px;
top: 0;
left: 0;
animation:
FatalError__rainbow 2s linear infinite alternate,
FatalError__shadow 4s linear infinite alternate,
FatalError__tfmX 3s infinite alternate,
FatalError__tfmY 4s infinite alternate;
white-space: pre;
}
.FatalError__header {
margin-top: 12px;
}
.FatalError__stack {
text-align: left;
white-space: pre-wrap;
word-break: break-all;
margin-top: 24px;
margin-bottom: 24px;
}
.FatalError__footer {
margin-bottom: 24px;
}
@keyframes FatalError__rainbow {
0% { color: #ff0; }
50% { color: #0ff; }
100% { color: #f0f; }
}
@keyframes FatalError__shadow {
0% {
left: -2px;
text-shadow: 4px 0 #f0f;
}
50% {
left: 0px;
text-shadow: 0px 0 #0ff;
}
100% {
left: 2px;
text-shadow: -4px 0 #ff0;
}
}
@keyframes FatalError__tfmX {
0% { left: 15px; }
100% { left: -15px; }
}
@keyframes FatalError__tfmY {
100% { top: -15px; }
}
</style>
</head>
<body>
<!-- Inline assets -->
<!-- tgui:assets -->
<!-- Inline HTML -->
<!-- tgui:html -->
<!-- tgui container -->
<div id="react-root"></div>
<!-- Fatal error container -->
<div id="FatalError" class="FatalError">
<div class="FatalError__logo">
ooooo ooo . .oooooo. .oooooo..o
`888b. `8' .o8 d8P' `Y8b d8P' `Y8
8 `88b. 8 .o888oo 888 888 Y88bo.
8 `88b. 8 888 888 888 `"Y8888o.
8 `88b.8 888 888 888 `"Y88b
8 `888 888 . `88b d88' oo .d8P
o8o `8 "888" `Y8bood8P' 8""88888P'
</div>
<marquee class="FatalError__header">
A fatal exception has occurred at 002B:C562F1B7 in TGUI.
The current application will be terminated.
Please remain calm. Get to the nearest NTNet workstation
and send the copy of the following stack trace to:
www.github.com/Citadel-Station-13/Citadel-Station-13/issues. Thank you for your cooperation.
</marquee>
<div id="FatalError__stack" class="FatalError__stack"></div>
<div class="FatalError__footer">
Nanotrasen (c) 2525-2559
</div>
</div>
<noscript>
<div class="NoticeBox">
<div>Javascript is required in order to use this interface.</div>
<div>Please enable Javascript and restart the game.</div>
</div>
</noscript>
</body>
</html>