2023-08-07 19:44:26 +00:00
|
|
|
<script lang="ts">
|
2023-08-03 15:52:20 +01:00
|
|
|
import { useCompletion } from 'ai/svelte';
|
2023-08-10 12:46:14 +01:00
|
|
|
import merge from 'lodash/merge';
|
2023-08-02 19:35:27 +01:00
|
|
|
import MODES from '$lib/utils/constants/mode.js';
|
2023-08-10 12:46:14 +01:00
|
|
|
import TrashCanIcon from 'carbon-icons-svelte/lib/TrashCan.svelte';
|
|
|
|
|
import IconButton from '$lib/components/IconButton/index.svelte';
|
|
|
|
|
import { formatYoutubeVideo } from '$lib/utils/functions/formatYoutubeVideo';
|
2023-08-07 19:44:26 +00:00
|
|
|
import Modal from '$lib/components/Modal/index.svelte';
|
2023-08-03 15:52:20 +01:00
|
|
|
import { Popover } from 'carbon-components-svelte';
|
|
|
|
|
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';
|
2023-08-02 19:35:27 +01:00
|
|
|
import Tabs from '$lib/components/Tabs/index.svelte';
|
|
|
|
|
import TabContent from '$lib/components/TabContent/index.svelte';
|
|
|
|
|
import MarkdownRender from '$lib/components/MarkdownRender/index.svelte';
|
|
|
|
|
import Box from '$lib/components/Box/index.svelte';
|
|
|
|
|
import EditContent from '$lib/components/EditContent/index.svelte';
|
2023-08-03 15:52:20 +01:00
|
|
|
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
2023-08-02 19:35:27 +01:00
|
|
|
import TextField from '$lib/components/Form/TextField.svelte';
|
2023-08-07 19:44:26 +00:00
|
|
|
import {
|
|
|
|
|
lesson,
|
|
|
|
|
lessons,
|
|
|
|
|
handleUpdateLessonMaterials,
|
|
|
|
|
isLessonDirty,
|
|
|
|
|
uploadCourseVideoStore,
|
|
|
|
|
deleteLessonVideo
|
|
|
|
|
} from '$lib/components/Course/components/Lesson/store/lessons';
|
|
|
|
|
import VideoUploader from '$lib/components/Course/components/Lesson/Materials/Video/Index.svelte';
|
2023-08-03 15:52:20 +01:00
|
|
|
import { course } from '$lib/components/Course/store';
|
2021-08-31 21:59:17 +03:00
|
|
|
import * as CONSTANTS from './constants';
|
2023-08-10 12:46:14 +01:00
|
|
|
import { fetchLesson } from '$lib/utils/services/courses';
|
|
|
|
|
import type { LessonPage } from '$lib/utils/types';
|
2021-08-28 22:44:06 +03:00
|
|
|
|
|
|
|
|
export let mode = MODES.view;
|
2023-08-07 19:44:26 +00:00
|
|
|
export let prevMode = '';
|
|
|
|
|
export let lessonId = '';
|
2021-08-28 22:44:06 +03:00
|
|
|
|
2021-08-31 21:59:17 +03:00
|
|
|
let tabs = CONSTANTS.tabs;
|
2021-08-28 22:44:06 +03:00
|
|
|
let currentTab = tabs[0].value;
|
|
|
|
|
let errors = {};
|
2023-08-03 15:52:20 +01:00
|
|
|
let textareaRef = {};
|
|
|
|
|
let aiButtonRef = {};
|
|
|
|
|
let openPopover = false;
|
|
|
|
|
let aiButtonClass =
|
|
|
|
|
'flex items-center px-5 py-2 border border-gray-300 hover:bg-gray-200 rounded-md w-full mb-2';
|
2021-08-28 22:44:06 +03:00
|
|
|
|
2023-08-07 19:44:26 +00:00
|
|
|
const onChange =
|
|
|
|
|
(tab = 0) =>
|
|
|
|
|
() =>
|
|
|
|
|
(currentTab = tab);
|
|
|
|
|
|
2023-08-10 12:46:14 +01:00
|
|
|
// problem: after we save an uploaded video,
|
|
|
|
|
async function saveLesson() {
|
|
|
|
|
const { data } = await fetchLesson(lessonId);
|
|
|
|
|
const materials = merge(data, $lesson.materials);
|
2023-08-07 19:44:26 +00:00
|
|
|
|
2023-08-10 12:46:14 +01:00
|
|
|
lesson.update((l) => ({
|
|
|
|
|
...l,
|
|
|
|
|
materials
|
|
|
|
|
}));
|
|
|
|
|
handleUpdateLessonMaterials($lesson, lessonId);
|
2023-08-07 19:44:26 +00:00
|
|
|
}
|
2021-08-28 22:44:06 +03:00
|
|
|
|
2023-08-10 12:46:14 +01:00
|
|
|
function handleSave(prevMode: string) {
|
2021-08-28 22:44:06 +03:00
|
|
|
if (prevMode === MODES.edit) {
|
2023-08-10 12:46:14 +01:00
|
|
|
saveLesson();
|
2021-08-28 22:44:06 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-31 21:59:17 +03:00
|
|
|
function addBadgeValueToTab(materials) {
|
2023-08-10 12:46:14 +01:00
|
|
|
const { slide_url, videos, note } = materials;
|
2021-08-31 21:59:17 +03:00
|
|
|
|
|
|
|
|
tabs = tabs.map((tab) => {
|
|
|
|
|
let badgeValue;
|
|
|
|
|
|
|
|
|
|
if (tab.value === 1) {
|
|
|
|
|
badgeValue = !!slide_url ? 1 : 0;
|
|
|
|
|
} else if (tab.value === 2) {
|
|
|
|
|
badgeValue = !!note ? 1 : 0;
|
|
|
|
|
} else {
|
2023-08-10 12:46:14 +01:00
|
|
|
badgeValue = Array.isArray(videos) ? videos.length : 0;
|
2021-08-31 21:59:17 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tab.badgeValue = badgeValue;
|
|
|
|
|
return tab;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-07 19:44:26 +00:00
|
|
|
const openAddVideoModal = () => {
|
|
|
|
|
$uploadCourseVideoStore.isModalOpen = true;
|
|
|
|
|
};
|
|
|
|
|
|
2023-08-03 15:52:20 +01:00
|
|
|
const { input, handleSubmit, completion, isLoading } = useCompletion({
|
|
|
|
|
api: '/api/completion'
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
function updateNoteByCompletion(completion) {
|
|
|
|
|
$lesson.materials.note = completion;
|
|
|
|
|
if (textareaRef) {
|
|
|
|
|
textareaRef.scrollTop = textareaRef.scrollHeight;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function callAI(type = '') {
|
|
|
|
|
const _lesson = $lessons.find((les) => les.id === $lesson.id);
|
|
|
|
|
$input = {
|
|
|
|
|
type,
|
|
|
|
|
lessonTitle: _lesson?.title || '',
|
|
|
|
|
courseTitle: $course.title
|
|
|
|
|
};
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
handleSubmit({ preventDefault: () => {} });
|
|
|
|
|
}, 500);
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-07 19:44:26 +00:00
|
|
|
const onClose = () => {
|
2023-08-10 12:46:14 +01:00
|
|
|
saveLesson();
|
2023-08-07 19:44:26 +00:00
|
|
|
$uploadCourseVideoStore.isModalOpen = false;
|
2023-08-10 12:46:14 +01:00
|
|
|
$uploadCourseVideoStore.formRes = null;
|
2023-08-07 19:44:26 +00:00
|
|
|
};
|
|
|
|
|
|
2021-08-28 22:44:06 +03:00
|
|
|
$: handleSave(prevMode);
|
2021-08-31 21:59:17 +03:00
|
|
|
|
|
|
|
|
$: addBadgeValueToTab($lesson.materials);
|
2023-08-03 15:52:20 +01:00
|
|
|
|
|
|
|
|
$: updateNoteByCompletion($completion);
|
2021-05-22 12:32:32 +03:00
|
|
|
</script>
|
|
|
|
|
|
2023-08-07 19:44:26 +00:00
|
|
|
<Modal
|
|
|
|
|
{onClose}
|
|
|
|
|
bind:open={$uploadCourseVideoStore.isModalOpen}
|
|
|
|
|
width="w-4/5 h-[566px]"
|
|
|
|
|
modalHeading="Add a Video"
|
|
|
|
|
>
|
2023-08-10 12:46:14 +01:00
|
|
|
<VideoUploader {lessonId} {saveLesson} />
|
2023-08-07 19:44:26 +00:00
|
|
|
</Modal>
|
|
|
|
|
|
2021-08-28 22:44:06 +03:00
|
|
|
<Tabs {tabs} {currentTab} {onChange}>
|
|
|
|
|
<slot:fragment slot="content">
|
|
|
|
|
<TabContent value={tabs[0].value} index={currentTab}>
|
2021-08-29 07:23:19 +03:00
|
|
|
{#if mode === MODES.edit}
|
|
|
|
|
<TextField
|
|
|
|
|
label="Slide link"
|
|
|
|
|
bind:value={$lesson.materials.slide_url}
|
|
|
|
|
onChange={() => ($isLessonDirty = true)}
|
|
|
|
|
/>
|
|
|
|
|
{:else if $lesson.materials.slide_url}
|
|
|
|
|
<iframe
|
|
|
|
|
title="Embeded Slides"
|
|
|
|
|
src={$lesson.materials.slide_url}
|
|
|
|
|
frameborder="0"
|
|
|
|
|
width="100%"
|
|
|
|
|
height="569"
|
2021-09-17 06:33:49 +03:00
|
|
|
class="iframe"
|
2021-08-29 07:23:19 +03:00
|
|
|
allowfullscreen="true"
|
|
|
|
|
mozallowfullscreen="true"
|
|
|
|
|
webkitallowfullscreen="true"
|
|
|
|
|
/>
|
|
|
|
|
{:else}
|
|
|
|
|
<Box>
|
2023-08-02 21:17:17 +00:00
|
|
|
<div class="flex justify-between flex-col items-center w-96">
|
|
|
|
|
<img src="/images/empty-slide-icon.svg" alt="No Slide" class="my-2.5 mx-auto" />
|
|
|
|
|
<h2 class="text-xl my-1.5 font-normal">No slides yet</h2>
|
|
|
|
|
<p class="text-sm text-center text-slate-500">
|
|
|
|
|
Share your knowledge with the world by creating engaging slides. Add your slide's link
|
|
|
|
|
now.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
2021-08-29 07:23:19 +03:00
|
|
|
</Box>
|
|
|
|
|
{/if}
|
|
|
|
|
</TabContent>
|
|
|
|
|
|
|
|
|
|
<TabContent value={tabs[1].value} index={currentTab}>
|
2021-08-28 22:44:06 +03:00
|
|
|
{#if mode === MODES.edit}
|
|
|
|
|
<EditContent
|
|
|
|
|
writeLabel="Note"
|
|
|
|
|
bind:value={$lesson.materials.note}
|
|
|
|
|
placeholder="Start typing your lesson"
|
|
|
|
|
onInputChange={() => ($isLessonDirty = true)}
|
2023-08-03 15:52:20 +01:00
|
|
|
bind:textareaRef
|
|
|
|
|
bind:buttonRef={aiButtonRef}
|
|
|
|
|
>
|
|
|
|
|
<div slot="buttons">
|
|
|
|
|
<PrimaryButton
|
|
|
|
|
className="flex items-center relative"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
openPopover = !openPopover;
|
|
|
|
|
}}
|
|
|
|
|
isLoading={$isLoading}
|
|
|
|
|
isDisabled={$isLoading}
|
|
|
|
|
>
|
|
|
|
|
<MachineLearningModel size={20} class="carbon-icon mr-3" />
|
|
|
|
|
AI Assistant
|
|
|
|
|
<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>
|
|
|
|
|
</EditContent>
|
2021-08-28 22:44:06 +03:00
|
|
|
{:else if $lesson.materials.note}
|
|
|
|
|
<MarkdownRender content={$lesson.materials.note} />
|
|
|
|
|
{:else}
|
|
|
|
|
<Box>
|
2023-08-02 21:17:17 +00:00
|
|
|
<div class="flex justify-between flex-col items-center w-96">
|
|
|
|
|
<img src="/images/empty-note-icon.svg" alt="No Note" class="my-2.5 mx-auto" />
|
|
|
|
|
<h2 class="text-xl my-1.5 font-normal">No Notes yet</h2>
|
|
|
|
|
<p class="text-sm text-center text-slate-500">
|
|
|
|
|
Share your knowledge with the world by creating engaging notes. Add your notes now.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
2021-08-28 22:44:06 +03:00
|
|
|
</Box>
|
|
|
|
|
{/if}
|
|
|
|
|
</TabContent>
|
2021-08-29 07:23:19 +03:00
|
|
|
|
|
|
|
|
<TabContent value={tabs[2].value} index={currentTab}>
|
2023-08-10 12:46:14 +01:00
|
|
|
{#if mode === MODES.edit && $lesson.materials.videos.length}
|
|
|
|
|
<PrimaryButton label="Add/Edit Video(s)" onClick={openAddVideoModal} className="mb-2" />
|
2023-08-07 19:44:26 +00:00
|
|
|
{/if}
|
2023-08-10 12:46:14 +01:00
|
|
|
{#if $lesson.materials.videos.length}
|
2023-08-07 19:44:26 +00:00
|
|
|
<div class="flex flex-col items-start w-full h-full">
|
2023-08-10 12:46:14 +01:00
|
|
|
{#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}
|
2023-08-07 19:44:26 +00:00
|
|
|
<div class="w-full h-full flex flex-col gap-2 overflow-hidden">
|
2023-08-10 12:46:14 +01:00
|
|
|
{#key video.link}
|
2023-08-07 19:44:26 +00:00
|
|
|
<div class="mb-5">
|
2023-08-10 12:46:14 +01:00
|
|
|
{#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}
|
|
|
|
|
<video class="plyr-video-trigger" playsinline controls>
|
|
|
|
|
<source src={video.link} type="video/mp4" />
|
|
|
|
|
<track kind="captions" />
|
|
|
|
|
</video>
|
|
|
|
|
{/if}
|
2023-08-07 19:44:26 +00:00
|
|
|
</div>
|
2023-08-10 12:46:14 +01:00
|
|
|
{/key}
|
2023-08-07 19:44:26 +00:00
|
|
|
</div>
|
|
|
|
|
{/each}
|
|
|
|
|
</div>
|
2021-08-28 22:44:06 +03:00
|
|
|
{:else}
|
|
|
|
|
<Box>
|
2023-08-07 19:44:26 +00:00
|
|
|
<img src="/no-video.svg" alt="Video not found" />
|
|
|
|
|
<h3 class="text-xl font-normal dark:text-white py-2">
|
|
|
|
|
No youtube video added for this lesson yet
|
|
|
|
|
</h3>
|
|
|
|
|
<p class="text-sm text-center font-normal py-2">
|
|
|
|
|
Share your knowledge with the world by creating engaging lessons.<br />
|
|
|
|
|
Start by clicking on New Lesson button.
|
|
|
|
|
</p>
|
|
|
|
|
<PrimaryButton label="Add Video" className="rounded-md" onClick={openAddVideoModal} />
|
2021-08-28 22:44:06 +03:00
|
|
|
</Box>
|
|
|
|
|
{/if}
|
|
|
|
|
</TabContent>
|
|
|
|
|
</slot:fragment>
|
|
|
|
|
</Tabs>
|
2021-09-17 06:33:49 +03:00
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
.iframe {
|
|
|
|
|
height: 569px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 760px) {
|
|
|
|
|
.iframe {
|
|
|
|
|
height: 209px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|