'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 (
setPage(current - 1)}
>
{t('previous', 'Previous')}
{totalPagesList.map((page) => (
setPage(page)}
className={clsx(
'cursor-pointer inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border hover:bg-forth h-10 w-10 hover:text-white border-[#1F1F1F]',
current === page
? 'bg-forth !text-white'
: 'text-textColor hover:text-white'
)}
>
{page + 1}
))}
setPage(current + 1)}
>
{t('next', 'Next')}
);
};
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 && (
{} : addMedia}
className="!text-white"
>
{t('add_selected_media', 'Add selected media')}
)}
>
)}
{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 ? (
) : (
)}
))}
{(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 (
<>
{t('insert_media', 'Insert Media')}
{!!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 && (
{t('design_media', 'Design Media')}
{!!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))}
/>
)}
{t('select', 'Select')}
{t('editor', 'Editor')}
{t('clear', 'Clear')}
);
};