Files
classroomio/src/lib/components/Course/components/Lesson/Materials/index.svelte
T
tunny17 a422f2f290 CLASSROOMIO-T-116: Replace Markdown with WYSIWIG
CLASSROOMIO-T-116: Resolved Feedbacks

CLASSROOMIO-T-116: Replace Markdown with WYSIWIG


Co-authored-by: Tunji Adeyemi <[email protected]>


Merge-request: CLASSROOMIO-MR-52
Merged-by: Best Admin <[email protected]>
2023-08-22 11:01:09 +00:00

364 lines
12 KiB
Svelte

<script lang="ts">
import { useCompletion } from 'ai/svelte';
import MODES from '$lib/utils/constants/mode.js';
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';
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';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
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';
import { course } from '$lib/components/Course/store';
import * as CONSTANTS from './constants';
export let mode = MODES.view;
export let prevMode = '';
export let lessonId = '';
export let isSaving = false;
let initAutoSave = false;
let timeoutId: NodeJS.Timeout;
let tabs = CONSTANTS.tabs;
let currentTab = tabs[0].value;
let errors = {};
let textareaRef = {};
let aiButtonRef = {};
let openPopover = false;
let player = null;
let aiButtonClass =
'flex items-center px-5 py-2 border border-gray-300 hover:bg-gray-200 rounded-md w-full mb-2';
const onChange =
(tab = 0) =>
() =>
(currentTab = tab);
async function saveLesson(materials = undefined) {
const _lesson = !!materials
? {
...$lesson,
materials
}
: $lesson;
handleUpdateLessonMaterials(_lesson, lessonId);
}
function handleSave(prevMode: string) {
if (prevMode === MODES.edit) {
saveLesson();
}
}
function addBadgeValueToTab(materials) {
const { slide_url, videos, note } = materials;
tabs = tabs.map((tab) => {
const badgeValue =
tab.value === 1
? note.length > 15
? 1
: 0
: tab.value === 2
? !!slide_url
? 1
: 0
: Array.isArray(videos)
? videos.length
: 0;
tab.badgeValue = badgeValue;
return tab;
});
}
const openAddVideoModal = () => {
$uploadCourseVideoStore.isModalOpen = true;
};
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;
}
}
function callAI(type = '') {
const _lesson = $lessons.find((les) => les.id === $lesson.id);
$input = {
type,
lessonTitle: _lesson?.title || '',
courseTitle: $course.title
};
setTimeout(() => {
handleSubmit({ preventDefault: () => {} });
}, 500);
}
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);
$: handleSave(prevMode);
$: addBadgeValueToTab($lesson.materials);
$: updateNoteByCompletion($completion);
$: 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>
<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);
}}
>
<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>
{: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>
</Box>
{/if}
</TabContent>
<TabContent value={tabs[1].value} index={currentTab}>
{#if mode === MODES.edit}
<TextField
label="Slide link"
bind:value={$lesson.materials.slide_url}
onInputChange={handleInputChange}
/>
{: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>
<TabContent value={tabs[2].value} index={currentTab}>
{#if mode === MODES.edit && $lesson.materials.videos.length}
<PrimaryButton label="Add/Edit Video(s)" onClick={openAddVideoModal} className="mb-2" />
{/if}
{#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>
{: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} />
</Box>
{/if}
</TabContent>
</slot:fragment>
</Tabs>
<style>
.iframe {
height: 569px;
}
@media (max-width: 760px) {
.iframe {
height: 209px;
}
}
</style>