diff --git a/apps/backend/src/api/routes/media.controller.ts b/apps/backend/src/api/routes/media.controller.ts
index 8b520f3c..18b807eb 100644
--- a/apps/backend/src/api/routes/media.controller.ts
+++ b/apps/backend/src/api/routes/media.controller.ts
@@ -1,6 +1,7 @@
import {
Body,
Controller,
+ Delete,
Get,
Param,
Post,
@@ -31,6 +32,10 @@ export class MediaController {
private _subscriptionService: SubscriptionService
) {}
+ @Delete('/:id')
+ deleteMedia(@GetOrgFromRequest() org: Organization, @Param('id') id: string) {
+ return this._mediaService.deleteMedia(org.id, id);
+ }
@Post('/generate-image')
async generateImage(
@GetOrgFromRequest() org: Organization,
@@ -96,15 +101,11 @@ export class MediaController {
}
@Post('/:endpoint')
- // @UseInterceptors(FileInterceptor('file'))
- // @UsePipes(new CustomFileValidationPipe())
async uploadFile(
@GetOrgFromRequest() org: Organization,
@Req() req: Request,
@Res() res: Response,
@Param('endpoint') endpoint: string
- // @UploadedFile('file')
- // file: Express.Multer.File
) {
const upload = await handleR2Upload(endpoint, req, res);
if (endpoint !== 'complete-multipart-upload') {
@@ -122,11 +123,6 @@ export class MediaController {
);
res.status(200).json({ ...upload, saved: saveFile });
- // const filePath =
- // file.path.indexOf('http') === 0
- // ? file.path
- // : file.path.replace(process.env.UPLOAD_DIRECTORY, '');
- // return this._mediaService.saveFile(org.id, file.originalname, filePath);
}
@Get('/')
diff --git a/apps/frontend/src/components/media/media.component.tsx b/apps/frontend/src/components/media/media.component.tsx
index e74b336d..da8540bb 100644
--- a/apps/frontend/src/components/media/media.component.tsx
+++ b/apps/frontend/src/components/media/media.component.tsx
@@ -1,6 +1,14 @@
'use client';
-import { FC, useCallback, useEffect, useState } from 'react';
+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';
@@ -16,11 +24,95 @@ import { MultipartFileUploader } from '@gitroom/frontend/components/media/new.up
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';
+
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 { current, totalPages, setPage } = props;
+
+ const totalPagesList = useMemo(() => {
+ return Array.from({ length: totalPages }, (_, i) => i);
+ }, [totalPages]);
+
+ return (
+
+ -
+
setPage(current - 1)}
+ >
+
+
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] text-white',
+ current === page && 'bg-forth'
+ )}
+ >
+ {page + 1}
+
+
+ ))}
+ -
+ setPage(current + 1)}
+ >
+ Next
+
+
+
+
+ );
+};
export const ShowMediaBoxModal: FC = () => {
const [showModal, setShowModal] = useState(false);
const [callBack, setCallBack] =
@@ -63,31 +155,132 @@ export const MediaBox: FC<{
closeModal: () => void;
}> = (props) => {
const { setMedia, type, closeModal } = props;
- const [pages, setPages] = useState(0);
const [mediaList, setListMedia] = useState([]);
const fetch = useFetch();
const mediaDirectory = useMediaDirectory();
+ const [page, setPage] = useState(0);
+ const [pages, setPages] = useState(0);
- const [loading, setLoading] = useState(false);
+ const [selectedMedia, setSelectedMedia] = useState([]);
+ const ref = useRef(null);
const loadMedia = useCallback(async () => {
- return (await fetch('/media')).json();
- }, []);
+ return (await fetch(`/media?page=${page + 1}`)).json();
+ }, [page]);
const setNewMedia = useCallback(
(media: Media) => () => {
- setMedia(media);
- closeModal();
+ setSelectedMedia(
+ selectedMedia.find((p) => p.id === media.id)
+ ? selectedMedia.filter((f) => f.id !== media.id)
+ : [...selectedMedia.map((p) => ({ ...p })), { ...media }]
+ );
+ // closeModal();
},
- []
+ [selectedMedia]
);
- const { data, mutate } = useSWR('get-media', loadMedia);
+ 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 () => {
+ const lastMedia = mediaList?.[0]?.id;
const newData = await mutate();
- setNewMedia(newData.results[0])();
- }, [mutate, setNewMedia]);
+ 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,
+ });
+
+ finishUpload();
+ },
+ [mutate, addNewMedia, mediaList, selectedMedia]
+ );
+
+ const removeItem = useCallback(
+ (media: Media) => async (e: any) => {
+ e.stopPropagation();
+ if (!(await deleteDialog('Are you sure you want to delete the image?'))) {
+ return;
+ }
+
+ await fetch(`/media/${media.id}`, {
+ method: 'DELETE',
+ });
+
+ mutate();
+ },
+ [mutate]
+ );
useEffect(() => {
if (data?.pages) {
@@ -100,110 +293,144 @@ export const MediaBox: FC<{
return (
-
-
-
-
-
-
-
- {!!mediaList.length && (
-