feat: navigate to next/previous lesson (#352)
* prev and next button logic in place * handled the edge case * done * updated the transation and the navigating function * cleanup * ease in animation, simplify disabled logic, avoid quick loader < 1s load time --------- Co-authored-by: Rotimi Best <best@classroomio.com>
This commit is contained in:
+1
-1
@@ -15,7 +15,7 @@
|
|||||||
$lessonByTranslation[lessonId],
|
$lessonByTranslation[lessonId],
|
||||||
$lesson.locale
|
$lesson.locale
|
||||||
);
|
);
|
||||||
$: hasAtLeastOneTranslation = Object.values($lessonByTranslation[lessonId]).some(
|
$: hasAtLeastOneTranslation = Object.values($lessonByTranslation[lessonId] || {}).some(
|
||||||
(c) => !!c.length
|
(c) => !!c.length
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
import { useCompletion } from 'ai/svelte';
|
import { useCompletion } from 'ai/svelte';
|
||||||
import MODES from '$lib/utils/constants/mode.js';
|
import MODES from '$lib/utils/constants/mode.js';
|
||||||
import TrashCanIcon from 'carbon-icons-svelte/lib/TrashCan.svelte';
|
import TrashCanIcon from 'carbon-icons-svelte/lib/TrashCan.svelte';
|
||||||
@@ -477,11 +478,13 @@
|
|||||||
</slot:fragment>
|
</slot:fragment>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
{:else if !isMaterialsEmpty($lesson.materials, $lessonByTranslation[lessonId])}
|
{:else if !isMaterialsEmpty($lesson.materials, $lessonByTranslation[lessonId])}
|
||||||
<div class="w-full">
|
{#key lessonId}
|
||||||
{#each componentsToRender as Component}
|
<div class="w-full" in:fade={{ delay: 500 }} out:fade>
|
||||||
<svelte:component this={Component} {lessonId} />
|
{#each componentsToRender as Component}
|
||||||
{/each}
|
<svelte:component this={Component} {lessonId} />
|
||||||
</div>
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/key}
|
||||||
{:else}
|
{:else}
|
||||||
<Box className="text-center">
|
<Box className="text-center">
|
||||||
<img src="/no-video.svg" alt="Video not found" />
|
<img src="/no-video.svg" alt="Video not found" />
|
||||||
|
|||||||
@@ -745,7 +745,9 @@
|
|||||||
"lock": "Sperren",
|
"lock": "Sperren",
|
||||||
"unlock": "Freischalten"
|
"unlock": "Freischalten"
|
||||||
},
|
},
|
||||||
"disabled": "Funktion ist deaktiviert"
|
"disabled": "Funktion ist deaktiviert",
|
||||||
|
"next": "Nächste",
|
||||||
|
"prev": "Vorher"
|
||||||
},
|
},
|
||||||
"news_feed": {
|
"news_feed": {
|
||||||
"heading": "Neuigkeiten",
|
"heading": "Neuigkeiten",
|
||||||
|
|||||||
@@ -529,7 +529,8 @@
|
|||||||
"mark_as": "Mark as",
|
"mark_as": "Mark as",
|
||||||
"incomplete": "Incomplete",
|
"incomplete": "Incomplete",
|
||||||
"complete": "Complete",
|
"complete": "Complete",
|
||||||
|
"next": "Next",
|
||||||
|
"prev": "Prev",
|
||||||
"materials": {
|
"materials": {
|
||||||
"body_heading": "No note, video or slide added for this lesson yet",
|
"body_heading": "No note, video or slide added for this lesson yet",
|
||||||
"body_content": "Share your knowledge with your students by creating engaging content. Start by clicking on",
|
"body_content": "Share your knowledge with your students by creating engaging content. Start by clicking on",
|
||||||
|
|||||||
@@ -745,7 +745,9 @@
|
|||||||
"lock": "Cerrar",
|
"lock": "Cerrar",
|
||||||
"unlock": "desbloquear"
|
"unlock": "desbloquear"
|
||||||
},
|
},
|
||||||
"disabled": "La característica está deshabilitada"
|
"disabled": "La característica está deshabilitada",
|
||||||
|
"next": "Próximo",
|
||||||
|
"prev": "Anterior"
|
||||||
},
|
},
|
||||||
"news_feed": {
|
"news_feed": {
|
||||||
"heading": "Noticias",
|
"heading": "Noticias",
|
||||||
|
|||||||
@@ -745,7 +745,9 @@
|
|||||||
"lock": "Verrouillage",
|
"lock": "Verrouillage",
|
||||||
"unlock": "Ouvrir"
|
"unlock": "Ouvrir"
|
||||||
},
|
},
|
||||||
"disabled": "La fonctionnalité est désactivée"
|
"disabled": "La fonctionnalité est désactivée",
|
||||||
|
"next": "Suivant",
|
||||||
|
"prev": "Précédent"
|
||||||
},
|
},
|
||||||
"news_feed": {
|
"news_feed": {
|
||||||
"heading": "Fil d'actualité",
|
"heading": "Fil d'actualité",
|
||||||
|
|||||||
@@ -745,7 +745,9 @@
|
|||||||
"lock": "ताला",
|
"lock": "ताला",
|
||||||
"unlock": "अनलॉक"
|
"unlock": "अनलॉक"
|
||||||
},
|
},
|
||||||
"disabled": "सुविधा अक्षम है"
|
"disabled": "सुविधा अक्षम है",
|
||||||
|
"next": "अगला",
|
||||||
|
"prev": "पिछला"
|
||||||
},
|
},
|
||||||
"news_feed": {
|
"news_feed": {
|
||||||
"heading": "समाचार फ़ीड",
|
"heading": "समाचार फ़ीड",
|
||||||
|
|||||||
@@ -745,7 +745,9 @@
|
|||||||
"lock": "Trancar",
|
"lock": "Trancar",
|
||||||
"unlock": "Desbloquear"
|
"unlock": "Desbloquear"
|
||||||
},
|
},
|
||||||
"disabled": "O recurso está desativado"
|
"disabled": "O recurso está desativado",
|
||||||
|
"next": "Próximo",
|
||||||
|
"prev": "Anterior"
|
||||||
},
|
},
|
||||||
"news_feed": {
|
"news_feed": {
|
||||||
"heading": "Notícias",
|
"heading": "Notícias",
|
||||||
|
|||||||
@@ -745,7 +745,9 @@
|
|||||||
"lock": "Замок",
|
"lock": "Замок",
|
||||||
"unlock": "Разблокировать"
|
"unlock": "Разблокировать"
|
||||||
},
|
},
|
||||||
"disabled": "Функция отключена"
|
"disabled": "Функция отключена",
|
||||||
|
"next": "Следующий",
|
||||||
|
"prev": "Предыдущий"
|
||||||
},
|
},
|
||||||
"news_feed": {
|
"news_feed": {
|
||||||
"heading": "Новостная лента",
|
"heading": "Новостная лента",
|
||||||
|
|||||||
@@ -745,7 +745,9 @@
|
|||||||
"lock": "Khóa",
|
"lock": "Khóa",
|
||||||
"unlock": "Mở khóa"
|
"unlock": "Mở khóa"
|
||||||
},
|
},
|
||||||
"disabled": "Tính năng bị vô hiệu hóa"
|
"disabled": "Tính năng bị vô hiệu hóa",
|
||||||
|
"next": "Kế tiếp",
|
||||||
|
"prev": "Trước đó"
|
||||||
},
|
},
|
||||||
"news_feed": {
|
"news_feed": {
|
||||||
"heading": "Nguồn cấp tin tức",
|
"heading": "Nguồn cấp tin tức",
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
import { getIsLessonComplete } from '$lib/components/Course/components/Lesson/functions';
|
import { getIsLessonComplete } from '$lib/components/Course/components/Lesson/functions';
|
||||||
import { t } from '$lib/utils/functions/translations';
|
import { t } from '$lib/utils/functions/translations';
|
||||||
import { LANGUAGES } from '$lib/utils/constants/translation';
|
import { LANGUAGES } from '$lib/utils/constants/translation';
|
||||||
|
import { ChevronLeft, ChevronRight } from 'carbon-icons-svelte';
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
|
|
||||||
@@ -61,11 +62,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function fetchReqData(lessonId = '', isMaterialsTabActive: boolean) {
|
async function fetchReqData(lessonId = '', isMaterialsTabActive: boolean) {
|
||||||
$lesson.isFetching = true;
|
const timeout = setTimeout(() => {
|
||||||
|
$lesson.isFetching = true;
|
||||||
|
}, 1000);
|
||||||
let lessonData;
|
let lessonData;
|
||||||
if (isMaterialsTabActive) {
|
if (isMaterialsTabActive) {
|
||||||
const lesson = await fetchLesson(lessonId);
|
const lesson = await fetchLesson(lessonId);
|
||||||
lessonData = lesson.data;
|
lessonData = lesson.data;
|
||||||
|
|
||||||
|
clearTimeout(timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log({ lessonData });
|
console.log({ lessonData });
|
||||||
@@ -240,6 +245,29 @@
|
|||||||
$apps.open = true;
|
$apps.open = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isNextOrPrevDisabled = (lessonId: string, isPrev: boolean) => {
|
||||||
|
const index = $lessons.findIndex((lesson) => lesson.id === lessonId);
|
||||||
|
|
||||||
|
return isPrev ? !$lessons[index - 1] : !$lessons[index + 1];
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToNextOrPrevLesson = (lessonId: string, isPrev: boolean) => {
|
||||||
|
const isDisabled = isNextOrPrevDisabled(lessonId, isPrev);
|
||||||
|
|
||||||
|
// Always use early return
|
||||||
|
if (isDisabled) return;
|
||||||
|
|
||||||
|
const index = $lessons.findIndex((lesson) => lesson.id === lessonId);
|
||||||
|
const nextOrPrevLesson = isPrev ? $lessons[index - 1] : $lessons[index + 1];
|
||||||
|
|
||||||
|
const isLocked = $globalStore.isStudent && !nextOrPrevLesson.is_unlocked;
|
||||||
|
|
||||||
|
if (isLocked) return;
|
||||||
|
|
||||||
|
const path = `/courses/${$course.id}/lessons/${nextOrPrevLesson.id}`;
|
||||||
|
goto(path);
|
||||||
|
};
|
||||||
|
|
||||||
$: path = $page.url?.pathname?.replace(/\/exercises[\/ 0-9 a-z -]*/, '');
|
$: path = $page.url?.pathname?.replace(/\/exercises[\/ 0-9 a-z -]*/, '');
|
||||||
|
|
||||||
$: if (data.courseId && browser) {
|
$: if (data.courseId && browser) {
|
||||||
@@ -248,6 +276,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$: isLessonComplete = getIsLessonComplete($lesson.lesson_completion, $profile.id);
|
$: isLessonComplete = getIsLessonComplete($lesson.lesson_completion, $profile.id);
|
||||||
|
$: isPrevDisabled = isNextOrPrevDisabled(data.lessonId, true);
|
||||||
|
$: isNextDisabled = isNextOrPrevDisabled(data.lessonId, false);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<CourseContainer
|
<CourseContainer
|
||||||
@@ -343,28 +373,6 @@
|
|||||||
bind:isSaving
|
bind:isSaving
|
||||||
isStudent={$globalStore.isStudent}
|
isStudent={$globalStore.isStudent}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- {#if $globalStore.isStudent}
|
|
||||||
<div class="w-full hidden lg:flex flex-row-reverse mt-10">
|
|
||||||
<PrimaryButton
|
|
||||||
onClick={() => markLessonComplete(data.lessonId)}
|
|
||||||
isLoading={isMarkingComplete}
|
|
||||||
isDisabled={isMarkingComplete}
|
|
||||||
variant={VARIANTS.OUTLINED}
|
|
||||||
className="mt-10"
|
|
||||||
>
|
|
||||||
{#if isLessonComplete}
|
|
||||||
<CheckmarkFilledIcon size={24} class="carbon-icon text-primary-600 mr-2" />
|
|
||||||
{:else}
|
|
||||||
<CheckmarkOutlineIcon size={24} class="carbon-icon mr-2" />
|
|
||||||
{/if}
|
|
||||||
{$t('course.navItem.lessons.mark_as')}
|
|
||||||
{isLessonComplete
|
|
||||||
? $t('course.navItem.lessons.incomplete')
|
|
||||||
: $t('course.navItem.lessons.complete')}
|
|
||||||
</PrimaryButton>
|
|
||||||
</div>
|
|
||||||
{/if} -->
|
|
||||||
</PageBody>
|
</PageBody>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@@ -373,6 +381,17 @@
|
|||||||
<div
|
<div
|
||||||
class="flex items-center gap-2 w-fit rounded-full shadow-xl bg-gray-100 dark:bg-neutral-700 px-5 py-1"
|
class="flex items-center gap-2 w-fit rounded-full shadow-xl bg-gray-100 dark:bg-neutral-700 px-5 py-1"
|
||||||
>
|
>
|
||||||
|
<button
|
||||||
|
disabled={isPrevDisabled}
|
||||||
|
class={`px-2 my-2 pr-4 border-t-0 border-b-0 border-l-0 border border-gray-300 flex items-center ${
|
||||||
|
isPrevDisabled && 'opacity-25 cursor-not-allowed'
|
||||||
|
}`}
|
||||||
|
on:click={() => goToNextOrPrevLesson(data.lessonId, true)}
|
||||||
|
>
|
||||||
|
<ChevronLeft size={24} />
|
||||||
|
|
||||||
|
<span class="hidden md:block">{$t('course.navItem.lessons.prev')}</span>
|
||||||
|
</button>
|
||||||
{#if data.isMaterialsTabActive}
|
{#if data.isMaterialsTabActive}
|
||||||
<button
|
<button
|
||||||
class="px-2 my-2 pr-4 border-t-0 border-b-0 border-l-0 border border-gray-300 flex items-center"
|
class="px-2 my-2 pr-4 border-t-0 border-b-0 border-l-0 border border-gray-300 flex items-center"
|
||||||
@@ -398,7 +417,7 @@
|
|||||||
<span class="ml-1">{$lesson.totalComments}</span>
|
<span class="ml-1">{$lesson.totalComments}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="px-2 my-2"
|
class="px-2 my-2 pr-4 border-t-0 border-b-0 border-l-0 border border-gray-300 flex items-center"
|
||||||
on:click={() => markLessonComplete(data.lessonId)}
|
on:click={() => markLessonComplete(data.lessonId)}
|
||||||
disabled={isMarkingComplete}
|
disabled={isMarkingComplete}
|
||||||
>
|
>
|
||||||
@@ -408,6 +427,14 @@
|
|||||||
<CheckmarkOutlineIcon size={24} class="carbon-icon" />
|
<CheckmarkOutlineIcon size={24} class="carbon-icon" />
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
disabled={isNextDisabled}
|
||||||
|
class={`px-2 my-2 flex items-center ${isNextDisabled && 'opacity-25 cursor-not-allowed'}`}
|
||||||
|
on:click={() => goToNextOrPrevLesson(data.lessonId, false)}
|
||||||
|
>
|
||||||
|
<span class="hidden md:block">{$t('course.navItem.lessons.next')}</span>
|
||||||
|
<ChevronRight size={24} />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CourseContainer>
|
</CourseContainer>
|
||||||
|
|||||||
Reference in New Issue
Block a user