CLASSROOMIO-T-108: Revamp landing page & sidebar improvement

This commit is contained in:
Rotimi Best
2023-09-30 09:56:42 +01:00
parent 8777ab57f9
commit 1fa1edecb9
16 changed files with 368 additions and 550 deletions
@@ -1,7 +1,8 @@
<script lang="ts">
import { lesson } from '$lib/components/Course/components/Lesson/store/lessons';
import HtmlRender from '$lib/components/HTMLRender/HTMLRender.svelte';
function isNoteEmpty(note) {
function isNoteEmpty(note: string) {
if (!note || typeof note !== 'string') return true;
if (!document) return false;
@@ -16,7 +17,11 @@
</script>
{#if !isNoteEmpty($lesson.materials?.note)}
<div>
{@html $lesson.materials.note}
</div>
<HtmlRender>
<svelte:fragment slot="content">
<div>
{@html $lesson.materials.note}
</div>
</svelte:fragment>
</HtmlRender>
{/if}
@@ -10,7 +10,6 @@
import AlignBoxTopLeftIcon from 'carbon-icons-svelte/lib/AlignBoxTopLeft.svelte';
import ListIcon from 'carbon-icons-svelte/lib/List.svelte';
import IbmWatsonKnowledgeStudioIcon from 'carbon-icons-svelte/lib/IbmWatsonKnowledgeStudio.svelte';
import MachineLearningModel from 'carbon-icons-svelte/lib/MachineLearningModel.svelte';
import MagicWandFilled from 'carbon-icons-svelte/lib/MagicWandFilled.svelte';
import Tabs from '$lib/components/Tabs/index.svelte';
import TabContent from '$lib/components/TabContent/index.svelte';
@@ -126,7 +125,7 @@
api: '/api/completion'
});
function updateNoteByCompletion(completion) {
function updateNoteByCompletion(completion: string) {
$lesson.materials.note = completion;
autoSave($lesson.materials);
@@ -240,143 +239,143 @@
<h1 class="text-4xl mt-0 capitalize">
{lessonTitle}
</h1>
{#if mode === MODES.edit}
<Tabs {tabs} {currentTab} {onChange}>
<slot:fragment slot="content">
<TabContent value={getValue('Note')} index={currentTab}>
<div bind:this={aiButtonRef} class="w-full flex flex-row-reverse">
<PrimaryButton
className="flex items-center relative"
onClick={() => {
openPopover = !openPopover;
}}
isLoading={$isLoading}
isDisabled={$isLoading}
variant={VARIANTS.OUTLINED}
>
<MagicWandFilled size={20} class="carbon-icon mr-3" />
AI
<Popover
caret
align="left"
bind:open={openPopover}
on:click:outside={({ detail }) => {
openPopover = aiButtonRef?.contains(detail.target);
}}
>
<div class="p-2">
<button class={aiButtonClass} on:click={() => callAI('outline')}>
<ListIcon class="carbon-icon mr-2" />
Generate Lesson Outline
</button>
<button class={aiButtonClass} on:click={() => callAI('note')}>
<AlignBoxTopLeftIcon class="carbon-icon mr-2" />
Generate Lesson Note
</button>
<button class={aiButtonClass} on:click={() => callAI('activities')}>
<IbmWatsonKnowledgeStudioIcon class="carbon-icon mr-2" />
Generate Lesson Activities
</button>
</div>
</Popover>
</PrimaryButton>
</div>
<div class="h-[60vh] mt-5">
<TextEditor
id={lessonId}
bind:editorWindowRef
value={$lesson.materials.note}
onChange={(html) => ($lesson.materials.note = html)}
placeholder="Write your lesson note here"
/>
</div>
</TabContent>
<TabContent value={getValue('Slide')} index={currentTab}>
{#if mode === MODES.edit}
<TextField
label="Slide link"
bind:value={$lesson.materials.slide_url}
onInputChange={handleInputChange}
/>
{/if}
</TabContent>
<TabContent value={getValue('Video')} index={currentTab}>
<PrimaryButton label="Add/Edit Video(s)" onClick={openAddVideoModal} className="mb-2" />
{#if $lesson.materials.videos.length}
<div class="flex flex-col items-start w-full h-full">
{#each $lesson.materials.videos as video, index}
{#if mode === MODES.edit}
<div class="ml-auto">
<IconButton
value="delete-video"
contained={true}
onClick={() => deleteLessonVideo(index)}
>
<TrashCanIcon size={20} class="carbon-icon dark:text-white" />
</IconButton>
</div>
{/if}
<div class="w-full h-full flex flex-col gap-2 overflow-hidden">
{#key video.link}
<div class="mb-5">
{#if video.type === 'youtube'}
<iframe
width="100%"
height="569"
class="iframe"
src={formatYoutubeVideo(video.link, errors)}
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
/>
{:else if video.metadata?.svid}
<div style="position:relative;padding-bottom:51.416579%">
<iframe
src="https://muse.ai/embed/{video.metadata
?.svid}?logo=https://app.classroomio.com/logo-512.png&subtitles=auto&cover_play_position=center"
style="width:100%;height:100%;position:absolute;left:0;top:0"
frameborder="0"
allowfullscreen
title="Muse AI Video Embed"
/>
</div>
{:else}
<video bind:this={player} class="plyr-video-trigger" playsinline controls>
<source src={video.link} type="video/mp4" />
<track kind="captions" />
</video>
{/if}
</div>
{/key}
</div>
{/each}
</div>
{/if}
</TabContent>
</slot:fragment>
</Tabs>
{:else if !isMaterialsEmpty($lesson.materials)}
<div class="w-full">
{#each componentsToRender as Component, index}
<svelte:component this={Component} key={index} />
{/each}
</div>
{:else}
<Box className="text-center">
<img src="/no-video.svg" alt="Video not found" />
<h3 class="text-xl font-normal dark:text-white py-2">
No note, video or slide added for this lesson yet
</h3>
<p class="text-sm text-center font-normal py-2">
Share your knowledge with your students by creating engaging content<br />
Start by clicking on <strong>Get started</strong> button.
</p>
<PrimaryButton label="Get started" className="rounded-md" onClick={toggleMode} />
</Box>
{/if}
</svelte:fragment>
</HtmlRender>
{#if mode === MODES.edit}
<Tabs {tabs} {currentTab} {onChange}>
<slot:fragment slot="content">
<TabContent value={getValue('Note')} index={currentTab}>
<div bind:this={aiButtonRef} class="w-full flex flex-row-reverse">
<PrimaryButton
className="flex items-center relative"
onClick={() => {
openPopover = !openPopover;
}}
isLoading={$isLoading}
isDisabled={$isLoading}
variant={VARIANTS.OUTLINED}
>
<MagicWandFilled size={20} class="carbon-icon mr-3" />
AI
<Popover
caret
align="left"
bind:open={openPopover}
on:click:outside={({ detail }) => {
openPopover = aiButtonRef?.contains(detail.target);
}}
>
<div class="p-2">
<button class={aiButtonClass} on:click={() => callAI('outline')}>
<ListIcon class="carbon-icon mr-2" />
Generate Lesson Outline
</button>
<button class={aiButtonClass} on:click={() => callAI('note')}>
<AlignBoxTopLeftIcon class="carbon-icon mr-2" />
Generate Lesson Note
</button>
<button class={aiButtonClass} on:click={() => callAI('activities')}>
<IbmWatsonKnowledgeStudioIcon class="carbon-icon mr-2" />
Generate Lesson Activities
</button>
</div>
</Popover>
</PrimaryButton>
</div>
<div class="h-[60vh] mt-5">
<TextEditor
id={lessonId}
bind:editorWindowRef
value={$lesson.materials.note}
onChange={(html) => ($lesson.materials.note = html)}
placeholder="Write your lesson note here"
/>
</div>
</TabContent>
<TabContent value={getValue('Slide')} index={currentTab}>
{#if mode === MODES.edit}
<TextField
label="Slide link"
bind:value={$lesson.materials.slide_url}
onInputChange={handleInputChange}
/>
{/if}
</TabContent>
<TabContent value={getValue('Video')} index={currentTab}>
<PrimaryButton label="Add/Edit Video(s)" onClick={openAddVideoModal} className="mb-2" />
{#if $lesson.materials.videos.length}
<div class="flex flex-col items-start w-full h-full">
{#each $lesson.materials.videos as video, index}
{#if mode === MODES.edit}
<div class="ml-auto">
<IconButton
value="delete-video"
contained={true}
onClick={() => deleteLessonVideo(index)}
>
<TrashCanIcon size={20} class="carbon-icon dark:text-white" />
</IconButton>
</div>
{/if}
<div class="w-full h-full flex flex-col gap-2 overflow-hidden">
{#key video.link}
<div class="mb-5">
{#if video.type === 'youtube'}
<iframe
width="100%"
height="569"
class="iframe"
src={formatYoutubeVideo(video.link, errors)}
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
/>
{:else if video.metadata?.svid}
<div style="position:relative;padding-bottom:51.416579%">
<iframe
src="https://muse.ai/embed/{video.metadata
?.svid}?logo=https://app.classroomio.com/logo-512.png&subtitles=auto&cover_play_position=center"
style="width:100%;height:100%;position:absolute;left:0;top:0"
frameborder="0"
allowfullscreen
title="Muse AI Video Embed"
/>
</div>
{:else}
<video bind:this={player} class="plyr-video-trigger" playsinline controls>
<source src={video.link} type="video/mp4" />
<track kind="captions" />
</video>
{/if}
</div>
{/key}
</div>
{/each}
</div>
{/if}
</TabContent>
</slot:fragment>
</Tabs>
{:else if !isMaterialsEmpty($lesson.materials)}
<div class="w-full">
{#each componentsToRender as Component, index}
<svelte:component this={Component} key={index} />
{/each}
</div>
{:else}
<Box className="text-center">
<img src="/no-video.svg" alt="Video not found" />
<h3 class="text-xl font-normal dark:text-white py-2">
No note, video or slide added for this lesson yet
</h3>
<p class="text-sm text-center font-normal py-2">
Share your knowledge with your students by creating engaging content<br />
Start by clicking on <strong>Get started</strong> button.
</p>
<PrimaryButton label="Get started" className="rounded-md" onClick={toggleMode} />
</Box>
{/if}
@@ -187,7 +187,7 @@
<a
href="{$page.url.pathname}{navItem.key}"
class="{navItem.key === activeNav &&
'active text-primary-700'} rounded-lg px-2 mr-6 text-slate-700 font-normal hover:bg-neutral-600 hover:text-slate-900 dark:text-white z-0"
'active text-primary-700'} rounded-lg px-2 mr-6 text-slate-700 font-normal hover:bg-neutral-600 dark:hover:text-slate-900 dark:text-white z-0"
>
{navItem.label}
</a>
+5 -4
View File
@@ -3,12 +3,14 @@
import Card from './components/Card/index.svelte';
import CardLoader from './components/Card/Loader.svelte';
import CoursesEmptyIcon from '../Icons/CoursesEmptyIcon.svelte';
// import CopyCourseModal from './components/CopyCourseModal/index.svelte';
import { courseMetaDeta } from './store';
import type { Course } from '$lib/utils/types';
import { appStore } from '$lib/utils/store/app';
export let courses: Course[] = [];
export let emptyTitle = 'No Courses Created';
export let emptyDescription =
'Share your knowledge with the world by creating engaging courses for your students.';
function calcProgressRate(progressRate?: number, totalLessons?: number): number {
if (!progressRate || !totalLessons) {
@@ -34,7 +36,6 @@
bannerImage={courseData.logo || '/images/classroomio-course-img-template.jpg'}
title={courseData.title}
description={courseData.description}
role_id={courseData.role_id}
isPublished={courseData.is_published}
cost={courseData.cost}
currency={courseData.currency}
@@ -49,9 +50,9 @@
{#if !$courseMetaDeta.isLoading && !courses.length}
<Box className="w-full">
<CoursesEmptyIcon />
<h3 class="dark:text-white text-2xl my-5">No Courses Created</h3>
<h3 class="dark:text-white text-2xl my-5">{emptyTitle}</h3>
<p class="dark:text-white w-1/3 text-center">
Share your knowledge with the world by creating engaging courses for your students.
{emptyDescription}
</p>
</Box>
{/if}
+8 -1
View File
@@ -2,9 +2,16 @@ import { writable, derived } from 'svelte/store';
import type { Course } from '$lib/utils/types';
export const courses = writable<Course[]>([]);
export const courseInProgress = derived(courses, ($courses) =>
export const coursesInProgress = derived(courses, ($courses) =>
$courses.length > 0 ? $courses.slice(0, 3) : []
);
export const coursesComplete = derived(courses, ($courses) =>
$courses.length > 0
? $courses.filter((course) => {
return course.total_lessons === course.progress_rate;
})
: []
);
export const courseMetaDeta = writable({
isLoading: true
});
+14 -13
View File
@@ -2,8 +2,7 @@
import { page } from '$app/stores';
import HelpIcon from 'carbon-icons-svelte/lib/Help.svelte';
import LicenseDraft from 'carbon-icons-svelte/lib/LicenseDraft.svelte';
import TextChip from '$lib/components/Chip/Text.svelte';
import OrgSelector from '$lib/components/OrgSelector/OrgSelector.svelte';
import Settings from 'carbon-icons-svelte/lib/Settings.svelte';
import HomeIcon from '$lib/components/Icons/HomeIcon.svelte';
import CourseIcon from '$lib/components/Icons/CourseIcon.svelte';
import CommunityIcon from '$lib/components/Icons/CommunityIcon.svelte';
@@ -59,19 +58,21 @@
>
<div class="h-full flex flex-col">
<div class="border-b border-gray-200 pt-5 px-4">
{#if $currentOrg.avatar_url}
<div class="w-full flex flex-col items-center">
<Avatar
src={$currentOrg.avatar_url}
name={$currentOrg.name}
shape="rounded-md"
width="w-7"
height="h-7"
src={$profile.avatar_url}
name={$profile.fullname}
shape="rounded-full"
width="w-20"
height="h-20"
/>
{:else}
<TextChip value={$currentOrg.shortName} className="bg-primary-200 font-bold" />
{/if}
<OrgSelector canAddOrg={false} />
<div class="mt-5">
<p class="dark:text-white text-lg font-bold whitespace-nowrap truncate">
{$profile.fullname}
</p>
</div>
</div>
<ul class="my-5">
{#each sideLinks as item (item.name)}
@@ -106,7 +107,7 @@
`/lms/settings`
) && NavClasses.active}"
>
<Avatar src={$profile.avatar_url} name={$profile.username} width="w-7" height="h-7" />
<Settings size={20} class="carbon-icon dark:text-white" />
<p class="dark:text-white ml-2">Settings</p>
</li>
</a>
@@ -3,7 +3,7 @@
import { goto } from '$app/navigation';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import CoursesEmptyIcon from '$lib/components/Icons/CoursesEmptyIcon.svelte';
import { courseInProgress } from '$lib/components/Courses/store';
import { coursesInProgress } from '$lib/components/Courses/store';
const gotoCourse = (id: string | undefined) => {
if (!id) return;
@@ -11,14 +11,14 @@
};
</script>
<section class="h-full mt-10 mb-20 xl:mt-2">
<section class="h-full">
<p class="text-base font-semibold text-[#040F2D] pb-3 dark:text-white">Currently Learning</p>
<div
class="flex items-center flex-col border border-[#EAEAEA] dark:bg-neutral-800 gap-2 rounded w-full h-fit xl:h-[516px] p-3"
class="flex items-center flex-col border border-[#EAEAEA] dark:bg-neutral-800 gap-2 rounded w-full lg:h-[40vh] p-3"
>
{#if $courseInProgress.length > 0}
<div class="w-full h-full xl:h-[516px] flex flex-col justify-start">
{#each $courseInProgress as course}
{#if $coursesInProgress.length > 0}
<div class="w-full h-full flex flex-col justify-start overflow-y-auto">
{#each $coursesInProgress as course}
<div class="p-5">
<span class="flex flex-col md:flex-row gap-3 items-start pb-5">
<img
@@ -29,7 +29,7 @@
<p class="text-base font-semibold text-[#040F2D] pb-3 dark:text-white">Your Schedule</p>
<div
class="flex items-center flex-col border border-[#EAEAEA] dark:bg-neutral-800 rounded w-full h-fit xl:h-[516px] p-3"
class="flex items-center flex-col border border-[#EAEAEA] dark:bg-neutral-800 rounded w-full h-fit lg:h-[40vh] p-3 overflow-y-auto"
>
<div class="w-full h-fit hidden xl:flex items-center justify-center">
<InlineCalendar bind:store {theme} />
+24 -2
View File
@@ -4,11 +4,14 @@
import NotificationIcon from 'carbon-icons-svelte/lib/Notification.svelte';
import MoonIcon from 'carbon-icons-svelte/lib/Moon.svelte';
import SunIcon from 'carbon-icons-svelte/lib/Sun.svelte';
import Avatar from '$lib/components/Avatar/index.svelte';
import TextChip from '$lib/components/Chip/Text.svelte';
import IconButton from '$lib/components/IconButton/index.svelte';
import { appStore } from '$lib/utils/store/app';
import { Close, Menu } from 'carbon-icons-svelte';
import { menu } from '$lib/components/Org/store';
import { currentOrg } from '$lib/utils/store/org';
import { toggleBodyByMode } from '$lib/utils/functions/app';
export let navClass = '';
@@ -39,8 +42,27 @@
</IconButton>
</li>
<div class="">
<a href={$page.url.pathname} title="Go to ClassroomIO Home" id="logo" class="text-lg">
ClassroomIO
<a
href={$page.url.pathname}
title="Go to ClassroomIO Home"
id="logo"
class="text-lg flex items-center"
>
{#if $currentOrg.avatar_url}
<Avatar
src={$currentOrg.avatar_url}
name={$currentOrg.name}
shape="rounded-md"
width="w-7"
height="h-7"
className="mr-2"
/>
{:else}
<TextChip value={$currentOrg.shortName} className="bg-primary-200 font-bold" />
{/if}
<span class="line-clamp-1">
{$currentOrg.name}
</span>
</a>
</div>