From 614ade6994da31b5b4b97035e1cd99e100b83b00 Mon Sep 17 00:00:00 2001 From: a2nr Date: Thu, 26 Mar 2026 21:19:24 +0700 Subject: [PATCH] feat: Enhance lesson tab functionality and UI improvements --- .../src/routes/lesson/[slug]/+page.svelte | 253 ++++++++++-------- 1 file changed, 134 insertions(+), 119 deletions(-) diff --git a/frontend/src/routes/lesson/[slug]/+page.svelte b/frontend/src/routes/lesson/[slug]/+page.svelte index 5f4cdc2..afb67bf 100644 --- a/frontend/src/routes/lesson/[slug]/+page.svelte +++ b/frontend/src/routes/lesson/[slug]/+page.svelte @@ -22,7 +22,7 @@ // UI state let showSolution = $state(false); - let activeTab = $state<'editor' | 'output'>('editor'); + let activeTab = $state<'info' | 'exercise' | 'editor' | 'output'>('info'); let editor = $state(null); let showCelebration = $state(false); @@ -34,7 +34,8 @@ let mobileExpanded = $state(true); let touchStartY = 0; let lessonContentEl = $state(); - let lessonInfoEl = $state(); + let infoTabEl = $state(); + let exerciseTabEl = $state(); // Drag & resize state for floating panel let dragging = $state(false); @@ -146,10 +147,11 @@ const node = sel.anchorNode; if (!node) return; - // Only clear if selection is inside lesson content or lesson info + // Only clear if selection is inside lesson content or info/exercise tab panels const inLesson = lessonContentEl?.contains(node); - const inInfo = lessonInfoEl?.contains(node); - if (inLesson || inInfo) { + const inInfo = infoTabEl?.contains(node); + const inExercise = exerciseTabEl?.contains(node); + if (inLesson || inInfo || inExercise) { sel.removeAllRanges(); } } @@ -208,7 +210,9 @@ compileError = ''; compileSuccess = null; showSolution = false; - activeTab = 'editor'; + if (lesson.lesson_info) activeTab = 'info'; + else if (lesson.exercise_content) activeTab = 'exercise'; + else activeTab = 'editor'; mobileExpanded = true; } }); @@ -288,16 +292,6 @@ {data?.lesson_title ?? 'Pelajaran'} - Elemes LMS - -{#if showCelebration} -
-
-
-

Selamat! Latihan Selesai!

