Files
classroomio/src/lib/components/Course/components/Lesson/Materials/index.svelte
T

364 lines
12 KiB
Svelte
Raw Normal View History

<script lang="ts">
2023-08-03 15:52:20 +01:00
import { useCompletion } from 'ai/svelte';
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';
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';
import Tabs from '$lib/components/Tabs/index.svelte';
import TabContent from '$lib/components/TabContent/index.svelte';
import Box from '$lib/components/Box/index.svelte';
2023-08-03 15:52:20 +01:00
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
2023-08-16 20:42:58 +00:00
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import TextField from '$lib/components/Form/TextField.svelte';
import TextEditor from '$lib/components/TextEditor/index.svelte';
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';
2021-08-28 22:44:06 +03:00
export let mode = MODES.view;
export let prevMode = '';
export let lessonId = '';
export let isSaving = false;
2021-08-28 22:44:06 +03:00
let initAutoSave = false;
let timeoutId: NodeJS.Timeout;
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;
2023-08-11 09:44:50 +01:00
let player = null;
2023-08-03 15:52:20 +01:00
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
const onChange =
(tab = 0) =>
() =>
(currentTab = tab);
async function saveLesson(materials = undefined) {
const _lesson = !!materials
? {
...$lesson,
materials
}
: $lesson;
handleUpdateLessonMaterials(_lesson, lessonId);
}
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) => {
2023-08-16 20:42:58 +00:00
const badgeValue =
tab.value === 1
? note.length > 15
2023-08-16 20:42:58 +00:00
? 1
: 0
: tab.value === 2
? !!slide_url
? 1
: 0
: Array.isArray(videos)
? videos.length
: 0;
2021-08-31 21:59:17 +03:00
tab.badgeValue = badgeValue;
return tab;
});
}
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?.children?.[0]) {
let qlEditor = textareaRef?.children?.[0];
qlEditor.scrollTop = qlEditor.scrollHeight;
2023-08-03 15:52:20 +01:00
}
}
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-11 09:44:50 +01:00
function initPlyr(_player: any, _video: string | undefined) {
if (!_player) return;
const players = Array.from(document.querySelectorAll('.plyr-video-trigger')).map((p) => {
// @ts-ignore
return new Plyr(p);
});
// @ts-ignore
window.players = players;
}
function autoSave(updatedMaterials) {
if (timeoutId) clearTimeout(timeoutId);
if (!initAutoSave) {
initAutoSave = true;
return;
}
isSaving = true;
timeoutId = setTimeout(async () => {
await saveLesson(updatedMaterials);
isSaving = false;
}, 1500);
}
function handleInputChange() {
$isLessonDirty = true;
}
function onLessonIdChange() {
initAutoSave = false;
}
const onClose = () => {
$uploadCourseVideoStore.isModalOpen = false;
};
$: autoSave($lesson.materials);
$: onLessonIdChange(lessonId);
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);
2023-08-11 09:44:50 +01:00
$: initPlyr(player, $lesson.materials.videos);
</script>
<Modal
{onClose}
bind:open={$uploadCourseVideoStore.isModalOpen}
width="w-4/5 h-[566px]"
modalHeading="Add a Video"
>
<VideoUploader {lessonId} />
</Modal>
2021-08-28 22:44:06 +03:00
<Tabs {tabs} {currentTab} {onChange}>
<slot:fragment slot="content">
<TabContent value={tabs[0].value} index={currentTab}>
{#if mode === MODES.edit}
<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}
>
<MachineLearningModel size={20} class="carbon-icon mr-3" />
AI
<Popover
caret
align="left"
bind:open={openPopover}
on:click:outside={({ detail }) => {
openPopover = aiButtonRef?.contains(detail.target);
2023-08-03 15:52:20 +01:00
}}
>
<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]">
<TextEditor
bind:container={textareaRef}
bind:content={$lesson.materials.note}
onChange={(c) => ($lesson.materials.note = c)}
/>
</div>
{:else if $lesson.materials.note.length > 15}
<div>
{@html $lesson.materials.note}
</div>
2021-08-28 22:44:06 +03:00
{:else}
<Box>
<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
2023-08-10 12:22:30 +00:00
<TabContent value={tabs[1].value} index={currentTab}>
{#if mode === MODES.edit}
<TextField
label="Slide link"
bind:value={$lesson.materials.slide_url}
onInputChange={handleInputChange}
2023-08-10 12:22:30 +00:00
/>
{:else if $lesson.materials.slide_url}
<iframe
title="Embeded Slides"
src={$lesson.materials.slide_url}
frameborder="0"
width="100%"
height="569"
class="iframe"
allowfullscreen="true"
mozallowfullscreen="true"
webkitallowfullscreen="true"
/>
{:else}
<Box>
<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>
</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" />
{/if}
2023-08-10 12:46:14 +01:00
{#if $lesson.materials.videos.length}
<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}
<div class="w-full h-full flex flex-col gap-2 overflow-hidden">
2023-08-10 12:46:14 +01:00
{#key video.link}
<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 if video.metadata?.svid}
<div style="position:relative;padding-bottom:51.416579%">
<iframe
src="https://muse.ai/embed/{video.metadata
2023-08-11 00:52:06 +01:00
?.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>
2023-08-10 12:46:14 +01:00
{:else}
2023-08-11 09:44:50 +01:00
<video bind:this={player} class="plyr-video-trigger" playsinline controls>
2023-08-10 12:46:14 +01:00
<source src={video.link} type="video/mp4" />
<track kind="captions" />
</video>
{/if}
</div>
2023-08-10 12:46:14 +01:00
{/key}
</div>
{/each}
</div>
2021-08-28 22:44:06 +03:00
{:else}
<Box>
<img src="/no-video.svg" alt="Video not found" />
<h3 class="text-xl font-normal dark:text-white py-2">
No video added for this lesson yet
</h3>
<p class="text-sm text-center font-normal py-2">
Share videos with your students and get auto transcription and in video search out of
the box.<br />
Start by clicking on Add Video 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>