2017-09-05 17:58:39 +08:00
|
|
|
<!DOCTYPE html>
|
|
|
|
<html>
|
|
|
|
<head>
|
|
|
|
<title>fivem runcode</title>
|
|
|
|
|
|
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
|
|
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|
|
|
|
|
2019-10-21 21:04:55 +08:00
|
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulmaswatch/0.7.2/cyborg/bulmaswatch.min.css">
|
|
|
|
<script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
|
|
|
|
|
2017-09-05 17:58:39 +08:00
|
|
|
<style type="text/css">
|
|
|
|
body {
|
|
|
|
font-family: "Segoe UI", sans-serif;
|
|
|
|
}
|
2019-10-21 21:04:55 +08:00
|
|
|
|
|
|
|
.navbar {
|
|
|
|
z-index: inherit;
|
|
|
|
}
|
|
|
|
|
|
|
|
html.in-nui {
|
|
|
|
overflow: hidden;
|
|
|
|
background: transparent;
|
|
|
|
|
|
|
|
margin-top: 5vh;
|
|
|
|
margin-left: 7.5vw;
|
|
|
|
margin-right: 7.5vw;
|
|
|
|
margin-bottom: 5vh;
|
|
|
|
|
|
|
|
height: calc(100% - 10vh);
|
|
|
|
|
|
|
|
position: relative;
|
|
|
|
}
|
|
|
|
|
|
|
|
html.in-nui body > div.bg {
|
|
|
|
background-color: rgba(0, 0, 0, 1);
|
|
|
|
position: absolute;
|
|
|
|
top: 0px;
|
|
|
|
bottom: 0px;
|
|
|
|
left: 0px;
|
|
|
|
right: 0px;
|
|
|
|
z-index: -999;
|
|
|
|
|
|
|
|
box-shadow: 0 22px 70px 4px rgba(0, 0, 0, 0.56);
|
|
|
|
}
|
|
|
|
|
|
|
|
span.nui-edition {
|
|
|
|
display: none;
|
|
|
|
}
|
|
|
|
|
|
|
|
html.in-nui span.nui-edition {
|
|
|
|
display: inline;
|
|
|
|
}
|
|
|
|
|
|
|
|
#close {
|
|
|
|
display: none;
|
|
|
|
}
|
|
|
|
|
|
|
|
html.in-nui #close {
|
|
|
|
display: block;
|
|
|
|
}
|
|
|
|
|
|
|
|
#result {
|
|
|
|
margin-top: 0.5em;
|
|
|
|
}
|
|
|
|
|
|
|
|
.navbar {
|
|
|
|
border-top: none;
|
|
|
|
border-left: none;
|
|
|
|
border-right: none;
|
|
|
|
}
|
2017-09-05 17:58:39 +08:00
|
|
|
</style>
|
|
|
|
</head>
|
|
|
|
<body>
|
2019-10-21 21:04:55 +08:00
|
|
|
<div class="bg">
|
|
|
|
|
2017-09-05 17:58:39 +08:00
|
|
|
</div>
|
|
|
|
|
2019-10-21 21:04:55 +08:00
|
|
|
<nav class="navbar" role="navigation" aria-label="main navigation">
|
|
|
|
<div class="container">
|
|
|
|
<div class="navbar-brand">
|
|
|
|
<a class="navbar-item" href="/runcode">
|
|
|
|
<strong>runcode</strong> <span class="nui-edition"> in-game</span>
|
|
|
|
</a>
|
|
|
|
|
|
|
|
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false" data-target="navbarMain">
|
|
|
|
<span aria-hidden="true"></span>
|
|
|
|
<span aria-hidden="true"></span>
|
|
|
|
<span aria-hidden="true"></span>
|
|
|
|
</a>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div id="navbarMain" class="navbar-menu">
|
|
|
|
<div class="navbar-end">
|
|
|
|
<div class="navbar-item">
|
|
|
|
<div class="field" id="cl-field">
|
|
|
|
<div class="control has-icons-left">
|
|
|
|
<div class="select">
|
|
|
|
<select id="cl-select">
|
|
|
|
</select>
|
|
|
|
</div>
|
|
|
|
<div class="icon is-small is-left">
|
|
|
|
<i class="fas fa-user"></i>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="navbar-item">
|
|
|
|
<div class="field has-addons" id="lang-toggle">
|
|
|
|
<p class="control">
|
|
|
|
<button class="button" id="lua-button">
|
|
|
|
<span class="icon is-small">
|
|
|
|
<i class="fas fa-moon"></i>
|
|
|
|
</span>
|
|
|
|
<span>Lua</span>
|
|
|
|
</button>
|
|
|
|
</p>
|
|
|
|
<p class="control">
|
|
|
|
<button class="button" id="js-button">
|
|
|
|
<span class="icon is-small">
|
|
|
|
<i class="fab fa-js"></i>
|
|
|
|
</span>
|
|
|
|
<span>JS</span>
|
|
|
|
</button>
|
|
|
|
</p>
|
|
|
|
<!-- TODO pending add-on resource that'll contain webpack'd compiler
|
|
|
|
<p class="control">
|
|
|
|
<button class="button" id="ts-button">
|
|
|
|
<span class="icon is-small">
|
|
|
|
<i class="fas fa-code"></i>
|
|
|
|
</span>
|
|
|
|
<span>TS</span>
|
|
|
|
</button>
|
|
|
|
</p>
|
|
|
|
-->
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="navbar-item">
|
|
|
|
<div class="field has-addons" id="cl-sv-toggle">
|
|
|
|
<p class="control">
|
|
|
|
<button class="button" id="cl-button">
|
|
|
|
<span class="icon is-small">
|
|
|
|
<i class="fas fa-user-friends"></i>
|
|
|
|
</span>
|
|
|
|
<span>Client</span>
|
|
|
|
</button>
|
|
|
|
</p>
|
|
|
|
<p class="control">
|
|
|
|
<button class="button" id="sv-button">
|
|
|
|
<span class="icon is-small">
|
|
|
|
<i class="fas fa-server"></i>
|
|
|
|
</span>
|
|
|
|
<span>Server</span>
|
|
|
|
</button>
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="navbar-item" id="close">
|
|
|
|
<button class="button is-danger">Close</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</nav>
|
|
|
|
|
|
|
|
<section class="section">
|
|
|
|
<div class="container">
|
|
|
|
<div id="code-container" style="width:100%;height:60vh;border:1px solid grey"></div><br>
|
|
|
|
<div class="field" id="passwordField">
|
|
|
|
<p class="control has-icons-left">
|
|
|
|
<input class="input" type="password" id="password" placeholder="RCon Password">
|
|
|
|
<span class="icon is-small is-left">
|
|
|
|
<i class="fas fa-lock"></i>
|
|
|
|
</span>
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
<button class="button is-primary" id="run">Run</button>
|
|
|
|
<div id="result">
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</section>
|
|
|
|
|
2017-09-05 17:58:39 +08:00
|
|
|
<!--
|
|
|
|
to use a local deployment, uncomment; do note currently the server isn't optimized to serve >1MB files
|
|
|
|
<script src="monaco-editor/vs/loader.js"></script>
|
|
|
|
-->
|
|
|
|
|
2019-10-21 21:04:55 +08:00
|
|
|
<script src="https://unpkg.com/monaco-editor@0.18.1/min/vs/loader.js"></script>
|
2017-09-05 17:58:39 +08:00
|
|
|
|
|
|
|
<script>
|
|
|
|
function fetchClients() {
|
|
|
|
fetch('/runcode/clients').then(res => res.json()).then(res => {
|
2019-10-21 21:04:55 +08:00
|
|
|
const el = document.querySelector('#cl-select');
|
2017-09-05 17:58:39 +08:00
|
|
|
|
|
|
|
const clients = res.clients;
|
2019-10-21 21:04:55 +08:00
|
|
|
const realClients = [['All', '-1'], ...clients];
|
2017-09-05 17:58:39 +08:00
|
|
|
|
2019-10-21 21:04:55 +08:00
|
|
|
const createdClients = new Set([...el.querySelectorAll('option').entries()].map(([i, el]) => el.value));
|
|
|
|
const existentClients = new Set(realClients.map(([ name, id ]) => id));
|
|
|
|
|
|
|
|
const toRemove = [...createdClients].filter(a => !existentClients.has(a));
|
|
|
|
|
|
|
|
for (const [name, id] of realClients) {
|
|
|
|
const ex = el.querySelector(`option[value="${id}"]`);
|
|
|
|
|
|
|
|
if (!ex) {
|
|
|
|
const l = document.createElement('option');
|
|
|
|
l.setAttribute('value', id);
|
|
|
|
l.appendChild(document.createTextNode(name));
|
2017-09-05 17:58:39 +08:00
|
|
|
|
2019-10-21 21:04:55 +08:00
|
|
|
el.appendChild(l);
|
|
|
|
}
|
|
|
|
}
|
2017-09-05 17:58:39 +08:00
|
|
|
|
2019-10-21 21:04:55 +08:00
|
|
|
for (const id of toRemove) {
|
|
|
|
const l = el.querySelector(`option[value="${id}"]`);
|
|
|
|
|
|
|
|
if (l) {
|
|
|
|
el.removeChild(l);
|
|
|
|
}
|
2017-09-05 17:58:39 +08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-10-21 21:04:55 +08:00
|
|
|
let useClient = false;
|
|
|
|
let editServerCb = null;
|
|
|
|
|
|
|
|
[['#cl-button', true], ['#sv-button', false]].forEach(([ selector, isClient ]) => {
|
|
|
|
const eh = () => {
|
|
|
|
if (isClient) {
|
|
|
|
document.querySelector('#cl-select').disabled = false;
|
|
|
|
useClient = true;
|
|
|
|
} else {
|
|
|
|
document.querySelector('#cl-select').disabled = true;
|
|
|
|
useClient = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
document.querySelectorAll('#cl-sv-toggle button').forEach(el => {
|
|
|
|
el.classList.remove('is-selected', 'is-info');
|
|
|
|
});
|
|
|
|
|
|
|
|
const tgt = document.querySelector(selector);
|
|
|
|
|
|
|
|
tgt.classList.add('is-selected', 'is-info');
|
|
|
|
|
|
|
|
if (editServerCb) {
|
|
|
|
editServerCb();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// default to not-client
|
|
|
|
if (!isClient) {
|
|
|
|
eh();
|
|
|
|
}
|
|
|
|
|
|
|
|
document.querySelector(selector).addEventListener('click', ev => {
|
|
|
|
eh();
|
|
|
|
|
|
|
|
ev.preventDefault();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
let lang = 'lua';
|
|
|
|
let editLangCb = null;
|
|
|
|
let initCb = null;
|
|
|
|
|
|
|
|
function getLangCode(lang) {
|
|
|
|
switch (lang) {
|
|
|
|
case 'js':
|
|
|
|
return 'javascript';
|
|
|
|
case 'ts':
|
|
|
|
return 'typescript';
|
|
|
|
}
|
|
|
|
|
|
|
|
return lang;
|
|
|
|
}
|
|
|
|
|
|
|
|
[['#lua-button', 'lua'], ['#js-button', 'js']/*, ['#ts-button', 'ts']*/].forEach(([ selector, langOpt ]) => {
|
|
|
|
const eh = () => {
|
|
|
|
lang = langOpt;
|
|
|
|
|
|
|
|
document.querySelectorAll('#lang-toggle button').forEach(el => {
|
|
|
|
el.classList.remove('is-selected', 'is-info');
|
|
|
|
});
|
|
|
|
|
|
|
|
const tgt = document.querySelector(selector);
|
|
|
|
|
|
|
|
tgt.classList.add('is-selected', 'is-info');
|
|
|
|
|
|
|
|
if (editLangCb) {
|
|
|
|
editLangCb();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// default to not-client
|
|
|
|
if (langOpt === 'lua') {
|
|
|
|
eh();
|
|
|
|
}
|
|
|
|
|
|
|
|
document.querySelector(selector).addEventListener('click', ev => {
|
|
|
|
eh();
|
|
|
|
|
|
|
|
ev.preventDefault();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
2017-09-05 17:58:39 +08:00
|
|
|
setInterval(() => fetchClients(), 1000);
|
|
|
|
|
2019-10-21 21:04:55 +08:00
|
|
|
const inNui = (!!window.invokeNative);
|
|
|
|
let openData = {};
|
|
|
|
|
|
|
|
if (inNui) {
|
|
|
|
document.querySelector('#passwordField').style.display = 'none';
|
|
|
|
document.querySelector('html').classList.add('in-nui');
|
|
|
|
|
|
|
|
fetch(`http://${window.parent.GetParentResourceName()}/getOpenData`, {
|
|
|
|
method: 'POST',
|
|
|
|
body: '{}'
|
|
|
|
}).then(a => a.json())
|
|
|
|
.then(a => {
|
|
|
|
openData = a;
|
|
|
|
|
|
|
|
if (!openData.options.canServer) {
|
|
|
|
document.querySelector('#cl-sv-toggle').style.display = 'none';
|
|
|
|
|
|
|
|
const trigger = document.createEvent('HTMLEvents');
|
|
|
|
trigger.initEvent('click', true, true);
|
|
|
|
|
|
|
|
document.querySelector('#cl-button').dispatchEvent(trigger);
|
|
|
|
} else if (!openData.options.canClient && !openData.options.canSelf) {
|
|
|
|
document.querySelector('#cl-sv-toggle').style.display = 'none';
|
|
|
|
document.querySelector('#cl-field').style.display = 'none';
|
|
|
|
|
|
|
|
const trigger = document.createEvent('HTMLEvents');
|
|
|
|
trigger.initEvent('click', true, true);
|
|
|
|
|
|
|
|
document.querySelector('#sv-button').dispatchEvent(trigger);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!openData.options.canClient && openData.options.canSelf) {
|
|
|
|
document.querySelector('#cl-field').style.display = 'none';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (openData.options.saveData) {
|
|
|
|
const cb = () => {
|
|
|
|
if (initCb) {
|
|
|
|
initCb({
|
|
|
|
lastLang: openData.options.saveData.lastLang,
|
|
|
|
lastSnippet: openData.options.saveData.lastSnippet
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
setTimeout(cb, 50);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
setTimeout(cb, 50);
|
|
|
|
}
|
|
|
|
|
|
|
|
fetch(`https://${window.parent.GetParentResourceName()}/doOk`, {
|
|
|
|
method: 'POST',
|
|
|
|
body: '{}'
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
document.querySelector('#close button').addEventListener('click', ev => {
|
|
|
|
fetch(`https://${window.parent.GetParentResourceName()}/doClose`, {
|
|
|
|
method: 'POST',
|
|
|
|
body: '{}'
|
|
|
|
});
|
|
|
|
|
|
|
|
ev.preventDefault();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const defFiles = ['index.d.ts'];
|
|
|
|
const defFilesServer = [...defFiles, 'natives_server.d.ts'];
|
|
|
|
const defFilesClient = [...defFiles, 'natives_universal.d.ts'];
|
|
|
|
|
|
|
|
const prefix = 'https://unpkg.com/@citizenfx/{}/';
|
|
|
|
const prefixClient = prefix.replace('{}', 'client');
|
|
|
|
const prefixServer = prefix.replace('{}', 'server');
|
|
|
|
|
|
|
|
require.config({ paths: { 'vs': 'https://unpkg.com/monaco-editor@0.18.1/min/vs' }});
|
2017-09-05 17:58:39 +08:00
|
|
|
require(['vs/editor/editor.main'], function() {
|
|
|
|
const editor = monaco.editor.create(document.getElementById('code-container'), {
|
|
|
|
value: 'return 42',
|
|
|
|
language: 'lua'
|
|
|
|
});
|
|
|
|
|
2019-10-21 21:04:55 +08:00
|
|
|
monaco.editor.setTheme('vs-dark');
|
|
|
|
|
|
|
|
let finalizers = [];
|
|
|
|
|
|
|
|
const updateScript = (client, lang) => {
|
|
|
|
finalizers.forEach(a => a());
|
|
|
|
finalizers = [];
|
|
|
|
|
|
|
|
if (lang === 'js' || lang === 'ts') {
|
|
|
|
const defaults = (lang === 'js') ? monaco.languages.typescript.javascriptDefaults :
|
|
|
|
monaco.languages.typescript.typescriptDefaults;
|
|
|
|
|
|
|
|
defaults.setCompilerOptions({
|
|
|
|
noLib: true,
|
|
|
|
allowNonTsExtensions: true
|
|
|
|
});
|
|
|
|
|
|
|
|
for (const file of (client ? defFilesClient : defFilesServer)) {
|
|
|
|
const prefix = (client ? prefixClient : prefixServer);
|
|
|
|
|
|
|
|
fetch(`${prefix}${file}`)
|
|
|
|
.then(a => a.text())
|
|
|
|
.then(a => {
|
|
|
|
const l = defaults.addExtraLib(a, file);
|
|
|
|
|
|
|
|
finalizers.push(() => l.dispose());
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
editLangCb = () => {
|
|
|
|
monaco.editor.setModelLanguage(editor.getModel(), getLangCode(lang));
|
|
|
|
|
|
|
|
updateScript(useClient, lang);
|
|
|
|
};
|
|
|
|
|
|
|
|
editServerCb = () => {
|
|
|
|
updateScript(useClient, lang);
|
|
|
|
};
|
|
|
|
|
|
|
|
initCb = (data) => {
|
|
|
|
if (data.lastLang) {
|
|
|
|
const trigger = document.createEvent('HTMLEvents');
|
|
|
|
trigger.initEvent('click', true, true);
|
|
|
|
document.querySelector(`#${data.lastLang}-button`).dispatchEvent(trigger);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data.lastSnippet) {
|
|
|
|
editor.getModel().setValue(data.lastSnippet);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-09-05 17:58:39 +08:00
|
|
|
document.querySelector('#run').addEventListener('click', e => {
|
|
|
|
const text = editor.getValue();
|
|
|
|
|
2019-10-21 21:04:55 +08:00
|
|
|
fetch((!inNui) ? '/runcode/' : `https://${openData.res}/runCodeInBand`, {
|
2017-09-05 17:58:39 +08:00
|
|
|
method: 'post',
|
|
|
|
body: JSON.stringify({
|
|
|
|
password: document.querySelector('#password').value,
|
2019-10-21 21:04:55 +08:00
|
|
|
client: (useClient) ? document.querySelector('#cl-select').value : '',
|
|
|
|
code: text,
|
|
|
|
lang: lang
|
2017-09-05 17:58:39 +08:00
|
|
|
})
|
|
|
|
}).then(res => res.json()).then(res => {
|
2019-10-21 21:04:55 +08:00
|
|
|
if (inNui) {
|
|
|
|
res = JSON.parse(res); // double packing for sad msgpack-to-json
|
|
|
|
}
|
|
|
|
|
2017-09-05 17:58:39 +08:00
|
|
|
const resultElement = document.querySelector('#result');
|
|
|
|
|
|
|
|
if (res.error) {
|
2019-10-21 21:04:55 +08:00
|
|
|
resultElement.classList.remove('notification', 'is-success');
|
|
|
|
resultElement.classList.add('notification', 'is-danger');
|
2017-09-05 17:58:39 +08:00
|
|
|
} else {
|
2019-10-21 21:04:55 +08:00
|
|
|
resultElement.classList.remove('notification', 'is-danger');
|
|
|
|
resultElement.classList.add('notification', 'is-success');
|
2017-09-05 17:58:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
resultElement.innerHTML = res.error || res.result;
|
|
|
|
|
|
|
|
if (res.from) {
|
|
|
|
resultElement.innerHTML += ' (from ' + res.from + ')';
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
</script>
|
|
|
|
|
|
|
|
</body>
|
|
|
|
</html>
|