-
-
-{/if} - {#if data}
@@ -309,17 +303,6 @@

{data.lesson_title}

- - {#if data.lesson_info} -
e.preventDefault()} - oncopy={(e) => e.preventDefault()} - oncontextmenu={(e) => e.preventDefault()}> - Informasi Pelajaran -
{@html data.lesson_info}
-
- {/if} -
@@ -332,13 +315,6 @@ oncut={(e) => e.preventDefault()} oncontextmenu={(e) => e.preventDefault()}>
{@html data.lesson_content}
- - {#if data.exercise_content} -
-

Latihan

-
{@html data.exercise_content}
-
- {/if}
@@ -358,21 +334,21 @@ class:mobile-collapsed={isMobile && !mobileExpanded} style={floatStyle}> - + {#if isMobile} {:else if editorFloating && !editorMinimized}
{ e.stopPropagation(); onResizeStart(e); }} title="Resize">◳ - Code Editor + Workspace
@@ -380,49 +356,95 @@ title="Dock editor">⊡
+ {:else if !isMobile} +
+ Workspace +
+ +
+
{/if}
- {#if isMobile} -
- - -
- {/if} + +
+ {#if data.lesson_info} + + {/if} + {#if data.exercise_content} + + {/if} + + +
- -
- - - {#if data.solution_code && auth.isLoggedIn && data.lesson_completed} - - {/if} - {data.language_display_name} - {#if !isMobile && !editorFloating} - + + {#if data.solution_code && auth.isLoggedIn && data.lesson_completed} + + {/if} + {data.language_display_name} +
+ +
+ (currentCode = val)} + /> +
+ + {#if data.expected_output} +
+ Expected Output +
{data.expected_output}
+
{/if}
- -
- (currentCode = val)} - /> -
- - -
+ +
- - - {#if data.expected_output} -
- Expected Output -
{data.expected_output}
-
- {/if}
+ + + {#if showCelebration} +
+
+
+

Selamat! Latihan Selesai!

+
+
+ {/if}
@@ -473,20 +497,7 @@ margin-bottom: 1rem; } - .lesson-info { - margin-bottom: 1rem; - border: 1px solid var(--color-border); - border-radius: var(--radius); - padding: 0.75rem; - background: var(--color-bg-secondary); - } - .lesson-info summary { - cursor: pointer; - font-weight: 600; - font-size: 0.9rem; - } .info-content { - margin-top: 0.75rem; font-size: 0.85rem; } @@ -507,12 +518,6 @@ -webkit-touch-callout: none; } - .lesson-info { - -webkit-user-select: none; - user-select: none; - -webkit-touch-callout: none; - } - .prose :global(pre) { background: var(--color-bg-secondary); padding: 0.75rem; @@ -533,19 +538,31 @@ } .exercise-section { - margin-top: 1.5rem; - padding-top: 1rem; - border-top: 2px solid var(--color-primary); + padding-top: 0.5rem; } .exercise-section h2 { color: var(--color-primary); font-size: 1.1rem; } - /* ── Editor area (inline mode) ─────────────────────────── */ + /* ── Editor area (docked mode — styled like floating) ──── */ .editor-area { position: sticky; top: 4rem; + background: var(--color-bg); + border: 1px solid var(--color-border); + border-radius: var(--radius); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18); + display: flex; + flex-direction: column; + overflow: hidden; + max-height: 85vh; + } + .editor-area .editor-body { + flex: 1; + overflow-y: auto; + padding: 0.5rem; + min-height: 0; } /* ── Single-column layout ──────────────────────────────── */ @@ -693,7 +710,7 @@ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18); display: flex; flex-direction: column; - overflow: auto; + overflow: hidden; } .resize-handle { cursor: nwse-resize; @@ -707,12 +724,6 @@ background: var(--color-border); color: var(--color-text); } - .editor-area.floating .editor-body { - flex: 1; - overflow-y: auto; - padding: 0.5rem; - min-height: 0; - } .editor-area.floating-hidden { display: none !important; } @@ -738,9 +749,6 @@ transform: translateY(calc(100% - 48px)); } .mobile-sheet .editor-body { - flex: 1; - overflow-y: auto; - padding: 0.5rem; overscroll-behavior: contain; } .sheet-handle { @@ -761,8 +769,8 @@ margin: 0 auto 0.25rem; } - /* ── Mobile tabs ───────────────────────────────────────── */ - .mobile-tabs { + /* ── Tabs ─────────────────────────────────────────────── */ + .panel-tabs { display: flex; gap: 0; margin-bottom: 0.5rem; @@ -778,16 +786,22 @@ cursor: pointer; font-weight: 500; font-size: 0.85rem; + white-space: nowrap; } .tab.active { background: var(--color-primary); color: #fff; } - /* ── Utility ───────────────────────────────────────────── */ - .hidden-mobile { + /* ── Tab panels ────────────────────────────────────────── */ + .tab-panel { + overflow-y: auto; + } + .tab-hidden { display: none; } + + /* ── Utility ───────────────────────────────────────────── */ .editor-body.body-hidden { display: none; } @@ -801,17 +815,18 @@ border-top: 1px solid var(--color-border); } - /* ── Celebration overlay ──────────────────────────────── */ + /* ── Celebration overlay (scoped to editor-area) ─────── */ .celebration-overlay { - position: fixed; + position: absolute; inset: 0; - z-index: 200; + z-index: 10; display: flex; align-items: center; justify-content: center; background: rgba(0, 0, 0, 0.4); animation: celebFadeIn 0.3s ease-out; pointer-events: none; + border-radius: inherit; } .celebration-content { text-align: center;