CLASSROOMIO-T-30:Developed the UI for the video upload
CLASSROOMIO-T-30: UI cleanup CLASSROOMIO-T-30: Improve UX Merge branch 'CLASSROOMIO-T-30' of https://gitlab.com/thenextgreat/classroomio into CLASSROOMIO-T-30 CLASSROOMIO-T-30:Made the input clear after upload and also chnged the icons CLASSROOMIO-T-30: Remove console log CLASSROOMIO-T-30: Better trim out white spaces in url CLASSROOMIO-T-30:Fixed the bug between the save and edit CLASSROOMIO-T-30:Trying to fix the bug between edit and save Merge branch 'main' of https://gitlab.com/thenextgreat/classroomio CLASSROOMIO-T-30:Stil working on the reviews CLASSROOMIO-T-30:Still working on some of the reviews and updates CLASSROOMIO-T-30: Remove unnessecary values in store CLASSROOMIO-T-30: Change button to div in modal CLASSROOMIO-T-100: Select only courses belonging to current org and current user is a member CLASSROOMIO-T-100: Update condition for fetchCourses CLASSROOMIO-T-100: Select only courses belonging to current org and current user is a member Co-authored-by: Rotimi Best <[email protected]> Merge-request: CLASSROOMIO-MR-35 Merged-by: Best Admin <[email protected]> Remove netlify app from redirect rule Add redirect to layout's load Redirect to about domain CLASSROOMIO-T-97: UX again? 😇 CLASSROOMIO-T-97: Better course UX 😍😍 CLASSROOMIO-T-97: Better button style and fix route not found CLASSROOMIO-T-97: Course UI improvement 😁😁😁 CLASSROOMIO-T-30: Add plyr CLASSROOMIO-T-30:Merged main into branch Merge branch 'main' of https://gitlab.com/thenextgreat/classroomio CLASSROOMIO-T-30:Wotking on the lms dashboard CLASSROOMIO-T-30:Made some changes based on the reviews Merge branch 'main' of https://gitlab.com/thenextgreat/classroomio CLASSROOMIO-T-30:Made some refactoring to keep the code DRY CLASSROOMIO-T-30:Developed the UI for the video upload Merge branch 'main' of https://gitlab.com/thenextgreat/classroomio Merge branch 'main' of https://gitlab.com/thenextgreat/classroomio CLASSROOMIO-T-27: Review Section for Instructors on landing page CLASSROOMIO-T-27: Remove unused variable Merge branch 'CLASSROOMIO-T-27' of https://gitlab.com/thenextgreat/classroomio into CLASSROOMIO-T-27 CLASSROOMIO-T-27: Complete reviews functionality CLASSROOMIO-T-27: Review Render Update CLASSROOMIO-T-27: - Resolved Feedbacks CLASSROOMIO-T-27: Fixed Ratings Data CLASSROOMIO-T-27: - Added Ratings Svg - Fixed avatar_url feedback CLASSROOMIO-T-27: Import Adjustment CLASSROOMIO-T-27: Code Review CLASSROOM-T-27: Added Add Review Functionality CLASSROOMIO-T-27: - Unresolved Review - Enhance Code Redability CLASSROOMIO-T-27: Completed the delete functionality CLASSROOMIO-T-27: Hide/Show functionality CLASSROOMIO-T-27: Double Quotation Update CLASSROOMIO-T-27: Testing toggle functinality CLASSROOMIO-T-27: Added the icons CLASSROOMIO-T-27: Completion of the hide/show functionality CLASSROOMIO-T-27: Display the store data on the review page for styling CLASSROOMIO-T-27: Styling of the review component & connecting it's data CLASSROOMIO-T-27: - Add ReviewsForm - Completion of the Reviews Form Structure/Styling CLASSROOMIO-T-27: Added a reviews modal CLASSROOMIO-T-27: Logic update for review section CLASSROOMIO-T-50: Logic for overlapping reviews CLASSROOMIO-T-27: - Completion of the structure & styling with mock data. - Addition of custom ratings assets CLASSROOMIO-T-27: - Review Section Code Structure - Import Profile Picture Assets Co-authored-by: Rotimi Best <[email protected]> Co-authored-by: Tunji Adeyemi <[email protected]> Co-authored-by: tunny17 <[email protected]> Merge-request: CLASSROOMIO-MR-22 Merged-by: Best Admin <[email protected]> CLASSROOMIO-T-62: Dark Mode Bug Fix CLASSROOMIO-T-62: Delete Unused class CLASSROOMIO-T-62: Dark Mode Bug Fix Co-authored-by: Tunji Adeyemi <[email protected]> Merge-request: CLASSROOMIO-MR-23 Merged-by: Best Admin <[email protected]> Co-authored-by: Ifeanyi Nwosu <[email protected]> Co-authored-by: Rotimi Best <[email protected]> Co-authored-by: Tunji Adeyemi <[email protected]> Co-authored-by: tunny17 <[email protected]> Merge-request: CLASSROOMIO-MR-33 Merged-by: Best Admin <[email protected]>
This commit is contained in:
committed by
Space Cloud
co-authored by
Rotimi Best
tunny17
Ifeanyi Nwosu
parent
4910ec4f56
commit
a27bc32c63
@@ -4,7 +4,7 @@
|
||||
|
||||
export let onClick;
|
||||
export let contained;
|
||||
export let color = '';
|
||||
export let color = 'text-blue-700 bg-blue-200 dark:bg-gray-500';
|
||||
export let size = 'large';
|
||||
</script>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
{#if size === 'large'}
|
||||
<CloseIcon size={24} class="carbon-icon dark:text-white" title="Delete" />
|
||||
{:else}
|
||||
<CloseIcon size={24} class="carbon-icon dark:text-white" title="Delete" />
|
||||
<CloseIcon size={20} class="carbon-icon dark:text-white" title="Delete" />
|
||||
{/if}
|
||||
</IconButton>
|
||||
<!-- color="text-blue-700 bg-blue-200 dark:bg-gray-200 dark:text-black" -->
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<script>
|
||||
import YoutubeVideo from './YoutubeVideo.svelte';
|
||||
import LocalVideo from './LocalVideo.svelte';
|
||||
import * as CONSTANTS from './constants';
|
||||
|
||||
let tabs = CONSTANTS.videoTabs;
|
||||
let currentTab = tabs[0].value;
|
||||
|
||||
const onChange = (tab) => () => (currentTab = tab);
|
||||
</script>
|
||||
|
||||
<section class="w-full h-full flex flex-row items-start gap-3">
|
||||
<div class="flex-1 flex-col">
|
||||
<p class="text-sm font-normal text-start text-[#4F4B4B] dark:text-[#b0a9a9] mb-3">Add By</p>
|
||||
{#each tabs as item (item.value)}
|
||||
<button
|
||||
on:click={onChange(item.value)}
|
||||
class={`w-full px-4 py-3 my-5 border ${
|
||||
currentTab === item.value
|
||||
? 'border border-[#0233BD] bg-[#F5F8FE] dark:text-black'
|
||||
: 'border border-gray-200'
|
||||
} rounded-md cursor-pointer flex flex-row items-center justify-start gap-2 whitespace-nowrap`}
|
||||
>
|
||||
<svelte:component
|
||||
this={item.icon}
|
||||
size={20}
|
||||
color={`${currentTab === item.value ? 'dark:invert-0' : 'dark:invert'}`}
|
||||
/>
|
||||
<p>{item.title}</p>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
<main class="w-full h-full">
|
||||
{#if currentTab === 1}
|
||||
<YoutubeVideo />
|
||||
{:else}
|
||||
<LocalVideo />
|
||||
{/if}
|
||||
</main>
|
||||
</section>
|
||||
@@ -0,0 +1,88 @@
|
||||
<script>
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import { deleteLessonVideo } from '$lib/components/Course/components/Lesson/store/lessons';
|
||||
|
||||
let isWrongFormat = false;
|
||||
let isUploaded = false;
|
||||
let isLoaded = false;
|
||||
let isBig = false;
|
||||
|
||||
const uploadVideo = () => {
|
||||
// if the file is too big or not the correct format
|
||||
isWrongFormat = true;
|
||||
|
||||
// when the upload has been sucessfully completed
|
||||
// here the you set the video_url to something and that would be displayed on the page
|
||||
isUploaded = false;
|
||||
|
||||
// closes the modal
|
||||
// $uploadCourseVideo.isModalOpen = false;
|
||||
};
|
||||
|
||||
const onUpload = () => {
|
||||
// PROBABLY GET THE SNAPSHOT OR SOMETHING
|
||||
isLoaded = true;
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if isLoaded == false}
|
||||
<button
|
||||
on:click={onUpload}
|
||||
class="h-full w-full flex flex-col items-center justify-center border border-blue-300 border-dashed rounded-xl"
|
||||
>
|
||||
<img src="/upload-video.svg" alt="upload" />
|
||||
<span class="pt-3">
|
||||
<h3 class="text-center text-xl font-normal dark:text-white">Upload video</h3>
|
||||
<p class=" text-center text-sm font-normal">
|
||||
Select file( Mp4, MOV, AVI) to upload or drag and drop video here.<br /> Size must be less than
|
||||
20 MB.
|
||||
</p>
|
||||
</span>
|
||||
</button>
|
||||
{:else if isBig && isUploaded == false}
|
||||
<div class="h-full w-full flex flex-col items-center justify-center rounded-xl">
|
||||
<img src="/video-upload-error.svg" alt="upload error" />
|
||||
<span class="pt-3 pb-2">
|
||||
<h3 class="text-center text-base font-normal dark:text-white">
|
||||
Oops 😬, couldn’t upload video
|
||||
</h3>
|
||||
<p class="text-center text-xs font-normal text-[#ADADAD]">
|
||||
Sorry we video wasn’t uploaded. The file size is too big,<br /> maximum size is 20 MB. Try again!
|
||||
</p>
|
||||
</span>
|
||||
<PrimaryButton label="Try again" />
|
||||
</div>
|
||||
{:else if isWrongFormat && isUploaded == false}
|
||||
<div class="h-full w-full flex flex-col items-center justify-center rounded-xl">
|
||||
<img src="/video-upload-error.svg" alt="upload error" />
|
||||
<span class="pt-3 pb-2">
|
||||
<h3 class="text-center text-base font-normal dark:text-white">
|
||||
Oops 😬, couldn’t upload video
|
||||
</h3>
|
||||
<p class="text-center text-xs font-normal text-[#ADADAD]">
|
||||
Sorry we video wasn’t uploaded, the file format isn’t supported.<br /> Upload MP4, MOV and AVI
|
||||
files.
|
||||
</p>
|
||||
</span>
|
||||
<PrimaryButton label="Try again" />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-col w-full h-full items-start justify-between">
|
||||
<div class="h-auto w-full border rounded-md px-8 py-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="flex items-center gap-2">
|
||||
<div class="rounded-sm overflow-hidden">
|
||||
<img src="/course-video.png" alt="coursevideo" class="object-cover" />
|
||||
</div>
|
||||
<p>Video.png</p>
|
||||
</span>
|
||||
<button on:click={deleteLessonVideo}>
|
||||
<img src="/delete-video.svg" alt="deletevideo" class="dark:invert" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-auto">
|
||||
<PrimaryButton label="upload" onClick={uploadVideo} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -0,0 +1,79 @@
|
||||
<script>
|
||||
import { CopyButton, Tag } from 'carbon-components-svelte';
|
||||
import TrashCanIcon from 'carbon-icons-svelte/lib/TrashCan.svelte';
|
||||
import TextField from '$lib/components/Form/TextField.svelte';
|
||||
import { lesson, isLessonDirty } from '$lib/components/Course/components/Lesson/store/lessons';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import Chip from '$lib/components/Chip/Text.svelte';
|
||||
import IconButton from '$lib/components/IconButton/index.svelte';
|
||||
|
||||
let youtubeLinks = '';
|
||||
let error = '';
|
||||
|
||||
function addVideo() {
|
||||
const links = youtubeLinks.split(',').map((link) => link.trim());
|
||||
const validLinks = links.filter(isValidYouTubeLink);
|
||||
|
||||
if (validLinks.length === 0) {
|
||||
error = 'Invalid YouTube link(s)';
|
||||
} else {
|
||||
const existingLinks = $lesson.materials.video_url
|
||||
.split(',')
|
||||
.map((link) => link.trim())
|
||||
.filter(Boolean);
|
||||
const updatedLinks = [
|
||||
...existingLinks,
|
||||
...validLinks.filter((link) => !existingLinks.includes(link))
|
||||
];
|
||||
$lesson.materials.video_url = updatedLinks.join(',').trim();
|
||||
youtubeLinks = '';
|
||||
error = '';
|
||||
}
|
||||
}
|
||||
|
||||
function isValidYouTubeLink(link) {
|
||||
const youtubeRegex =
|
||||
/^(https?:\/\/)?(www\.)?(youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]{11})/;
|
||||
|
||||
return youtubeRegex.test(link.trim());
|
||||
}
|
||||
|
||||
function removeVideo(index) {
|
||||
const links = $lesson.materials.video_url.split(',').map((link) => link.trim());
|
||||
links.splice(index, 1);
|
||||
$lesson.materials.video_url = links.join(',').trim();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="w-full flex items-{error ? 'center' : 'end'} justify-between gap-5">
|
||||
<TextField
|
||||
label="Youtube link"
|
||||
bind:value={youtubeLinks}
|
||||
className="flex-1 text-left "
|
||||
inputClassName="text-sm"
|
||||
onChange={() => ($isLessonDirty = true)}
|
||||
placeholder="https://www.youtube.com/watch?v="
|
||||
errorMessage={error}
|
||||
/>
|
||||
<PrimaryButton label="Add Video" className="rounded-md" onClick={addVideo} />
|
||||
</div>
|
||||
<p class="mt-4 pl-2 text-sm">
|
||||
Added : <strong
|
||||
>{($lesson?.materials?.video_url && $lesson.materials.video_url.trim().split(',').length) || 0} Videos</strong
|
||||
>
|
||||
</p>
|
||||
<div class="">
|
||||
{#each $lesson?.materials?.video_url && $lesson.materials.video_url
|
||||
.trim()
|
||||
.split(',') as videoLink, index}
|
||||
<div class="flex items-center gap-1">
|
||||
<Tag type="blue">
|
||||
{videoLink}
|
||||
</Tag>
|
||||
<CopyButton text={videoLink} feedback="Copied to clipboard" />
|
||||
<IconButton value="delete-video" onClick={() => removeVideo(index)}>
|
||||
<TrashCanIcon size={16} class="carbon-icon dark:text-white" />
|
||||
</IconButton>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -0,0 +1,14 @@
|
||||
import Link from 'carbon-icons-svelte/lib/Link.svelte';
|
||||
import Video from 'carbon-icons-svelte/lib/Video.svelte';
|
||||
export const videoTabs = [
|
||||
{
|
||||
value: 1,
|
||||
title: 'Youtube Link',
|
||||
icon: Link
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
title: 'Video',
|
||||
icon: Video
|
||||
}
|
||||
];
|
||||
@@ -1,11 +1,13 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { useCompletion } from 'ai/svelte';
|
||||
import MODES from '$lib/utils/constants/mode.js';
|
||||
import { getEmbedId } 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 TrashCan from 'carbon-icons-svelte/lib/TrashCan.svelte';
|
||||
import ListIcon from 'carbon-icons-svelte/lib/List.svelte';
|
||||
import IbmWatsonKnowledgeStudioIcon from 'carbon-icons-svelte/lib/IbmWatsonKnowledgeStudio.svelte';
|
||||
import { formatYoutubeVideo } from '$lib/utils/functions/formatYoutubeVideo';
|
||||
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';
|
||||
@@ -14,24 +16,50 @@
|
||||
import EditContent from '$lib/components/EditContent/index.svelte';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import TextField from '$lib/components/Form/TextField.svelte';
|
||||
import { lesson, lessons, handleUpdateLessonMaterials, isLessonDirty } from '../store/lessons';
|
||||
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';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
export let mode = MODES.view;
|
||||
export let prevMode = null;
|
||||
export let lessonId;
|
||||
export let prevMode = '';
|
||||
export let lessonId = '';
|
||||
|
||||
let tabs = CONSTANTS.tabs;
|
||||
let currentTab = tabs[0].value;
|
||||
let errors = {};
|
||||
let player = {};
|
||||
let textareaRef = {};
|
||||
let aiButtonRef = {};
|
||||
let openPopover = false;
|
||||
let videoUrls: Array<string> = [];
|
||||
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) => () => (currentTab = tab);
|
||||
const onChange =
|
||||
(tab = 0) =>
|
||||
() =>
|
||||
(currentTab = tab);
|
||||
|
||||
function initPlyr(_player, videoUrl = '') {
|
||||
if (!browser) return;
|
||||
|
||||
const players = Array.from(document.querySelectorAll('.plyr-video-trigger')).map((p) => {
|
||||
// @ts-ignore
|
||||
return new Plyr(p);
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
window.players = players;
|
||||
}
|
||||
|
||||
function handleSave(prevMode) {
|
||||
if (prevMode === MODES.edit) {
|
||||
@@ -58,6 +86,10 @@
|
||||
});
|
||||
}
|
||||
|
||||
const openAddVideoModal = () => {
|
||||
$uploadCourseVideoStore.isModalOpen = true;
|
||||
};
|
||||
|
||||
const { input, handleSubmit, completion, isLoading } = useCompletion({
|
||||
api: '/api/completion'
|
||||
});
|
||||
@@ -81,13 +113,31 @@
|
||||
}, 500);
|
||||
}
|
||||
|
||||
const onClose = () => {
|
||||
handleUpdateLessonMaterials($lesson, lessonId);
|
||||
$uploadCourseVideoStore.isModalOpen = false;
|
||||
};
|
||||
|
||||
$: handleSave(prevMode);
|
||||
|
||||
$: initPlyr(player, $lesson.materials.video_url);
|
||||
|
||||
$: addBadgeValueToTab($lesson.materials);
|
||||
|
||||
$: updateNoteByCompletion($completion);
|
||||
|
||||
$: videoUrls = ($lesson?.materials?.video_url || '').split(',').filter((url) => !!url.trim());
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
{onClose}
|
||||
bind:open={$uploadCourseVideoStore.isModalOpen}
|
||||
width="w-4/5 h-[566px]"
|
||||
modalHeading="Add a Video"
|
||||
>
|
||||
<VideoUploader {onClose} />
|
||||
</Modal>
|
||||
|
||||
<Tabs {tabs} {currentTab} {onChange}>
|
||||
<slot:fragment slot="content">
|
||||
<TabContent value={tabs[0].value} index={currentTab}>
|
||||
@@ -186,37 +236,48 @@
|
||||
</TabContent>
|
||||
|
||||
<TabContent value={tabs[2].value} index={currentTab}>
|
||||
{#if mode === MODES.edit}
|
||||
<TextField
|
||||
label="Youtube link"
|
||||
bind:value={$lesson.materials.video_url}
|
||||
onChange={() => ($isLessonDirty = true)}
|
||||
/>
|
||||
{:else if !!$lesson.materials.video_url && $lesson.materials.video_url.includes('youtube')}
|
||||
{#each $lesson.materials.video_url.split(',') as url}
|
||||
<div class="mb-5">
|
||||
<iframe
|
||||
width="100%"
|
||||
height="569"
|
||||
class="iframe"
|
||||
src={formatYoutubeVideo(url, errors)}
|
||||
title="YouTube video player"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowfullscreen
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
{#if mode === MODES.edit && videoUrls.length}
|
||||
<PrimaryButton label="Add/Edit Video" onClick={openAddVideoModal} className="mb-2" />
|
||||
{/if}
|
||||
<!-- Line 230 would run if its in edit mode or the video is truthy but then in line 235 we are mapping for an empty string -->
|
||||
{#if videoUrls.length}
|
||||
<div class="flex flex-col items-start w-full h-full">
|
||||
{#each videoUrls as url, index}
|
||||
<div class="w-full h-full flex flex-col gap-2 overflow-hidden">
|
||||
{#if url.includes('youtube')}
|
||||
<div class="mb-5">
|
||||
<div
|
||||
bind:this={player}
|
||||
class="plyr-video-trigger"
|
||||
data-plyr-provider="youtube"
|
||||
data-plyr-embed-id={getEmbedId(url)}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<video bind:this={player} class="plyr-video-trigger" playsinline controls>
|
||||
<source
|
||||
src="https://pub-bd38ff7251aa402a93762181a947f7ea.r2.dev/generate-lesson-draft.mp4"
|
||||
type="video/mp4"
|
||||
/>
|
||||
<!-- <source src="/path/to/video.webm" type="video/webm" /> -->
|
||||
<!-- Captions are optional -->
|
||||
<track kind="captions" />
|
||||
</video>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<Box>
|
||||
<div class="flex justify-between flex-col items-center w-96">
|
||||
<img src="/images/empty-video-icon.svg" alt="No Video" class="my-2.5 mx-auto" />
|
||||
<h2 class="text-xl my-1.5 font-normal">No videos yet</h2>
|
||||
<p class="text-sm text-center text-slate-500">
|
||||
Share your knowledge with the world by creating engaging videos. Add your video link
|
||||
now.
|
||||
</p>
|
||||
</div>
|
||||
<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} />
|
||||
</Box>
|
||||
{/if}
|
||||
</TabContent>
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import type { Writable, Updater } from 'svelte/store';
|
||||
import { createLesson, updateLesson, deleteLesson } from '$lib/utils/services/courses';
|
||||
|
||||
import type { Lesson, Course } from '$lib/utils/types';
|
||||
|
||||
export const uploadCourseVideoStore = writable({
|
||||
isModalOpen: false
|
||||
});
|
||||
|
||||
export const lessons: Writable<Lesson[]> = writable([]);
|
||||
export const lesson = writable({
|
||||
id: null,
|
||||
totalExercises: 0,
|
||||
is_complete: false,
|
||||
materials: {
|
||||
note: null,
|
||||
slide_url: null,
|
||||
video_url: null
|
||||
note: '',
|
||||
slide_url: '',
|
||||
video_url: ''
|
||||
},
|
||||
exercises: []
|
||||
});
|
||||
@@ -92,7 +95,23 @@ export async function handleUpdateLessonMaterials(lesson: any, lessonId: Lesson[
|
||||
...lesson.materials
|
||||
};
|
||||
|
||||
delete materials.profile;
|
||||
delete materials.totalExercises;
|
||||
|
||||
return await updateLesson(materials, lessonId);
|
||||
}
|
||||
|
||||
export const deleteLessonVideo = (index: any) => {
|
||||
lesson.update((currentLesson) => {
|
||||
const links = currentLesson.materials.video_url.split(',').map((link) => link.trim());
|
||||
links.splice(index, 1);
|
||||
const updatedVideoUrl = links.join(',').trim();
|
||||
return {
|
||||
...currentLesson,
|
||||
materials: {
|
||||
...currentLesson.materials,
|
||||
video_url: updatedVideoUrl
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
@@ -172,7 +172,7 @@
|
||||
</RoleBasedSecurity>
|
||||
</slot:fragment>
|
||||
</PageNav> -->
|
||||
<div class="h-full mt-2 overflow-y-auto">
|
||||
<div class="h-full py-3 overflow-y-auto">
|
||||
{#each navItems as navItem}
|
||||
{#if !navItem.show || (typeof navItem.show === 'function' && navItem.show())}
|
||||
<NavExpandable
|
||||
@@ -270,7 +270,6 @@
|
||||
max-width: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<script>
|
||||
export let color = 'currentColor';
|
||||
</script>
|
||||
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M14.2588 0.5V2C15.2534 2 16.2072 2.39509 16.9104 3.09835C17.6137 3.80161 18.0088 4.75544 18.0088 5.75C18.0088 6.74456 17.6137 7.69839 16.9104 8.40165C16.2072 9.10491 15.2534 9.5 14.2588 9.5V11C15.6512 11 16.9865 10.4469 17.9711 9.46231C18.9557 8.47774 19.5088 7.14239 19.5088 5.75C19.5088 4.35761 18.9557 3.02226 17.9711 2.03769C16.9865 1.05312 15.6512 0.5 14.2588 0.5ZM14.2588 21.5H15.7588V17.75C15.7572 16.3581 15.2035 15.0237 14.2193 14.0395C13.2351 13.0553 11.9007 12.5016 10.5088 12.5H6.00879C4.6169 12.5016 3.28249 13.0553 2.29828 14.0395C1.31406 15.0237 0.760417 16.3581 0.758789 17.75V21.5H2.25879V17.75C2.25996 16.7558 2.65542 15.8027 3.35843 15.0996C4.06144 14.3966 5.01459 14.0012 6.00879 14H10.5088C11.503 14.0012 12.4561 14.3966 13.1591 15.0996C13.8622 15.8027 14.2576 16.7558 14.2588 17.75V21.5ZM20.2588 21.5H21.7588V17.75C21.7572 16.3581 21.2036 15.0236 20.2194 14.0394C19.2352 13.0552 17.9007 12.5015 16.5088 12.5V14C17.503 14.0011 18.4562 14.3966 19.1592 15.0996C19.8622 15.8026 20.2577 16.7558 20.2588 17.75V21.5ZM10.3422 2.63199C9.7255 2.21993 9.00047 2 8.25879 2C7.26423 2 6.3104 2.39509 5.60714 3.09835C4.90388 3.80161 4.50879 4.75544 4.50879 5.75C4.50879 6.49168 4.72873 7.2167 5.14078 7.83339C5.55284 8.45007 6.13851 8.93072 6.82373 9.21455C7.50895 9.49838 8.26295 9.57264 8.99038 9.42795C9.71781 9.28325 10.386 8.9261 10.9104 8.40165C11.4349 7.8772 11.792 7.20902 11.9367 6.48159C12.0814 5.75416 12.0072 5.00016 11.7233 4.31494C11.4395 3.62971 10.9589 3.04404 10.3422 2.63199ZM5.34205 1.38478C6.20541 0.807907 7.22044 0.5 8.25879 0.5C9.65118 0.5 10.9865 1.05312 11.9711 2.03769C12.9557 3.02226 13.5088 4.35761 13.5088 5.75C13.5088 6.78835 13.2009 7.80339 12.624 8.66674C12.0471 9.5301 11.2272 10.203 10.2679 10.6004C9.30857 10.9977 8.25297 11.1017 7.23457 10.8991C6.21617 10.6966 5.28071 10.1965 4.54648 9.46231C3.81226 8.72808 3.31224 7.79262 3.10967 6.77422C2.9071 5.75582 3.01106 4.70022 3.40842 3.74091C3.80578 2.7816 4.47869 1.96166 5.34205 1.38478Z"
|
||||
fill={color}
|
||||
/>
|
||||
</svg>
|
||||
@@ -1,5 +1,5 @@
|
||||
<script>
|
||||
export let fill = '#7A797D';
|
||||
export let color = 'currentColor';
|
||||
</script>
|
||||
|
||||
<svg
|
||||
@@ -12,6 +12,6 @@
|
||||
>
|
||||
<path
|
||||
d="M18 22.5H6C5.60232 22.4995 5.22105 22.3414 4.93984 22.0602C4.65864 21.7789 4.50046 21.3977 4.5 21V3C4.5004 2.6023 4.65856 2.221 4.93978 1.93978C5.221 1.65856 5.6023 1.5004 6 1.5H18C18.3977 1.50046 18.779 1.65864 19.0602 1.93984C19.3414 2.22105 19.4995 2.60232 19.5 3V15.4636L15.75 13.5886L12 15.4636V3H6V21H18V18H19.5V21C19.4995 21.3977 19.3413 21.7789 19.0601 22.0601C18.7789 22.3413 18.3977 22.4995 18 22.5ZM15.75 11.9114L18 13.0364V3H13.5V13.0364L15.75 11.9114Z"
|
||||
{fill}
|
||||
fill={color}
|
||||
/>
|
||||
</svg>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<script>
|
||||
export let color = 'currentColor';
|
||||
</script>
|
||||
|
||||
<svg
|
||||
width="25"
|
||||
height="24"
|
||||
@@ -8,35 +12,35 @@
|
||||
>
|
||||
<path
|
||||
d="M16.9999 2H8.99991C8.46948 2 7.96077 2.21071 7.58569 2.58579C7.21062 2.96086 6.99991 3.46957 6.99991 4V20C6.99991 20.5304 7.21062 21.0391 7.58569 21.4142C7.96077 21.7893 8.46948 22 8.99991 22H20.9999C21.5303 22 22.039 21.7893 22.4141 21.4142C22.7892 21.0391 22.9999 20.5304 22.9999 20V8L16.9999 2Z"
|
||||
stroke="#7A797D"
|
||||
stroke={color}
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M16.9999 2V8H22.9999"
|
||||
stroke="#7A797D"
|
||||
stroke={color}
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M18.9999 13H10.9999"
|
||||
stroke="#7A797D"
|
||||
stroke={color}
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M18.9999 17H10.9999"
|
||||
stroke="#7A797D"
|
||||
stroke={color}
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M12.9999 9H11.9999H10.9999"
|
||||
stroke="#7A797D"
|
||||
stroke={color}
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,3 +1,7 @@
|
||||
<script>
|
||||
export let color = 'currentColor';
|
||||
</script>
|
||||
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
@@ -10,6 +14,6 @@
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M3 1.5H21C21.3977 1.50046 21.7789 1.65864 22.0602 1.93984C22.3414 2.22105 22.4995 2.60232 22.5 3V21C22.4995 21.3977 22.3413 21.7789 22.0601 22.0601C21.7789 22.3413 21.3977 22.4995 21 22.5H3C2.60232 22.4995 2.22105 22.3414 1.93984 22.0602C1.65864 21.7789 1.50046 21.3977 1.5 21V3C1.5004 2.6023 1.65856 2.221 1.93978 1.93978C2.221 1.65856 2.6023 1.5004 3 1.5ZM10.5 8.25H21V3H10.5V8.25ZM9 8.25V3H3V8.25H9ZM3 9.75V21H21.0015L21.0005 9.75H3ZM8.25 19.5C7.2558 19.4988 6.30265 19.1034 5.59964 18.4004C4.89664 17.6973 4.50117 16.7442 4.5 15.75H6C6 16.195 6.13196 16.63 6.37919 17C6.62643 17.37 6.97783 17.6584 7.38896 17.8287C7.8001 17.999 8.2525 18.0436 8.68895 17.9568C9.12541 17.87 9.52632 17.6557 9.84099 17.341C10.1557 17.0263 10.37 16.6254 10.4568 16.189C10.5436 15.7525 10.499 15.3001 10.3287 14.889C10.1584 14.4778 9.87004 14.1264 9.50003 13.8792C9.13002 13.632 8.69501 13.5 8.25 13.5V12C9.24456 12 10.1984 12.3951 10.9017 13.0983C11.6049 13.8016 12 14.7554 12 15.75C12 16.7446 11.6049 17.6984 10.9017 18.4016C10.1984 19.1049 9.24456 19.5 8.25 19.5ZM15 12H16.5V19.5H15V12ZM18 15.75H19.5V19.5H18V15.75Z"
|
||||
fill="#262626"
|
||||
fill={color}
|
||||
/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,20 @@
|
||||
<script>
|
||||
export let color = 'currentColor';
|
||||
</script>
|
||||
|
||||
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_3647_18153)">
|
||||
<path
|
||||
d="M24 15C24 15.5304 23.7893 16.0391 23.4142 16.4142C23.0391 16.7893 22.5304 17 22 17H10L6 21V5C6 4.46957 6.21071 3.96086 6.58579 3.58579C6.96086 3.21071 7.46957 3 8 3H22C22.5304 3 23.0391 3.21071 23.4142 3.58579C23.7893 3.96086 24 4.46957 24 5V15Z"
|
||||
stroke={color}
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_3647_18153">
|
||||
<rect width="24" height="24" fill={color} transform="translate(0.258789)" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
@@ -1,11 +1,12 @@
|
||||
<script>
|
||||
import { page } from '$app/stores';
|
||||
import HelpIcon from 'carbon-icons-svelte/lib/Help.svelte';
|
||||
|
||||
import TextChip from '$lib/components/Chip/Text.svelte';
|
||||
import OrgSelector from '$lib/components/OrgSelector/OrgSelector.svelte';
|
||||
import HomeIcon from '$lib/components/Icons/HomeIcon.svelte';
|
||||
import CourseIcon from '$lib/components/Icons/CourseIcon.svelte';
|
||||
import ReviewsIcon from '$lib/components/Icons/ReviewsIcon.svelte';
|
||||
import CommunityIcon from '$lib/components/Icons/CommunityIcon.svelte';
|
||||
import ExerciseIcon from '$lib/components/Icons/ExerciseIcon.svelte';
|
||||
import Avatar from '$lib/components/Avatar/index.svelte';
|
||||
import { currentOrg } from '$lib/utils/store/org';
|
||||
@@ -23,6 +24,33 @@
|
||||
|
||||
return pagePath.includes(itemPath);
|
||||
}
|
||||
let sideLinks = [
|
||||
{
|
||||
name: 'Home',
|
||||
icon: HomeIcon,
|
||||
link: '/lms'
|
||||
},
|
||||
{
|
||||
name: 'My Learning',
|
||||
icon: CourseIcon,
|
||||
link: '/lms/mylearning'
|
||||
},
|
||||
{
|
||||
name: 'Exercise',
|
||||
icon: ExerciseIcon,
|
||||
link: '/lms/exercises'
|
||||
},
|
||||
{
|
||||
name: 'Reviews',
|
||||
icon: ReviewsIcon,
|
||||
link: '/lms/reviews'
|
||||
},
|
||||
{
|
||||
name: 'Community',
|
||||
icon: CommunityIcon,
|
||||
link: '/lms/community'
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<aside class="root bg-gray-100 dark:bg-gray-700 h-full">
|
||||
@@ -43,39 +71,22 @@
|
||||
<OrgSelector />
|
||||
|
||||
<ul class="my-5">
|
||||
<a href="/lms" class="text-black">
|
||||
<li
|
||||
class="flex items-center py-3 px-4 mb-2 rounded hover:bg-gray-200 dark:hover:bg-gray-800 {isActive(
|
||||
$page.url.pathname,
|
||||
`/lms`
|
||||
) && activeClass}"
|
||||
>
|
||||
<HomeIcon />
|
||||
<p class="dark:text-white ml-2">Home</p>
|
||||
</li>
|
||||
</a>
|
||||
<a href="/lms/mylearning" class="text-black">
|
||||
<li
|
||||
class="flex items-center py-3 px-4 mb-2 rounded hover:bg-gray-200 dark:hover:bg-gray-800 {isActive(
|
||||
$page.url.pathname,
|
||||
`/lms/mylearning`
|
||||
) && activeClass}"
|
||||
>
|
||||
<CourseIcon />
|
||||
<p class="dark:text-white ml-2">My Learning</p>
|
||||
</li>
|
||||
</a>
|
||||
<a href="/lms/exercises" class="text-black">
|
||||
<li
|
||||
class="flex items-center py-3 px-4 mb-2 rounded hover:bg-gray-200 dark:hover:bg-gray-800 {isActive(
|
||||
$page.url.pathname,
|
||||
`/lms/exercises`
|
||||
) && activeClass}"
|
||||
>
|
||||
<ExerciseIcon />
|
||||
<p class="dark:text-white ml-2">Exercises</p>
|
||||
</li>
|
||||
</a>
|
||||
{#each sideLinks as item (item.name)}
|
||||
<a href={item.link} class="text-black">
|
||||
<li
|
||||
class="flex items-center py-3 px-4 mb-2 rounded hover:bg-gray-200 dark:hover:bg-gray-500 {isActive(
|
||||
$page.url.pathname,
|
||||
`${item.link}`
|
||||
) && activeClass}"
|
||||
>
|
||||
<svelte:component
|
||||
this={item.icon}
|
||||
color={isActive($page.url.pathname, `${item.link}`) ? 'black' : 'gray'}
|
||||
/>
|
||||
<p class="dark:text-white ml-2">{item.name}</p>
|
||||
</li>
|
||||
</a>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
<span class="flex-grow" />
|
||||
|
||||
@@ -22,29 +22,33 @@
|
||||
</script>
|
||||
|
||||
{#if open}
|
||||
<button
|
||||
<div
|
||||
class="dialog fixed overflow-x-hidden overflow-y-auto inset-0 flex justify-center items-center z-50 bg-gray-100 bg-opacity-50"
|
||||
on:click={onClose}
|
||||
role="presentation"
|
||||
>
|
||||
<div aria-hidden="true" class="backdrop" />
|
||||
<button
|
||||
<div
|
||||
class="{maxWidth || 'container'} {size === 'sm'
|
||||
? 'small'
|
||||
: ''} bg-white dark:bg-gray-800 mx-auto {width} shadow-lg p-5 pt-2 rounded-md"
|
||||
on:click={(e) => e.stopPropagation()}
|
||||
role="presentation"
|
||||
>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="dark:text-white text-xl m-0">
|
||||
{modalHeading}
|
||||
</h3>
|
||||
<CloseButton onClick={onClose} contained={true} />
|
||||
<div class="rounded-full">
|
||||
<CloseButton onClick={onClose} contained={true} size="small" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="body p-1 h-4/5 overflow-y-auto {containerClass}">
|
||||
<slot />
|
||||
</div>
|
||||
</button>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { setCourse, course } from '$lib/components/Course/store';
|
||||
import { lessons } from '$lib/components/Course/components/Lesson/store/lessons';
|
||||
import { fetchCourse } from '$lib/utils/services/courses';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
export let data;
|
||||
|
||||
@@ -13,6 +14,7 @@
|
||||
let loading = false;
|
||||
|
||||
async function triggerSetCourse(_profile) {
|
||||
if (!browser) return;
|
||||
loading = true;
|
||||
const { data: _data } = await fetchCourse(undefined, data.slug);
|
||||
setCourse(_data);
|
||||
|
||||
@@ -14,12 +14,13 @@
|
||||
// import TabContent from 'carbon-components-svelte/src/Tabs/TabContent.svelte';
|
||||
import StudentCertificate from '$lib/components/Course/components/Ceritficate/StudentCertificate/Index.svelte';
|
||||
import { fetchCourse } from '$lib/utils/services/courses';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
export let courseId: string = '';
|
||||
let isStudent = false;
|
||||
|
||||
onMount(async () => {
|
||||
if ($course.id) return;
|
||||
if ($course.id || !browser) return;
|
||||
const { data } = await fetchCourse(courseId);
|
||||
setCourse(data);
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import { setCourse, course } from '$lib/components/Course/store';
|
||||
import { lessons } from '$lib/components/Course/components/Lesson/store/lessons';
|
||||
import { fetchCourse } from '$lib/utils/services/courses';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
export let data;
|
||||
const { courseId } = data;
|
||||
@@ -14,7 +15,7 @@
|
||||
let courseData = {};
|
||||
|
||||
async function triggerSetCourse(_profile) {
|
||||
if ($course.id) return;
|
||||
if ($course.id || !browser) return;
|
||||
const { data: _data } = await fetchCourse(courseId);
|
||||
setCourse(_data);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import MODES from '$lib/utils/constants/mode.js';
|
||||
import { setCourse, course } from '$lib/components/Course/store';
|
||||
import { lesson, handleSaveLesson } from '$lib/components/Course/components/Lesson/store/lessons';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
export let data;
|
||||
|
||||
@@ -62,9 +63,14 @@
|
||||
}));
|
||||
}
|
||||
|
||||
function toggleMode() {
|
||||
prevMode = mode;
|
||||
mode = mode === MODES.edit ? MODES.view : MODES.edit;
|
||||
}
|
||||
|
||||
$: path = $page.url.pathname.replace(/\/exercises[\/ 0-9 a-z -]*/, '');
|
||||
|
||||
$: if (data.courseId) {
|
||||
$: if (data.courseId && browser) {
|
||||
mode = MODES.view;
|
||||
fetchReqData(data.lessonId);
|
||||
}
|
||||
@@ -94,10 +100,7 @@
|
||||
className="mr-2"
|
||||
label={mode === MODES.edit ? 'Save' : 'Edit'}
|
||||
variant={VARIANTS.CONTAINED_INFO}
|
||||
onClick={() => {
|
||||
prevMode = mode;
|
||||
mode = mode === MODES.edit ? MODES.view : MODES.edit;
|
||||
}}
|
||||
onClick={toggleMode}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -109,7 +112,7 @@
|
||||
<Exercises lessonId={data.lessonId} exerciseId={data.exerciseId} path={`${path}/exercises`} />
|
||||
{:else if !!data.lessonId}
|
||||
<PageBody>
|
||||
<Materials lessonId={data.lessonId} {mode} {prevMode} />
|
||||
<Materials lessonId={data.lessonId} {mode} {prevMode} {toggleMode} />
|
||||
|
||||
<div class="w-full flex flex-row-reverse">
|
||||
<PrimaryButton
|
||||
|
||||
@@ -1 +1,15 @@
|
||||
<h1>Your Journey into progress begins today. Welcome !!!</h1>
|
||||
<script>
|
||||
</script>
|
||||
|
||||
<section class="m-5 w-[calc(100vw - 250px)] h-full gap-5">
|
||||
<h1 class="text-3xl font-semibold">Dashboard</h1>
|
||||
|
||||
<main class="w-full h-full flex flex-row gap-3">
|
||||
<div class="w-full h-full border-2 border-dashed flex items-center justify-center">
|
||||
<p>Student section</p>
|
||||
</div>
|
||||
<div class="w-full h-full border-2 border-dashed flex items-center justify-center">
|
||||
<p>Currently Learning</p>
|
||||
</div>
|
||||
</main>
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
community page
|
||||
@@ -0,0 +1 @@
|
||||
reviews page
|
||||
Reference in New Issue
Block a user