fix: load documents on client
This commit is contained in:
@@ -61,7 +61,7 @@
|
|||||||
});
|
});
|
||||||
snackbar.success('snackbar.course_settings.success.saved');
|
snackbar.success('snackbar.course_settings.success.saved');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.message) {
|
if (error instanceof Error) {
|
||||||
errors.description = error.message;
|
errors.description = error.message;
|
||||||
} else {
|
} else {
|
||||||
errors.description = $t('course.navItem.certificates.unexpected_error');
|
errors.description = $t('course.navItem.certificates.unexpected_error');
|
||||||
@@ -87,13 +87,13 @@
|
|||||||
|
|
||||||
<UpgradeBanner>{$t('upgrade.certificate')}</UpgradeBanner>
|
<UpgradeBanner>{$t('upgrade.certificate')}</UpgradeBanner>
|
||||||
|
|
||||||
<main class="md:-ml-3 md:-mr-3 px-2 mt-2">
|
<main class="mt-2 px-2 md:-ml-3 md:-mr-3">
|
||||||
<div class="flex-1 flex flex-col lg:flex-row justify-between gap-3 w-full mb-3 h-4/5">
|
<div class="mb-3 flex h-4/5 w-full flex-1 flex-col justify-between gap-3 lg:flex-row">
|
||||||
<section class="w-full lg:w-2/5 h-full">
|
<section class="h-full w-full lg:w-2/5">
|
||||||
<strong class="my-2 text-base font-semibold text-black dark:text-gray-100"
|
<strong class="my-2 text-base font-semibold text-black dark:text-gray-100"
|
||||||
>{$t('course.navItem.certificates.certificate_settings')}</strong
|
>{$t('course.navItem.certificates.certificate_settings')}</strong
|
||||||
>
|
>
|
||||||
<p class="text-xs font-normal my-4 dark:text-gray-100">
|
<p class="my-4 text-xs font-normal dark:text-gray-100">
|
||||||
{$t('course.navItem.certificates.theme')}
|
{$t('course.navItem.certificates.theme')}
|
||||||
</p>
|
</p>
|
||||||
<RadioButtonGroup
|
<RadioButtonGroup
|
||||||
@@ -103,23 +103,23 @@
|
|||||||
>
|
>
|
||||||
<div class="flex flex-wrap justify-between gap-y-5">
|
<div class="flex flex-wrap justify-between gap-y-5">
|
||||||
{#each themes as theme}
|
{#each themes as theme}
|
||||||
<div class="flex mr-3">
|
<div class="mr-3 flex">
|
||||||
<RadioButton value={theme} />
|
<RadioButton value={theme} />
|
||||||
<img
|
<img
|
||||||
src={`/images/certificate_theme_${theme}.png`}
|
src={`/images/certificate_theme_${theme}.png`}
|
||||||
alt="themes"
|
alt="themes"
|
||||||
class="w-[110px] h-[82px]"
|
class="h-[82px] w-[110px]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</RadioButtonGroup>
|
</RadioButtonGroup>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-xs font-normal text-black my-2 dark:text-gray-100">
|
<p class="my-2 text-xs font-normal text-black dark:text-gray-100">
|
||||||
{$t('course.navItem.certificates.logo')}
|
{$t('course.navItem.certificates.logo')}
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-base mt-1 dark:text-gray-100">
|
<p class="mt-1 text-base dark:text-gray-100">
|
||||||
{$t('course.navItem.certificates.to_update')}
|
{$t('course.navItem.certificates.to_update')}
|
||||||
<strong class="font-semibold">{$t('course.navItem.certificates.settings')}</strong>
|
<strong class="font-semibold">{$t('course.navItem.certificates.settings')}</strong>
|
||||||
{$t('course.navItem.certificates.and_upload')}
|
{$t('course.navItem.certificates.and_upload')}
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span class="my-4">
|
<span class="my-4">
|
||||||
<p class="dark:text-gray-100 text-xs font-normal mt-4 mb-2">
|
<p class="mb-2 mt-4 text-xs font-normal dark:text-gray-100">
|
||||||
{$t('course.navItem.certificates.description')}
|
{$t('course.navItem.certificates.description')}
|
||||||
</p>
|
</p>
|
||||||
<TextArea
|
<TextArea
|
||||||
@@ -160,9 +160,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section
|
<section
|
||||||
class="bg-gray-100 dark:bg-neutral-800 flex justify-center items-center rounded-md w-full lg:w-3/5"
|
class="flex w-full items-center justify-center rounded-md bg-gray-100 lg:w-3/5 dark:bg-neutral-800"
|
||||||
>
|
>
|
||||||
<div class="certificate-container flex justify-center items-center">
|
<div class="certificate-container flex items-center justify-center">
|
||||||
{#if $course.certificate_theme === 'professional'}
|
{#if $course.certificate_theme === 'professional'}
|
||||||
<Professional studentName={studentNamePlaceholder} />
|
<Professional studentName={studentNamePlaceholder} />
|
||||||
{:else if $course.certificate_theme === 'plain'}
|
{:else if $course.certificate_theme === 'plain'}
|
||||||
|
|||||||
+10
-6
@@ -8,16 +8,18 @@
|
|||||||
import EyeIcon from 'carbon-icons-svelte/lib/View.svelte';
|
import EyeIcon from 'carbon-icons-svelte/lib/View.svelte';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import type { LessonDocument } from '$lib/utils/types';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
export let mode = MODES.view;
|
export let mode = MODES.view;
|
||||||
export let displayDocuments: any[] = [];
|
export let displayDocuments: LessonDocument[] = [];
|
||||||
export let downloadingDocuments: Set<string>;
|
export let downloadingDocuments: Set<string>;
|
||||||
export let formatFileSize: (bytes: number) => string;
|
export let formatFileSize: (bytes: number) => string;
|
||||||
export let openDocumentUploadModal: () => void;
|
export let openDocumentUploadModal: () => void;
|
||||||
export let deleteDocument: (index: number) => void;
|
export let deleteDocument: (index: number) => void;
|
||||||
export let downloadDocument: (doc: any) => Promise<void>;
|
export let downloadDocument: (doc: LessonDocument) => Promise<void>;
|
||||||
|
export let isLoading = false;
|
||||||
|
|
||||||
function getFileIcon(type: string) {
|
function getFileIcon(type: string) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@@ -28,7 +30,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleViewPDF(document: any) {
|
function handleViewPDF(document: LessonDocument) {
|
||||||
dispatch('viewPDF', document);
|
dispatch('viewPDF', document);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -137,15 +139,17 @@
|
|||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
variant={VARIANTS.CONTAINED_DARK}
|
variant={VARIANTS.CONTAINED_DARK}
|
||||||
onClick={() => handleViewPDF(document)}
|
onClick={() => handleViewPDF(document)}
|
||||||
|
{isLoading}
|
||||||
>
|
>
|
||||||
<EyeIcon size={16} class="mr-1 inline" />
|
<EyeIcon size={16} class="mr-1 inline" />
|
||||||
{$t('course.navItem.lessons.materials.tabs.document.view_pdf')}
|
{$t('course.navItem.lessons.materials.tabs.document.view_pdf')}
|
||||||
</PrimaryButton>
|
</PrimaryButton>
|
||||||
{:else}
|
{:else}
|
||||||
<a
|
<a
|
||||||
href={document.link}
|
href={isLoading ? '#' : document.link}
|
||||||
target="_blank"
|
target={isLoading ? null : '_blank'}
|
||||||
class="text-sm text-blue-600 underline hover:text-blue-800"
|
aria-disabled={isLoading}
|
||||||
|
class="text-sm text-blue-600 underline hover:text-blue-800 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{$t('course.navItem.lessons.materials.tabs.document.view_document')}
|
{$t('course.navItem.lessons.materials.tabs.document.view_document')}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
+57
-8
@@ -3,7 +3,10 @@
|
|||||||
lesson,
|
lesson,
|
||||||
deleteLessonDocument
|
deleteLessonDocument
|
||||||
} from '$lib/components/Course/components/Lesson/store/lessons';
|
} from '$lib/components/Course/components/Lesson/store/lessons';
|
||||||
import { uploadCourseDocumentStore } from '$lib/components/Course/components/Lesson/store/lessons';
|
import {
|
||||||
|
uploadCourseDocumentStore,
|
||||||
|
presignedDocUrls
|
||||||
|
} from '$lib/components/Course/components/Lesson/store/lessons';
|
||||||
import MODES from '$lib/utils/constants/mode';
|
import MODES from '$lib/utils/constants/mode';
|
||||||
import IconButton from '$lib/components/IconButton/index.svelte';
|
import IconButton from '$lib/components/IconButton/index.svelte';
|
||||||
import CloseIcon from 'carbon-icons-svelte/lib/Close.svelte';
|
import CloseIcon from 'carbon-icons-svelte/lib/Close.svelte';
|
||||||
@@ -14,11 +17,12 @@
|
|||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import DocumentList from '../Document/DocumentList.svelte';
|
import DocumentList from '../Document/DocumentList.svelte';
|
||||||
import { t } from '$lib/utils/functions/translations';
|
import { t } from '$lib/utils/functions/translations';
|
||||||
|
import { apiClient } from '$lib/utils/services/api';
|
||||||
|
import type { LessonDocument } from '$lib/utils/types';
|
||||||
|
import { snackbar } from '$lib/components/Snackbar/store';
|
||||||
|
|
||||||
export let mode = MODES.view;
|
export let mode = MODES.view;
|
||||||
|
|
||||||
// Use real documents from store
|
|
||||||
$: displayDocuments = $lesson?.materials?.documents || [];
|
|
||||||
let downloadingDocuments = new Set<string>();
|
let downloadingDocuments = new Set<string>();
|
||||||
let viewingPDF: any = null;
|
let viewingPDF: any = null;
|
||||||
let pdfViewerOpen = false;
|
let pdfViewerOpen = false;
|
||||||
@@ -67,12 +71,47 @@
|
|||||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadDocument(doc: any) {
|
async function getPresignedDocs(docs: LessonDocument[]) {
|
||||||
|
if (!docs.length) return {};
|
||||||
|
if (docs.every((d) => $presignedDocUrls[d.key || ''])) return;
|
||||||
|
|
||||||
|
isLoading = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await apiClient.post('/course/presign/download/document', {
|
||||||
|
keys: docs.map((d) => d.key)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!data.success) {
|
||||||
|
throw new Error(data.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key in data.urls) {
|
||||||
|
presignedDocUrls.update((urls) => ({
|
||||||
|
...urls,
|
||||||
|
[key]: data.urls[key]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
isLoading = false;
|
||||||
|
console.error('Error getting presigned docs:', error);
|
||||||
|
snackbar.error('Error getting presigned docs');
|
||||||
|
} finally {
|
||||||
|
isLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadDocument(doc: LessonDocument) {
|
||||||
downloadingDocuments.add(doc.name);
|
downloadingDocuments.add(doc.name);
|
||||||
downloadingDocuments = downloadingDocuments;
|
downloadingDocuments = downloadingDocuments;
|
||||||
|
|
||||||
|
if (!doc.key) {
|
||||||
|
snackbar.error('Document not loaded correctly');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(doc.link);
|
const response = await fetch($presignedDocUrls[doc.key]);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Failed to fetch document');
|
throw new Error('Failed to fetch document');
|
||||||
}
|
}
|
||||||
@@ -104,7 +143,7 @@
|
|||||||
}, 150); // 150ms debounce
|
}, 150); // 150ms debounce
|
||||||
}
|
}
|
||||||
|
|
||||||
async function viewPDF(document: any) {
|
async function viewPDF(document: LessonDocument) {
|
||||||
// Wait for PDF.js to be fully loaded
|
// Wait for PDF.js to be fully loaded
|
||||||
if (!pdfjsLib) {
|
if (!pdfjsLib) {
|
||||||
// Show loading state while waiting for PDF.js
|
// Show loading state while waiting for PDF.js
|
||||||
@@ -136,7 +175,12 @@
|
|||||||
error = null;
|
error = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(document.link);
|
if (!document.key) {
|
||||||
|
snackbar.error('Document not loaded correctly');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch($presignedDocUrls[document.key]);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Failed to fetch PDF');
|
throw new Error('Failed to fetch PDF');
|
||||||
@@ -227,7 +271,7 @@
|
|||||||
debouncedRender();
|
debouncedRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleViewPDF(event: CustomEvent) {
|
function handleViewPDF(event: CustomEvent<LessonDocument>) {
|
||||||
viewPDF(event.detail);
|
viewPDF(event.detail);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,10 +333,15 @@
|
|||||||
function handleDragStart(event: DragEvent) {
|
function handleDragStart(event: DragEvent) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: displayDocuments = $lesson?.materials?.documents || [];
|
||||||
|
|
||||||
|
$: getPresignedDocs(displayDocuments);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="mx-auto w-full max-w-lg">
|
<div class="mx-auto w-full max-w-lg">
|
||||||
<DocumentList
|
<DocumentList
|
||||||
|
{isLoading}
|
||||||
{mode}
|
{mode}
|
||||||
{displayDocuments}
|
{displayDocuments}
|
||||||
{downloadingDocuments}
|
{downloadingDocuments}
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ export const uploadCourseDocumentStore = writable({
|
|||||||
isCancelled: false
|
isCancelled: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const presignedDocUrls = writable<Record<string, string>>({});
|
||||||
|
|
||||||
export const lessons: Writable<Lesson[]> = writable([]);
|
export const lessons: Writable<Lesson[]> = writable([]);
|
||||||
|
|
||||||
export const lessonSections: Writable<LessonSection[]> = writable([]);
|
export const lessonSections: Writable<LessonSection[]> = writable([]);
|
||||||
|
|||||||
Reference in New Issue
Block a user