Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions src/components/FavoriteMediaButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import FavoriteIcon from '@mui/icons-material/Favorite';
import IconButton from '@mui/material/IconButton';
import React, { useState } from 'react';

interface FavoriteMediaButtonProps {
thumbnail: {
id: string;
mediaType?: string;
isFavorite?: boolean;
};
handleSingleFavorites?: (id: string, actionAdd: boolean) => void;
}

const FavoriteMediaButton: React.FC<FavoriteMediaButtonProps> = ({
thumbnail,
handleSingleFavorites,
}) => {
const [hovered, setHovered] = useState(false);

const handleOnClick = (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
handleSingleFavorites?.(thumbnail.id, !thumbnail.isFavorite);
};

return (
<IconButton
size="small"
onClick={handleOnClick}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
sx={{
position: 'absolute',
bottom: 9,
left: 7,
zIndex: 2,
}}
>
{(() => {
if (hovered && thumbnail.isFavorite != null && !thumbnail.isFavorite) {
return <FavoriteIcon sx={{ color: 'white', fontSize: 24 }} />; // red when hovered and not favorite
} else {
return <FavoriteIcon sx={{ opacity: 0, fontSize: 24 }} />;
}
})()}
</IconButton>
);
};

export default FavoriteMediaButton;
20 changes: 15 additions & 5 deletions src/components/FavoritesGallery.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import CancelIcon from '@mui/icons-material/Cancel';
import CloseIcon from '@mui/icons-material/Close';
import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder';
import HeartBrokenIcon from '@mui/icons-material/HeartBroken';
import {
Button,
Dialog,
Expand All @@ -27,10 +28,7 @@ import toast from 'react-hot-toast';
import { useInView } from 'react-intersection-observer';
import { IThumbnail } from 'types/types';

import {
fetchFavoritesIds,
removeFavorites,
} from '../api/api';
import { fetchFavoritesIds, removeFavorites } from '../api/api';
import GalleryItemPaper from './GalleryItemPaper';
import Loading from './Loading';
import Preview from './Preview';
Expand Down Expand Up @@ -128,6 +126,17 @@ export const FavoritesGallery: React.FC = () => {
);
};

const handleSingleFavorites = (id: string) => {
unfavoritesMutation.mutate(
{ objectIds: [id] },
{
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['fetchFavoritesIds'] });
},
}
);
};

const theme = useTheme();

const [openUnfavoriteDialog, setOpenUnfavoriteDialog] = useState(false);
Expand Down Expand Up @@ -170,7 +179,7 @@ export const FavoritesGallery: React.FC = () => {
color="inherit"
onClick={() => setOpenUnfavoriteDialog(true)}
>
<CancelIcon />
<HeartBrokenIcon />
</IconButton>
</Tooltip>
<Dialog
Expand Down Expand Up @@ -238,6 +247,7 @@ export const FavoritesGallery: React.FC = () => {
}}
onPreview={() => openPreview(index)}
thumbnail={thumbnail}
handleSingleFavorites={handleSingleFavorites}
/>
</Grid>
))}
Expand Down
189 changes: 105 additions & 84 deletions src/components/GalleryItemPaper.tsx
Original file line number Diff line number Diff line change
@@ -1,99 +1,120 @@
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder';
import Box from '@mui/material/Box';
import Paper from '@mui/material/Paper';
import React from 'react';

import FavoriteMediaButton from './FavoriteMediaButton';
import ImageThumbnail from './ImageThumbnail';
import SelectImageButton from './SelectImageButton';

interface GalleryItemPaperProps {
selected: boolean;
onSelect: (e: React.MouseEvent<HTMLButtonElement>) => void;
onPreview: () => void;
thumbnail: {
id: string;
mediaType?: string;
isFavorite?: boolean;
};
selected: boolean;
onSelect: (e: React.MouseEvent<HTMLButtonElement>) => void;
onPreview: () => void;
thumbnail: {
id: string;
mediaType?: string;
isFavorite?: boolean;
};
handleSingleFavorites?: (id: string, actionAdd: boolean) => void;
}

