Skip to content

Commit c13d078

Browse files
remove, rename added
1 parent fae65dd commit c13d078

6 files changed

Lines changed: 409 additions & 28 deletions

File tree

docs/backend/backend_python/openapi.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,9 +1117,14 @@
11171117
"in": "query",
11181118
"required": false,
11191119
"schema": {
1120-
"$ref": "#/components/schemas/InputType",
1120+
"allOf": [
1121+
{
1122+
"$ref": "#/components/schemas/InputType"
1123+
}
1124+
],
11211125
"description": "Choose input type: 'path' or 'base64'",
1122-
"default": "path"
1126+
"default": "path",
1127+
"title": "Input Type"
11231128
},
11241129
"description": "Choose input type: 'path' or 'base64'"
11251130
}
@@ -2199,7 +2204,6 @@
21992204
"metadata": {
22002205
"anyOf": [
22012206
{
2202-
"additionalProperties": true,
22032207
"type": "object"
22042208
},
22052209
{

frontend/src/api/api-functions/albums.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,37 @@ export const removeImageFromAlbum = async (
8787
throw error;
8888
}
8989
};
90+
91+
export const deleteAlbum = async (albumId: string) => {
92+
try {
93+
const response = await apiClient.delete(
94+
albumsEndpoints.deleteAlbum(albumId),
95+
);
96+
return response.data;
97+
} catch (error) {
98+
throw error;
99+
}
100+
};
101+
102+
interface UpdateAlbumRequest {
103+
name: string;
104+
description?: string;
105+
is_hidden: boolean;
106+
current_password?: string;
107+
password?: string;
108+
}
109+
110+
export const updateAlbum = async (
111+
albumId: string,
112+
payload: UpdateAlbumRequest,
113+
) => {
114+
try {
115+
const response = await apiClient.put(
116+
albumsEndpoints.updateAlbum(albumId),
117+
payload,
118+
);
119+
return response.data;
120+
} catch (error) {
121+
throw error;
122+
}
123+
};

frontend/src/api/apiEndpoints.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ export const albumsEndpoints = {
3636
getAlbums: '/albums/',
3737
addImagesToAlbum: (albumId: string) => `/albums/${albumId}/images`,
3838
getAlbumImages: (albumId: string) => `/albums/${albumId}/images/get`,
39+
updateAlbum: (albumId: string) => `/albums/${albumId}`,
3940
removeImageFromAlbum: (albumId: string, imageId: string) =>
4041
`/albums/${albumId}/images/${imageId}`,
42+
deleteAlbum: (albumId: string) => `/albums/${albumId}`,
4143
};
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import { AspectRatio } from '@/components/ui/aspect-ratio';
2+
import { Button } from '@/components/ui/button';
3+
import { cn } from '@/lib/utils';
4+
import { Check, Heart, MoreVertical, Trash } from 'lucide-react';
5+
import { useCallback, useState } from 'react';
6+
import { Image } from '@/types/Media';
7+
import { ImageTags } from './ImageTags';
8+
import { convertFileSrc } from '@tauri-apps/api/core';
9+
import { useToggleFav } from '@/hooks/useToggleFav';
10+
import {
11+
DropdownMenu,
12+
DropdownMenuContent,
13+
DropdownMenuItem,
14+
DropdownMenuTrigger,
15+
} from '@/components/ui/dropdown-menu';
16+
17+
interface AlbumImageCardProps {
18+
image: Image;
19+
className?: string;
20+
isSelected?: boolean;
21+
showTags?: boolean;
22+
onClick?: () => void;
23+
onRemoveFromAlbum: (imageId: string) => void;
24+
}
25+
26+
export function AlbumImageCard({
27+
image,
28+
className,
29+
isSelected = false,
30+
showTags = true,
31+
onClick,
32+
onRemoveFromAlbum,
33+
}: AlbumImageCardProps) {
34+
const [isImageHovered, setIsImageHovered] = useState(false);
35+
// Default to empty array if no tags are provided
36+
const tags = image.tags || [];
37+
const { toggleFavourite } = useToggleFav();
38+
39+
const handleToggleFavourite = useCallback(() => {
40+
if (image?.id) {
41+
toggleFavourite(image.id);
42+
}
43+
}, [image, toggleFavourite]);
44+
45+
return (
46+
<>
47+
<div
48+
className={cn(
49+
'group bg-card cursor-pointer overflow-hidden rounded-lg border transition-all hover:shadow-md',
50+
isSelected ? 'ring-2 ring-[#4088fa]' : '',
51+
className,
52+
)}
53+
onMouseEnter={() => setIsImageHovered(true)}
54+
onMouseLeave={() => setIsImageHovered(false)}
55+
onClick={onClick}
56+
>
57+
<div className="relative">
58+
{/* Selection tick mark */}
59+
{isSelected && (
60+
<div className="absolute top-2 right-2 z-10 rounded-full bg-[#4088fa] p-1">
61+
<Check className="h-4 w-4 text-white" />
62+
</div>
63+
)}
64+
65+
<AspectRatio ratio={1}>
66+
<img
67+
src={convertFileSrc(
68+
image.thumbnailPath || image.path || '/placeholder.svg',
69+
)}
70+
alt={'Sample Title'}
71+
className={cn(
72+
'h-full w-full object-cover transition-transform group-hover:scale-105',
73+
isSelected ? 'opacity-95' : '',
74+
)}
75+
/>
76+
{/* Dark overlay on hover */}
77+
<div className="absolute inset-0 bg-black/30 opacity-0 transition-opacity duration-200 group-hover:opacity-100" />
78+
79+
{/* Image actions on hover */}
80+
<div className="absolute inset-x-0 top-2 flex items-center justify-between px-2 opacity-0 transition-opacity duration-200 group-hover:opacity-100">
81+
{/* Left side actions */}
82+
<div></div>
83+
84+
{/* Right side actions */}
85+
<div className="flex gap-1" onClick={(e) => e.stopPropagation()}>
86+
<Button
87+
variant="ghost"
88+
size="icon"
89+
className={`h-8 w-8 rounded-full text-white transition-all duration-300 ${
90+
image.isFavourite
91+
? 'bg-rose-500/80 hover:bg-rose-600'
92+
: 'bg-black/40 hover:bg-black/60'
93+
}`}
94+
onClick={(e) => {
95+
e.stopPropagation();
96+
handleToggleFavourite();
97+
}}
98+
>
99+
{image.isFavourite ? (
100+
<Heart className="h-4 w-4" fill="currentColor"></Heart>
101+
) : (
102+
<Heart className="h-4 w-4" />
103+
)}
104+
</Button>
105+
106+
<DropdownMenu>
107+
<DropdownMenuTrigger asChild>
108+
<Button
109+
variant="ghost"
110+
size="icon"
111+
className="h-8 w-8 rounded-full bg-black/40 text-white transition-all duration-300 hover:bg-black/60"
112+
>
113+
<MoreVertical className="h-4 w-4" />
114+
</Button>
115+
</DropdownMenuTrigger>
116+
<DropdownMenuContent align="end">
117+
<DropdownMenuItem
118+
onClick={() => onRemoveFromAlbum(image.id)}
119+
className="text-red-500 focus:bg-red-50 focus:text-red-500"
120+
>
121+
<Trash className="mr-2 h-4 w-4" />
122+
Remove from Album
123+
</DropdownMenuItem>
124+
</DropdownMenuContent>
125+
</DropdownMenu>
126+
</div>
127+
</div>
128+
</AspectRatio>
129+
130+
{/* Tag section */}
131+
<ImageTags
132+
tags={tags}
133+
showTags={showTags}
134+
isImageHovered={isImageHovered}
135+
/>
136+
</div>
137+
</div>
138+
</>
139+
);
140+
}

frontend/src/components/Media/ChronologicalGallery.tsx

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { useMemo, useRef, useEffect, useCallback } from 'react';
22
import { useDispatch, useSelector } from 'react-redux';
3-
import { ImageCard } from '@/components/Media/ImageCard';
43
import { Image } from '@/types/Media';
4+
import { ImageCard } from '@/components/Media/ImageCard';
5+
import { AlbumImageCard } from './AlbumImageCard';
56
import { groupImagesByYearMonthFromMetadata } from '@/utils/dateUtils';
67
import { setCurrentViewIndex } from '@/features/imageSlice';
78
import { MediaView } from './MediaView';
@@ -168,15 +169,28 @@ export const ChronologicalGallery = ({
168169

169170
return (
170171
<div key={img.id} className="group relative">
171-
<ImageCard
172-
image={img}
173-
onClick={() =>
174-
dispatch(setCurrentViewIndex(chronologicalIndex))
175-
}
176-
className="w-full transition-transform duration-200 group-hover:scale-105"
177-
albumId={albumId}
178-
onRemoveFromAlbum={onRemoveFromAlbum}
179-
/>
172+
{albumId && onRemoveFromAlbum ? (
173+
<AlbumImageCard
174+
image={img}
175+
onClick={() =>
176+
dispatch(
177+
setCurrentViewIndex(chronologicalIndex),
178+
)
179+
}
180+
className="w-full transition-transform duration-200 group-hover:scale-105"
181+
onRemoveFromAlbum={onRemoveFromAlbum}
182+
/>
183+
) : (
184+
<ImageCard
185+
image={img}
186+
onClick={() =>
187+
dispatch(
188+
setCurrentViewIndex(chronologicalIndex),
189+
)
190+
}
191+
className="w-full transition-transform duration-200 group-hover:scale-105"
192+
/>
193+
)}
180194
</div>
181195
);
182196
})}

0 commit comments

Comments
 (0)