CLASSROOMIO-T-90:Implemented the course settings
only update order when lesson id changes CLASSROOMIO-T-90: Update banner image on course card CLASSROOMIO-T-90: Fix merge conflicts Merge branch 'main' into CLASSROOMIO-T-90 CLASSROOMIO-T-90:removed unused values and console log in the Course Settings CLASSROOMIO-T-90:removed unused import in the Course Settings CLASSROOMIO-T-90:fixed bugs in the Course Settings Merge branch 'main' into CLASSROOMIO-T-90 CLASSROOMIO-T-90:Implemented the Course Settings CLASSROOMIO-T-90:Worked on the course settings Merge branch 'main' into CLASSROOMIO-T-90 CLASSROOMIO-T-90:Fixed the landing page issue CLASSROOMIO-T-146:Made updates to the code following the reviews CLASSROOMIO-T-146:Made updates to the Z-Index Merge branch 'main' into CLASSROOMIO-T-146 Merge branch 'main' into CLASSROOMIO-T-146 CLASSROOMIO-T-146:Made the course page responsive CLASSROOMIO-T-146:working on the course page responsiveness Merge branch 'main' into CLASSROOMIO-T-145 CLASSROOMIO-T-146:Making the course page responsive CLASSROOMIO-T-145: All done and ready CLASSROOMIO-T-145: All done and ready CLASSROOMIO-T-145: Fixed the Z-index issue Merge branch 'main' into CLASSROOMIO-T-145 CLASSROOMIO-T-145: Finished implementing the responsive design for the Main dashboard and LMS dashboard Merge branch 'main' into CLASSROOMIO-T-145 CLASSROOMIO-T-144:Updating the dashboard responsiveness ooth for LMS and the Main dashboard CLASSROOMIO-T-144:Updated the dashboard responsiveness ooth for LMS and the Main dashboard Merge branch 'main' into CLASSROOMIO-T-145 CLASSROOMIO-T-145:Made the LMS dashboard responsive Co-authored-by: Ifeanyi Nwosu <[email protected]> Co-authored-by: Rotimi Best <[email protected]> Merge-request: CLASSROOMIO-MR-76 Merged-by: Best Admin <[email protected]>
This commit is contained in:
committed by
Space Cloud
co-authored by
Rotimi Best
parent
e44353ac0a
commit
103a2eda7b
@@ -14,7 +14,6 @@
|
||||
import Notes from './components/Notes/index.svelte';
|
||||
import Poll from './components/Poll/index.svelte';
|
||||
import APPS_CONSTANTS from './constants';
|
||||
import { OverflowMenu, OverflowMenuItem } from 'carbon-components-svelte';
|
||||
|
||||
let selectedApp;
|
||||
|
||||
@@ -28,8 +27,8 @@
|
||||
} else {
|
||||
selectedApp = appName;
|
||||
}
|
||||
$apps.dropdown = false;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
hotkeys('A+1,A+2,A+3,A+4', function (event, handler) {
|
||||
event.preventDefault();
|
||||
@@ -146,7 +145,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
@media screen and (max-width: 1023px) {
|
||||
.root {
|
||||
position: absolute;
|
||||
height: calc(100vh - 109px);
|
||||
@@ -166,4 +165,33 @@
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1024px) {
|
||||
.root {
|
||||
height: calc(100vh - 48px);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
border-left: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
z-index: 1;
|
||||
|
||||
& .apps {
|
||||
padding-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 60px;
|
||||
border-right: 1px solid var(--border-color);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
& .app {
|
||||
width: 400px;
|
||||
transition: all 1s ease-out;
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { lesson } from '$lib/components/Course/components/Lesson/store/lessons';
|
||||
|
||||
function isNoteEmpty(note) {
|
||||
if (!note || typeof note !== 'string') return true;
|
||||
|
||||
if (!document) return false;
|
||||
|
||||
const dummyDiv = document.createElement('div');
|
||||
dummyDiv.innerHTML = note;
|
||||
|
||||
const rawText = dummyDiv.textContent?.trim();
|
||||
|
||||
return rawText === '';
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if !isNoteEmpty($lesson.materials?.note)}
|
||||
<article class="preview prose prose-sm sm:prose p-2">
|
||||
{@html $lesson.materials.note}
|
||||
</article>
|
||||
{/if}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
<script>
|
||||
import { lesson } from '../../store/lessons';
|
||||
</script>
|
||||
|
||||
{#if $lesson.materials.slide_url}
|
||||
<iframe
|
||||
title="Embeded Slides"
|
||||
src={$lesson.materials.slide_url}
|
||||
frameborder="0"
|
||||
width="100%"
|
||||
height="569"
|
||||
class="iframe my-3"
|
||||
allowfullscreen="true"
|
||||
mozallowfullscreen="true"
|
||||
webkitallowfullscreen="true"
|
||||
/>
|
||||
{/if}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
<script lang="ts">
|
||||
import { formatYoutubeVideo } from '$lib/utils/functions/formatYoutubeVideo';
|
||||
import { lesson } from '../../store/lessons';
|
||||
|
||||
let errors = {};
|
||||
let player = null;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
$: initPlyr(player, $lesson.materials.videos);
|
||||
</script>
|
||||
|
||||
{#if $lesson.materials.videos.length}
|
||||
<div class="w-full">
|
||||
{#each $lesson.materials.videos as video}
|
||||
<div class="w-full overflow-hidden mb-5">
|
||||
{#key video.link}
|
||||
<div class="mb-5">
|
||||
{#if video.type === 'youtube'}
|
||||
<iframe
|
||||
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}
|
||||
|
||||
<style>
|
||||
.iframe {
|
||||
width: 100%;
|
||||
height: 450px;
|
||||
}
|
||||
@media (max-width: 760px) {
|
||||
.iframe {
|
||||
height: 209px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Video, Notebook, PresentationFile } from 'carbon-icons-svelte';
|
||||
|
||||
export const tabs = [
|
||||
export let tabs = [
|
||||
{
|
||||
label: 'Note',
|
||||
icon: Notebook,
|
||||
@@ -20,3 +20,20 @@ export const tabs = [
|
||||
badgeValue: 0
|
||||
}
|
||||
];
|
||||
|
||||
export function orderedTabs(tabs, Settingtabs) {
|
||||
if (!Array.isArray(Settingtabs)) return tabs;
|
||||
const reorderedTabs = [];
|
||||
|
||||
const tabMap = new Map(tabs.map((tab) => [tab.label, tab]));
|
||||
|
||||
Settingtabs.forEach((settingTab, index) => {
|
||||
const tab = tabMap.get(settingTab.name);
|
||||
if (tab) {
|
||||
reorderedTabs[index] = tab;
|
||||
}
|
||||
});
|
||||
|
||||
const filteredReorderedTabs = reorderedTabs.filter((tab) => tab !== undefined);
|
||||
return filteredReorderedTabs;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,10 @@
|
||||
import { course } from '$lib/components/Course/store';
|
||||
import TextEditor from '$lib/components/TextEditor/index.svelte';
|
||||
import * as CONSTANTS from './constants';
|
||||
import { orderedTabs } from './constants';
|
||||
import ComponentNote from './components/ComponentNote.svelte';
|
||||
import ComponentSlide from './components/ComponentSlide.svelte';
|
||||
import ComponentVideo from './components/ComponentVideo.svelte';
|
||||
|
||||
export let mode = MODES.view;
|
||||
export let prevMode = '';
|
||||
@@ -47,6 +51,7 @@
|
||||
let aiButtonRef = {};
|
||||
let openPopover = false;
|
||||
let player = null;
|
||||
let componentsToRender = getComponentOrder(tabs);
|
||||
let aiButtonClass =
|
||||
'flex items-center px-5 py-2 border border-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-md w-full mb-2';
|
||||
|
||||
@@ -55,6 +60,10 @@
|
||||
() =>
|
||||
(currentTab = tab);
|
||||
|
||||
const getValue = (label) => {
|
||||
const tabValue = tabs.find((tab) => tab.label === label)?.value;
|
||||
return tabValue;
|
||||
};
|
||||
async function saveLesson(materials = undefined) {
|
||||
const _lesson = !!materials
|
||||
? {
|
||||
@@ -174,12 +183,33 @@
|
||||
function onLessonIdChange(_lid: string) {
|
||||
initAutoSave = false;
|
||||
isSaving = false;
|
||||
|
||||
tabs = orderedTabs(tabs, $course.metadata?.lessonTabsOrder);
|
||||
currentTab = tabs[0].value;
|
||||
componentsToRender = getComponentOrder(tabs);
|
||||
}
|
||||
|
||||
const onClose = () => {
|
||||
$uploadCourseVideoStore.isModalOpen = false;
|
||||
};
|
||||
|
||||
function getComponentOrder(tabs) {
|
||||
const componentMap = {
|
||||
Video: ComponentVideo,
|
||||
Slide: ComponentSlide,
|
||||
Note: ComponentNote
|
||||
};
|
||||
|
||||
const componentNames = tabs
|
||||
.map((tab) => {
|
||||
const component = componentMap[tab.label];
|
||||
return component || null;
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
return componentNames;
|
||||
}
|
||||
|
||||
$: autoSave($lesson.materials, $isLoading, lessonId);
|
||||
|
||||
$: onLessonIdChange(lessonId);
|
||||
@@ -211,7 +241,7 @@
|
||||
{#if mode === MODES.edit}
|
||||
<Tabs {tabs} {currentTab} {onChange}>
|
||||
<slot:fragment slot="content">
|
||||
<TabContent value={tabs[0].value} index={currentTab}>
|
||||
<TabContent value={getValue('Note')} index={currentTab}>
|
||||
<div bind:this={aiButtonRef} class="w-full flex flex-row-reverse">
|
||||
<PrimaryButton
|
||||
className="flex items-center relative"
|
||||
@@ -261,7 +291,7 @@
|
||||
</div>
|
||||
</TabContent>
|
||||
|
||||
<TabContent value={tabs[1].value} index={currentTab}>
|
||||
<TabContent value={getValue('Slide')} index={currentTab}>
|
||||
{#if mode === MODES.edit}
|
||||
<TextField
|
||||
label="Slide link"
|
||||
@@ -270,8 +300,7 @@
|
||||
/>
|
||||
{/if}
|
||||
</TabContent>
|
||||
|
||||
<TabContent value={tabs[2].value} index={currentTab}>
|
||||
<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">
|
||||
@@ -328,64 +357,11 @@
|
||||
</slot:fragment>
|
||||
</Tabs>
|
||||
{:else if !isMaterialsEmpty($lesson.materials)}
|
||||
{#if $lesson.materials.videos.length}
|
||||
<div class="w-full">
|
||||
{#each $lesson.materials.videos as video}
|
||||
<div class="w-full overflow-hidden mb-5">
|
||||
{#key video.link}
|
||||
<div class="mb-5">
|
||||
{#if video.type === 'youtube'}
|
||||
<iframe
|
||||
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}
|
||||
|
||||
{#if $lesson.materials.slide_url}
|
||||
<iframe
|
||||
title="Embeded Slides"
|
||||
src={$lesson.materials.slide_url}
|
||||
frameborder="0"
|
||||
width="100%"
|
||||
height="569"
|
||||
class="iframe my-3"
|
||||
allowfullscreen="true"
|
||||
mozallowfullscreen="true"
|
||||
webkitallowfullscreen="true"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if !isNoteEmpty($lesson.materials?.note)}
|
||||
<article class="preview prose prose-sm sm:prose p-2">
|
||||
{@html $lesson.materials.note}
|
||||
</article>
|
||||
{/if}
|
||||
<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" />
|
||||
@@ -399,16 +375,3 @@
|
||||
<PrimaryButton label="Get started" className="rounded-md" onClick={toggleMode} />
|
||||
</Box>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.iframe {
|
||||
width: 100%;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.iframe {
|
||||
height: 209px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import ApplicationWeb from 'carbon-icons-svelte/lib/ApplicationWeb.svelte';
|
||||
import Badge from 'carbon-icons-svelte/lib/Badge.svelte';
|
||||
import IbmWatsonKnowledgeCatalog from 'carbon-icons-svelte/lib/IbmWatsonKnowledgeCatalog.svelte';
|
||||
import Settings from 'carbon-icons-svelte/lib/Settings.svelte';
|
||||
|
||||
export let name = '';
|
||||
</script>
|
||||
@@ -27,4 +28,6 @@
|
||||
<ApplicationWeb size={20} class="carbon-icon mr-2" />
|
||||
{:else if name === 'Certificates'}
|
||||
<Badge size={20} class="carbon-icon mr-2" />
|
||||
{:else if name === 'Settings'}
|
||||
<Settings size={20} class="carbon-icon mr-2" />
|
||||
{/if}
|
||||
|
||||
@@ -5,20 +5,10 @@
|
||||
import hotkeys from 'hotkeys-js';
|
||||
import { browser } from '$app/environment';
|
||||
import LockedIcon from 'carbon-icons-svelte/lib/Locked.svelte';
|
||||
// import ArrowLeftIcon from 'carbon-icons-svelte/lib/ArrowLeft.svelte';
|
||||
import CheckmarkFilled from 'carbon-icons-svelte/lib/CheckmarkFilled.svelte';
|
||||
// import SettingsIcon from 'carbon-icons-svelte/lib/Settings.svelte';
|
||||
import ChevronLeftIcon from 'carbon-icons-svelte/lib/ChevronLeft.svelte';
|
||||
import ChevronRightIcon from 'carbon-icons-svelte/lib/ChevronRight.svelte';
|
||||
import NavExpandable from './NavExpandable.svelte';
|
||||
import IconButton from '$lib/components/IconButton/index.svelte';
|
||||
// import Avatar from '$lib/components/Avatar/index.svelte';
|
||||
import { getNavItemRoute, getLessonsRoute, getLectureNo } from '$lib/components/Course/function';
|
||||
|
||||
import TextChip from '$lib/components/Chip/Text.svelte';
|
||||
// import RoleBasedSecurity from '$lib/components/RoleBasedSecurity/index.svelte';
|
||||
import Settings from '../Settings/index.svelte';
|
||||
// import { settingsDialog } from '../Settings/store';
|
||||
import { lessons } from '../Lesson/store/lessons';
|
||||
import { course } from '$lib/components/Course/store';
|
||||
import { updateCourse } from '$lib/utils/services/courses';
|
||||
@@ -26,7 +16,6 @@
|
||||
import { isMobile } from '$lib/utils/store/useMobile';
|
||||
import { menu } from '$lib/components/Org/store';
|
||||
|
||||
// export let lessonId;
|
||||
export let path;
|
||||
export let isStudent = null;
|
||||
|
||||
@@ -142,19 +131,25 @@
|
||||
show() {
|
||||
return !isStudent;
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
to: getNavItemRoute($course.id, 'settings'),
|
||||
hideSortIcon: true,
|
||||
show() {
|
||||
return !isStudent;
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
</script>
|
||||
|
||||
<Settings />
|
||||
|
||||
<aside
|
||||
class={`${
|
||||
$menu.hidden
|
||||
? '-translate-x-[100%] absolute md:translate-x-0 md:relative z-[40]'
|
||||
: 'translate-x-0 absolute md:relative z-[40]'
|
||||
} transition w-[350px] min-w-[350px] md:max-w-[350px] bg-gray-100 dark:bg-black h-[calc(100vh-48px)] overflow-y-auto border border-l-0 border-t-0 border-b-0 border-r-1`}
|
||||
} transition w-[90vw] md:max-w-[350px] bg-gray-100 dark:bg-black h-[calc(100vh-48px)] overflow-y-auto border border-l-0 border-t-0 border-b-0 border-r-1`}
|
||||
>
|
||||
<div class="h-full flex flex-col">
|
||||
<ul class="my-5">
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<script>
|
||||
import { flip } from 'svelte/animate';
|
||||
import { settings } from './store';
|
||||
import { dndzone } from 'svelte-dnd-action';
|
||||
import Draggable from 'carbon-icons-svelte/lib/Draggable.svelte';
|
||||
|
||||
const flipDurationMs = 300;
|
||||
|
||||
function handleDndConsider(e) {
|
||||
$settings.tabs = e.detail.items;
|
||||
}
|
||||
|
||||
function handleDndFinalize(e) {
|
||||
$settings.tabs = e.detail.items;
|
||||
}
|
||||
</script>
|
||||
|
||||
<section
|
||||
use:dndzone={{
|
||||
items: $settings.tabs,
|
||||
flipDurationMs,
|
||||
dropTargetStyle: {
|
||||
border: '2px #1d4ed8 solid',
|
||||
'border-style': 'dashed'
|
||||
}
|
||||
}}
|
||||
on:consider={handleDndConsider}
|
||||
on:finalize={handleDndFinalize}
|
||||
class="w-fit flex gap-1 md:gap-3 p-1"
|
||||
>
|
||||
{#each $settings.tabs as item (item.id)}
|
||||
<div
|
||||
animate:flip={{ duration: flipDurationMs }}
|
||||
class="flex items-center justify-start text-center text-xs md:text-base gap-1 md:gap-2 dark:text-white bg-slate-100 dark:bg-slate-700 p-2 rounded-md"
|
||||
>
|
||||
<Draggable size={16} />
|
||||
{item.name}
|
||||
</div>
|
||||
{/each}
|
||||
</section>
|
||||
@@ -1,112 +1,342 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import Modal from '$lib/components/Modal/index.svelte';
|
||||
import { Grid, Row, Column, Toggle } from 'carbon-components-svelte';
|
||||
|
||||
import SectionTitle from '$lib/components/Org/SectionTitle.svelte';
|
||||
import TextField from '$lib/components/Form/TextField.svelte';
|
||||
import TextArea from '$lib/components/Form/TextArea.svelte';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import UploadImage from '$lib/components/UploadImage/index.svelte';
|
||||
import DragAndDrop from './DragAndDrop.svelte';
|
||||
|
||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||
import { settingsDialog } from './store';
|
||||
import { settings } from './store';
|
||||
import { course } from '$lib/components/Course/store';
|
||||
import { updateCourse, deleteCourse } from '$lib/utils/services/courses';
|
||||
import { currentOrgPath } from '$lib/utils/store/org';
|
||||
import { supabase } from '$lib/utils/functions/supabase';
|
||||
import { Loading } from 'carbon-components-svelte';
|
||||
import { isObject } from '$lib/utils/functions/isObject';
|
||||
import { lessons } from '../Lesson/store/lessons';
|
||||
import { currentOrg } from '$lib/utils/store/org';
|
||||
import { snackbarStore } from '$lib/components/Snackbar/store';
|
||||
import { SNACKBAR_SEVERITY } from '$lib/components/Snackbar/constants';
|
||||
|
||||
let shouldDelete = false;
|
||||
let avatar;
|
||||
let uploadingImage = false;
|
||||
let isSaving = false;
|
||||
let fileInput;
|
||||
let isLoading = false;
|
||||
let isDeleting = false;
|
||||
let errors = {};
|
||||
let avatar;
|
||||
let imagebuffer;
|
||||
|
||||
async function updateDescription() {
|
||||
if (!$course.title) {
|
||||
errors.title = 'Title cannot be empty';
|
||||
return;
|
||||
// FILE UPLOAD
|
||||
const uploadImage = async (image: File) => {
|
||||
if (image) {
|
||||
const filename = `bannerimage/${Date.now()}` + image.name;
|
||||
const { data } = await supabase.storage.from('avatars').upload(filename, image, {
|
||||
cacheControl: '3600',
|
||||
upsert: false
|
||||
});
|
||||
|
||||
if (data) {
|
||||
const { data: response } = await supabase.storage.from('avatars').getPublicUrl(filename);
|
||||
|
||||
$settings.image = response.publicUrl;
|
||||
|
||||
uploadingImage = false;
|
||||
}
|
||||
}
|
||||
if (!$course.description) {
|
||||
errors.description = 'Description cannot be empty';
|
||||
return;
|
||||
};
|
||||
|
||||
const onFileSelected = () => {
|
||||
uploadingImage = true;
|
||||
const image = fileInput.files[0];
|
||||
if (image) {
|
||||
let reader = new FileReader();
|
||||
reader.readAsDataURL(image);
|
||||
reader.onload = (e) => {
|
||||
imagebuffer = image;
|
||||
uploadImage(image);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
$settingsDialog.open = false;
|
||||
const logo = await updateCourse($course.id, avatar, {
|
||||
title: $course.title,
|
||||
description: $course.description
|
||||
});
|
||||
|
||||
if (logo) {
|
||||
$course.logo = logo;
|
||||
function getLessonOrder(id) {
|
||||
const index = $lessons.findIndex((lesson) => lesson.id === id);
|
||||
if (index < 9) {
|
||||
return '0' + (index + 1);
|
||||
} else {
|
||||
return index + 1;
|
||||
}
|
||||
|
||||
avatar = undefined;
|
||||
}
|
||||
|
||||
async function handleDelete() {
|
||||
$settingsDialog.open = false;
|
||||
await deleteCourse($course.id);
|
||||
goto($currentOrgPath + '/courses');
|
||||
const downloadCourse = async () => {
|
||||
isLoading = true;
|
||||
try {
|
||||
const lessonsList = $lessons.map((lesson) => ({
|
||||
lessonTitle: lesson.title,
|
||||
lessonNumber: getLessonOrder(lesson.id),
|
||||
lessonNote: lesson.note,
|
||||
slideUrl: lesson.slide_url || '',
|
||||
video: lesson.videos || ''
|
||||
}));
|
||||
const response = await fetch('https://classroomio-server.fly.dev/downloadCourse', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
courseTitle: $course.title,
|
||||
orgName: $currentOrg.name,
|
||||
orgTheme: $currentOrg.theme || '',
|
||||
lessons: lessonsList
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(await response.text());
|
||||
}
|
||||
const data = await response.blob();
|
||||
const file = new Blob([data], { type: 'application/pdf' });
|
||||
const fileURL = URL.createObjectURL(file);
|
||||
|
||||
let a = document.createElement('a');
|
||||
document.body.append(a);
|
||||
a.download = $course.title + ' - ' + 'Course ';
|
||||
a.href = fileURL;
|
||||
a.click();
|
||||
a.remove();
|
||||
|
||||
$snackbarStore.message = 'Download Complete';
|
||||
$snackbarStore.severity = SNACKBAR_SEVERITY.SUCCESS;
|
||||
$snackbarStore.open = true;
|
||||
} catch (error) {
|
||||
$snackbarStore.message = "Something's not right - Please try later";
|
||||
$snackbarStore.severity = SNACKBAR_SEVERITY.ERROR;
|
||||
$snackbarStore.open = true;
|
||||
}
|
||||
isLoading = false;
|
||||
};
|
||||
|
||||
const deleteBannerImage = () => {
|
||||
$course.logo = '';
|
||||
};
|
||||
|
||||
async function handleDeleteCourse() {
|
||||
isDeleting = true;
|
||||
try {
|
||||
await deleteCourse($course.id);
|
||||
isDeleting = false;
|
||||
goto($currentOrgPath + '/courses');
|
||||
} catch (error) {
|
||||
$snackbarStore.message = 'Something went wrong - Please try later';
|
||||
$snackbarStore.severity = SNACKBAR_SEVERITY.ERROR;
|
||||
$snackbarStore.open = true;
|
||||
isDeleting = false;
|
||||
}
|
||||
}
|
||||
|
||||
// function setAvatar(logo) {
|
||||
// avatar = logo;
|
||||
// }
|
||||
const handleSave = async () => {
|
||||
if (!$settings.course_title) {
|
||||
errors.title = 'title cannot be empty';
|
||||
return;
|
||||
}
|
||||
if (!$settings.course_description) {
|
||||
errors.description = 'description cannot be empty';
|
||||
return;
|
||||
}
|
||||
isSaving = true;
|
||||
try {
|
||||
const { course_title, course_description, image, tabs, grading, lesson_download } = $settings;
|
||||
await updateCourse($course.id, avatar, {
|
||||
title: course_title,
|
||||
description: course_description,
|
||||
logo: image,
|
||||
metadata: {
|
||||
...(isObject($course.metadata) ? $course.metadata : {}),
|
||||
lessonTabsOrder: tabs,
|
||||
grading: grading,
|
||||
lessonDownload: lesson_download
|
||||
}
|
||||
});
|
||||
|
||||
// $: setAvatar($course.logo);
|
||||
$course.title = course_title;
|
||||
$course.description = course_description;
|
||||
$course.logo = image;
|
||||
$course.metadata = {
|
||||
...(isObject($course.metadata) ? $course.metadata : {}),
|
||||
lessonTabsOrder: tabs,
|
||||
grading: grading,
|
||||
lessonDownload: lesson_download
|
||||
};
|
||||
$snackbarStore.message = 'saved successfully';
|
||||
$snackbarStore.severity = SNACKBAR_SEVERITY.SUCCESS;
|
||||
$snackbarStore.open = true;
|
||||
} catch (error) {
|
||||
$snackbarStore.message = 'Something went wrong - Please try later';
|
||||
$snackbarStore.severity = SNACKBAR_SEVERITY.ERROR;
|
||||
$snackbarStore.open = true;
|
||||
}
|
||||
|
||||
isSaving = false;
|
||||
};
|
||||
|
||||
function setDefault(course) {
|
||||
if (course && Object.keys(course).length) {
|
||||
$settings = {
|
||||
course_title: course.title,
|
||||
course_description: course.description,
|
||||
image: course.logo,
|
||||
tabs: course.metadata.lessonTabsOrder || $settings.tabs,
|
||||
grading: course.metadata.grading,
|
||||
lesson_download: course.metadata.lessonDownload
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
$: $settings.course_description = $course.description;
|
||||
$: $settings.course_title = $course.title;
|
||||
$: setDefault($course);
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
onClose={() => ($settingsDialog.open = false)}
|
||||
bind:open={$settingsDialog.open}
|
||||
width="w-96"
|
||||
modalHeading="Update Course"
|
||||
>
|
||||
{#if shouldDelete}
|
||||
<form on:submit|preventDefault={() => (shouldDelete = false)}>
|
||||
<h1 class="dark:text-white text-2xl">Are you sure?</h1>
|
||||
<Grid class="border-c rounded border-gray-200">
|
||||
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
|
||||
<Column sm={8} md={8} lg={8}>
|
||||
<SectionTitle>Banner Image</SectionTitle>
|
||||
<p>
|
||||
This optional image will show up on your welcome page. If you include one, it should be at
|
||||
least 300 x 200"
|
||||
</p>
|
||||
<span class="flex items-center justify-start">
|
||||
<PrimaryButton
|
||||
variant={VARIANTS.LINK}
|
||||
label="Replace Banner image"
|
||||
className="-ml-6"
|
||||
onClick={() => fileInput.click()}
|
||||
/>
|
||||
<PrimaryButton
|
||||
variant={VARIANTS.CONTAINED_DANGER}
|
||||
label="Delete"
|
||||
onClick={deleteBannerImage}
|
||||
/>
|
||||
</span>
|
||||
</Column>
|
||||
|
||||
<div class="mt-5 flex items-center justify-between">
|
||||
<PrimaryButton
|
||||
className="px-6 py-3"
|
||||
variant={VARIANTS.OUTLINED}
|
||||
label="No, cancel"
|
||||
type="submit"
|
||||
<Column sm={8} md={8} lg={6}>
|
||||
<div class="w-fit relative">
|
||||
<input
|
||||
type="file"
|
||||
accept=".jpg, .jpeg, .png"
|
||||
style="display: none;"
|
||||
bind:this={fileInput}
|
||||
on:change={onFileSelected}
|
||||
/>
|
||||
<PrimaryButton
|
||||
className="px-6 py-3"
|
||||
variant={VARIANTS.OUTLINED}
|
||||
label="Yes, delete"
|
||||
onClick={handleDelete}
|
||||
<img
|
||||
style="width:250px; height:150px"
|
||||
alt="About us"
|
||||
src={$settings.image ? $settings.image : '/images/classroomio-course-img-template.jpg'}
|
||||
class="mt-2 md:mt-0 rounded-md w-full relative"
|
||||
/>
|
||||
{#if uploadingImage}
|
||||
<Loading withOverlay={true} small class="absolute top-0 w-[250px]" />
|
||||
{/if}
|
||||
</div>
|
||||
</form>
|
||||
{:else}
|
||||
<form on:submit|preventDefault={updateDescription}>
|
||||
<UploadImage bind:avatar src={$course.logo} />
|
||||
</Column>
|
||||
</Row>
|
||||
|
||||
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
|
||||
<Column sm={8} md={8} lg={8}>
|
||||
<SectionTitle>Update course details</SectionTitle>
|
||||
</Column>
|
||||
|
||||
<Column sm={8} md={8} lg={8}>
|
||||
<TextField
|
||||
bind:value={$course.title}
|
||||
placeholder="Course title"
|
||||
className="mb-4"
|
||||
isRequired={true}
|
||||
label="Course title"
|
||||
placeholder="Write the course title here"
|
||||
className="w-full mb-5"
|
||||
bind:value={$settings.course_title}
|
||||
errorMessage={errors.title}
|
||||
/>
|
||||
<TextArea
|
||||
label="Short description"
|
||||
bind:value={$course.description}
|
||||
rows="4"
|
||||
maxRows="4"
|
||||
placeholder="A little description about this course"
|
||||
className="mb-4"
|
||||
isRequired={true}
|
||||
label="Course description"
|
||||
placeholder="Write your course description here"
|
||||
className="w-full mb-5"
|
||||
bind:value={$settings.course_description}
|
||||
errorMessage={errors.description}
|
||||
/>
|
||||
|
||||
<div class="mt-5 flex items-center justify-between">
|
||||
<PrimaryButton
|
||||
className="px-6 py-3"
|
||||
variant={VARIANTS.CONTAINED}
|
||||
label="Delete"
|
||||
onClick={() => (shouldDelete = true)}
|
||||
/>
|
||||
<PrimaryButton className="px-6 py-3" label="Update" type="submit" />
|
||||
</div>
|
||||
</form>
|
||||
{/if}
|
||||
</Modal>
|
||||
</Column>
|
||||
</Row>
|
||||
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
|
||||
<Column sm={8} md={8} lg={8}>
|
||||
<SectionTitle>Grading</SectionTitle>
|
||||
<p>Make grading reports available for lessons</p>
|
||||
</Column>
|
||||
<Column sm={8} md={8} lg={8}>
|
||||
<Toggle size="sm" bind:toggled={$settings.grading}>
|
||||
<span slot="labelA" style="color: gray">Disable</span>
|
||||
<span slot="labelB" style="color: gray">Enable</span>
|
||||
</Toggle>
|
||||
</Column>
|
||||
</Row>
|
||||
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
|
||||
<Column sm={8} md={8} lg={8}>
|
||||
<SectionTitle>Order Lessons Tab</SectionTitle>
|
||||
<p>Drag and drop the labels to re-order your material tabs</p>
|
||||
</Column>
|
||||
<Column sm={8} md={8} lg={8}>
|
||||
<DragAndDrop />
|
||||
</Column>
|
||||
</Row>
|
||||
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
|
||||
<Column sm={8} md={8} lg={8}>
|
||||
<SectionTitle>Lesson Download</SectionTitle>
|
||||
<p>Make the Lesson available for Download in pdf for registered students</p>
|
||||
</Column>
|
||||
<Column sm={8} md={8} lg={8}>
|
||||
<Toggle size="sm" bind:toggled={$settings.lesson_download}>
|
||||
<span slot="labelA" style="color: gray">Disable</span>
|
||||
<span slot="labelB" style="color: gray">Enable</span>
|
||||
</Toggle>
|
||||
</Column>
|
||||
</Row>
|
||||
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
|
||||
<Column sm={8} md={8} lg={8}>
|
||||
<SectionTitle>Course Download</SectionTitle>
|
||||
<p>Make the Course available for Download for registered students</p>
|
||||
</Column>
|
||||
<Column sm={8} md={8} lg={8}>
|
||||
<PrimaryButton
|
||||
variant={VARIANTS.LINK}
|
||||
label="Download Course"
|
||||
onClick={downloadCourse}
|
||||
isDisabled={isLoading}
|
||||
{isLoading}
|
||||
/>
|
||||
</Column>
|
||||
</Row>
|
||||
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
|
||||
<Column sm={8} md={8} lg={8}>
|
||||
<SectionTitle>Delete course</SectionTitle>
|
||||
<p>Delete this course,this action cannot be undone</p>
|
||||
</Column>
|
||||
<Column sm={8} md={8} lg={8}>
|
||||
<PrimaryButton
|
||||
variant={VARIANTS.CONTAINED_DANGER}
|
||||
label="Delete Course"
|
||||
onClick={handleDeleteCourse}
|
||||
isLoading={isDeleting}
|
||||
isDisabled={isDeleting}
|
||||
/>
|
||||
</Column>
|
||||
</Row>
|
||||
<Row class="p-5 w-full flex items-center justify-end">
|
||||
<PrimaryButton
|
||||
label="Save Changes"
|
||||
isLoading={isSaving}
|
||||
isDisabled={isSaving}
|
||||
onClick={handleSave}
|
||||
/>
|
||||
</Row>
|
||||
</Grid>
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const settingsDialog = writable({
|
||||
open: false,
|
||||
export const settings = writable({
|
||||
image: '',
|
||||
course_title: '',
|
||||
course_description: '',
|
||||
grading: false,
|
||||
tabs: [
|
||||
{ id: 1, name: 'Note' },
|
||||
{ id: 2, name: 'Slide' },
|
||||
{ id: 3, name: 'Video' }
|
||||
],
|
||||
lesson_download: false
|
||||
});
|
||||
|
||||
@@ -32,7 +32,14 @@ export const course: Writable<Course> = writable({
|
||||
coursesNo: 0,
|
||||
description: '',
|
||||
imgUrl: ''
|
||||
}
|
||||
},
|
||||
lessonTabsOrder: [
|
||||
{ id: 1, name: 'Note' },
|
||||
{ id: 2, name: 'Slide' },
|
||||
{ id: 3, name: 'Video' }
|
||||
],
|
||||
grading: false,
|
||||
lessonDownload: true
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script>
|
||||
// @ts-nocheck
|
||||
|
||||
import { Moon } from 'svelte-loading-spinners';
|
||||
import Navigation from '../Course/components/Navigation/index.svelte';
|
||||
import Backdrop from '$lib/components/Backdrop/index.svelte';
|
||||
@@ -54,8 +56,4 @@
|
||||
flex-grow: 1;
|
||||
width: calc(100% - 360px);
|
||||
}
|
||||
|
||||
.rightBar.isMobile {
|
||||
/* margin-left: 10px; */
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -146,8 +146,8 @@
|
||||
|
||||
<aside
|
||||
class={`${
|
||||
show ? '-translate-x-[100%] fixed z-[50]' : 'translate-x-0 fixed z-[50]'
|
||||
} left-0 transition w-[350px] min-w-[300px] bg-gray-100 dark:bg-black h-full border border-l-0 border-t-0 border-b-0 border-r-1`}
|
||||
show ? '-translate-x-[100%] fixed md:absolute z-[50]' : 'translate-x-0 fixed md:relative z-[50]'
|
||||
}left-0 transition w-[350px] min-w-[300px] bg-gray-100 dark:bg-gray-800 h-full border border-l-0 border-t-0 border-b-0 border-r-1`}
|
||||
>
|
||||
<div class="toggler rounded-full shadow-lg absolute">
|
||||
<IconButton
|
||||
|
||||
@@ -146,10 +146,10 @@
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) and (max-width: 1024px) {
|
||||
@media screen and (max-width: 1024px) {
|
||||
.price-container {
|
||||
width: 405px;
|
||||
min-width: unset;
|
||||
width: 350px;
|
||||
min-width: 250px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -159,17 +159,17 @@
|
||||
{/if}
|
||||
</div>
|
||||
</header>
|
||||
<PricingSection className="md:hidden" {courseData} {editMode} bind:startCoursePayment />
|
||||
<!-- Body -->
|
||||
<div class="bg-white dark:bg-black w-full">
|
||||
<div
|
||||
class="py-8 lg:w-11/12 w-full m-2 lg:m-auto flex flex-col-reverse lg:flex-row items-center lg:items-start justify-between max-w-[1200px]"
|
||||
>
|
||||
<!-- Course Details -->
|
||||
<div class="course-content w-full p-3 lg:w-10/12 lg:mr-14">
|
||||
<div class="course-content w-full p-3 lg:w-10/12 lg:mr-10">
|
||||
<!-- Navigation -->
|
||||
<nav
|
||||
class="flex items-center border-b border-gray-300 py-3 sticky top-11 bg-white dark:bg-neutral-800"
|
||||
class="flex items-center border-b border-gray-300 py-3 sticky top-0 {!editMode &&
|
||||
'lg:top-11'} bg-white dark:bg-neutral-800"
|
||||
>
|
||||
{#each NAV_ITEMS as navItem}
|
||||
<a
|
||||
@@ -416,7 +416,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Pricing Details -->
|
||||
<PricingSection {courseData} />
|
||||
<PricingSection {courseData} {editMode} bind:startCoursePayment />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -464,7 +464,8 @@
|
||||
}
|
||||
|
||||
.course-content {
|
||||
max-width: 800px;
|
||||
max-width: 608px;
|
||||
min-width: 60%;
|
||||
}
|
||||
|
||||
nav {
|
||||
@@ -495,4 +496,9 @@
|
||||
color: blue;
|
||||
text-decoration: underline;
|
||||
}
|
||||
@media screen and (max-width: 1023px) {
|
||||
.course-content {
|
||||
min-width: 80%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
// export let onDelete = (id: string) => {};
|
||||
|
||||
let formatter = getCurrencyFormatter(currency);
|
||||
|
||||
// function getOptions() {
|
||||
// return [
|
||||
// {
|
||||
@@ -51,7 +50,7 @@
|
||||
<a
|
||||
rel="prefetch"
|
||||
href={isOnLandingPage ? `/course/${slug}` : `/courses/${id}`}
|
||||
class=" text-black w-fit"
|
||||
class=" text-black w-full"
|
||||
>
|
||||
<div
|
||||
class="border border-gray rounded w-full max-w-[350px] relative hover:shadow-lg transition-all ease-in-out"
|
||||
@@ -59,12 +58,12 @@
|
||||
<div class="p-4">
|
||||
<div class=" mb-5">
|
||||
<ImageLoader
|
||||
src={bannerImage ?? '/images/classroomio-course-img-template.jpg'}
|
||||
src={bannerImage ? bannerImage : '/images/classroomio-course-img-template.jpg'}
|
||||
alt="Course Logo"
|
||||
class="h-2/5 w-full rounded dark:border dark:border-white"
|
||||
class="h-[200px] w-full rounded dark:border dark:border-white"
|
||||
>
|
||||
<svelte:fragment slot="loading">
|
||||
<SkeletonPlaceholder style="width: 100%; height: 10rem;" />
|
||||
<SkeletonPlaceholder style="width: 100%; height: 200px;" />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="error">An error occurred.</svelte:fragment>
|
||||
</ImageLoader>
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
import CoursesEmptyIcon from '../Icons/CoursesEmptyIcon.svelte';
|
||||
import CopyCourseModal from './components/CopyCourseModal/index.svelte';
|
||||
import { courses, courseMetaDeta } from './store';
|
||||
import { isMobile } from '$lib/utils/store/useMobile';
|
||||
</script>
|
||||
|
||||
<CopyCourseModal />
|
||||
@@ -21,7 +20,7 @@
|
||||
{#each $courses as courseData}
|
||||
<Card
|
||||
id={courseData.id}
|
||||
bannerImage={courseData.banner_image}
|
||||
bannerImage={courseData.logo}
|
||||
title={courseData.title}
|
||||
description={courseData.description}
|
||||
role_id={courseData.role_id}
|
||||
@@ -30,14 +29,6 @@
|
||||
currency={courseData.currency}
|
||||
totalLessons={courseData.total_lessons}
|
||||
/>
|
||||
<!-- {:else}
|
||||
<Box className="w-full">
|
||||
<CoursesEmptyIcon />
|
||||
<h3 class="dark:text-white text-2xl my-5">No Courses Created</h3>
|
||||
<p class="dark:text-white w-1/3 text-center">
|
||||
Share your knowledge with the world by creating engaging courses for your students.
|
||||
</p>
|
||||
</Box> -->
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script>
|
||||
import { page } from '$app/stores';
|
||||
import { browser } from '$app/environment';
|
||||
import NotificationIcon from 'carbon-icons-svelte/lib/Notification.svelte';
|
||||
import Moon from 'carbon-icons-svelte/lib/Moon.svelte';
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
export let padding = 'pb-5 px-4';
|
||||
export let className = '';
|
||||
export let headerClassName = '';
|
||||
export let onClick = () => {};
|
||||
</script>
|
||||
|
||||
<div class="root mt-4 mx-auto {width} {className} relative {padding}">
|
||||
<div class="root mt-4 mx-auto {width} {className} relative {padding}" on:click={onClick}>
|
||||
{#if $$slots.header}
|
||||
<div
|
||||
class="head dark:bg-black flex items-center justify-between sticky right-0 w-full px-3 {headerClassName}"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script>
|
||||
import TextChip from '$lib/components/Chip/Text.svelte';
|
||||
|
||||
export let tabs = [];
|
||||
export let currentTab;
|
||||
export let onChange;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export function isObject(obj) {
|
||||
return obj === Object(obj);
|
||||
}
|
||||
@@ -21,6 +21,9 @@ interface CourseMetadata {
|
||||
imgUrl: string;
|
||||
};
|
||||
reviews?: Array<Review>;
|
||||
lessonTabsOrder: Array<Tabs>;
|
||||
grading: boolean;
|
||||
lessonDownload: boolean;
|
||||
}
|
||||
|
||||
// Generated from https://supabase-schema.vercel.app/
|
||||
@@ -255,3 +258,8 @@ export interface Review {
|
||||
created_at: number;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface Tabs {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
interface Data {
|
||||
course: Course;
|
||||
}
|
||||
|
||||
export let data: Data;
|
||||
|
||||
let courseData: Course;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { fly } from 'svelte/transition';
|
||||
import CourseLandingPage from '$lib/components/CourseLandingPage/index.svelte';
|
||||
import Editor from '$lib/components/CourseLandingPage/components/Editor/index.svelte';
|
||||
import { isMobile } from '$lib/utils/store/useMobile';
|
||||
import { profile } from '$lib/utils/store/user';
|
||||
import { setCourse, course } from '$lib/components/Course/store';
|
||||
import { lessons } from '$lib/components/Course/components/Lesson/store/lessons';
|
||||
@@ -30,7 +29,7 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="absolute inset-0 z-50 bg-white"
|
||||
class="absolute flex inset-0 z-50 bg-white"
|
||||
in:fly={{ y: 500, duration: 500 }}
|
||||
out:fly={{ y: 500, duration: 500 }}
|
||||
>
|
||||
@@ -45,6 +44,7 @@
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
import { currentOrg } from '$lib/utils/store/org';
|
||||
import { snackbarStore } from '$lib/components/Snackbar/store';
|
||||
import { SNACKBAR_SEVERITY } from '$lib/components/Snackbar/constants';
|
||||
import { FaceSatisfied } from 'carbon-icons-svelte';
|
||||
|
||||
export let data;
|
||||
|
||||
@@ -55,7 +56,7 @@
|
||||
if (!$course.id) {
|
||||
const { data: _data } = await fetchCourse(data.courseId);
|
||||
|
||||
lessonData = _data.lessons.find((lesson = { id: '' }) => lesson.id === lessonId);
|
||||
lessonData = _data?.lessons.find((lesson = { id: '' }) => lesson.id === lessonId);
|
||||
setCourse(_data);
|
||||
} else if (prevLessonId !== lessonId) {
|
||||
const lesson = await fetchLesson(lessonId);
|
||||
@@ -163,7 +164,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<CourseContainer {isFetching} {path} isExercisePage={!data.isMaterialsTabActive && data.exerciseId}>
|
||||
<CourseContainer {path} isExercisePage={!data.isMaterialsTabActive && data.exerciseId}>
|
||||
<PageNav
|
||||
navItems={[
|
||||
{
|
||||
@@ -182,7 +183,7 @@
|
||||
<svelte:fragment slot="widget">
|
||||
<RoleBasedSecurity allowedRoles={[1, 2]}>
|
||||
{#if data.isMaterialsTabActive}
|
||||
<div class="lg:hidden">
|
||||
<div class="tab">
|
||||
<IconButton onClick={toggleApps} buttonClassName="">
|
||||
<OverflowMenuVertical size={24} />
|
||||
</IconButton>
|
||||
@@ -190,7 +191,7 @@
|
||||
<div
|
||||
class={`flex-row ${
|
||||
$apps.dropdown && $apps.open
|
||||
? 'absolute lg:relative top-[85px] lg:top-0 right-14 lg:right-0 z-40 dark:bg-black p-3 lg:p-0'
|
||||
? 'absolute lg:relative top-[85px] lg:top-0 right-14 lg:right-0 z-40 dark:bg-slate-800 p-3 lg:p-0'
|
||||
: 'hidden'
|
||||
} lg:flex items-center`}
|
||||
>
|
||||
@@ -221,7 +222,13 @@
|
||||
{#if !data.isMaterialsTabActive}
|
||||
<Exercises lessonId={data.lessonId} exerciseId={data.exerciseId} path={`${path}/exercises`} />
|
||||
{:else if !!data.lessonId}
|
||||
<PageBody>
|
||||
<PageBody
|
||||
onClick={() => {
|
||||
$apps.open = false;
|
||||
$apps.dropdown = false;
|
||||
}}
|
||||
width="lg:w-full xl:w-11/12"
|
||||
>
|
||||
<Materials lessonId={data.lessonId} {mode} {prevMode} {toggleMode} bind:isSaving />
|
||||
|
||||
<div class="w-full flex flex-row-reverse mt-10">
|
||||
@@ -239,3 +246,11 @@
|
||||
</PageBody>
|
||||
{/if}
|
||||
</CourseContainer>
|
||||
|
||||
<style>
|
||||
@media screen and (min-width: 1023px) {
|
||||
.tab {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export const load = ({ params = { id: '' } }) => {
|
||||
return {
|
||||
courseId: params.id
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
<script>
|
||||
import { course, setCourse } from '$lib/components/Course/store';
|
||||
import { fetchCourse } from '$lib/utils/services/courses';
|
||||
import { onMount } from 'svelte';
|
||||
import CourseContainer from '$lib/components/CourseContainer/index.svelte';
|
||||
import PageNav from '$lib/components/PageNav/index.svelte';
|
||||
import PageBody from '$lib/components/PageBody/index.svelte';
|
||||
import Settings from '$lib/components/Course/components/Settings/index.svelte';
|
||||
|
||||
export let data;
|
||||
|
||||
const { courseId } = data;
|
||||
let isStudent = false;
|
||||
|
||||
onMount(async () => {
|
||||
if ($course.id) return;
|
||||
const { data } = await fetchCourse(courseId);
|
||||
setCourse(data);
|
||||
});
|
||||
</script>
|
||||
|
||||
<CourseContainer bind:isStudent>
|
||||
<PageNav title="Settings" />
|
||||
<PageBody>
|
||||
<Settings />
|
||||
</PageBody>
|
||||
</CourseContainer>
|
||||
@@ -20,11 +20,8 @@
|
||||
import VisitOrgSiteButton from '$lib/components/Buttons/VisitOrgSite.svelte';
|
||||
|
||||
export let data;
|
||||
const { orgName } = data;
|
||||
|
||||
function createCourse() {
|
||||
goto(`/org/${orgName}/courses?create=true`);
|
||||
}
|
||||
const { orgName } = data;
|
||||
|
||||
const theme = {
|
||||
calendar: {
|
||||
@@ -93,6 +90,10 @@
|
||||
}
|
||||
];
|
||||
|
||||
function createCourse() {
|
||||
goto(`/org/${orgName}/courses?create=true`);
|
||||
}
|
||||
|
||||
const addDotsToCalendar = (currentMonthIndexInRenderedMonth: number) =>
|
||||
setTimeout(() => {
|
||||
if (!calendar) calendar = document.getElementById('calendar');
|
||||
|
||||
Reference in New Issue
Block a user