From fda30dcd0b83bde0abd1be53a48ac617c2e81f67 Mon Sep 17 00:00:00 2001 From: teo-crth Date: Thu, 23 Oct 2025 16:53:07 +0200 Subject: [PATCH 1/2] add button to import a thumbnail manually on a video with the MediaBox, only one file has been updated --- .../helpers/media.settings.component.tsx | 384 ++++++++++++------ 1 file changed, 256 insertions(+), 128 deletions(-) diff --git a/apps/frontend/src/components/launches/helpers/media.settings.component.tsx b/apps/frontend/src/components/launches/helpers/media.settings.component.tsx index 3ce19f2c3..c7a397b5e 100644 --- a/apps/frontend/src/components/launches/helpers/media.settings.component.tsx +++ b/apps/frontend/src/components/launches/helpers/media.settings.component.tsx @@ -5,7 +5,7 @@ import React, { FC, useCallback, useEffect, useRef, useState } from 'react'; import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component'; import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; import { useLaunchStore } from '@gitroom/frontend/components/new-launch/store'; -import { useVariables } from '@gitroom/react/helpers/variable.context'; +import { MediaBox } from '../../media/media.component'; const postUrlEmitter = new EventEmitter(); export const MediaSettingsLayout = () => { @@ -98,22 +98,26 @@ export const CreateThumbnail: FC<{ altText?: string; onAltTextChange?: (altText: string) => void; }> = (props) => { - const { onSelect, media } = props; - const { backendUrl } = useVariables(); + const { onSelect, media, altText, onAltTextChange } = props; const videoRef = useRef(null); const canvasRef = useRef(null); + const fileInputRef = useRef(null); const [currentTime, setCurrentTime] = useState(0); const [duration, setDuration] = useState(0); const [isLoaded, setIsLoaded] = useState(false); const [isCapturing, setIsCapturing] = useState(false); const handleLoadedMetadata = useCallback(() => { - setDuration(videoRef?.current?.duration); - setIsLoaded(true); + if (videoRef.current) { + setDuration(videoRef.current.duration); + setIsLoaded(true); + } }, []); const handleTimeUpdate = useCallback(() => { - setCurrentTime(videoRef?.current?.currentTime); + if (videoRef.current) { + setCurrentTime(videoRef.current.currentTime); + } }, []); const handleSeek = useCallback((e: React.ChangeEvent) => { @@ -125,6 +129,8 @@ export const CreateThumbnail: FC<{ }, []); const captureFrame = useCallback(async () => { + if (!videoRef.current || !canvasRef.current) return; + setIsCapturing(true); try { @@ -206,6 +212,41 @@ export const CreateThumbnail: FC<{ return `${mins}:${secs.toString().padStart(2, '0')}`; }, []); + const handleFileUpload = useCallback( + (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + + // Vérifier que c'est bien une image + if (!file.type.startsWith('image/')) { + alert('Veuillez sélectionner un fichier image valide'); + return; + } + + // Convertir le fichier en blob et appeler onSelect + const reader = new FileReader(); + reader.onload = () => { + // Créer un blob à partir du fichier + const blob = new Blob([file], { type: file.type }); + // Utiliser 0 comme timestamp pour les images téléchargées + onSelect(blob, 0); + }; + reader.readAsArrayBuffer(file); + + // Réinitialiser l'input pour permettre de sélectionner le même fichier à nouveau + if (fileInputRef.current) { + fileInputRef.current.value = ''; + } + }, + [onSelect] + ); + + const triggerFileInput = useCallback(() => { + if (fileInputRef.current) { + fileInputRef.current.click(); + } + }, []); + if (!media) return null; return ( @@ -213,9 +254,7 @@ export const CreateThumbnail: FC<{
)} @@ -287,6 +341,8 @@ export const CreateThumbnail: FC<{ ); }; +// Dans media.settings.component.tsx, modifiez le composant MediaComponentInner + export const MediaComponentInner: FC<{ onClose: () => void; onSelect: (media: { @@ -321,6 +377,9 @@ export const MediaComponentInner: FC<{ props.media?.thumbnailTimestamp || null ); + // AJOUT: État pour afficher la modal MediaBox + const [showMediaBoxModal, setShowMediaBoxModal] = useState(false); + useEffect(() => { setActivateExitButton(false); return () => { @@ -328,6 +387,30 @@ export const MediaComponentInner: FC<{ }; }, []); + // AJOUT: Fonction pour gérer l'import depuis MediaBox + const handleImportThumbnail = useCallback( + (selectedMedia: Array<{ id: string; path: string }>) => { + if (selectedMedia && selectedMedia.length > 0) { + const selected = selectedMedia[0]; + // Utiliser directement le chemin de l'image sélectionnée + setNewThumbnail(selected.path); + setThumbnailTimestamp(null); // Pas de timestamp pour les images importées + setShowMediaBoxModal(false); + } + }, + [] + ); + + // AJOUT: Fonction pour ouvrir la modal MediaBox + const openMediaBoxForThumbnail = useCallback(() => { + setShowMediaBoxModal(true); + }, []); + + // AJOUT: Fonction pour fermer la modal MediaBox + const closeMediaBoxModal = useCallback(() => { + setShowMediaBoxModal(false); + }, []); + const save = useCallback(async () => { setLoading(true); let path = thumbnail || ''; @@ -362,132 +445,177 @@ export const MediaComponentInner: FC<{ }, [altText, newThumbnail, thumbnail, thumbnailTimestamp]); return ( -
-
- - setAltText(e.target.value)} - placeholder="Describe the image/video content..." - className="w-full px-3 py-2 bg-fifth border border-tableBorder rounded-lg text-textColor placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-forth focus:border-transparent" +
+ {/* AJOUT: Afficher MediaBox quand nécessaire */} + {showMediaBoxModal && ( + -
- {media?.path.indexOf('mp4') > -1 && ( - <> - {/* Alt Text Input */} -
- {!isEditingThumbnail ? ( -
- {/* Show existing thumbnail if it exists */} - {(newThumbnail || thumbnail) && ( -
- - Current Thumbnail: - - Current thumbnail -
- )} - - {/* Action Buttons */} -
- - {(thumbnail || newThumbnail) && ( - - )} -
-
- ) : ( -
- {/* Back button */} -
- +
+
+
+ + setAltText(e.target.value)} + placeholder="Describe the image/video content..." + className="w-full px-3 py-2 bg-fifth border border-tableBorder rounded-lg text-textColor placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-forth focus:border-transparent" + /> +
+ {media?.path.indexOf('mp4') > -1 && ( + <> +
+ {!isEditingThumbnail ? ( +
+ {/* Show existing thumbnail if it exists */} + {(newThumbnail || thumbnail) && ( +
+ + Current Thumbnail: + + Current thumbnail +
+ )} + + {/* Action Buttons */} +
+ + {(thumbnail || newThumbnail) && ( + + )} +
+ {/* MODIFICATION: Bouton Import Thumbnail use MediaBox */} + +
+ ) : ( +
+ {/* Back button */} +
+ +
+ + {/* Thumbnail Editor */} + { + const reader = new FileReader(); + reader.onload = () => { + const url = URL.createObjectURL(blob); + setNewThumbnail(url); + setThumbnailTimestamp(timestampMs); + setIsEditingThumbnail(false); + }; + reader.readAsDataURL(blob); + }} + media={media} + altText={altText} + onAltTextChange={setAltText} /> - - Back - +
+ )}
+ + )} - {/* Thumbnail Editor */} - { - // Convert blob to base64 or handle as needed - const reader = new FileReader(); - reader.onload = () => { - // You can handle the result here - for now just call onSelect with the blob URL - const url = URL.createObjectURL(blob); - setNewThumbnail(url); - setThumbnailTimestamp(timestampMs); - setIsEditingThumbnail(false); - }; - reader.readAsDataURL(blob); - }} - media={media} - altText={altText} - onAltTextChange={setAltText} - /> + {!isEditingThumbnail && ( +
+ +
)}
- - )} - - {!isEditingThumbnail && ( -
- -
- )} +
); }; From 7ed32ff3f6a083c7ab05560e2e33d2d6a762fcb9 Mon Sep 17 00:00:00 2001 From: teo-crth Date: Thu, 23 Oct 2025 17:54:31 +0200 Subject: [PATCH 2/2] update function save to take blob --- .../helpers/media.settings.component.tsx | 45 ++++++++----------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/apps/frontend/src/components/launches/helpers/media.settings.component.tsx b/apps/frontend/src/components/launches/helpers/media.settings.component.tsx index c7a397b5e..455f086d1 100644 --- a/apps/frontend/src/components/launches/helpers/media.settings.component.tsx +++ b/apps/frontend/src/components/launches/helpers/media.settings.component.tsx @@ -217,23 +217,19 @@ export const CreateThumbnail: FC<{ const file = e.target.files?.[0]; if (!file) return; - // Vérifier que c'est bien une image if (!file.type.startsWith('image/')) { alert('Veuillez sélectionner un fichier image valide'); return; } - // Convertir le fichier en blob et appeler onSelect const reader = new FileReader(); reader.onload = () => { - // Créer un blob à partir du fichier const blob = new Blob([file], { type: file.type }); - // Utiliser 0 comme timestamp pour les images téléchargées + onSelect(blob, 0); }; reader.readAsArrayBuffer(file); - // Réinitialiser l'input pour permettre de sélectionner le même fichier à nouveau if (fileInputRef.current) { fileInputRef.current.value = ''; } @@ -304,7 +300,6 @@ export const CreateThumbnail: FC<{ {isCapturing ? 'Capturing...' : 'Select This Frame'} - {/* Bouton pour télécharger une image */}