mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
feat(server,web): favorite albums per user
This commit is contained in:
@@ -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) =>
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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)]);
|
||||
|
||||
+17
@@ -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,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user