CLASSROOMIO-T-108: Revamp landing page & sidebar improvement
This commit is contained in:
+9
-4
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user