'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 (
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;
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 && (
{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={setNewMedia(media)}
>
X
{media.path.indexOf('mp4') > -1 ? (
) : (
)}
))}
{(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 && (
)}
{t('insert_media', 'Insert Media')}
{t('design_media', 'Design Media')}
{!!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))}
/>
)}
{t('select', 'Select')}
{t('editor', 'Editor')}
{t('clear', 'Clear')}
);
};