feat: add download functionality to Image, PDF, and Video block components

This commit is contained in:
swve 2025-04-13 13:06:58 +02:00
parent 3f2a008bef
commit b25505465b
3 changed files with 133 additions and 33 deletions

View file

@ -1,7 +1,7 @@
import { NodeViewWrapper } from '@tiptap/react' import { NodeViewWrapper } from '@tiptap/react'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import { Resizable } from 're-resizable' import { Resizable } from 're-resizable'
import { AlertTriangle, Image } from 'lucide-react' import { AlertTriangle, Image, Download } from 'lucide-react'
import { uploadNewImageFile } from '../../../../../services/blocks/Image/images' import { uploadNewImageFile } from '../../../../../services/blocks/Image/images'
import { getActivityBlockMediaDirectory } from '@services/media/media' import { getActivityBlockMediaDirectory } from '@services/media/media'
import { useOrg } from '@components/Contexts/OrgContext' import { useOrg } from '@components/Contexts/OrgContext'
@ -52,6 +52,29 @@ function ImageBlockComponent(props: any) {
}) })
} }
const handleDownload = () => {
if (!fileId) return;
const imageUrl = getActivityBlockMediaDirectory(
org?.org_uuid,
course?.courseStructure.course_uuid,
props.extension.options.activity.activity_uuid,
blockObject.block_uuid,
fileId,
'imageBlock'
);
const link = document.createElement('a');
link.href = imageUrl || '';
link.download = `image-${blockObject?.block_uuid || 'download'}.${blockObject?.content.file_format || 'jpg'}`;
link.setAttribute('download', '');
link.setAttribute('target', '_blank');
link.setAttribute('rel', 'noopener noreferrer');
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
useEffect(() => {}, [course, org]) useEffect(() => {}, [course, org])
return ( return (
@ -119,19 +142,28 @@ function ImageBlockComponent(props: any) {
{blockObject && !isEditable && ( {blockObject && !isEditable && (
<div className="w-full flex justify-center"> <div className="w-full flex justify-center">
<img <div className="relative">
src={`${getActivityBlockMediaDirectory( <img
org?.org_uuid, src={`${getActivityBlockMediaDirectory(
course?.courseStructure.course_uuid, org?.org_uuid,
props.extension.options.activity.activity_uuid, course?.courseStructure.course_uuid,
blockObject.block_uuid, props.extension.options.activity.activity_uuid,
blockObject ? fileId : ' ', blockObject.block_uuid,
'imageBlock' blockObject ? fileId : ' ',
)}`} 'imageBlock'
alt="" )}`}
className="rounded-lg shadow-sm max-w-full h-auto" alt=""
style={{ width: imageSize.width, maxWidth: '100%' }} className="rounded-lg shadow-sm max-w-full h-auto"
/> style={{ width: imageSize.width, maxWidth: '100%' }}
/>
<button
onClick={handleDownload}
className="absolute top-2 right-2 p-2 bg-black/50 hover:bg-black/70 rounded-full transition-colors"
title="Download image"
>
<Download className="w-4 h-4 text-white" />
</button>
</div>
</div> </div>
)} )}

View file

@ -1,7 +1,7 @@
import { NodeViewWrapper } from '@tiptap/react' import { NodeViewWrapper } from '@tiptap/react'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { AlertTriangle, FileText } from 'lucide-react' import { AlertTriangle, FileText, Download } from 'lucide-react'
import { uploadNewPDFFile } from '../../../../../services/blocks/Pdf/pdf' import { uploadNewPDFFile } from '../../../../../services/blocks/Pdf/pdf'
import { getActivityBlockMediaDirectory } from '@services/media/media' import { getActivityBlockMediaDirectory } from '@services/media/media'
import { useOrg } from '@components/Contexts/OrgContext' import { useOrg } from '@components/Contexts/OrgContext'
@ -47,6 +47,29 @@ function PDFBlockComponent(props: any) {
}) })
} }
const handleDownload = () => {
if (!fileId) return;
const pdfUrl = getActivityBlockMediaDirectory(
org?.org_uuid,
course?.courseStructure.course_uuid,
props.extension.options.activity.activity_uuid,
blockObject.block_uuid,
fileId,
'pdfBlock'
);
const link = document.createElement('a');
link.href = pdfUrl || '';
link.download = `document-${blockObject?.block_uuid || 'download'}.${blockObject?.content.file_format || 'pdf'}`;
link.setAttribute('download', '');
link.setAttribute('target', '_blank');
link.setAttribute('rel', 'noopener noreferrer');
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
useEffect(() => { }, [course, org]) useEffect(() => { }, [course, org])
return ( return (
@ -58,17 +81,28 @@ function PDFBlockComponent(props: any) {
{blockObject && ( {blockObject && (
<BlockPDF> <BlockPDF>
<iframe <div className="relative">
className="shadow-sm rounded-lg h-96 w-full object-scale-down bg-black" <iframe
src={`${getActivityBlockMediaDirectory( className="shadow-sm rounded-lg h-96 w-full object-scale-down bg-black"
org?.org_uuid, src={`${getActivityBlockMediaDirectory(
course?.courseStructure.course_uuid, org?.org_uuid,
props.extension.options.activity.activity_uuid, course?.courseStructure.course_uuid,
blockObject.block_uuid, props.extension.options.activity.activity_uuid,
blockObject ? fileId : ' ', blockObject.block_uuid,
'pdfBlock' blockObject ? fileId : ' ',
)}`} 'pdfBlock'
/> )}`}
/>
{!isEditable && (
<button
onClick={handleDownload}
className="absolute top-2 right-2 p-2 bg-black/50 hover:bg-black/70 rounded-full transition-colors"
title="Download PDF"
>
<Download className="w-4 h-4 text-white" />
</button>
)}
</div>
</BlockPDF> </BlockPDF>
)} )}
{isLoading && ( {isLoading && (

View file

@ -3,7 +3,7 @@ import { Node } from '@tiptap/core'
import { import {
Loader2, Video, Upload, X, HelpCircle, Loader2, Video, Upload, X, HelpCircle,
Maximize2, Minimize2, ArrowLeftRight, Maximize2, Minimize2, ArrowLeftRight,
CheckCircle2, AlertCircle CheckCircle2, AlertCircle, Download
} from 'lucide-react' } from 'lucide-react'
import React from 'react' import React from 'react'
import { uploadNewVideoFile } from '../../../../../services/blocks/Video/video' import { uploadNewVideoFile } from '../../../../../services/blocks/Video/video'
@ -308,6 +308,21 @@ function VideoBlockComponent(props: ExtendedNodeViewProps) {
'videoBlock' 'videoBlock'
) : null ) : null
const handleDownload = () => {
if (!videoUrl) return;
// Create a temporary link element
const link = document.createElement('a');
link.href = videoUrl;
link.download = `video-${blockObject?.block_uuid || 'download'}.${blockObject?.content.file_format || 'mp4'}`;
link.setAttribute('download', '');
link.setAttribute('target', '_blank');
link.setAttribute('rel', 'noopener noreferrer');
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
// If we're in preview mode and have a video, show only the video player // If we're in preview mode and have a video, show only the video player
if (!isEditable && blockObject && videoUrl) { if (!isEditable && blockObject && videoUrl) {
const width = VIDEO_SIZES[blockObject.size].width const width = VIDEO_SIZES[blockObject.size].width
@ -317,7 +332,7 @@ function VideoBlockComponent(props: ExtendedNodeViewProps) {
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
transition={{ duration: 0.3 }} transition={{ duration: 0.3 }}
className="w-full flex justify-center" className="w-full flex justify-center relative"
> >
<div <div
style={{ style={{
@ -325,11 +340,20 @@ function VideoBlockComponent(props: ExtendedNodeViewProps) {
width: '100%' width: '100%'
}} }}
> >
<video <div className="relative">
controls <video
className="w-full aspect-video object-contain rounded-lg shadow-sm" controls
src={videoUrl} className="w-full aspect-video object-contain rounded-lg shadow-sm"
/> src={videoUrl}
/>
<button
onClick={handleDownload}
className="absolute top-2 right-2 p-2 bg-black/50 hover:bg-black/70 rounded-full transition-colors"
title="Download video"
>
<Download className="w-4 h-4 text-white" />
</button>
</div>
</div> </div>
</motion.div> </motion.div>
</NodeViewWrapper> </NodeViewWrapper>
@ -465,6 +489,16 @@ function VideoBlockComponent(props: ExtendedNodeViewProps) {
{VIDEO_SIZES[size].label} {VIDEO_SIZES[size].label}
</SizeButton> </SizeButton>
))} ))}
<SizeButton
isActive={false}
onClick={handleDownload}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
className="ml-auto"
>
<Download size={14} />
Download
</SizeButton>
</div> </div>
<VideoContainer> <VideoContainer>