feat(server,web): favorite albums per user

This commit is contained in:
Alex
2026-04-23 03:33:32 +00:00
parent f0835d06f8
commit 166f36e5bf
17 changed files with 170 additions and 20 deletions
@@ -4,8 +4,8 @@
import { getContextMenuPositionFromEvent, type ContextMenuPosition } from '$lib/utils/context-menu';
import { getShortDateRange } from '$lib/utils/date-time';
import { type AlbumResponseDto } from '@immich/sdk';
import { IconButton } from '@immich/ui';
import { mdiDotsVertical } from '@mdi/js';
import { Icon, IconButton } from '@immich/ui';
import { mdiDotsVertical, mdiHeart } from '@mdi/js';
import { t } from 'svelte-i18n';
interface Props {
@@ -60,11 +60,14 @@
<div class="mt-4">
<p
class="w-full leading-6 text-lg line-clamp-2 font-semibold text-black dark:text-white group-hover:text-primary"
class="flex items-center gap-1 w-full leading-6 text-lg line-clamp-2 font-semibold text-black dark:text-white group-hover:text-primary"
data-testid="album-name"
title={album.albumName}
>
{album.albumName}
{#if album.isFavorite}
<Icon icon={mdiHeart} size="1em" aria-label={$t('favorite')} class="text-primary shrink-0" />
{/if}
<span class="line-clamp-2">{album.albumName}</span>
</p>
{#if showDateRange && album.startDate && album.endDate}
@@ -131,6 +131,15 @@
case AlbumFilter.Shared: {
return sharedAlbums;
}
case AlbumFilter.Favorites: {
const nonOwnedFavorites = sharedAlbums.filter(
(album) =>
album.isFavorite &&
album.albumUsers.find(({ user: { id } }) => id === authManager.user.id)?.role !== AlbumUserRole.Owner,
);
const ownedFavorites = ownedAlbums.filter((album) => album.isFavorite);
return nonOwnedFavorites.length > 0 ? ownedFavorites.concat(nonOwnedFavorites) : ownedFavorites;
}
default: {
const nonOwnedAlbums = sharedAlbums.filter(
(album) =>
+18
View File
@@ -173,6 +173,24 @@ export const handleUpdateUserAlbumRole = async ({
}
};
export const toggleAlbumFavorite = async (album: AlbumResponseDto): Promise<AlbumResponseDto | undefined> => {
const $t = await getFormatter();
const next = !album.isFavorite;
try {
await updateAlbumUser({
id: album.id,
userId: authManager.user.id,
updateAlbumUserDto: { isFavorite: next },
});
const updated = { ...album, isFavorite: next };
eventManager.emit('AlbumUpdate', updated);
return updated;
} catch (error) {
handleError(error, $t('errors.unable_to_update_album_info'));
}
};
export const handleAddUsersToAlbum = async (album: AlbumResponseDto, users: UserResponseDto[]) => {
const $t = await getFormatter();
+1
View File
@@ -94,6 +94,7 @@ export enum AlbumFilter {
All = 'All',
Owned = 'Owned',
Shared = 'Shared',
Favorites = 'Favorites',
}
export enum AlbumGroupBy {
@@ -89,6 +89,7 @@
[AlbumFilter.All]: $t('all'),
[AlbumFilter.Owned]: $t('owned'),
[AlbumFilter.Shared]: $t('shared'),
[AlbumFilter.Favorites]: $t('favorites'),
});
let selectedFilterOption = $derived(albumFilterNames[findFilterOption($albumViewSettings.filter)]);
@@ -44,6 +44,7 @@
getAlbumAssetsActions,
handleDeleteAlbum,
handleDownloadAlbum,
toggleAlbumFavorite,
} from '$lib/services/album.service';
import { getGlobalActions } from '$lib/services/app.service';
import { getAssetBulkActions } from '$lib/services/asset.service';
@@ -68,6 +69,8 @@
mdiDeleteOutline,
mdiDotsVertical,
mdiDownload,
mdiHeart,
mdiHeartOutline,
mdiImageOutline,
mdiImagePlusOutline,
mdiLink,
@@ -500,6 +503,20 @@
{#snippet trailing()}
<ActionButton action={Cast} />
<IconButton
shape="round"
variant="ghost"
color="secondary"
aria-label={album.isFavorite ? $t('remove_from_favorites') : $t('to_favorite')}
icon={album.isFavorite ? mdiHeart : mdiHeartOutline}
onclick={async () => {
const updated = await toggleAlbumFavorite(album);
if (updated) {
album = updated;
}
}}
/>
{#if isEditor}
<IconButton
variant="ghost"
@@ -14,5 +14,6 @@ export const albumFactory = Sync.makeFactory<AlbumResponseDto>({
albumUsers: [],
hasSharedLink: false,
isActivityEnabled: true,
isFavorite: false,
order: AssetOrder.Desc,
});