You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

146 lines
5.9 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!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="/">&#8592;</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>