'use client'; import { ClipboardEvent, FC, useCallback, useEffect, useMemo, useRef, useState, } from 'react'; import { Button } from '@gitroom/react/form/button'; import useSWR from 'swr'; import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; import { Media } from '@prisma/client'; import { useMediaDirectory } from '@gitroom/react/helpers/use.media.directory'; import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; import EventEmitter from 'events'; import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component'; import clsx from 'clsx'; import { VideoFrame } from '@gitroom/react/helpers/video.frame'; import { LoadingComponent } from '@gitroom/frontend/components/layout/loading'; import { MultipartFileUploader } from '@gitroom/frontend/components/media/new.uploader'; import dynamic from 'next/dynamic'; import { useUser } from '@gitroom/frontend/components/layout/user.context'; import { AiImage } from '@gitroom/frontend/components/launches/ai.image'; import Image from 'next/image'; import { DropFiles } from '@gitroom/frontend/components/layout/drop.files'; import { deleteDialog } from '@gitroom/react/helpers/delete.dialog'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; import { ThirdPartyMedia } from '@gitroom/frontend/components/third-parties/third-party.media'; const Polonto = dynamic( () => import('@gitroom/frontend/components/launches/polonto') ); const showModalEmitter = new EventEmitter(); export const Pagination: FC<{ current: number; totalPages: number; setPage: (num: number) => void; }> = (props) => { const t = useT(); const { current, totalPages, setPage } = props; const totalPagesList = useMemo(() => { return Array.from( { length: totalPages, }, (_, i) => i ); }, [totalPages]); return ( ); }; export const ShowMediaBoxModal: FC = () => { const [showModal, setShowModal] = useState(false); const [callBack, setCallBack] = useState<(params: { id: string; path: string }) => void | undefined>(); const closeModal = useCallback(() => { setShowModal(false); setCallBack(undefined); }, []); useEffect(() => { showModalEmitter.on('show-modal', (cCallback) => { setShowModal(true); setCallBack(() => cCallback); }); return () => { showModalEmitter.removeAllListeners('show-modal'); }; }, []); if (!showModal) return null; return (
); }; export const showMediaBox = ( callback: (params: { id: string; path: string }) => void ) => { showModalEmitter.emit('show-modal', callback); }; const CHUNK_SIZE = 1024 * 1024; export const MediaBox: FC<{ setMedia: (params: { id: string; path: string }) => void; type?: 'image' | 'video'; closeModal: () => void; }> = (props) => { const { setMedia, type, closeModal } = props; const [mediaList, setListMedia] = useState([]); const fetch = useFetch(); const mediaDirectory = useMediaDirectory(); const [page, setPage] = useState(0); const [pages, setPages] = useState(0); const [selectedMedia, setSelectedMedia] = useState([]); const ref = useRef(null); const loadMedia = useCallback(async () => { return (await fetch(`/media?page=${page + 1}`)).json(); }, [page]); const setNewMedia = useCallback( (media: Media) => () => { setSelectedMedia( selectedMedia.find((p) => p.id === media.id) ? selectedMedia.filter((f) => f.id !== media.id) : [ ...selectedMedia.map((p) => ({ ...p, })), { ...media, }, ] ); }, [selectedMedia] ); const removeMedia = useCallback( (media: Media) => () => { setSelectedMedia(selectedMedia.filter((f) => f.id !== media.id)); setListMedia(mediaList.filter((f) => f.id !== media.id)); }, [selectedMedia] ); const addNewMedia = useCallback( (media: Media[]) => () => { setSelectedMedia((currentMedia) => [...currentMedia, ...media]); // closeModal(); }, [selectedMedia] ); const addMedia = useCallback(async () => { // @ts-ignore setMedia(selectedMedia); closeModal(); }, [selectedMedia]); const { data, mutate } = useSWR(`get-media-${page}`, loadMedia); const finishUpload = useCallback(async (res: any) => { const lastMedia = mediaList?.[0]?.id; const newData = await mutate(); const untilLastMedia = newData.results.findIndex( (f: any) => f.id === lastMedia ); const onlyNewMedia = newData.results.slice( 0, untilLastMedia === -1 ? newData.results.length : untilLastMedia ); addNewMedia(onlyNewMedia)(); }, [mutate, addNewMedia, mediaList, selectedMedia]); const dragAndDrop = useCallback( async (event: ClipboardEvent | File[]) => { // @ts-ignore const clipboardItems = event.map((p) => ({ kind: 'file', getAsFile: () => p, })); if (!clipboardItems) { return; } const files: File[] = []; // @ts-ignore for (const item of clipboardItems) { if (item.kind === 'file') { const file = item.getAsFile(); if (file) { const isImage = file.type.startsWith('image/'); const isVideo = file.type.startsWith('video/'); if (isImage || isVideo) { files.push(file); // Collect images or videos } } } } if (files.length === 0) { return; } ref.current.setOptions({ autoProceed: false, }); for (const file of files) { ref.current.addFile(file); await ref.current.upload(); ref.current.clear(); } ref.current.setOptions({ autoProceed: true, }); }, [mutate, addNewMedia, mediaList, selectedMedia] ); const removeItem = useCallback( (media: Media) => async (e: any) => { e.stopPropagation(); if ( !(await deleteDialog( t( 'are_you_sure_you_want_to_delete_the_image', 'Are you sure you want to delete the image?' ) )) ) { return; } await fetch(`/media/${media.id}`, { method: 'DELETE', }); mutate(); }, [mutate] ); useEffect(() => { if (data?.pages) { setPages(data.pages); } if (data?.results && data?.results?.length) { setListMedia([...data.results]); } }, [data]); const t = useT(); return (
{t( 'select_or_upload_pictures_maximum_5_at_a_time', 'Select or upload pictures (maximum 5 at a time)' )}
{t( 'you_can_also_drag_drop_pictures', 'You can also drag & drop pictures' )}
{!!mediaList.length && ( <>
)}
{!mediaList.length ? (
{t( 'you_don_t_have_any_assets_yet', "You don't have any assets yet." )}
{t( 'click_the_button_below_to_upload_one', 'Click the button below to upload one' )}
) : ( <> {selectedMedia.length > 0 && (
)} )} {mediaList .filter((f) => { if (type === 'video') { return f.path.indexOf('mp4') > -1; } else if (type === 'image') { return f.path.indexOf('mp4') === -1; } return true; }) .map((media) => (
p.id === media.id) ? 'border-4 border-forth' : 'border-tableBorder border-2' )} onClick={setNewMedia(media)} >
X
{media.path.indexOf('mp4') > -1 ? ( ) : ( media )}
))}
{(pages || 0) > 1 && ( )}
); }; export const MultiMediaComponent: FC<{ label: string; description: string; allData: { content: string; id?: string; image?: Array<{ id: string; path: string; }>; }[]; value?: Array<{ path: string; id: string; }>; text: string; name: string; error?: any; onOpen?: () => void; onClose?: () => void; onChange: (event: { target: { name: string; value?: Array<{ id: string; path: string; }>; }; }) => void; }> = (props) => { const { onOpen, onClose, name, error, text, onChange, value, allData } = props; const user = useUser(); useEffect(() => { if (value) { setCurrentMedia(value); } }, [value]); const [modal, setShowModal] = useState(false); const [mediaModal, setMediaModal] = useState(false); const [currentMedia, setCurrentMedia] = useState(value); const mediaDirectory = useMediaDirectory(); const changeMedia = useCallback( ( m: | { path: string; id: string; } | { path: string; id: string; }[] ) => { const mediaArray = Array.isArray(m) ? m : [m]; const newMedia = [...(currentMedia || []), ...mediaArray]; setCurrentMedia(newMedia); onChange({ target: { name, value: newMedia, }, }); }, [currentMedia] ); const showModal = useCallback(() => { if (!modal) { onOpen?.(); } else { onClose?.(); } setShowModal(!modal); }, [modal, onOpen, onClose]); const closeDesignModal = useCallback(() => { onClose?.(); setMediaModal(false); }, [modal]); const clearMedia = useCallback( (topIndex: number) => () => { const newMedia = currentMedia?.filter((f, index) => index !== topIndex); setCurrentMedia(newMedia); onChange({ target: { name, value: newMedia, }, }); }, [currentMedia] ); const designMedia = useCallback(() => { onOpen?.(); setMediaModal(true); }, []); const t = useT(); return ( <>
{modal && } {mediaModal && !!user?.tier?.ai && ( )}
{!!user?.tier?.ai && ( )}
{!!currentMedia && currentMedia.map((media, index) => ( <>
window.open(mediaDirectory.set(media?.path))} > {media?.path?.indexOf('mp4') > -1 ? ( ) : ( )}
x
))}
{error}
); }; export const MediaComponent: FC<{ label: string; description: string; value?: { path: string; id: string; }; name: string; onChange: (event: { target: { name: string; value?: { id: string; path: string; }; }; }) => void; type?: 'image' | 'video'; width?: number; height?: number; }> = (props) => { const t = useT(); const { name, type, label, description, onChange, value, width, height } = props; const { getValues } = useSettings(); const user = useUser(); useEffect(() => { const settings = getValues()[props.name]; if (settings) { setCurrentMedia(settings); } }, []); const [modal, setShowModal] = useState(false); const [mediaModal, setMediaModal] = useState(false); const [currentMedia, setCurrentMedia] = useState(value); const mediaDirectory = useMediaDirectory(); const closeDesignModal = useCallback(() => { setMediaModal(false); }, [modal]); const showDesignModal = useCallback(() => { setMediaModal(true); }, [modal]); const changeMedia = useCallback((m: { path: string; id: string }) => { setCurrentMedia(m); onChange({ target: { name, value: m, }, }); }, []); const showModal = useCallback(() => { setShowModal(!modal); }, [modal]); const clearMedia = useCallback(() => { setCurrentMedia(undefined); onChange({ target: { name, value: undefined, }, }); }, [value]); return (
{modal && ( )} {mediaModal && !!user?.tier?.ai && ( )}
{label}
{description}
{!!currentMedia && (
window.open(mediaDirectory.set(currentMedia.path))} />
)}
); };