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 {
initialCode = '',
initialCircuit = '',
onTransfer,
onSave
}: {
initialCode?: string;
initialCircuit?: string;
onTransfer?: (state: { code: string; circuit: string }) => void;
onSave?: (state: { code: string; circuit: string }) => void;
} = $props();
@ -63,17 +65,23 @@
showFeedback('Dimuat ulang dari dokumen');
}
function handleSave() {
function handleTransfer() {
const state = getCurrentState();
if (!state) {
showFeedback('Gagal membaca state simulator');
return;
}
if (onSave) {
if (onTransfer) {
onTransfer(state);
} else if (onSave) {
onSave(state);
}
showFeedback('Tersimpan ke dokumen!');
showFeedback('Hasil dipindahkan ke editor');
}
function handleSave() {
handleTransfer();
}
function showFeedback(msg: string) {
@ -137,17 +145,7 @@
<div class="teacher-velxio-container">
<div class="teacher-velxio-toolbar">
<div class="toolbar-status">
<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">
<div class="toolbar-left">
<button
type="button"
class="toolbar-btn btn-reload"
@ -163,19 +161,33 @@
<span>Muat Ulang</span>
</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
type="button"
class="toolbar-btn btn-save"
onclick={handleSave}
title="Simpan rangkaian dan kode saat ini langsung ke dokumen markdown"
class="toolbar-btn btn-transfer"
onclick={handleTransfer}
title="Pindahkan rangkaian dan kode dari simulator ke dokumen editor"
disabled={!isReady}
>
<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>
<polyline points="17 21 17 13 7 13 7 21"></polyline>
<polyline points="7 3 7 8 15 8"></polyline>
<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="14 2 14 8 20 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>
<span>Simpan ke Dokumen</span>
<span>Pindahkan Hasil</span>
</button>
</div>
</div>
@ -215,6 +227,13 @@
flex-wrap: wrap;
}
.toolbar-left {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.toolbar-status {
display: flex;
align-items: center;
@ -292,13 +311,13 @@
cursor: not-allowed;
}
.btn-save {
.btn-transfer {
background: var(--color-primary, #0d6efd);
color: #ffffff;
border-color: var(--color-primary, #0d6efd);
}
.btn-save:hover:not(:disabled) {
.btn-transfer:hover:not(:disabled) {
background: var(--color-primary-dark, #0a58ca);
border-color: var(--color-primary-dark, #0a58ca);
}

View File

@ -163,12 +163,21 @@
deployMessage = 'Mengompilasi kode...';
let code = '';
let boardFqbn = 'arduino:avr:uno';
if (velxioIframe) {
const state = getVelxioState(velxioIframe);
if (state?.code) {
code = state.code;
}
if (state?.circuit) {
try {
const parsed = JSON.parse(state.circuit);
if (parsed?.board) {
boardFqbn = parsed.board;
}
} catch {}
}
}
if (!code) {
@ -179,7 +188,7 @@
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) {
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);
await mgr.handleSave();
}
async function handleSaveAction() {
@ -375,10 +374,40 @@
'\n---EXPECTED_SERIAL_OUTPUT---\n' +
'LED ON\nLED OFF\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' +
'kata_kunci\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',
label: 'Set Flowchart',
@ -648,7 +677,7 @@
{#snippet saveActions()}
{#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 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 ? '⏳' : '💾'}
</button>
<button class="panel-btn publish-btn" onclick={() => handlePublishAction()} disabled={mgr.publishing || !mgr.draftId} title="Publish">
@ -870,7 +899,8 @@
bind:this={velxioEditorRef}
initialCode={extractedCodes.velxio}
initialCircuit={extractedCircuitVelxio}
onSave={handleSaveVelxioFromTab}
onTransfer={handleTransferVelxioFromTab}
onSave={handleTransferVelxioFromTab}
/>
</div>
{:else if dynTab.id === 'flowchart'}