const GalleryItemPaper: React.FC<GalleryItemPaperProps> = ({
selected,
onSelect,
onPreview,
thumbnail,
selected,
onSelect,
onPreview,
thumbnail,
handleSingleFavorites,
}) => (
<Paper elevation={0}>
<SelectImageButton
selected={selected}
onClick={onSelect}
/>
<div onClick={onPreview}>
{selected ? (
<Box component="section" sx={{ p: 1.5 }}>
<ImageThumbnail
id={thumbnail.id}
mediaType={thumbnail.mediaType}
isFavorite={thumbnail?.isFavorite}
/>
</Box>
) : (
<Box
component="section"
sx={{
position: 'relative',
'&:hover .check-icon': {
opacity: 1,
},
'&:hover .selection-gradient': {
opacity: 1,
},
}}
>
<ImageThumbnail
id={thumbnail.id}
mediaType={thumbnail.mediaType}
isFavorite={thumbnail?.isFavorite}
/>
{/* Gradient overlay, appears on hover */}
<Box
className="selection-gradient"
sx={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '50%',
pointerEvents: 'none',
opacity: 0,
transition: 'opacity 0.2s',
zIndex: 2,
background:
'linear-gradient(to bottom, rgba(26,115,232,0.18) 0%, rgba(26,115,232,0.01) 100%)',
borderTopLeftRadius: 8,
borderTopRightRadius: 8,
}}
/>
<Box
className="check-icon"
sx={{
position: 'absolute',
top: 7,
left: 3,
opacity: 0,
transition: 'opacity 0.2s',
zIndex: 3,
pointerEvents: 'none',
}}
>
<CheckCircleIcon
sx={{ color: 'white', fontSize: 24, opacity: 0.5 }}
/>
</Box>
</Box>
)}
</div>
</Paper>
<Paper elevation={0}>
<SelectImageButton selected={selected} onClick={onSelect} />
<FavoriteMediaButton
thumbnail={thumbnail}
handleSingleFavorites={handleSingleFavorites}
/>
<div onClick={onPreview}>
{selected ? (
<Box component="section" sx={{ p: 1.5 }}>
<ImageThumbnail
id={thumbnail.id}
mediaType={thumbnail.mediaType}
isFavorite={thumbnail?.isFavorite}
/>
</Box>
) : (
<Box
component="section"
sx={{
position: 'relative',
'&:hover .check-icon': {
opacity: 1,
},
'&:hover .selection-gradient': {
opacity: 1,
},
}}
>
<ImageThumbnail
id={thumbnail.id}
mediaType={thumbnail.mediaType}
isFavorite={thumbnail?.isFavorite}
/>
{/* Gradient overlay, appears on hover */}
<Box
className="selection-gradient"
sx={{
position: 'absolute',
top: 0,
left: 0,
width: '90%',
height: '90%',
pointerEvents: 'none',
opacity: 0,
transition: 'opacity 0.2s',
zIndex: 2,
background:
'linear-gradient(to right, rgba(26,115,232,0.18) 0%, rgba(26,115,232,0.01) 100%)',
borderTopLeftRadius: 8,
borderTopRightRadius: 8,
}}
/>
<Box
className="check-icon"
sx={{
position: 'absolute',
top: 7,
left: 3,
opacity: 0,
transition: 'opacity 0.2s',
zIndex: 3,
pointerEvents: 'none',
}}
>
<CheckCircleOutlineIcon
sx={{ color: 'white', fontSize: 24, opacity: 0.8 }}
/>
</Box>
{thumbnail.isFavorite != null && !thumbnail.isFavorite && (
<Box
className="check-icon"
sx={{
position: 'absolute',
bottom: 8,
left: 4,
opacity: 0,
transition: 'opacity 0.2s',
zIndex: 3,
pointerEvents: 'none',
}}
>
<FavoriteBorderIcon sx={{ color: 'white', fontSize: 24, opacity: 0.8 }} />{' '} {/* yellow when not favorite */}
</Box>
)}
</Box>
)}
</div>
</Paper>
);

export default GalleryItemPaper;
40 changes: 39 additions & 1 deletion src/components/ImageGallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@ import toast from 'react-hot-toast';
import { useInView } from 'react-intersection-observer';
import { IThumbnail } from 'types/types';

import { addFavorites, downloadObjectsAsZip, fetchImageIds, trashObjects } from '../api/api';
import {
addFavorites,
downloadObjectsAsZip,
fetchImageIds,
removeFavorites,
trashObjects,
} from '../api/api';
import GalleryItemPaper from './GalleryItemPaper';
import Loading from './Loading';
import Preview from './Preview';
Expand Down Expand Up @@ -80,6 +86,14 @@ export const ImageGallery: React.FC = () => {
},
});

const singleAddFavoritesMutation = useMutation({
mutationFn: addFavorites,
});

const singleRemoveFavoritesMutation = useMutation({
mutationFn: removeFavorites,
});

const lastId = data?.pages.slice(-1)[0].lastId;
const imageIds = data?.pages.map((page) => page.properties).flat();
const lastImage = data?.pages.slice(-1)[0].properties?.slice(-1)[0].id;
Expand Down Expand Up @@ -179,6 +193,28 @@ export const ImageGallery: React.FC = () => {
}
);
};
const handleSingleFavorites = (id: string, actionAdd: boolean) => {
if (actionAdd) {
singleAddFavoritesMutation.mutate(
{ objectIds: [id] },
{
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['fetchIds'] });
},
}
);
return;
}

singleRemoveFavoritesMutation.mutate(
{ objectIds: [id] },
{
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['fetchIds'] });
},
}
);
};
const theme = useTheme();

const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
Expand Down Expand Up @@ -309,6 +345,7 @@ export const ImageGallery: React.FC = () => {
}}
onPreview={() => openPreview(index)}
thumbnail={thumbnail}
handleSingleFavorites={handleSingleFavorites}
/>
</Grid>
))}
Expand All @@ -329,6 +366,7 @@ export const ImageGallery: React.FC = () => {
media={imageIds[currentImage]}
disablePrevButton={disablePrevButton}
disableNextButton={disableNextButton}
handleSingleFavorites={handleSingleFavorites}
/>
)}
<Typography
Expand Down
Loading
Loading