Unicode / views /subject.ejs
HuuDatLego's picture
Upload folder using huggingface_hub
bc6743c verified
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Trình biên dịch - UniCode</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/@phosphor-icons/web"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.44.0/min/vs/loader.min.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Be+Vietnam+Pro:wght@300;400;500;600;700&display=swap');
body {
font-family: 'Be Vietnam Pro', sans-serif;
background-color: #f8fafc;
}
.glass-panel {
background: rgba(255, 255, 255, 0.95);
border-right: 1px solid #e2e8f0;
}
/* Scrollbar */
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-thumb {
background: rgba(156, 163, 175, 0.5);
border-radius: 3px;
}
#editor-container {
height: 100%;
width: 100%;
}
</style>
</head>
<body class="bg-slate-50 text-slate-800 h-screen flex overflow-hidden">
<!-- MOBILE OVERLAY BACKDROP -->
<div id="sidebarBackdrop" onclick="toggleSidebar()"
class="fixed inset-0 bg-slate-900/50 backdrop-blur-sm z-40 hidden min-[1400px]:hidden transition-opacity"></div>
<!-- SIDEBAR -->
<aside id="sidebar"
class="w-72 glass-panel h-full fixed min-[1400px]:static inset-y-0 left-0 z-50 flex flex-col transform -translate-x-full min-[1400px]:translate-x-0 transition-transform duration-300 shadow-xl min-[1400px]:shadow-lg bg-white">
<div class="p-6 flex items-center justify-between">
<div class="flex items-center gap-3 cursor-pointer" onclick="window.location.href='/'">
<div class="w-10 h-10 bg-indigo-600 rounded-xl flex items-center justify-center text-white shadow-lg">
<i class="ph-bold ph-code text-xl"></i>
</div>
<span class="font-bold text-xl text-slate-800">UniCode</span>
</div>
<button onclick="toggleSidebar()"
class="min-[1400px]:hidden p-2 text-slate-400 hover:text-slate-600 hover:bg-slate-100 rounded-lg transition">
<i class="ph-bold ph-x text-xl"></i>
</button>
</div>
<div id="sidebarScrollContainer" class="flex-1 overflow-y-auto px-4 py-2">
<div class="text-xs font-bold text-slate-400 uppercase tracking-wider mb-3 pl-2">Chọn môn học</div>
<nav id="sidebarList" class="space-y-1">
<div class="text-center text-sm p-4"><i class="ph ph-spinner animate-spin"></i> Đang tải menu...</div>
</nav>
</div>
<div class="p-4 border-t border-slate-200">
<a href="/"
class="flex items-center gap-3 px-4 py-3 text-slate-500 hover:text-indigo-600 transition rounded-xl">
<i class="ph ph-arrow-left text-lg"></i> Trang chủ
</a>
</div>
</aside>
<!-- CONTENT -->
<div class="flex-1 flex flex-col h-full relative z-10 overflow-hidden">
<!-- Header -->
<header class="h-16 border-b border-slate-200 bg-white flex items-center justify-between px-4 md:px-6 shrink-0 shadow-sm gap-3">
<div class="flex items-center gap-3 overflow-hidden">
<button onclick="toggleSidebar()" class="min-[1400px]:hidden p-2 -ml-2 text-slate-500 hover:bg-slate-100 rounded-lg">
<i class="ph-bold ph-list text-2xl"></i>
</button>
<h1 class="text-lg font-bold text-slate-800 flex items-center gap-2 truncate" id="pageTitle">
Loading...
</h1>
</div>
<div class="flex items-center gap-2">
<button id="runBtn" class="bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-bold px-6 py-2 rounded-lg transition flex items-center gap-2 shadow-lg shadow-indigo-200">
<i class="ph-bold ph-play"></i> RUN
</button>
</div>
</header>
<main class="flex-1 flex overflow-hidden">
<!-- File List (Left) -->
<div class="w-full md:w-1/4 max-w-sm border-r border-slate-200 bg-white flex flex-col">
<div class="p-3 border-b border-slate-50 bg-slate-50/50 flex justify-between text-xs font-semibold text-slate-500 uppercase">
<span>Mã nguồn</span>
<span>Ngôn ngữ</span>
</div>
<div class="flex-1 overflow-y-auto p-3 space-y-1" id="fileList">
<!-- Files will be loaded here -->
</div>
</div>
<!-- Editor & Output (Right) -->
<div class="flex-1 flex flex-col bg-slate-100 overflow-hidden">
<!-- Editor Container -->
<div class="flex-1 relative border-b border-slate-200">
<div id="editor-container"></div>
</div>
<!-- Output Panel -->
<div class="h-1/3 bg-[#1e1e1e] text-slate-300 font-mono text-sm overflow-hidden flex flex-col">
<div class="bg-[#252526] px-4 py-2 flex justify-between items-center border-b border-[#333]">
<span class="text-xs font-bold uppercase tracking-wider text-slate-500">Output</span>
<button onclick="clearOutput()" class="text-slate-500 hover:text-white transition">
<i class="ph ph-trash"></i>
</button>
</div>
<div id="output" class="flex-1 p-4 overflow-y-auto whitespace-pre-wrap"></div>
</div>
</div>
</main>
</div>
<script>
let editor;
let currentFile;
require.config({ paths: { vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.44.0/min/vs' } });
require(['vs/editor/editor.main'], function () {
editor = monaco.editor.create(document.getElementById('editor-container'), {
value: '// Chọn một file để bắt đầu lập trình',
language: 'javascript',
theme: 'vs-dark',
automaticLayout: true,
fontSize: 14,
fontFamily: "'Fira Code', 'Be Vietnam Pro', monospace",
minimap: { enabled: false }
});
});
document.addEventListener('DOMContentLoaded', async () => {
const params = new URLSearchParams(window.location.search);
const subjectId = params.get('id');
await loadSidebar(subjectId);
if (subjectId) {
loadSubjectDetails(subjectId);
} else {
document.getElementById('pageTitle').innerText = "Vui lòng chọn môn học";
}
});
function toggleSidebar() {
const sidebar = document.getElementById('sidebar');
const backdrop = document.getElementById('sidebarBackdrop');
sidebar.classList.toggle('-translate-x-full');
backdrop.classList.toggle('hidden');
}
async function loadSidebar(activeId) {
try {
const res = await fetch('data/subjects.json');
const subjects = await res.json();
const container = document.getElementById('sidebarList');
container.innerHTML = '';
// Simple render for demo
subjects.forEach(sub => {
const isActive = sub.id === activeId;
const a = document.createElement('a');
a.href = `/subject?id=${sub.id}`;
a.className = `block px-4 py-2 rounded-lg text-sm ${isActive ? 'bg-indigo-100 text-indigo-700 font-bold' : 'text-slate-600 hover:bg-slate-50'}`;
a.innerText = sub.name;
container.appendChild(a);
});
} catch (e) { console.error(e); }
}
async function loadSubjectDetails(id) {
try {
const res = await fetch(`data/details/${id}.json`);
const data = await res.json();
document.getElementById('pageTitle').innerText = data.name;
const fileList = document.getElementById('fileList');
fileList.innerHTML = '';
data.files.forEach(file => {
const div = document.createElement('div');
div.className = "p-3 rounded-lg border border-slate-100 hover:border-indigo-200 cursor-pointer flex justify-between items-center bg-white transition";
div.onclick = () => selectFile(file);
div.innerHTML = `
<div class="flex items-center gap-2">
<i class="ph ph-file-code text-indigo-600"></i>
<span class="text-sm font-medium">${file.name}</span>
</div>
<span class="text-[10px] bg-slate-100 px-1.5 py-0.5 rounded text-slate-500 uppercase">${file.type || 'code'}</span>
`;
fileList.appendChild(div);
});
} catch (e) { console.error(e); }
}
async function selectFile(file) {
currentFile = file;
try {
const res = await fetch(file.url);
const code = await res.text();
let lang = 'javascript';
if (file.url.endsWith('.c')) lang = 'cpp';
if (file.url.endsWith('.java')) lang = 'java';
if (file.url.endsWith('.py')) lang = 'python';
monaco.editor.setModelLanguage(editor.getModel(), lang);
editor.setValue(code);
clearOutput();
document.getElementById('output').innerText = `Loaded ${file.name}\nReady to run...`;
} catch (e) {
console.error(e);
editor.setValue(`// Error loading file: ${file.name}`);
}
}
function clearOutput() {
document.getElementById('output').innerText = '';
}
document.getElementById('runBtn').onclick = async () => {
if (!currentFile) {
alert('Vui lòng chọn file trước!');
return;
}
const code = editor.getValue();
const output = document.getElementById('output');
output.innerText = 'Running...\n';
let language = 'python'; // default
if (currentFile.url.endsWith('.c')) language = 'c';
if (currentFile.url.endsWith('.java')) language = 'java';
if (currentFile.url.endsWith('.py')) language = 'python';
try {
const res = await fetch('http://localhost:3000/run', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code, language })
});
const result = await res.json();
output.innerText = '';
if (result.stdout) output.innerText += result.stdout;
if (result.stderr) {
output.innerHTML += `<span class="text-red-400">${result.stderr}</span>`;
}
if (!result.stdout && !result.stderr) output.innerText = 'Program finished with no output.';
} catch (e) {
output.innerText = `Error connecting to backend: ${e.message}\nMake sure server.js is running.`;
}
};
</script>
</body>
</html>