|
|
<!DOCTYPE html>
|
|
|
<html lang="en">
|
|
|
<head>
|
|
|
<meta charset="UTF-8">
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
|
<title>Lua Console</title>
|
|
|
<style>
|
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
|
body { background: #0a0e1a; color: #e0e0e0; font-family: 'Consolas', 'SF Mono', 'Fira Code', monospace; height: 100vh; height: 100dvh; display: flex; flex-direction: column; overflow: hidden; }
|
|
|
.header { background: #111827; padding: 12px 16px; display: flex; align-items: center; gap: 12px; border-bottom: 1px solid #1f2937; flex-shrink: 0; }
|
|
|
.header a { color: #6b7280; text-decoration: none; font-size: 20px; }
|
|
|
.header h1 { font-size: 16px; font-weight: 600; color: #f3f4f6; font-family: 'Segoe UI', system-ui, sans-serif; }
|
|
|
.output { flex: 1; overflow-y: auto; padding: 12px; display: flex; flex-direction: column; gap: 4px; }
|
|
|
.entry { padding: 6px 10px; border-radius: 6px; font-size: 13px; line-height: 1.6; white-space: pre-wrap; word-break: break-all; animation: fi 0.12s ease; }
|
|
|
@keyframes fi { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; } }
|
|
|
.entry.input { color: #93c5fd; background: rgba(59,130,246,0.06); border-left: 2px solid #3b82f6; }
|
|
|
.entry.input::before { content: "❯ "; color: #3b82f6; }
|
|
|
.entry.result { color: #a5f3c4; background: rgba(34,197,94,0.05); border-left: 2px solid #22c55e; }
|
|
|
.entry.error { color: #fca5a5; background: rgba(239,68,68,0.06); border-left: 2px solid #ef4444; }
|
|
|
.entry.info { color: #6b7280; font-size: 12px; }
|
|
|
.input-area { background: #111827; padding: 10px 12px; border-top: 1px solid #1f2937; flex-shrink: 0; }
|
|
|
.input-row { display: flex; gap: 8px; }
|
|
|
.input-area textarea { flex: 1; background: #1f2937; border: 1px solid #374151; border-radius: 8px; padding: 10px 14px; color: #f3f4f6; font-size: 14px; font-family: inherit; outline: none; resize: none; min-height: 40px; max-height: 120px; line-height: 1.5; }
|
|
|
.input-area textarea:focus { border-color: #3b82f6; }
|
|
|
.input-area textarea::placeholder { color: #6b7280; }
|
|
|
.input-area button { background: #3b82f6; color: white; border: none; border-radius: 8px; padding: 10px 16px; font-size: 14px; font-weight: 600; cursor: pointer; white-space: nowrap; align-self: flex-end; }
|
|
|
.input-area button:hover { background: #2563eb; }
|
|
|
.input-area button:active { background: #1d4ed8; }
|
|
|
.shortcuts { display: flex; gap: 6px; margin-top: 6px; flex-wrap: wrap; }
|
|
|
.shortcuts button { background: #1f2937; border: 1px solid #374151; color: #9ca3af; border-radius: 6px; padding: 4px 10px; font-size: 11px; font-family: inherit; cursor: pointer; }
|
|
|
.shortcuts button:hover { background: #374151; color: #f3f4f6; }
|
|
|
.output::-webkit-scrollbar { width: 6px; }
|
|
|
.output::-webkit-scrollbar-track { background: transparent; }
|
|
|
.output::-webkit-scrollbar-thumb { background: #374151; border-radius: 3px; }
|
|
|
</style>
|
|
|
</head>
|
|
|
<body>
|
|
|
<div class="header">
|
|
|
<a href="/">←</a>
|
|
|
<h1>Lua Console</h1>
|
|
|
</div>
|
|
|
<div class="output" id="out">
|
|
|
<div class="entry info">Connected to game. Type Lua expressions or statements.</div>
|
|
|
</div>
|
|
|
<div class="input-area">
|
|
|
<div class="input-row">
|
|
|
<textarea id="inp" rows="1" placeholder="Lua code..." autocomplete="off"></textarea>
|
|
|
<button onclick="run()">Run</button>
|
|
|
</div>
|
|
|
<div class="shortcuts" id="shorts"></div>
|
|
|
</div>
|
|
|
<script>
|
|
|
const out = document.getElementById('out');
|
|
|
const inp = document.getElementById('inp');
|
|
|
let history = JSON.parse(localStorage.getItem('lua_history') || '[]');
|
|
|
let histIdx = -1;
|
|
|
|
|
|
const shortcuts = [
|
|
|
'getCharHealth(playerHandle)',
|
|
|
'sampGetPlayerNickname(select(2, sampGetPlayerIdByCharHandle(playerHandle)))',
|
|
|
'getAllVehicles()',
|
|
|
'sampSendChat("/time")',
|
|
|
'memory.hex(getModuleHandle("samp.dll"), 32)',
|
|
|
];
|
|
|
const shortsEl = document.getElementById('shorts');
|
|
|
shortcuts.forEach(s => {
|
|
|
const b = document.createElement('button');
|
|
|
b.textContent = s.length > 30 ? s.slice(0, 28) + '...' : s;
|
|
|
b.title = s;
|
|
|
b.onclick = () => { inp.value = s; run(); };
|
|
|
shortsEl.appendChild(b);
|
|
|
});
|
|
|
|
|
|
function addEntry(cls, text) {
|
|
|
const d = document.createElement('div');
|
|
|
d.className = 'entry ' + cls;
|
|
|
d.textContent = text;
|
|
|
out.appendChild(d);
|
|
|
while (out.children.length > 200) out.removeChild(out.firstChild);
|
|
|
out.scrollTop = out.scrollHeight;
|
|
|
}
|
|
|
|
|
|
async function run() {
|
|
|
const code = inp.value.trim();
|
|
|
if (!code) return;
|
|
|
|
|
|
// Save history
|
|
|
if (history[history.length - 1] !== code) {
|
|
|
history.push(code);
|
|
|
if (history.length > 50) history.shift();
|
|
|
localStorage.setItem('lua_history', JSON.stringify(history));
|
|
|
}
|
|
|
histIdx = -1;
|
|
|
|
|
|
addEntry('input', code);
|
|
|
inp.value = '';
|
|
|
autoResize();
|
|
|
|
|
|
try {
|
|
|
const res = await fetch('/api/console/exec', {
|
|
|
method: 'POST',
|
|
|
headers: {'Content-Type': 'application/json'},
|
|
|
body: JSON.stringify({code})
|
|
|
});
|
|
|
const data = await res.json();
|
|
|
if (data.ok) {
|
|
|
addEntry('result', data.result);
|
|
|
} else {
|
|
|
addEntry('error', data.error || 'Unknown error');
|
|
|
}
|
|
|
} catch(e) {
|
|
|
addEntry('error', 'Network error: ' + e.message);
|
|
|
}
|
|
|
inp.focus();
|
|
|
}
|
|
|
|
|
|
inp.addEventListener('keydown', e => {
|
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
|
e.preventDefault();
|
|
|
run();
|
|
|
}
|
|
|
// History navigation
|
|
|
if (e.key === 'ArrowUp' && inp.value === '') {
|
|
|
e.preventDefault();
|
|
|
if (histIdx < 0) histIdx = history.length;
|
|
|
histIdx--;
|
|
|
if (histIdx >= 0) inp.value = history[histIdx];
|
|
|
}
|
|
|
if (e.key === 'ArrowDown' && histIdx >= 0) {
|
|
|
e.preventDefault();
|
|
|
histIdx++;
|
|
|
inp.value = histIdx < history.length ? history[histIdx] : '';
|
|
|
if (histIdx >= history.length) histIdx = -1;
|
|
|
}
|
|
|
});
|
|
|
|
|
|
function autoResize() {
|
|
|
inp.style.height = 'auto';
|
|
|
inp.style.height = Math.min(inp.scrollHeight, 120) + 'px';
|
|
|
}
|
|
|
inp.addEventListener('input', autoResize);
|
|
|
inp.focus();
|
|
|
</script>
|
|
|
</body>
|
|
|
</html>
|