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