mirror of
https://github.com/citizenfx/cfx-server-data.git
synced 2025-01-06 21:02:57 +08:00
Compare commits
2 Commits
64a26ad1de
...
bbc77613f0
Author | SHA1 | Date | |
---|---|---|---|
|
bbc77613f0 | ||
|
54f75bca86 |
@ -9,7 +9,9 @@ AddEventHandler('rlPlayerActivated', function()
|
||||
|
||||
names[source] = { name = GetPlayerName(source), id = source }
|
||||
|
||||
TriggerClientEvent('rlUpdateNames', GetHostId())
|
||||
if GetHostId() then
|
||||
TriggerClientEvent('rlUpdateNames', GetHostId())
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterServerEvent('rlUpdateNamesResult')
|
||||
|
1
resources/runcode/.gitignore
vendored
Normal file
1
resources/runcode/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
data.json
|
@ -2,7 +2,16 @@ client_script 'runcode_cl.lua'
|
||||
server_script 'runcode_sv.lua'
|
||||
server_script 'runcode_web.lua'
|
||||
|
||||
client_script 'runcode_shared.lua'
|
||||
server_script 'runcode_shared.lua'
|
||||
shared_script 'runcode_shared.lua'
|
||||
|
||||
resource_manifest_version '44febabe-d386-4d18-afbe-5e627f4af937'
|
||||
shared_script 'runcode.js'
|
||||
|
||||
resource_manifest_version '44febabe-d386-4d18-afbe-5e627f4af937'
|
||||
|
||||
client_script 'runcode_ui.lua'
|
||||
|
||||
ui_page 'web/nui.html'
|
||||
|
||||
files {
|
||||
'web/nui.html'
|
||||
}
|
11
resources/runcode/runcode.js
Normal file
11
resources/runcode/runcode.js
Normal file
@ -0,0 +1,11 @@
|
||||
exports('runJS', (snippet) => {
|
||||
if (IsDuplicityVersion() && GetInvokingResource() !== GetCurrentResourceName()) {
|
||||
return [ 'Invalid caller.', false ];
|
||||
}
|
||||
|
||||
try {
|
||||
return [ new Function(snippet)(), false ];
|
||||
} catch (e) {
|
||||
return [ false, e.toString() ];
|
||||
}
|
||||
});
|
@ -1,7 +1,7 @@
|
||||
RegisterNetEvent('runcode:gotSnippet')
|
||||
|
||||
AddEventHandler('runcode:gotSnippet', function(id, code)
|
||||
local res, err = RunCode(code)
|
||||
AddEventHandler('runcode:gotSnippet', function(id, lang, code)
|
||||
local res, err = RunCode(lang, code)
|
||||
|
||||
if not err then
|
||||
if type(res) == 'vector3' then
|
||||
|
@ -1,5 +1,12 @@
|
||||
function RunCode(code)
|
||||
local code, err = load(code, '@runcode')
|
||||
local runners = {}
|
||||
|
||||
function runners.lua(arg)
|
||||
local code, err = load('return ' .. arg, '@runcode')
|
||||
|
||||
-- if failed, try without return
|
||||
if err then
|
||||
code, err = load(arg, '@runcode')
|
||||
end
|
||||
|
||||
if err then
|
||||
print(err)
|
||||
@ -11,7 +18,15 @@ function RunCode(code)
|
||||
|
||||
if status then
|
||||
return result
|
||||
else
|
||||
return nil, result
|
||||
end
|
||||
|
||||
return nil, result
|
||||
end
|
||||
|
||||
function runners.js(arg)
|
||||
return table.unpack(exports[GetCurrentResourceName()]:runJS(arg))
|
||||
end
|
||||
|
||||
function RunCode(lang, str)
|
||||
return runners[lang](str)
|
||||
end
|
@ -1,7 +1,42 @@
|
||||
function GetPrivs(source)
|
||||
return {
|
||||
canServer = IsPlayerAceAllowed(source, 'command.run'),
|
||||
canClient = IsPlayerAceAllowed(source, 'command.crun'),
|
||||
canSelf = IsPlayerAceAllowed(source, 'runcode.self'),
|
||||
}
|
||||
end
|
||||
|
||||
RegisterCommand('run', function(source, args, rawCommand)
|
||||
local res, err = RunCode('return ' .. rawCommand:sub(4))
|
||||
local res, err = RunCode('lua', rawCommand:sub(4))
|
||||
end, true)
|
||||
|
||||
RegisterCommand('crun', function(source, args, rawCommand)
|
||||
TriggerClientEvent('runcode:gotSnippet', source, -1, 'return ' .. rawCommand:sub(5))
|
||||
if not source then
|
||||
return
|
||||
end
|
||||
|
||||
TriggerClientEvent('runcode:gotSnippet', source, -1, 'lua', rawCommand:sub(5))
|
||||
end, true)
|
||||
|
||||
RegisterCommand('runcode', function(source, args, rawCommand)
|
||||
if not source then
|
||||
return
|
||||
end
|
||||
|
||||
local df = LoadResourceFile(GetCurrentResourceName(), 'data.json')
|
||||
local saveData = {}
|
||||
|
||||
if df then
|
||||
saveData = json.decode(df)
|
||||
end
|
||||
|
||||
local p = GetPrivs(source)
|
||||
|
||||
if not p.canServer and not p.canClient and not p.canSelf then
|
||||
return
|
||||
end
|
||||
|
||||
p.saveData = saveData
|
||||
|
||||
TriggerClientEvent('runcode:openUi', source, p)
|
||||
end, true)
|
66
resources/runcode/runcode_ui.lua
Normal file
66
resources/runcode/runcode_ui.lua
Normal file
@ -0,0 +1,66 @@
|
||||
local openData
|
||||
|
||||
RegisterNetEvent('runcode:openUi')
|
||||
|
||||
AddEventHandler('runcode:openUi', function(options)
|
||||
openData = {
|
||||
type = 'open',
|
||||
options = options,
|
||||
url = 'http://' .. GetCurrentServerEndpoint() .. '/' .. GetCurrentResourceName() .. '/',
|
||||
res = GetCurrentResourceName()
|
||||
}
|
||||
|
||||
SendNuiMessage(json.encode(openData))
|
||||
end)
|
||||
|
||||
RegisterNUICallback('getOpenData', function(args, cb)
|
||||
cb(openData)
|
||||
end)
|
||||
|
||||
RegisterNUICallback('doOk', function(args, cb)
|
||||
SendNuiMessage(json.encode({
|
||||
type = 'ok'
|
||||
}))
|
||||
|
||||
SetNuiFocus(true, true)
|
||||
|
||||
cb('ok')
|
||||
end)
|
||||
|
||||
RegisterNUICallback('doClose', function(args, cb)
|
||||
SendNuiMessage(json.encode({
|
||||
type = 'close'
|
||||
}))
|
||||
|
||||
SetNuiFocus(false, false)
|
||||
|
||||
cb('ok')
|
||||
end)
|
||||
|
||||
local rcCbs = {}
|
||||
local id = 1
|
||||
|
||||
RegisterNUICallback('runCodeInBand', function(args, cb)
|
||||
id = id + 1
|
||||
|
||||
rcCbs[id] = cb
|
||||
|
||||
TriggerServerEvent('runcode:runInBand', id, args)
|
||||
end)
|
||||
|
||||
RegisterNetEvent('runcode:inBandResult')
|
||||
|
||||
AddEventHandler('runcode:inBandResult', function(id, result)
|
||||
if rcCbs[id] then
|
||||
local cb = rcCbs[id]
|
||||
rcCbs[id] = nil
|
||||
|
||||
cb(result)
|
||||
end
|
||||
end)
|
||||
|
||||
AddEventHandler('onResourceStop', function(resourceName)
|
||||
if resourceName == GetCurrentResourceName() then
|
||||
SetNuiFocus(false, false)
|
||||
end
|
||||
end)
|
@ -21,6 +21,69 @@ end
|
||||
local codeId = 1
|
||||
local codes = {}
|
||||
|
||||
local attempts = 0
|
||||
local lastAttempt
|
||||
|
||||
local function handleRunCode(data, res)
|
||||
if not data.lang then
|
||||
data.lang = 'lua'
|
||||
end
|
||||
|
||||
if not data.client or data.client == '' then
|
||||
CreateThread(function()
|
||||
local result, err = RunCode(data.lang, data.code)
|
||||
|
||||
res.send(json.encode({
|
||||
result = result,
|
||||
error = err
|
||||
}))
|
||||
end)
|
||||
else
|
||||
codes[codeId] = {
|
||||
timeout = GetGameTimer() + 1000,
|
||||
res = res
|
||||
}
|
||||
|
||||
TriggerClientEvent('runcode:gotSnippet', tonumber(data.client), codeId, data.lang, data.code)
|
||||
|
||||
codeId = codeId + 1
|
||||
end
|
||||
end
|
||||
|
||||
RegisterNetEvent('runcode:runInBand')
|
||||
|
||||
AddEventHandler('runcode:runInBand', function(id, data)
|
||||
local s = source
|
||||
local privs = GetPrivs(s)
|
||||
|
||||
local res = {
|
||||
send = function(str)
|
||||
TriggerClientEvent('runcode:inBandResult', s, id, str)
|
||||
end
|
||||
}
|
||||
|
||||
if (not data.client or data.client == '') and not privs.canServer then
|
||||
res.send(json.encode({ error = 'Insufficient permissions.'}))
|
||||
return
|
||||
end
|
||||
|
||||
if (data.client and data.client ~= '') and not privs.canClient then
|
||||
if privs.canSelf then
|
||||
data.client = s
|
||||
else
|
||||
res.send(json.encode({ error = 'Insufficient permissions.'}))
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
SaveResourceFile(GetCurrentResourceName(), 'data.json', json.encode({
|
||||
lastSnippet = data.code,
|
||||
lastLang = data.lang or 'lua'
|
||||
}), -1)
|
||||
|
||||
handleRunCode(data, res)
|
||||
end)
|
||||
|
||||
local function handlePost(req, res)
|
||||
req.setDataHandler(function(body)
|
||||
local data = json.decode(body)
|
||||
@ -35,33 +98,29 @@ local function handlePost(req, res)
|
||||
return
|
||||
end
|
||||
|
||||
if data.password ~= GetConvar('rcon_password', '') then
|
||||
if attempts > 5 or data.password ~= GetConvar('rcon_password', '') then
|
||||
attempts = attempts + 1
|
||||
lastAttempt = GetGameTimer()
|
||||
|
||||
res.send(json.encode({ error = 'Bad password.'}))
|
||||
return
|
||||
end
|
||||
|
||||
if not data.client or data.client == '' then
|
||||
CreateThread(function()
|
||||
local result, err = RunCode(data.code)
|
||||
|
||||
res.send(json.encode({
|
||||
result = result,
|
||||
error = err
|
||||
}))
|
||||
end)
|
||||
else
|
||||
codes[codeId] = {
|
||||
timeout = GetGameTimer() + 1000,
|
||||
res = res
|
||||
}
|
||||
|
||||
TriggerClientEvent('runcode:gotSnippet', tonumber(data.client), codeId, data.code)
|
||||
|
||||
codeId = codeId + 1
|
||||
end
|
||||
handleRunCode(data, res)
|
||||
end)
|
||||
end
|
||||
|
||||
CreateThread(function()
|
||||
while true do
|
||||
Wait(1000)
|
||||
|
||||
if attempts > 0 and (GetGameTimer() - lastAttempt) > 5000 then
|
||||
attempts = 0
|
||||
lastAttempt = 0
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
local function returnCode(id, res, err)
|
||||
if not codes[id] then
|
||||
return
|
||||
|
@ -6,79 +6,468 @@
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|
||||
|
||||
<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>
|
||||
|
||||
<style type="text/css">
|
||||
body {
|
||||
font-family: "Segoe UI", sans-serif;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="code-container" style="width:800px;height:600px;border:1px solid grey"></div><br>
|
||||
Password: <input type="password" id="password"> (use your rcon password)<br>
|
||||
Client ID: <input type="text" id="client"> (leave blank for server)<br>
|
||||
<div id="clients"></div>
|
||||
<button id="run">Run</button>
|
||||
<div id="result">
|
||||
<div class="bg">
|
||||
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<!--
|
||||
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>
|
||||
-->
|
||||
|
||||
<script src="https://unpkg.com/monaco-editor@0.10.0/min/vs/loader.js"></script>
|
||||
<script src="https://unpkg.com/monaco-editor@0.18.1/min/vs/loader.js"></script>
|
||||
|
||||
<script>
|
||||
function fetchClients() {
|
||||
fetch('/runcode/clients').then(res => res.json()).then(res => {
|
||||
const el = document.querySelector('#clients');
|
||||
el.innerHTML = '';
|
||||
const el = document.querySelector('#cl-select');
|
||||
|
||||
const clients = res.clients;
|
||||
clients.push(['All', -1]);
|
||||
const realClients = [['All', '-1'], ...clients];
|
||||
|
||||
for (const client of clients) {
|
||||
const l = document.createElement('a');
|
||||
l.addEventListener('click', e => {
|
||||
document.querySelector('#client').value = client[1];
|
||||
e.preventDefault();
|
||||
});
|
||||
const createdClients = new Set([...el.querySelectorAll('option').entries()].map(([i, el]) => el.value));
|
||||
const existentClients = new Set(realClients.map(([ name, id ]) => id));
|
||||
|
||||
l.setAttribute('href', 'javascript:void(0)');
|
||||
l.appendChild(document.createTextNode(client[0]));
|
||||
const toRemove = [...createdClients].filter(a => !existentClients.has(a));
|
||||
|
||||
el.appendChild(l);
|
||||
el.appendChild(document.createTextNode(' '));
|
||||
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));
|
||||
|
||||
el.appendChild(l);
|
||||
}
|
||||
}
|
||||
|
||||
for (const id of toRemove) {
|
||||
const l = el.querySelector(`option[value="${id}"]`);
|
||||
|
||||
if (l) {
|
||||
el.removeChild(l);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
setInterval(() => fetchClients(), 1000);
|
||||
|
||||
require.config({ paths: { 'vs': 'https://unpkg.com/monaco-editor@0.10.0/min/vs' }});
|
||||
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' }});
|
||||
require(['vs/editor/editor.main'], function() {
|
||||
const editor = monaco.editor.create(document.getElementById('code-container'), {
|
||||
value: 'return 42',
|
||||
language: 'lua'
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
document.querySelector('#run').addEventListener('click', e => {
|
||||
const text = editor.getValue();
|
||||
|
||||
fetch('/runcode/', {
|
||||
fetch((!inNui) ? '/runcode/' : `https://${openData.res}/runCodeInBand`, {
|
||||
method: 'post',
|
||||
body: JSON.stringify({
|
||||
password: document.querySelector('#password').value,
|
||||
client: document.querySelector('#client').value,
|
||||
code: text
|
||||
client: (useClient) ? document.querySelector('#cl-select').value : '',
|
||||
code: text,
|
||||
lang: lang
|
||||
})
|
||||
}).then(res => res.json()).then(res => {
|
||||
if (inNui) {
|
||||
res = JSON.parse(res); // double packing for sad msgpack-to-json
|
||||
}
|
||||
|
||||
const resultElement = document.querySelector('#result');
|
||||
|
||||
if (res.error) {
|
||||
resultElement.style.color = '#aa0000';
|
||||
resultElement.classList.remove('notification', 'is-success');
|
||||
resultElement.classList.add('notification', 'is-danger');
|
||||
} else {
|
||||
resultElement.style.color = '#000000';
|
||||
resultElement.classList.remove('notification', 'is-danger');
|
||||
resultElement.classList.add('notification', 'is-success');
|
||||
}
|
||||
|
||||
resultElement.innerHTML = res.error || res.result;
|
||||
|
60
resources/runcode/web/nui.html
Normal file
60
resources/runcode/web/nui.html
Normal file
@ -0,0 +1,60 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset="utf-8">
|
||||
<title>runcode nui</title>
|
||||
|
||||
<style type="text/css">
|
||||
html {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: transparent;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="holder">
|
||||
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
let openData = null;
|
||||
|
||||
window.addEventListener('message', ev => {
|
||||
switch (ev.data.type) {
|
||||
case 'open':
|
||||
const frame = document.createElement('iframe');
|
||||
|
||||
frame.name = 'rc';
|
||||
frame.allow = 'microphone *;';
|
||||
frame.src = ev.data.url;
|
||||
frame.style.visibility = 'hidden';
|
||||
|
||||
openData = ev.data;
|
||||
openData.frame = frame;
|
||||
|
||||
document.querySelector('#holder').appendChild(frame);
|
||||
break;
|
||||
case 'ok':
|
||||
openData.frame.style.visibility = 'visible';
|
||||
break;
|
||||
case 'close':
|
||||
document.querySelector('#holder').removeChild(openData.frame);
|
||||
|
||||
openData = null;
|
||||
break;
|
||||
}
|
||||
});
|
||||
</script>
|
Loading…
Reference in New Issue
Block a user