feat(velxio): improve teacher arduino toolbar, add expected_wiring template, and harden deployer board detection

- Add EXPECTED_WIRING block to set-arduino template and standalone expected-wiring feature in teacher content editor
- Reposition reload button to the far left inline with status dot, and shorten status text to 'Siap' / 'Menghubungkan...'
- Decouple transfer from save: replace 'Simpan ke Dokumen' with 'Pindahkan Hasil' and centralize document saving to top header save button
- Dynamically detect board FQBN from circuit in DeployTab with arduino:avr:uno fallback
This commit is contained in:
a2nr 2026-09-06 05:10:13 +07:00
parent 5429dc17f9
commit e4ab058078
3 changed files with 86 additions and 28 deletions

View File

@ -5,10 +5,12 @@
let { let {
initialCode = '', initialCode = '',
initialCircuit = '', initialCircuit = '',
onTransfer,
onSave onSave
}: { }: {
initialCode?: string; initialCode?: string;
initialCircuit?: string; initialCircuit?: string;
onTransfer?: (state: { code: string; circuit: string }) => void;
onSave?: (state: { code: string; circuit: string }) => void; onSave?: (state: { code: string; circuit: string }) => void;
} = $props(); } = $props();
@ -63,17 +65,23 @@
showFeedback('Dimuat ulang dari dokumen'); showFeedback('Dimuat ulang dari dokumen');
} }
function handleSave() { function handleTransfer() {
const state = getCurrentState(); const state = getCurrentState();
if (!state) { if (!state) {
showFeedback('Gagal membaca state simulator'); showFeedback('Gagal membaca state simulator');
return; return;
} }
if (onSave) { if (onTransfer) {
onTransfer(state);
} else if (onSave) {
onSave(state); onSave(state);
} }
showFeedback('Tersimpan ke dokumen!'); showFeedback('Hasil dipindahkan ke editor');
}
function handleSave() {
handleTransfer();
} }
function showFeedback(msg: string) { function showFeedback(msg: string) {
@ -137,17 +145,7 @@
<div class="teacher-velxio-container"> <div class="teacher-velxio-container">
<div class="teacher-velxio-toolbar"> <div class="teacher-velxio-toolbar">
<div class="toolbar-status"> <div class="toolbar-left">
<span class="status-dot" class:ready={isReady}></span>
<span class="status-text">
{isReady ? 'Simulator Siap (Mode Lengkap)' : 'Menghubungkan Simulator...'}
</span>
{#if saveFeedback}
<span class="save-feedback">{saveFeedback}</span>
{/if}
</div>
<div class="toolbar-actions">
<button <button
type="button" type="button"
class="toolbar-btn btn-reload" class="toolbar-btn btn-reload"
@ -163,19 +161,33 @@
<span>Muat Ulang</span> <span>Muat Ulang</span>
</button> </button>
<div class="toolbar-status">
<span class="status-dot" class:ready={isReady}></span>
<span class="status-text">
{isReady ? 'Siap' : 'Menghubungkan...'}
</span>
{#if saveFeedback}
<span class="save-feedback">{saveFeedback}</span>
{/if}
</div>
</div>
<div class="toolbar-actions">
<button <button
type="button" type="button"
class="toolbar-btn btn-save" class="toolbar-btn btn-transfer"
onclick={handleSave} onclick={handleTransfer}
title="Simpan rangkaian dan kode saat ini langsung ke dokumen markdown" title="Pindahkan rangkaian dan kode dari simulator ke dokumen editor"
disabled={!isReady} disabled={!isReady}
> >
<svg class="icon" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"> <svg class="icon" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2">
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path> <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="17 21 17 13 7 13 7 21"></polyline> <polyline points="14 2 14 8 20 8"></polyline>
<polyline points="7 3 7 8 15 8"></polyline> <line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg> </svg>
<span>Simpan ke Dokumen</span> <span>Pindahkan Hasil</span>
</button> </button>
</div> </div>
</div> </div>
@ -215,6 +227,13 @@
flex-wrap: wrap; flex-wrap: wrap;
} }
.toolbar-left {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.toolbar-status { .toolbar-status {
display: flex; display: flex;
align-items: center; align-items: center;
@ -292,13 +311,13 @@
cursor: not-allowed; cursor: not-allowed;
} }
.btn-save { .btn-transfer {
background: var(--color-primary, #0d6efd); background: var(--color-primary, #0d6efd);
color: #ffffff; color: #ffffff;
border-color: var(--color-primary, #0d6efd); border-color: var(--color-primary, #0d6efd);
} }
.btn-save:hover:not(:disabled) { .btn-transfer:hover:not(:disabled) {
background: var(--color-primary-dark, #0a58ca); background: var(--color-primary-dark, #0a58ca);
border-color: var(--color-primary-dark, #0a58ca); border-color: var(--color-primary-dark, #0a58ca);
} }

View File

@ -163,12 +163,21 @@
deployMessage = 'Mengompilasi kode...'; deployMessage = 'Mengompilasi kode...';
let code = ''; let code = '';
let boardFqbn = 'arduino:avr:uno';
if (velxioIframe) { if (velxioIframe) {
const state = getVelxioState(velxioIframe); const state = getVelxioState(velxioIframe);
if (state?.code) { if (state?.code) {
code = state.code; code = state.code;
} }
if (state?.circuit) {
try {
const parsed = JSON.parse(state.circuit);
if (parsed?.board) {
boardFqbn = parsed.board;
}
} catch {}
}
} }
if (!code) { if (!code) {
@ -179,7 +188,7 @@
throw new Error('Tidak ada kode Arduino untuk di-deploy'); throw new Error('Tidak ada kode Arduino untuk di-deploy');
} }
const res = await getHexContent({ code, board_fqbn: 'arduino:avr:uno' }); const res = await getHexContent({ code, board_fqbn: boardFqbn });
if (!res.success || !res.hex_content) { if (!res.success || !res.hex_content) {
throw new Error(res.error || 'Kompilasi gagal'); throw new Error(res.error || 'Kompilasi gagal');

View File

@ -209,9 +209,8 @@
} }
} }
async function handleSaveVelxioFromTab(state: { code: string; circuit: string }) { function handleTransferVelxioFromTab(state: { code: string; circuit: string }) {
applyVelxioToBody(state.code, state.circuit); applyVelxioToBody(state.code, state.circuit);
await mgr.handleSave();
} }
async function handleSaveAction() { async function handleSaveAction() {
@ -375,10 +374,40 @@
'\n---EXPECTED_SERIAL_OUTPUT---\n' + '\n---EXPECTED_SERIAL_OUTPUT---\n' +
'LED ON\nLED OFF\n' + 'LED ON\nLED OFF\n' +
'---END_EXPECTED_SERIAL_OUTPUT---\n' + '---END_EXPECTED_SERIAL_OUTPUT---\n' +
'\n---EXPECTED_WIRING---\n' +
'{\n' +
' "wires": [\n' +
' {\n' +
' "start": { "componentId": "arduino-uno", "pinName": "13" },\n' +
' "end": { "componentId": "led-builtin", "pinName": "A" }\n' +
' },\n' +
' {\n' +
' "start": { "componentId": "arduino-uno", "pinName": "GND" },\n' +
' "end": { "componentId": "led-builtin", "pinName": "C" }\n' +
' }\n' +
' ]\n' +
'}\n' +
'---END_EXPECTED_WIRING---\n' +
'\n---KEY_TEXT---\n' + '\n---KEY_TEXT---\n' +
'kata_kunci\n' + 'kata_kunci\n' +
'---END_KEY_TEXT---\n', '---END_KEY_TEXT---\n',
}, },
{
id: 'expected-wiring',
label: 'Expected Wiring',
group: 'set_soal',
snippet:
'\n---EXPECTED_WIRING---\n' +
'{\n' +
' "wires": [\n' +
' {\n' +
' "start": { "componentId": "arduino-uno", "pinName": "13" },\n' +
' "end": { "componentId": "led-builtin", "pinName": "A" }\n' +
' }\n' +
' ]\n' +
'}\n' +
'---END_EXPECTED_WIRING---\n',
},
{ {
id: 'set-flowchart', id: 'set-flowchart',
label: 'Set Flowchart', label: 'Set Flowchart',
@ -648,7 +677,7 @@
{#snippet saveActions()} {#snippet saveActions()}
{#if mgr.activePath && !isAssetFile} {#if mgr.activePath && !isAssetFile}
<button class="panel-btn toolbar-toggle" class:active={featureSidebarOpen} onclick={() => featureSidebarOpen = !featureSidebarOpen} title="Tampilkan/sembunyikan panel Fitur">🧩</button> <button class="panel-btn toolbar-toggle" class:active={featureSidebarOpen} onclick={() => featureSidebarOpen = !featureSidebarOpen} title="Tampilkan/sembunyikan panel Fitur">🧩</button>
<button class="panel-btn save-btn" onclick={() => handleSaveAction()} disabled={mgr.saving || !mgr.dirty} title="Simpan"> <button class="panel-btn save-btn" onclick={() => handleSaveAction()} disabled={mgr.saving || (!mgr.dirty && mgr.activeTab !== 'velxio')} title="Simpan">
{mgr.saving ? '⏳' : '💾'} {mgr.saving ? '⏳' : '💾'}
</button> </button>
<button class="panel-btn publish-btn" onclick={() => handlePublishAction()} disabled={mgr.publishing || !mgr.draftId} title="Publish"> <button class="panel-btn publish-btn" onclick={() => handlePublishAction()} disabled={mgr.publishing || !mgr.draftId} title="Publish">
@ -870,7 +899,8 @@
bind:this={velxioEditorRef} bind:this={velxioEditorRef}
initialCode={extractedCodes.velxio} initialCode={extractedCodes.velxio}
initialCircuit={extractedCircuitVelxio} initialCircuit={extractedCircuitVelxio}
onSave={handleSaveVelxioFromTab} onTransfer={handleTransferVelxioFromTab}
onSave={handleTransferVelxioFromTab}
/> />
</div> </div>
{:else if dynTab.id === 'flowchart'} {:else if dynTab.id === 'flowchart'}