'use client'; import React, { ClipboardEvent, FC, Fragment, 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 { 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'; import { ReactSortable } from 'react-sortablejs'; import { MediaComponentInner, useMediaSettings, } from '@gitroom/frontend/components/launches/helpers/media.settings.component'; import { useLaunchStore } from '@gitroom/frontend/components/new-launch/store'; import { AiVideo } from '@gitroom/frontend/components/launches/ai.video'; import { useModals } from '@gitroom/frontend/components/layout/new-modal'; 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; standalone?: boolean; type?: 'image' | 'video'; closeModal: () => void; }> = (props) => { const { setMedia, type, closeModal } = props; const [mediaList, setListMedia] = useState([]); const setActivateExitButton = useLaunchStore((e) => e.setActivateExitButton); const fetch = useFetch(); const mediaDirectory = useMediaDirectory(); const [page, setPage] = useState(0); const [pages, setPages] = useState(0); const [selectedMedia, setSelectedMedia] = useState([]); const ref = useRef(null); useEffect(() => { setActivateExitButton(false); return () => { setActivateExitButton(true); }; }, []); const loadMedia = useCallback(async () => { return (await fetch(`/media?page=${page + 1}`)).json(); }, [page]); const setNewMedia = useCallback( (media: Media) => () => { if (props.standalone) { return; } setSelectedMedia( selectedMedia.find((p) => p.id === media.id) ? selectedMedia.filter((f) => f.id !== media.id) : [ ...selectedMedia.map((p) => ({ ...p, })), { ...media, }, ] ); }, [selectedMedia] ); const addNewMedia = useCallback( (media: Media[]) => () => { if (props.standalone) { return; } setSelectedMedia((currentMedia) => [...currentMedia, ...media]); // closeModal(); }, [selectedMedia] ); const addMedia = useCallback(async () => { if (props.standalone) { return; } // @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 ); if (props.standalone) { return; } addNewMedia(onlyNewMedia)(); }, [mutate, addNewMedia, mediaList, selectedMedia] ); const dragAndDrop = useCallback( async (event: ClipboardEvent | File[]) => { if (!ref?.current?.setOptions) { return; } // @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] ); const refNew = useRef(null); useEffect(() => { if (data?.pages) { setPages(data.pages); } if (data?.results && data?.results?.length) { setListMedia([...data.results]); } }, [data]); useEffect(() => { refNew?.current?.scrollIntoView({ behavior: 'smooth', }); }, []); const t = useT(); return (
{!props.standalone ? ( ) : (
)}
{!props.standalone ? ( ) : (
)}
{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={props.standalone ? () => {} : setNewMedia(media)} >
X
{media.path.indexOf('mp4') > -1 ? ( ) : ( media )}
))}
{(pages || 0) > 1 && ( )}
); }; export const MultiMediaComponent: FC<{ label: string; description: string; dummy: boolean; 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; alt?: string; thumbnail?: string; thumbnailTimestamp?: number; }>; }; }) => void; }> = (props) => { const { onOpen, onClose, name, error, text, onChange, value, allData, dummy, } = props; const user = useUser(); const modals = useModals(); useEffect(() => { if (value) { setCurrentMedia(value); } }, [value]); 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(() => { modals.openModal({ askClose: false, children: (close) => ( ), }); }, [changeMedia]); const clearMedia = useCallback( (topIndex: number) => () => { const newMedia = currentMedia?.filter((f, index) => index !== topIndex); setCurrentMedia(newMedia); onChange({ target: { name, value: newMedia, }, }); }, [currentMedia] ); const designMedia = useCallback(() => { if (!!user?.tier?.ai && !dummy) { modals.openModal({ askClose: false, title: 'Design Media', size: '80%', children: (close) => ( ), }); } }, [changeMedia]); const mediaSettings = useMediaSettings(); const t = useT(); return ( <>
{!!currentMedia && ( onChange({ target: { name: 'upload', value } }) } className="flex gap-[10px] sortable-container" animation={200} swap={true} handle=".dragging" > {currentMedia.map((media, index) => (
::
{ modals.openModal({ title: 'Media Settings', children: (close) => ( { console.log(value); onChange({ target: { name: 'upload', value: currentMedia.map((p) => { if (p.id === media.id) { return { ...p, ...value, }; } return p; }), }, }); }} /> ), }); }} className="absolute top-[50%] left-[50%] -translate-x-[50%] -translate-y-[50%] bg-black/80 rounded-[10px] opacity-0 group-hover:opacity-100 transition-opacity z-[100]" >
{media?.path?.indexOf('mp4') > -1 ? ( ) : ( )}
x
))}
)}
{!dummy && (
{!!user?.tier?.ai && ( <> )}
)}
{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[0]); onChange({ target: { name, value: m[0], }, }); }, []); 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))} />
)}
); };