Files
musicland/src/components/DocumentDropZone.js
T
2026-01-12 16:01:32 +01:00

398 lines
10 KiB
JavaScript

import { useActionSheet } from '@expo/react-native-action-sheet'
import * as DocumentPicker from 'expo-document-picker'
import * as ImagePicker from 'expo-image-picker'
import moment from 'moment'
import { Image, Pressable, Text, View } from 'react-native'
import Compressor from 'react-native-compressor'
import useMinuit from 'react-native-minuit/src/hooks/useMinuit'
import React, { useCallback, useEffect, useRef, useState } from 'reactn'
import { arrayUnion } from '../config/firebase'
import { icons } from '../assets'
import { Fonts, Palette } from '../styles'
import Style, { gutterConstant, mainBorderRadius } from '../styles/Style'
import { uploadFileToFirebase } from '../helpers/uploadToFirebase'
import useLayoutType from '../hooks/useLayoutType'
const NATIVE_OPTIONS = [
'Importer depuis la galerie',
'Ajouter un fichier',
'Prendre une photo ou une vidéo',
'Annuler',
]
const DocumentDropZone = ({
containerStyle = {},
documentID = null,
documentExists = false,
collectionRef = null,
setFiles = null,
customElement = null,
shouldReturnObject = false,
}) => {
const dropRef = useRef(null)
const [isDragging, setIsDragging] = useState(false)
const { setIsLoading, setTooltip } = useMinuit()
const { isDesktop, isWeb, isNative } = useLayoutType()
const { showActionSheetWithOptions } = useActionSheet()
useEffect(() => {
if (isWeb && dropRef.current) {
const el = dropRef.current
const handleDragIn = (e) => {
e.preventDefault()
e.stopPropagation()
setIsDragging(true)
}
const handleDragOut = (e) => {
e.preventDefault()
e.stopPropagation()
if (!dropRef.current.contains(e.relatedTarget)) {
console.log('drag left')
setIsDragging(false)
}
}
const handleDrop = (e) => {
e.preventDefault()
e.stopPropagation()
setIsDragging(false)
let files = [...e.dataTransfer.files]
if (files.length > 0) {
for (const file of files) {
handleWebFile({ file })
}
}
}
el.addEventListener('dragenter', handleDragIn)
el.addEventListener('dragleave', handleDragOut)
el.addEventListener('dragover', (e) => e.preventDefault())
el.addEventListener('drop', handleDrop)
return () => {
el.removeEventListener('dragenter', handleDragIn)
el.removeEventListener('dragleave', handleDragOut)
el.removeEventListener('dragover', (e) => e.preventDefault())
el.removeEventListener('drop', handleDrop)
}
}
}, [dropRef?.current])
const handleWebFile = ({ file }) => {
try {
const { type = '' } = file
const reader = new FileReader()
reader.onloadend = () => {
const base64 = reader.result.split(',')[1] // Vous pourriez avoir besoin de cette valeur base64 pour un upload direct
const uri = reader.result // URI en base64 du fichier
console.log('file', file)
onUploadDocument({ files: [{ name: file.name, uri, type }] })
}
reader.onerror = (err) => {
console.error('FileReader error', err)
}
reader.readAsDataURL(file) // Lire le fichier et déclencher reader.onloadend lorsque c'est fait
} catch (error) {
console.log(error)
setTooltip({ text: error.message, type: 'error' })
}
}
const onAddFile = async ({} = {}) => {
try {
if (isNative) {
const cancelButtonIndex = NATIVE_OPTIONS.length - 1
showActionSheetWithOptions(
{
options: NATIVE_OPTIONS,
cancelButtonIndex,
userInterfaceStyle: 'dark',
...Style.actionSheet,
},
async (selectedIndex) => {
if (selectedIndex !== cancelButtonIndex) {
switch (selectedIndex) {
case 0:
onChooseLibrary()
break
case 1:
onChooseDocumentPicker()
break
case 2:
onTakePicture()
break
default:
break
}
}
}
)
} else {
onChooseDocumentPicker()
}
} catch (error) {
console.log(error)
setTooltip({ text: error.message, type: 'error' })
} finally {
setIsLoading(false)
}
}
const onChooseLibrary = async ({} = {}) => {
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
allowsEditing: false,
quality: 3,
})
if (result?.assets?.[0]?.uri) {
const { uri, fileName = '' } = result?.assets?.[0]
onUploadDocument({
files: [
{
name: getAssetName({ fileName, uri }),
uri,
type: 'IMAGE',
},
],
})
} else {
throw new Error('Aucune image sélectionnée')
}
}
const onChooseDocumentPicker = async ({} = {}) => {
const result = await DocumentPicker.getDocumentAsync({
type: '*/*',
copyToCacheDirectory: false,
})
if (result?.assets?.length > 0) {
const { name = '', uri = null } = result?.assets[0] || {}
if (!uri) {
throw new Error("Erreur lors de l'ajout du document")
}
onUploadDocument({
files: [
{
name: name || getDefaultFileName(),
uri,
},
],
})
} else {
console.log(result)
throw new Error("Erreur lors de l'ajout du document")
}
}
const onTakePicture = useCallback(async () => {
const { status } = await ImagePicker.requestCameraPermissionsAsync()
if (status !== 'granted') {
throw new Error('Permissions caméra non accordées')
}
const result = await ImagePicker.launchCameraAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
allowsEditing: true,
quality: 1,
})
if (result?.assets?.[0]?.uri) {
const { uri, fileName = '' } = result?.assets?.[0]
onUploadDocument({
files: [
{
name: getAssetName({ fileName, uri }),
uri,
type: 'IMAGE',
},
],
})
} else {
throw new Error('Aucune image sélectionnée')
}
}, [])
const getDefaultFileName = () => {
const randomID = Math.random().toString(36).substring(7)
return `${moment().format(`DD_MM_YYYY_HH_mm_ss`)}_${randomID}`
}
const getAssetName = ({ fileName = '', uri = '' }) => {
let name = fileName
if (!name && uri?.startsWith('file://')) {
const splittedArray = uri?.split('/') || []
name = splittedArray?.[splittedArray?.length - 1] || ''
}
if (!name?.length) {
let extension = uri?.split(';')?.[0]?.split('/')?.[1] || ''
const defaultFileName = getDefaultFileName()
if (extension?.length) {
name = `${defaultFileName}.${extension}`
} else {
name = `${defaultFileName}`
}
}
return name
}
const onUploadDocument = async ({ files = [] } = {}) => {
try {
setIsLoading(true)
const filesToUpdate = []
await Promise.all(
files.map(async (file) => {
const { name = '', uri = null } = file
console.log(file)
if (!uri) {
throw new Error("Erreur lors de l'ajout du document")
}
const fileName = name || getDefaultFileName()
let type = 'FILE'
if (name?.toLowerCase()?.match(/\.(jpeg|jpg|gif|png)$/) != null) {
type = 'IMAGE'
}
if (name?.toLowerCase()?.match(/\.(mp4|mov|avi|mkv)$/) != null) {
type = 'VIDEO'
}
let compressedURI = uri
let thumbnailURI = null
if (isNative) {
if (type === 'IMAGE') {
compressedURI = await Compressor.Image.compress(uri, {
compressionMethod: 'manual',
maxWidth: 1000,
quality: 0.8,
})
} else if (type === 'VIDEO') {
compressedURI = await Compressor.Video.compress(uri)
}
}
const { resultURI = null } = await uploadFileToFirebase({
uri: compressedURI,
path: `documents/${documentID}/files/${fileName}`,
})
if (resultURI) {
if (shouldReturnObject) {
filesToUpdate.push({
name: fileName,
uri: resultURI,
type,
thumbnailURI,
})
} else {
filesToUpdate.push(resultURI)
}
} else {
console.log('resultURI is null')
}
})
)
if (filesToUpdate.length > 0) {
if (documentExists) {
await collectionRef.doc(documentID).update({
files: arrayUnion(...filesToUpdate),
})
}
if (setFiles) {
setFiles((prev) => [...prev, ...filesToUpdate])
}
}
setTooltip({ text: 'Document(s) ajouté(s) avec succès' })
} catch (error) {
console.log(error)
setTooltip({ text: error.message, type: 'error' })
} finally {
setIsLoading(false)
}
}
if (!documentID) {
return null
}
return (
<View ref={dropRef}>
<Pressable onPress={onAddFile}>
{customElement?.() || (
<View
style={{
...Style.containerCenter,
backgroundColor: isDragging ? Palette.transparentGreen : Palette.transparentPrimary,
borderRadius: mainBorderRadius,
borderStyle: 'dashed',
borderWidth: 2,
borderColor: isDragging ? Palette.green : Palette.primary,
...containerStyle,
}}
>
<Image
source={icons.screenshot}
style={{
...Style.iconLarge,
marginBottom: gutterConstant,
}}
/>
<Text
style={{
...Fonts({ type: 'default' }),
textAlign: 'center',
color: Palette.white,
}}
>
{isWeb && isDesktop
? `Glissez-déposez ici\nles fichiers, images et vidéos\nà ajouter.`
: 'Ajouter une image,\nun fichier ou une vidéo.'}
</Text>
</View>
)}
</Pressable>
</View>
)
}
export default DocumentDropZone