@chayns-components/gallery
Version:
A set of beautiful React components for developing your own applications with chayns.
305 lines (294 loc) • 9.61 kB
JavaScript
import { uploadFile } from '@chayns-components/core';
import { createDialog, DialogType, MediaType, openMedia } from 'chayns-api';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { v4 as uuidv4 } from 'uuid';
import { GalleryViewMode } from '../types/gallery';
import { filterDuplicateFile, generatePreviewUrl, generateVideoThumbnail } from '../utils/file';
import AddFile from './add-file/AddFile';
import GalleryItem from './gallery-item/GalleryItem';
import { StyledGallery, StyledGalleryEditModeWrapper, StyledGalleryItemWrapper } from './Gallery.styles';
const Gallery = ({
allowDragAndDrop = false,
doubleFileDialogMessage = 'Diese Datei ist bereits vorhanden',
isEditMode = false,
fileMinWidth = 100,
files,
maxFiles,
onAdd,
onFileCountChange,
onRemove,
viewMode = GalleryViewMode.GRID
}) => {
const [fileItems, setFileItems] = useState([]);
/**
* This function adds a previewUrl to fileItems
*/
const handlePreviewUrlCallback = (previewUrl, file) => {
setFileItems(prevState => prevState.map(prevFile => {
if (prevFile.id === file.id) {
return {
...prevFile,
previewUrl
};
}
return prevFile;
}));
};
const callDuplicateFileDialog = useCallback(() => {
createDialog({
type: DialogType.ALERT,
text: doubleFileDialogMessage
});
}, [doubleFileDialogMessage]);
/**
* This function adds uploaded files to fileItems
*/
const handleUploadFileCallback = useCallback((file, uploadedFile) => {
setFileItems(prevState => {
const updatedState = prevState.map(prevFile => {
if (prevFile.uploadedFile?.url === uploadedFile.url) {
callDuplicateFileDialog();
return undefined;
}
if (prevFile.id === file.id) {
if (typeof onAdd === 'function') {
const prevElement = prevState.find(({
uploadedFile: newUploadedFile
}) => newUploadedFile?.url === uploadedFile?.url);
if (!prevElement) {
onAdd({
file: uploadedFile,
id: file.id
});
}
}
return {
...prevFile,
uploadedFile,
state: 'uploaded'
};
}
return prevFile;
});
const tmp = [];
updatedState.forEach(updatedFile => {
if (updatedFile !== undefined) {
tmp.push(updatedFile);
}
});
return tmp ?? [];
});
}, [callDuplicateFileDialog, onAdd]);
/**
* Returns the current count to check if all files are uploaded
*/
useEffect(() => {
if (typeof onFileCountChange === 'function') {
onFileCountChange(fileItems.length);
}
}, [fileItems.length, onFileCountChange]);
/**
* Prepares files for previewUrl and upload
*/
useEffect(() => {
const filesToGeneratePreview = fileItems.filter(file => file.file && !file.previewUrl && (file.state === 'none' || !file.state));
const filesToUpload = fileItems.filter(file => !file.uploadedFile && file.state !== 'uploading');
filesToGeneratePreview.forEach(file => {
if (!file.file) {
return;
}
if (file.file.type.includes('video/')) {
generateVideoThumbnail({
file: file.file,
callback: previewUrl => handlePreviewUrlCallback(previewUrl, file)
});
return;
}
generatePreviewUrl({
file: file.file,
callback: previewUrl => handlePreviewUrlCallback(previewUrl, file)
});
});
filesToUpload.forEach(file => {
setFileItems(prevState => prevState.map(prevFile => {
if (prevFile.id === file.id) {
return {
...prevFile,
state: 'uploading'
};
}
return prevFile;
}));
void uploadFile({
fileToUpload: file,
callback: UploadedFile => handleUploadFileCallback(file, UploadedFile)
});
});
}, [fileItems, handleUploadFileCallback]);
/**
* This function formats and adds files to fileItems
*/
const handleAddFiles = useCallback(filesToAdd => {
const newFileItems = [];
filesToAdd.forEach(file => {
if (file && !filterDuplicateFile({
files: fileItems,
newFile: file
})) {
newFileItems.push({
id: uuidv4(),
file,
state: 'none'
});
}
});
let tmp = newFileItems;
if (maxFiles) {
tmp = newFileItems.slice(0, maxFiles - (fileItems.length + filesToAdd.length - 1));
}
setFileItems(prevState => [...prevState, ...tmp]);
}, [fileItems, maxFiles]);
/**
* This function adds external files to fileItems
*/
useEffect(() => {
if (files) {
const newFileItems = [];
files.forEach(file => {
newFileItems.push({
id: file.id ?? uuidv4(),
uploadedFile: file.file,
file: undefined,
state: 'uploaded',
previewUrl: undefined
});
});
setFileItems(prevState => {
const updatedItems = prevState.map(prevItem => {
const newItem = newFileItems.find(item => item.uploadedFile && item.uploadedFile.url === (prevItem.uploadedFile && prevItem.uploadedFile.url));
return newItem || prevItem;
});
return updatedItems.concat(newFileItems.filter(newItem => !prevState.some(prevItem => prevItem.uploadedFile && newItem.uploadedFile && prevItem.uploadedFile.url === newItem.uploadedFile.url)));
});
}
}, [files]);
/**
* This function deletes a selected file from the file list
*/
const handleDeleteFile = useCallback(id => {
let fileToDelete;
const filteredFiles = fileItems.filter(file => {
const fileId = file.id;
if (fileId === id && file.uploadedFile) {
fileToDelete = {
file: file.uploadedFile,
id
};
}
return fileId !== id;
});
setFileItems(filteredFiles);
if (!fileToDelete || typeof onRemove !== 'function') {
return;
}
onRemove(fileToDelete);
}, [fileItems, onRemove]);
/**
* This function handles the drag and drop
*/
const handleDrop = useCallback(e => {
if (!allowDragAndDrop) {
return;
}
e.preventDefault();
const draggedFiles = Array.from(e.dataTransfer.files);
handleAddFiles(draggedFiles);
}, [allowDragAndDrop, handleAddFiles]);
/**
* Opens the files in a slideShow
*/
const openFiles = useCallback(file => {
const startIndex = fileItems.findIndex(item => item.id === file.id);
const items = fileItems.map(item => ({
url: item.uploadedFile?.url.replace('_0.mp4', '.mp4') ?? '',
mediaType: item.uploadedFile && 'thumbnailUrl' in item.uploadedFile ? MediaType.VIDEO : MediaType.IMAGE
}));
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
void openMedia({
items,
startIndex
});
}, [fileItems]);
/**
* Returns the ratio of the single file
*/
const ratio = useMemo(() => {
switch (fileItems.length) {
case 0:
return 0;
case 1:
return Math.max(fileItems[0]?.uploadedFile?.ratio ?? 1, 1);
case 2:
return 2;
case 3:
return 3;
default:
return 1;
}
}, [fileItems]);
const galleryContent = useMemo(() => {
const combinedFilesLength = fileItems.length;
if (isEditMode) {
const items = fileItems.map(file => /*#__PURE__*/React.createElement(GalleryItem, {
key: file.id,
fileItem: file,
isEditMode: true,
onClick: openFiles,
handleDeleteFile: handleDeleteFile
}));
if (maxFiles && maxFiles <= combinedFilesLength) {
return items;
}
items.push(/*#__PURE__*/React.createElement(AddFile, {
key: "add_file",
onAdd: handleAddFiles
}));
return items;
}
const shortedFiles = fileItems.slice(0, 4);
return shortedFiles.map((file, index) => {
let imageRatio = 1;
if (viewMode === GalleryViewMode.GRID) {
if (combinedFilesLength === 1) {
imageRatio = fileItems[0]?.uploadedFile?.ratio ?? 1;
} else if (combinedFilesLength === 2 && (index === 0 || index === 1)) {
imageRatio = 0.5;
} else if (index === 0 && combinedFilesLength > 2 || combinedFilesLength === 3 && (index === 1 || index === 2)) {
imageRatio = 1.5;
}
}
return /*#__PURE__*/React.createElement(GalleryItem, {
key: file.id,
fileItem: file,
isEditMode: false,
handleDeleteFile: handleDeleteFile,
onClick: openFiles,
ratio: imageRatio,
remainingItemsLength: combinedFilesLength > 4 && index === 3 ? combinedFilesLength : undefined
});
});
}, [fileItems, isEditMode, maxFiles, handleAddFiles, openFiles, handleDeleteFile, viewMode]);
return useMemo(() => /*#__PURE__*/React.createElement(StyledGallery, null, isEditMode ? /*#__PURE__*/React.createElement(StyledGalleryEditModeWrapper, {
$fileMinWidth: fileMinWidth,
onDragOver: e => e.preventDefault(),
onDrop: e => void handleDrop(e)
}, galleryContent) : /*#__PURE__*/React.createElement(StyledGalleryItemWrapper, {
$ratio: ratio,
$uploadedFileLength: fileItems.length,
$viewMode: viewMode
}, galleryContent)), [isEditMode, fileMinWidth, galleryContent, ratio, fileItems.length, viewMode, handleDrop]);
};
Gallery.displayName = 'Gallery';
export default Gallery;
//# sourceMappingURL=Gallery.js.map