From 459ceff2d87ff290d39fcb45eb6c89aa57f38d27 Mon Sep 17 00:00:00 2001 From: a2nr Date: Sun, 10 May 2026 09:14:00 +0700 Subject: [PATCH] fix: prevent localStorage leakage and fix navigation by implementing component isolation and reactive state management --- examples/content/button_input_arduino.md | 91 +++-- frontend/src/lib/components/CodeEditor.svelte | 1 + .../src/routes/lesson/[slug]/+page.svelte | 362 +++++++++--------- .../src/routes/lesson/[slug]/CodeTab.svelte | 6 +- .../src/routes/lesson/[slug]/lesson.svelte.ts | 2 +- 5 files changed, 249 insertions(+), 213 deletions(-) diff --git a/examples/content/button_input_arduino.md b/examples/content/button_input_arduino.md index ce7a9d0..e979be8 100644 --- a/examples/content/button_input_arduino.md +++ b/examples/content/button_input_arduino.md @@ -150,32 +150,20 @@ void loop() { { "type": "led", "id": "led-builtin", - "x": 956.2066448009604, - "y": -15.285273464964305, + "x": 188, + "y": -158, "rotation": 0, "props": { "color": "red", "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 } }, { "type": "pushbutton", - "id": "pushbutton-1776299852057-07xh6qh7g", - "x": 829.2414304859469, - "y": 230.19576790585754, + "id": "pushbutton-1778378142489-v899irppo", + "x": 300, + "y": 59, "rotation": 0, "props": { "color": "red", @@ -183,10 +171,33 @@ void loop() { "label": "", "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--- ---EXPECTED_SERIAL_OUTPUT--- @@ -196,9 +207,9 @@ PRESSED ---EXPECTED_WIRING--- { "wires": [ - { + { "start": { - "componentId": "resistor-1776299815587-z0q0jzny8", + "componentId": "resistor-1778378205248-h9g1m1wml", "pinName": "2" }, "end": { @@ -206,6 +217,16 @@ PRESSED "pinName": "A" } }, + { + "start": { + "componentId": "resistor-1778378205248-h9g1m1wml", + "pinName": "1" + }, + "end": { + "componentId": "arduino-uno", + "pinName": "13" + } + }, { "start": { "componentId": "arduino-uno", @@ -218,22 +239,22 @@ PRESSED }, { "start": { - "componentId": "resistor-1776299815587-z0q0jzny8", - "pinName": "1" + "componentId": "arduino-uno", + "pinName": "5V" }, "end": { - "componentId": "arduino-uno", - "pinName": "13" + "componentId": "resistor-1778378159414-9y3d8f1uk", + "pinName": "2" } }, { "start": { - "componentId": "pushbutton-1776299852057-07xh6qh7g", - "pinName": "1.l" + "componentId": "resistor-1778378159414-9y3d8f1uk", + "pinName": "1" }, "end": { - "componentId": "arduino-uno", - "pinName": "2" + "componentId": "pushbutton-1778378142489-v899irppo", + "pinName": "1.r" } }, { @@ -242,8 +263,18 @@ PRESSED "pinName": "GND.3" }, "end": { - "componentId": "pushbutton-1776299852057-07xh6qh7g", - "pinName": "2.l" + "componentId": "pushbutton-1778378142489-v899irppo", + "pinName": "2.r" + } + }, + { + "start": { + "componentId": "arduino-uno", + "pinName": "2" + }, + "end": { + "componentId": "pushbutton-1778378142489-v899irppo", + "pinName": "1.r" } } ] diff --git a/frontend/src/lib/components/CodeEditor.svelte b/frontend/src/lib/components/CodeEditor.svelte index 8afa685..a30a0c2 100644 --- a/frontend/src/lib/components/CodeEditor.svelte +++ b/frontend/src/lib/components/CodeEditor.svelte @@ -252,6 +252,7 @@ onDestroy(() => { cleanupNoPaste?.(); view?.destroy(); + clearTimeout(saveTimeout); }); // Recreate editor ONLY when theme actually changes (not on ready/container changes) diff --git a/frontend/src/routes/lesson/[slug]/+page.svelte b/frontend/src/routes/lesson/[slug]/+page.svelte index 95b20e0..b3dc0f1 100644 --- a/frontend/src/routes/lesson/[slug]/+page.svelte +++ b/frontend/src/routes/lesson/[slug]/+page.svelte @@ -26,9 +26,11 @@ const mgr = new LessonManager(); const float = createFloatingPanel(); - // Sync lesson data when navigating + // Initialize manager with lesson data whenever it changes. $effect(() => { - if (pageData.lesson) mgr.init(pageData.lesson); + if (pageData.lesson) { + mgr.init(pageData.lesson); + } }); // Mobile behavior for floating panel @@ -79,191 +81,193 @@ {mgr.data?.lesson_title ?? 'Pelajaran'} - Elemes LMS -{#if mgr.data} -
- -
e.preventDefault()} - oncopy={(e) => e.preventDefault()} - oncut={(e) => e.preventDefault()} - oncontextmenu={(e) => e.preventDefault()}> - - {#if mgr.data.locked} -
- 🔒 -
- Pelajaran ini terkunci. Kamu dapat membaca materinya, namun workspace (editor) dinonaktifkan hingga prasyarat selesai. - {#if mgr.data.missing_prerequisites?.length} -
- Belum selesai: - {#each mgr.data.missing_prerequisites as p, i} - {mgr.getLessonTitle(p)}{i < mgr.data.missing_prerequisites.length - 1 ? ', ' : ''} - {/each} +{#if pageData.lesson} + {#key pageData.lesson.filename} +
+ +
e.preventDefault()} + oncopy={(e) => e.preventDefault()} + oncut={(e) => e.preventDefault()} + oncontextmenu={(e) => e.preventDefault()}> + + {#if mgr.data?.locked} +
+ 🔒 +
+ Pelajaran ini terkunci. Kamu dapat membaca materinya, namun workspace (editor) dinonaktifkan hingga prasyarat selesai. + {#if mgr.data.missing_prerequisites?.length} +
+ Belum selesai: + {#each mgr.data.missing_prerequisites as p, i} + {mgr.getLessonTitle(p)}{i < mgr.data.missing_prerequisites.length - 1 ? ', ' : ''} + {/each} +
+ {/if} +
+
+ {/if} + + {#if mgr.isQuizMode} +
+
+
🫣
+

Materi Disembunyikan

+

Selesaikan atau batalkan kuis untuk melihat materi kembali.

+ +
+
+ {:else} +
{@html mgr.data?.lesson_content ?? ''}
+ + {/if} +
+ + {#if float.floating && float.minimized && !mgr.isMobile} + + {/if} + + +
+ + + +
+ {#if mgr.data?.locked} +
+
+
🔒
+

Workspace Terkunci

+

Selesaikan materi sebelumnya untuk mulai mengerjakan latihan ini.

+
+
+ {/if} + +
+ {#if mgr.data?.lesson_info} +
{@html mgr.data.lesson_info}
+ {/if} +
+ +
+ {#if mgr.data?.exercise_content} +
+

Latihan

+ {@html mgr.data.exercise_content}
{/if}
-
- {/if} - {#if mgr.isQuizMode} -
-
-
🫣
-

Materi Disembunyikan

-

Selesaikan atau batalkan kuis untuk melihat materi kembali.

- -
-
- {:else} -
{@html mgr.data.lesson_content}
- - {/if} -
- - {#if float.floating && float.minimized && !mgr.isMobile} - - {/if} - - -
- - - -
- {#if mgr.data.locked} -
-
-
🔒
-

Workspace Terkunci

-

Selesaikan materi sebelumnya untuk mulai mengerjakan latihan ini.

-
-
- {/if} - -
- {#if mgr.data.lesson_info} -
{@html mgr.data.lesson_info}
- {/if} -
- -
- {#if mgr.data.exercise_content} -
-

Latihan

- {@html mgr.data.exercise_content} + {#if mgr.data?.active_tabs?.includes('circuit')} +
+
{/if} + + {#if mgr.isVelxio} +
+ +
+ {/if} + + {#if mgr.isFlowchart} +
+ +
+ {/if} + + {#if !mgr.data?.active_tabs?.length || mgr.data.active_tabs.includes('c') || mgr.data.active_tabs.includes('python')} +
+ +
+ {/if} + + {#if mgr.data?.active_tabs?.includes('quiz')} +
+ mgr.completeLesson(status)} + /> +
+ {/if} + +
+ + {#snippet actions()} + + {/snippet} + +
- {#if mgr.data.active_tabs?.includes('circuit')} -
- -
- {/if} - - {#if mgr.isVelxio} -
- -
- {/if} - - {#if mgr.isFlowchart} -
- -
- {/if} - - {#if !mgr.data.active_tabs?.length || mgr.data.active_tabs.includes('c') || mgr.data.active_tabs.includes('python')} -
- -
- {/if} - - {#if mgr.data.active_tabs?.includes('quiz')} -
- mgr.completeLesson(status)} - /> -
- {/if} - -
- - {#snippet actions()} - - {/snippet} - -
+
- -
-
+ {/key} {/if} diff --git a/frontend/src/routes/lesson/[slug]/CodeTab.svelte b/frontend/src/routes/lesson/[slug]/CodeTab.svelte index 062ff83..8a351f2 100644 --- a/frontend/src/routes/lesson/[slug]/CodeTab.svelte +++ b/frontend/src/routes/lesson/[slug]/CodeTab.svelte @@ -36,7 +36,7 @@ {compiling ? 'Compiling...' : 'â–¶ Run'} - {#if data.solution_code && authLoggedIn && lessonCompleted} + {#if data?.solution_code && authLoggedIn && lessonCompleted} @@ -57,9 +57,9 @@ {/key}
-{#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)}
Expected Output -
{currentLanguage === 'python' ? (data.expected_output_python || data.expected_output) : data.expected_output}
+
{currentLanguage === 'python' ? (data?.expected_output_python || data?.expected_output) : data?.expected_output}
{/if} diff --git a/frontend/src/routes/lesson/[slug]/lesson.svelte.ts b/frontend/src/routes/lesson/[slug]/lesson.svelte.ts index c6f521d..423fee1 100644 --- a/frontend/src/routes/lesson/[slug]/lesson.svelte.ts +++ b/frontend/src/routes/lesson/[slug]/lesson.svelte.ts @@ -54,7 +54,7 @@ export class LessonManager { circuitEditor = $state(null); flowchartTab = $state(null); - slug = $derived(get(page).params.slug); + get slug() { return get(page).params.slug; } isVelxio = $derived(this.data?.active_tabs?.includes('velxio') ?? false); isFlowchart = $derived(this.data?.active_tabs?.includes('flowchart') ?? false);