feat: add actions snippet to OutputPanel and implement run-all evaluation logic for lessons

master
a2nr 2026-04-10 14:15:16 +07:00
parent 997ab78f56
commit 314975ac65
2 changed files with 68 additions and 18 deletions

View File

@ -18,9 +18,10 @@
interface Props { interface Props {
sections?: OutputEntry[]; sections?: OutputEntry[];
actions?: import('svelte').Snippet;
} }
let { sections = [] }: Props = $props(); let { sections = [], actions }: Props = $props();
let showDebug = $state<Record<string, boolean>>({}); let showDebug = $state<Record<string, boolean>>({});
@ -36,13 +37,20 @@
<div class="output-panel"> <div class="output-panel">
<div class="output-header"> <div class="output-header">
<span class="output-title">Output</span> <div class="header-info">
{#if anyLoading} <span class="output-title">Output</span>
<span class="status-badge running">Compiling...</span> {#if anyLoading}
{:else if overallSuccess === true} <span class="status-badge running">Compiling...</span>
<span class="status-badge success">Berhasil</span> {:else if overallSuccess === true}
{:else if overallSuccess === false} <span class="status-badge success">Berhasil</span>
<span class="status-badge error">Error</span> {:else if overallSuccess === false}
<span class="status-badge error">Error</span>
{/if}
</div>
{#if actions}
<div class="header-actions">
{@render actions()}
</div>
{/if} {/if}
</div> </div>
@ -94,6 +102,16 @@
font-size: 0.8rem; font-size: 0.8rem;
font-family: system-ui, sans-serif; font-family: system-ui, sans-serif;
} }
.header-info {
display: flex;
align-items: center;
gap: 0.5rem;
}
.header-actions {
display: flex;
align-items: center;
gap: 0.4rem;
}
.output-title { .output-title {
font-weight: 600; font-weight: 600;
} }

View File

@ -212,6 +212,7 @@
/** Mark lesson as complete: track progress + celebration. Called when ALL exercises pass. */ /** Mark lesson as complete: track progress + celebration. Called when ALL exercises pass. */
async function completeLesson() { async function completeLesson() {
if (lessonCompleted) return;
showCelebration = true; showCelebration = true;
if (auth.isLoggedIn) { if (auth.isLoggedIn) {
const lessonName = slug.replace('.md', ''); const lessonName = slug.replace('.md', '');
@ -298,17 +299,16 @@
} }
} }
async function handleRun() { async function evaluateLanguage(lang: 'c' | 'python') {
if (activeTab === 'circuit') { await evaluateCircuit(); return; }
if (!data) return; if (!data) return;
const out = getCodeOut(); const out = lang === 'c' ? cOut : pyOut;
Object.assign(out, { loading: true, output: '', error: '', success: null }); Object.assign(out, { loading: true, output: '', error: '', success: null });
activeTab = 'output'; activeTab = 'output';
try { try {
const code = editor?.getCode() ?? currentCode; const code = (currentLanguage === lang) ? (editor?.getCode() ?? currentCode) : (lang === 'c' ? cCode : pythonCode);
const res = await compileCode({ code, language: currentLanguage }); const res = await compileCode({ code, language: lang });
if (!res.success) { if (!res.success) {
Object.assign(out, { error: res.error || 'Compilation failed', success: false }); Object.assign(out, { error: res.error || 'Compilation failed', success: false });
@ -319,13 +319,13 @@
out.success = true; out.success = true;
if (data.expected_output) { if (data.expected_output) {
const currentCCode = currentLanguage === 'c' ? code : cCode; const currentCCode = (currentLanguage === 'c') ? code : cCode;
const currentPythonCode = currentLanguage === 'python' ? code : pythonCode; const currentPythonCode = (currentLanguage === 'python') ? code : pythonCode;
const mergedCode = currentCCode + '\n' + currentPythonCode; const mergedCode = currentCCode + '\n' + currentPythonCode;
const passed = res.output.trim() === data.expected_output.trim() && checkKeyText(mergedCode, data.key_text ?? ''); const passed = res.output.trim() === data.expected_output.trim() && checkKeyText(mergedCode, data.key_text ?? '');
if (passed) { if (passed) {
if (currentLanguage === 'c') cPassed = true; if (lang === 'c') cPassed = true;
else if (currentLanguage === 'python') pythonPassed = true; else if (lang === 'python') pythonPassed = true;
if (!checkAllPassed()) { if (!checkAllPassed()) {
out.output += '\n✅ Kode benar!\n⏳ Selesaikan juga tantangan di tab lainnya untuk menyelesaikan pelajaran ini.'; out.output += '\n✅ Kode benar!\n⏳ Selesaikan juga tantangan di tab lainnya untuk menyelesaikan pelajaran ini.';
@ -352,6 +352,28 @@
} }
} }
async function handleRun() {
if (activeTab === 'circuit') { await evaluateCircuit(); return; }
if (!data) return;
activeTab = 'output';
await evaluateLanguage(currentLanguage as 'c' | 'python');
}
async function handleRunAll() {
if (!data) return;
activeTab = 'output';
const tabs = data.active_tabs ?? [];
const hasC = tabs.includes('c') || (!tabs.length && !tabs.includes('python'));
const hasPython = tabs.includes('python');
if (hasC) await evaluateLanguage('c');
if (hasPython) await evaluateLanguage('python');
if (tabs.includes('circuit')) await evaluateCircuit();
if (tabs.includes('velxio')) await handleVelxioSubmit();
}
function handleReset() { function handleReset() {
if (!data) return; if (!data) return;
if (activeTab === 'circuit') { if (activeTab === 'circuit') {
@ -922,7 +944,13 @@
<!-- Output tab panel --> <!-- Output tab panel -->
<div class="tab-panel" class:tab-hidden={activeTab !== 'output'}> <div class="tab-panel" class:tab-hidden={activeTab !== 'output'}>
<OutputPanel sections={outputSections} /> <OutputPanel sections={outputSections}>
{#snippet actions()}
<button class="btn btn-success btn-sm btn-run-all" onclick={handleRunAll} disabled={compiling}>
{compiling ? 'Mengevaluasi...' : '▶ Run Keseluruhan'}
</button>
{/snippet}
</OutputPanel>
</div> </div>
</div> </div>
@ -1039,6 +1067,10 @@
.btn-secondary:hover { .btn-secondary:hover {
background: var(--color-border); background: var(--color-border);
} }
.btn-run-all {
padding: 0.3rem 0.6rem;
font-size: 0.8rem;
}
.lang-label { .lang-label {
margin-left: auto; margin-left: auto;
font-size: 0.75rem; font-size: 0.75rem;