fix: prevent localStorage leakage and fix navigation by implementing component isolation and reactive state management
This commit is contained in:
parent
ea3556d05f
commit
459ceff2d8
|
|
@ -150,32 +150,20 @@ void loop() {
|
||||||
{
|
{
|
||||||
"type": "led",
|
"type": "led",
|
||||||
"id": "led-builtin",
|
"id": "led-builtin",
|
||||||
"x": 956.2066448009604,
|
"x": 188,
|
||||||
"y": -15.285273464964305,
|
"y": -158,
|
||||||
"rotation": 0,
|
"rotation": 0,
|
||||||
"props": {
|
"props": {
|
||||||
"color": "red",
|
"color": "red",
|
||||||
"pin": 13,
|
"pin": 13,
|
||||||
"state": false,
|
|
||||||
"value": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "resistor",
|
|
||||||
"id": "resistor-1776299815587-z0q0jzny8",
|
|
||||||
"x": 808.6031046903261,
|
|
||||||
"y": 44.96592399354325,
|
|
||||||
"rotation": 0,
|
|
||||||
"props": {
|
|
||||||
"value": false,
|
|
||||||
"state": false
|
"state": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "pushbutton",
|
"type": "pushbutton",
|
||||||
"id": "pushbutton-1776299852057-07xh6qh7g",
|
"id": "pushbutton-1778378142489-v899irppo",
|
||||||
"x": 829.2414304859469,
|
"x": 300,
|
||||||
"y": 230.19576790585754,
|
"y": 59,
|
||||||
"rotation": 0,
|
"rotation": 0,
|
||||||
"props": {
|
"props": {
|
||||||
"color": "red",
|
"color": "red",
|
||||||
|
|
@ -183,10 +171,33 @@ void loop() {
|
||||||
"label": "",
|
"label": "",
|
||||||
"xray": false
|
"xray": false
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "resistor",
|
||||||
|
"id": "resistor-1778378159414-9y3d8f1uk",
|
||||||
|
"x": 409,
|
||||||
|
"y": 68,
|
||||||
|
"rotation": 0,
|
||||||
|
"props": {
|
||||||
|
"value": "1000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "resistor",
|
||||||
|
"id": "resistor-1778378205248-h9g1m1wml",
|
||||||
|
"x": 114,
|
||||||
|
"y": -89,
|
||||||
|
"rotation": 0,
|
||||||
|
"props": {
|
||||||
|
"value": "1000"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"wires": []
|
"wires": [
|
||||||
|
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
---END_VELXIO_CIRCUIT---
|
---END_VELXIO_CIRCUIT---
|
||||||
|
|
||||||
---EXPECTED_SERIAL_OUTPUT---
|
---EXPECTED_SERIAL_OUTPUT---
|
||||||
|
|
@ -196,9 +207,9 @@ PRESSED
|
||||||
---EXPECTED_WIRING---
|
---EXPECTED_WIRING---
|
||||||
{
|
{
|
||||||
"wires": [
|
"wires": [
|
||||||
{
|
{
|
||||||
"start": {
|
"start": {
|
||||||
"componentId": "resistor-1776299815587-z0q0jzny8",
|
"componentId": "resistor-1778378205248-h9g1m1wml",
|
||||||
"pinName": "2"
|
"pinName": "2"
|
||||||
},
|
},
|
||||||
"end": {
|
"end": {
|
||||||
|
|
@ -206,6 +217,16 @@ PRESSED
|
||||||
"pinName": "A"
|
"pinName": "A"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"start": {
|
||||||
|
"componentId": "resistor-1778378205248-h9g1m1wml",
|
||||||
|
"pinName": "1"
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"componentId": "arduino-uno",
|
||||||
|
"pinName": "13"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"start": {
|
"start": {
|
||||||
"componentId": "arduino-uno",
|
"componentId": "arduino-uno",
|
||||||
|
|
@ -218,22 +239,22 @@ PRESSED
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"start": {
|
"start": {
|
||||||
"componentId": "resistor-1776299815587-z0q0jzny8",
|
"componentId": "arduino-uno",
|
||||||
"pinName": "1"
|
"pinName": "5V"
|
||||||
},
|
},
|
||||||
"end": {
|
"end": {
|
||||||
"componentId": "arduino-uno",
|
"componentId": "resistor-1778378159414-9y3d8f1uk",
|
||||||
"pinName": "13"
|
"pinName": "2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"start": {
|
"start": {
|
||||||
"componentId": "pushbutton-1776299852057-07xh6qh7g",
|
"componentId": "resistor-1778378159414-9y3d8f1uk",
|
||||||
"pinName": "1.l"
|
"pinName": "1"
|
||||||
},
|
},
|
||||||
"end": {
|
"end": {
|
||||||
"componentId": "arduino-uno",
|
"componentId": "pushbutton-1778378142489-v899irppo",
|
||||||
"pinName": "2"
|
"pinName": "1.r"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -242,8 +263,18 @@ PRESSED
|
||||||
"pinName": "GND.3"
|
"pinName": "GND.3"
|
||||||
},
|
},
|
||||||
"end": {
|
"end": {
|
||||||
"componentId": "pushbutton-1776299852057-07xh6qh7g",
|
"componentId": "pushbutton-1778378142489-v899irppo",
|
||||||
"pinName": "2.l"
|
"pinName": "2.r"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": {
|
||||||
|
"componentId": "arduino-uno",
|
||||||
|
"pinName": "2"
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"componentId": "pushbutton-1778378142489-v899irppo",
|
||||||
|
"pinName": "1.r"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -252,6 +252,7 @@
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
cleanupNoPaste?.();
|
cleanupNoPaste?.();
|
||||||
view?.destroy();
|
view?.destroy();
|
||||||
|
clearTimeout(saveTimeout);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Recreate editor ONLY when theme actually changes (not on ready/container changes)
|
// Recreate editor ONLY when theme actually changes (not on ready/container changes)
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,11 @@
|
||||||
const mgr = new LessonManager();
|
const mgr = new LessonManager();
|
||||||
const float = createFloatingPanel();
|
const float = createFloatingPanel();
|
||||||
|
|
||||||
// Sync lesson data when navigating
|
// Initialize manager with lesson data whenever it changes.
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (pageData.lesson) mgr.init(pageData.lesson);
|
if (pageData.lesson) {
|
||||||
|
mgr.init(pageData.lesson);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mobile behavior for floating panel
|
// Mobile behavior for floating panel
|
||||||
|
|
@ -79,191 +81,193 @@
|
||||||
<title>{mgr.data?.lesson_title ?? 'Pelajaran'} - Elemes LMS</title>
|
<title>{mgr.data?.lesson_title ?? 'Pelajaran'} - Elemes LMS</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
{#if mgr.data}
|
{#if pageData.lesson}
|
||||||
<div class="lesson-layout" class:single-col={float.floating || mgr.isMobile}>
|
{#key pageData.lesson.filename}
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<div class="lesson-layout" class:single-col={float.floating || mgr.isMobile}>
|
||||||
<div class="lesson-content" bind:this={contentEl} use:noSelect use:renderMath
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
role="region" aria-label="Konten pelajaran"
|
<div class="lesson-content" bind:this={contentEl} use:noSelect use:renderMath
|
||||||
class:full-width={float.floating || mgr.isMobile}
|
role="region" aria-label="Konten pelajaran"
|
||||||
onselectstart={(e) => e.preventDefault()}
|
class:full-width={float.floating || mgr.isMobile}
|
||||||
oncopy={(e) => e.preventDefault()}
|
onselectstart={(e) => e.preventDefault()}
|
||||||
oncut={(e) => e.preventDefault()}
|
oncopy={(e) => e.preventDefault()}
|
||||||
oncontextmenu={(e) => e.preventDefault()}>
|
oncut={(e) => e.preventDefault()}
|
||||||
|
oncontextmenu={(e) => e.preventDefault()}>
|
||||||
{#if mgr.data.locked}
|
|
||||||
<div class="locked-banner">
|
{#if mgr.data?.locked}
|
||||||
<span class="locked-banner-icon">🔒</span>
|
<div class="locked-banner">
|
||||||
<div>
|
<span class="locked-banner-icon">🔒</span>
|
||||||
<strong>Pelajaran ini terkunci.</strong> Kamu dapat membaca materinya, namun workspace (editor) dinonaktifkan hingga prasyarat selesai.
|
<div>
|
||||||
{#if mgr.data.missing_prerequisites?.length}
|
<strong>Pelajaran ini terkunci.</strong> Kamu dapat membaca materinya, namun workspace (editor) dinonaktifkan hingga prasyarat selesai.
|
||||||
<div class="missing-list">
|
{#if mgr.data.missing_prerequisites?.length}
|
||||||
Belum selesai:
|
<div class="missing-list">
|
||||||
{#each mgr.data.missing_prerequisites as p, i}
|
Belum selesai:
|
||||||
<a href="/lesson/{p}" class="prereq-link">{mgr.getLessonTitle(p)}</a>{i < mgr.data.missing_prerequisites.length - 1 ? ', ' : ''}
|
{#each mgr.data.missing_prerequisites as p, i}
|
||||||
{/each}
|
<a href="/lesson/{p}" class="prereq-link">{mgr.getLessonTitle(p)}</a>{i < mgr.data.missing_prerequisites.length - 1 ? ', ' : ''}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if mgr.isQuizMode}
|
||||||
|
<div class="quiz-blur-container">
|
||||||
|
<div class="quiz-blur-content">
|
||||||
|
<div class="quiz-blur-icon">🫣</div>
|
||||||
|
<h3>Materi Disembunyikan</h3>
|
||||||
|
<p>Selesaikan atau batalkan kuis untuk melihat materi kembali.</p>
|
||||||
|
<button class="btn btn-outline" onclick={() => mgr.isQuizMode = false}>Batal Kuis</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="prose">{@html mgr.data?.lesson_content ?? ''}</div>
|
||||||
|
<LessonList lessons={mgr.data?.ordered_lessons ?? []} currentSlug={mgr.slug} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if float.floating && float.minimized && !mgr.isMobile}
|
||||||
|
<button type="button" class="float-restore-btn" onclick={float.restore}>▶ Editor</button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
|
<div class="editor-area"
|
||||||
|
class:floating={float.floating && !mgr.isMobile && !float.minimized}
|
||||||
|
class:floating-hidden={float.floating && float.minimized && !mgr.isMobile}
|
||||||
|
class:mobile-sheet={mgr.isMobile}
|
||||||
|
class:mobile-hidden={mgr.isMobile && mgr.mobileMode === 'hidden'}
|
||||||
|
class:mobile-half={mgr.isMobile && mgr.mobileMode === 'half'}
|
||||||
|
class:mobile-full={mgr.isMobile && mgr.mobileMode === 'full'}
|
||||||
|
class:editor-locked={mgr.data?.locked}
|
||||||
|
style={float.style}>
|
||||||
|
|
||||||
|
<WorkspaceHeader
|
||||||
|
isMobile={mgr.isMobile}
|
||||||
|
bind:mobileMode={mgr.mobileMode}
|
||||||
|
bind:activeTab={mgr.activeTab}
|
||||||
|
bind:currentLanguage={mgr.currentLanguage}
|
||||||
|
hasInfo={!!mgr.data?.lesson_info}
|
||||||
|
hasExercise={!!mgr.data?.exercise_content}
|
||||||
|
activeTabs={mgr.data?.active_tabs ?? []}
|
||||||
|
floating={float.floating}
|
||||||
|
minimized={float.minimized}
|
||||||
|
onDragStart={float.onDragStart}
|
||||||
|
onResizeStart={float.onResizeStart}
|
||||||
|
onFloatToggle={float.toggle}
|
||||||
|
onMinimize={float.minimize}
|
||||||
|
locked={mgr.data?.locked}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="editor-body" bind:this={tabsEl} use:renderMath style="position: relative;">
|
||||||
|
{#if mgr.data?.locked}
|
||||||
|
<div class="workspace-lock-overlay">
|
||||||
|
<div class="lock-overlay-content">
|
||||||
|
<div class="lock-overlay-icon">🔒</div>
|
||||||
|
<h3>Workspace Terkunci</h3>
|
||||||
|
<p>Selesaikan materi sebelumnya untuk mulai mengerjakan latihan ini.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="tab-panel" class:tab-hidden={mgr.activeTab !== 'info'} use:noSelect>
|
||||||
|
{#if mgr.data?.lesson_info}
|
||||||
|
<div class="tab-content">{@html mgr.data.lesson_info}</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-panel" class:tab-hidden={mgr.activeTab !== 'exercise'} use:noSelect>
|
||||||
|
{#if mgr.data?.exercise_content}
|
||||||
|
<div class="tab-content">
|
||||||
|
<h2 class="tab-heading">Latihan</h2>
|
||||||
|
{@html mgr.data.exercise_content}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if mgr.isQuizMode}
|
{#if mgr.data?.active_tabs?.includes('circuit')}
|
||||||
<div class="quiz-blur-container">
|
<div class="tab-panel" class:tab-hidden={mgr.activeTab !== 'circuit'}>
|
||||||
<div class="quiz-blur-content">
|
<CircuitTab
|
||||||
<div class="quiz-blur-icon">🫣</div>
|
data={mgr.data}
|
||||||
<h3>Materi Disembunyikan</h3>
|
bind:circuitEditor={mgr.circuitEditor}
|
||||||
<p>Selesaikan atau batalkan kuis untuk melihat materi kembali.</p>
|
compiling={mgr.compiling}
|
||||||
<button class="btn btn-outline" onclick={() => mgr.isQuizMode = false}>Batal Kuis</button>
|
authLoggedIn={$authLoggedIn}
|
||||||
</div>
|
lessonCompleted={mgr.lessonCompleted}
|
||||||
</div>
|
showSolution={mgr.showSolution}
|
||||||
{:else}
|
slug={mgr.slug}
|
||||||
<div class="prose">{@html mgr.data.lesson_content}</div>
|
onRun={mgr.handleRun.bind(mgr)}
|
||||||
<LessonList lessons={mgr.data.ordered_lessons ?? []} currentSlug={mgr.slug} />
|
onReset={mgr.handleReset.bind(mgr)}
|
||||||
{/if}
|
onShowSolution={mgr.handleShowSolution.bind(mgr)}
|
||||||
</div>
|
/>
|
||||||
|
|
||||||
{#if float.floating && float.minimized && !mgr.isMobile}
|
|
||||||
<button type="button" class="float-restore-btn" onclick={float.restore}>▶ Editor</button>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
||||||
<div class="editor-area"
|
|
||||||
class:floating={float.floating && !mgr.isMobile && !float.minimized}
|
|
||||||
class:floating-hidden={float.floating && float.minimized && !mgr.isMobile}
|
|
||||||
class:mobile-sheet={mgr.isMobile}
|
|
||||||
class:mobile-hidden={mgr.isMobile && mgr.mobileMode === 'hidden'}
|
|
||||||
class:mobile-half={mgr.isMobile && mgr.mobileMode === 'half'}
|
|
||||||
class:mobile-full={mgr.isMobile && mgr.mobileMode === 'full'}
|
|
||||||
class:editor-locked={mgr.data.locked}
|
|
||||||
style={float.style}>
|
|
||||||
|
|
||||||
<WorkspaceHeader
|
|
||||||
isMobile={mgr.isMobile}
|
|
||||||
bind:mobileMode={mgr.mobileMode}
|
|
||||||
bind:activeTab={mgr.activeTab}
|
|
||||||
bind:currentLanguage={mgr.currentLanguage}
|
|
||||||
hasInfo={!!mgr.data.lesson_info}
|
|
||||||
hasExercise={!!mgr.data.exercise_content}
|
|
||||||
activeTabs={mgr.data.active_tabs ?? []}
|
|
||||||
floating={float.floating}
|
|
||||||
minimized={float.minimized}
|
|
||||||
onDragStart={float.onDragStart}
|
|
||||||
onResizeStart={float.onResizeStart}
|
|
||||||
onFloatToggle={float.toggle}
|
|
||||||
onMinimize={float.minimize}
|
|
||||||
locked={mgr.data.locked}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="editor-body" bind:this={tabsEl} use:renderMath style="position: relative;">
|
|
||||||
{#if mgr.data.locked}
|
|
||||||
<div class="workspace-lock-overlay">
|
|
||||||
<div class="lock-overlay-content">
|
|
||||||
<div class="lock-overlay-icon">🔒</div>
|
|
||||||
<h3>Workspace Terkunci</h3>
|
|
||||||
<p>Selesaikan materi sebelumnya untuk mulai mengerjakan latihan ini.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="tab-panel" class:tab-hidden={mgr.activeTab !== 'info'} use:noSelect>
|
|
||||||
{#if mgr.data.lesson_info}
|
|
||||||
<div class="tab-content">{@html mgr.data.lesson_info}</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tab-panel" class:tab-hidden={mgr.activeTab !== 'exercise'} use:noSelect>
|
|
||||||
{#if mgr.data.exercise_content}
|
|
||||||
<div class="tab-content">
|
|
||||||
<h2 class="tab-heading">Latihan</h2>
|
|
||||||
{@html mgr.data.exercise_content}
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if mgr.isVelxio}
|
||||||
|
<div class="tab-panel velxio-panel" class:tab-hidden={mgr.activeTab !== 'velxio'}>
|
||||||
|
<VelxioTab
|
||||||
|
hasArduinoCode={!!mgr.data?.initial_code_arduino}
|
||||||
|
velxioError={mgr.velxioError}
|
||||||
|
authLoggedIn={$authLoggedIn}
|
||||||
|
velxioSaving={mgr.velxioSaving}
|
||||||
|
onSetupBridge={mgr.setupVelxioBridge.bind(mgr)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if mgr.isFlowchart}
|
||||||
|
<div class="tab-panel flowchart-panel" class:tab-hidden={mgr.activeTab !== 'flowchart'}>
|
||||||
|
<FlowchartTab
|
||||||
|
bind:this={mgr.flowchartTab}
|
||||||
|
storageKey={mgr.flowchartStorageKey}
|
||||||
|
initialData={mgr.data?.initial_flowchart}
|
||||||
|
onRun={mgr.handleRun.bind(mgr)}
|
||||||
|
onReset={mgr.handleReset.bind(mgr)}
|
||||||
|
compiling={mgr.compiling}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if !mgr.data?.active_tabs?.length || mgr.data.active_tabs.includes('c') || mgr.data.active_tabs.includes('python')}
|
||||||
|
<div class="tab-panel" class:tab-hidden={mgr.activeTab !== 'editor'}>
|
||||||
|
<CodeTab
|
||||||
|
data={mgr.data}
|
||||||
|
bind:currentLanguage={mgr.currentLanguage}
|
||||||
|
bind:currentCode={mgr.currentCode}
|
||||||
|
bind:editor={mgr.editor}
|
||||||
|
compiling={mgr.compiling}
|
||||||
|
authLoggedIn={$authLoggedIn}
|
||||||
|
lessonCompleted={mgr.lessonCompleted}
|
||||||
|
showSolution={mgr.showSolution}
|
||||||
|
slug={mgr.slug}
|
||||||
|
onRun={mgr.handleRun.bind(mgr)}
|
||||||
|
onReset={mgr.handleReset.bind(mgr)}
|
||||||
|
onShowSolution={mgr.handleShowSolution.bind(mgr)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if mgr.data?.active_tabs?.includes('quiz')}
|
||||||
|
<div class="tab-panel" class:tab-hidden={mgr.activeTab !== 'quiz'}>
|
||||||
|
<QuizTab
|
||||||
|
quizData={mgr.data.quiz_data ?? []}
|
||||||
|
bind:isQuizMode={mgr.isQuizMode}
|
||||||
|
completedStatus={mgr.data.lesson_progress_status}
|
||||||
|
onComplete={(status) => mgr.completeLesson(status)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="tab-panel" class:tab-hidden={mgr.activeTab !== 'output'}>
|
||||||
|
<OutputPanel sections={mgr.outputSections}>
|
||||||
|
{#snippet actions()}
|
||||||
|
<button class="btn btn-success btn-sm btn-run-all" onclick={() => mgr.handleRunAll()} disabled={mgr.compiling}>
|
||||||
|
{mgr.compiling ? 'Mengevaluasi...' : '▶ Run Keseluruhan'}
|
||||||
|
</button>
|
||||||
|
{/snippet}
|
||||||
|
</OutputPanel>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if mgr.data.active_tabs?.includes('circuit')}
|
<CelebrationOverlay bind:visible={mgr.showCelebration} />
|
||||||
<div class="tab-panel" class:tab-hidden={mgr.activeTab !== 'circuit'}>
|
|
||||||
<CircuitTab
|
|
||||||
data={mgr.data}
|
|
||||||
bind:circuitEditor={mgr.circuitEditor}
|
|
||||||
compiling={mgr.compiling}
|
|
||||||
authLoggedIn={$authLoggedIn}
|
|
||||||
lessonCompleted={mgr.lessonCompleted}
|
|
||||||
showSolution={mgr.showSolution}
|
|
||||||
slug={mgr.slug}
|
|
||||||
onRun={mgr.handleRun.bind(mgr)}
|
|
||||||
onReset={mgr.handleReset.bind(mgr)}
|
|
||||||
onShowSolution={mgr.handleShowSolution.bind(mgr)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if mgr.isVelxio}
|
|
||||||
<div class="tab-panel velxio-panel" class:tab-hidden={mgr.activeTab !== 'velxio'}>
|
|
||||||
<VelxioTab
|
|
||||||
hasArduinoCode={!!mgr.data.initial_code_arduino}
|
|
||||||
velxioError={mgr.velxioError}
|
|
||||||
authLoggedIn={$authLoggedIn}
|
|
||||||
velxioSaving={mgr.velxioSaving}
|
|
||||||
onSetupBridge={mgr.setupVelxioBridge.bind(mgr)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if mgr.isFlowchart}
|
|
||||||
<div class="tab-panel flowchart-panel" class:tab-hidden={mgr.activeTab !== 'flowchart'}>
|
|
||||||
<FlowchartTab
|
|
||||||
bind:this={mgr.flowchartTab}
|
|
||||||
storageKey={mgr.flowchartStorageKey}
|
|
||||||
initialData={mgr.data.initial_flowchart}
|
|
||||||
onRun={mgr.handleRun.bind(mgr)}
|
|
||||||
onReset={mgr.handleReset.bind(mgr)}
|
|
||||||
compiling={mgr.compiling}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if !mgr.data.active_tabs?.length || mgr.data.active_tabs.includes('c') || mgr.data.active_tabs.includes('python')}
|
|
||||||
<div class="tab-panel" class:tab-hidden={mgr.activeTab !== 'editor'}>
|
|
||||||
<CodeTab
|
|
||||||
data={mgr.data}
|
|
||||||
bind:currentLanguage={mgr.currentLanguage}
|
|
||||||
bind:currentCode={mgr.currentCode}
|
|
||||||
bind:editor={mgr.editor}
|
|
||||||
compiling={mgr.compiling}
|
|
||||||
authLoggedIn={$authLoggedIn}
|
|
||||||
lessonCompleted={mgr.lessonCompleted}
|
|
||||||
showSolution={mgr.showSolution}
|
|
||||||
slug={mgr.slug}
|
|
||||||
onRun={mgr.handleRun.bind(mgr)}
|
|
||||||
onReset={mgr.handleReset.bind(mgr)}
|
|
||||||
onShowSolution={mgr.handleShowSolution.bind(mgr)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if mgr.data.active_tabs?.includes('quiz')}
|
|
||||||
<div class="tab-panel" class:tab-hidden={mgr.activeTab !== 'quiz'}>
|
|
||||||
<QuizTab
|
|
||||||
quizData={mgr.data.quiz_data ?? []}
|
|
||||||
bind:isQuizMode={mgr.isQuizMode}
|
|
||||||
completedStatus={mgr.data.lesson_progress_status}
|
|
||||||
onComplete={(status) => mgr.completeLesson(status)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="tab-panel" class:tab-hidden={mgr.activeTab !== 'output'}>
|
|
||||||
<OutputPanel sections={mgr.outputSections}>
|
|
||||||
{#snippet actions()}
|
|
||||||
<button class="btn btn-success btn-sm btn-run-all" onclick={() => mgr.handleRunAll()} disabled={mgr.compiling}>
|
|
||||||
{mgr.compiling ? 'Mengevaluasi...' : '▶ Run Keseluruhan'}
|
|
||||||
</button>
|
|
||||||
{/snippet}
|
|
||||||
</OutputPanel>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CelebrationOverlay bind:visible={mgr.showCelebration} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{/key}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
{compiling ? 'Compiling...' : '▶ Run'}
|
{compiling ? 'Compiling...' : '▶ Run'}
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-secondary" onclick={onReset}>Reset</button>
|
<button type="button" class="btn btn-secondary" onclick={onReset}>Reset</button>
|
||||||
{#if data.solution_code && authLoggedIn && lessonCompleted}
|
{#if data?.solution_code && authLoggedIn && lessonCompleted}
|
||||||
<button type="button" class="btn btn-secondary" onclick={onShowSolution}>
|
<button type="button" class="btn btn-secondary" onclick={onShowSolution}>
|
||||||
{showSolution ? 'Sembunyikan Solusi' : 'Lihat Solusi'}
|
{showSolution ? 'Sembunyikan Solusi' : 'Lihat Solusi'}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -57,9 +57,9 @@
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if (currentLanguage === 'python' ? (data.expected_output_python || data.expected_output) : data.expected_output)}
|
{#if (currentLanguage === 'python' ? (data?.expected_output_python || data?.expected_output) : data?.expected_output)}
|
||||||
<details class="expected-output">
|
<details class="expected-output">
|
||||||
<summary>Expected Output</summary>
|
<summary>Expected Output</summary>
|
||||||
<pre>{currentLanguage === 'python' ? (data.expected_output_python || data.expected_output) : data.expected_output}</pre>
|
<pre>{currentLanguage === 'python' ? (data?.expected_output_python || data?.expected_output) : data?.expected_output}</pre>
|
||||||
</details>
|
</details>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ export class LessonManager {
|
||||||
circuitEditor = $state<CircuitEditor | null>(null);
|
circuitEditor = $state<CircuitEditor | null>(null);
|
||||||
flowchartTab = $state<any>(null);
|
flowchartTab = $state<any>(null);
|
||||||
|
|
||||||
slug = $derived(get(page).params.slug);
|
get slug() { return get(page).params.slug; }
|
||||||
|
|
||||||
isVelxio = $derived(this.data?.active_tabs?.includes('velxio') ?? false);
|
isVelxio = $derived(this.data?.active_tabs?.includes('velxio') ?? false);
|
||||||
isFlowchart = $derived(this.data?.active_tabs?.includes('flowchart') ?? false);
|
isFlowchart = $derived(this.data?.active_tabs?.includes('flowchart') ?? false);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue