refactor(web): use actions and ui lib context menu (#31729)
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { shortcut } from '$lib/actions/shortcut';
|
||||
import ButtonContextMenu from '$lib/components/shared-components/context-menu/ButtonContextMenu.svelte';
|
||||
import MenuOption from '$lib/components/shared-components/context-menu/MenuOption.svelte';
|
||||
import { timeBeforeShowLoadingSpinner } from '$lib/constants';
|
||||
import { activityManager } from '$lib/managers/activity-manager.svelte';
|
||||
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
|
||||
@@ -13,8 +11,8 @@
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { isTenMinutesApart } from '$lib/utils/timesince';
|
||||
import { ReactionType, type ActivityResponseDto, type AlbumUserResponseDto, type AssetTypeEnum } from '@immich/sdk';
|
||||
import { Icon, IconButton, LoadingSpinner, Textarea, toastManager } from '@immich/ui';
|
||||
import { mdiClose, mdiDeleteOutline, mdiDotsVertical, mdiSend, mdiThumbUp } from '@mdi/js';
|
||||
import { ContextMenuButton, Icon, IconButton, LoadingSpinner, Textarea, toastManager } from '@immich/ui';
|
||||
import { mdiClose, mdiSend, mdiThumbUp, mdiTrashCanOutline } from '@mdi/js';
|
||||
import * as luxon from 'luxon';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fromAction } from 'svelte/attachments';
|
||||
@@ -110,6 +108,25 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
{#snippet reactionMenu(reaction: ActivityResponseDto, index: number, title: string)}
|
||||
{#if reaction.user.id === authManager.user.id || isAlbumOwner}
|
||||
<ContextMenuButton
|
||||
translations={{ open_menu: title }}
|
||||
position="top-right"
|
||||
class="shrink-0"
|
||||
size="small"
|
||||
items={[
|
||||
{
|
||||
icon: mdiTrashCanOutline,
|
||||
title: $t('remove'),
|
||||
color: 'danger',
|
||||
onAction: () => handleDeleteReaction(reaction, index),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
<div class="relative h-full overflow-y-hidden border-l border-subtle bg-subtle" bind:offsetHeight={innerHeight}>
|
||||
<div class="size-full">
|
||||
<div class="flex h-fit w-full bg-subtle p-2 dark:text-immich-dark-fg" bind:clientHeight={activityHeight}>
|
||||
@@ -133,7 +150,7 @@
|
||||
>
|
||||
{#each activityManager.activities as reaction, index (reaction.id)}
|
||||
{#if reaction.type === ReactionType.Comment}
|
||||
<div class="mt-3 flex justify-start gap-4 rounded-lg bg-gray-200 py-3 ps-3 dark:bg-gray-800">
|
||||
<div class="mt-3 flex justify-start gap-4 rounded-lg bg-gray-200 p-3 dark:bg-gray-800">
|
||||
<div class="flex items-center">
|
||||
<UserAvatar user={reaction.user} size="sm" />
|
||||
</div>
|
||||
@@ -148,24 +165,7 @@
|
||||
/>
|
||||
</a>
|
||||
{/if}
|
||||
{#if reaction.user.id === authManager.user.id || isAlbumOwner}
|
||||
<div class="me-4">
|
||||
<ButtonContextMenu
|
||||
icon={mdiDotsVertical}
|
||||
title={$t('comment_options')}
|
||||
align="top-right"
|
||||
direction="left"
|
||||
size="small"
|
||||
>
|
||||
<MenuOption
|
||||
activeColor="bg-red-200"
|
||||
icon={mdiDeleteOutline}
|
||||
text={$t('remove')}
|
||||
onClick={() => handleDeleteReaction(reaction, index)}
|
||||
/>
|
||||
</ButtonContextMenu>
|
||||
</div>
|
||||
{/if}
|
||||
{@render reactionMenu(reaction, index, $t('comment_options'))}
|
||||
</div>
|
||||
|
||||
{#if (index !== activityManager.activities.length - 1 && !shouldGroup(activityManager.activities[index].createdAt, activityManager.activities[index + 1].createdAt)) || index === activityManager.activities.length - 1}
|
||||
@@ -178,7 +178,7 @@
|
||||
{/if}
|
||||
{:else if reaction.type === ReactionType.Like}
|
||||
<div class="relative">
|
||||
<div class="mt-3 flex items-center gap-4 py-3 ps-3 text-sm">
|
||||
<div class="mt-3 flex items-center gap-4 p-3 text-sm">
|
||||
<div class="text-primary"><Icon icon={mdiThumbUp} size="20" /></div>
|
||||
|
||||
<div class="w-full" title={`${reaction.user.name} (${reaction.user.email})`}>
|
||||
@@ -198,24 +198,7 @@
|
||||
/>
|
||||
</a>
|
||||
{/if}
|
||||
{#if reaction.user.id === authManager.user.id || isAlbumOwner}
|
||||
<div class="me-4">
|
||||
<ButtonContextMenu
|
||||
icon={mdiDotsVertical}
|
||||
title={$t('reaction_options')}
|
||||
align="top-right"
|
||||
direction="left"
|
||||
size="small"
|
||||
>
|
||||
<MenuOption
|
||||
activeColor="bg-red-200"
|
||||
icon={mdiDeleteOutline}
|
||||
text={$t('remove')}
|
||||
onClick={() => handleDeleteReaction(reaction, index)}
|
||||
/>
|
||||
</ButtonContextMenu>
|
||||
</div>
|
||||
{/if}
|
||||
{@render reactionMenu(reaction, index, $t('reaction_options'))}
|
||||
</div>
|
||||
{#if (index !== activityManager.activities.length - 1 && isTenMinutesApart(activityManager.activities[index].createdAt, activityManager.activities[index + 1].createdAt)) || index === activityManager.activities.length - 1}
|
||||
<div
|
||||
|
||||
@@ -161,46 +161,6 @@
|
||||
}
|
||||
};
|
||||
|
||||
const handleHidePerson = async (detail: PersonResponseDto) => {
|
||||
try {
|
||||
const updatedPerson = await updatePerson({
|
||||
id: detail.id,
|
||||
personUpdateDto: { isHidden: true },
|
||||
});
|
||||
|
||||
people = people.map((person: PersonResponseDto) => {
|
||||
if (person.id === updatedPerson.id) {
|
||||
return updatedPerson;
|
||||
}
|
||||
return person;
|
||||
});
|
||||
|
||||
toastManager.primary($t('changed_visibility_successfully'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_hide_person'));
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggleFavorite = async (detail: PersonResponseDto) => {
|
||||
try {
|
||||
const updatedPerson = await updatePerson({
|
||||
id: detail.id,
|
||||
personUpdateDto: { isFavorite: !detail.isFavorite },
|
||||
});
|
||||
|
||||
people = people.map((person: PersonResponseDto) => {
|
||||
if (person.id === updatedPerson.id) {
|
||||
return updatedPerson;
|
||||
}
|
||||
return person;
|
||||
});
|
||||
|
||||
toastManager.primary(updatedPerson.isFavorite ? $t('added_to_favorites') : $t('removed_from_favorites'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_add_remove_favorites', { values: { favorite: detail.isFavorite } }));
|
||||
}
|
||||
};
|
||||
|
||||
const handleMergePeople = async (detail: PersonResponseDto) => {
|
||||
await goto(Route.viewPerson(detail, { previousRoute: Route.people(), action: 'merge' }));
|
||||
};
|
||||
@@ -344,12 +304,7 @@
|
||||
<div
|
||||
class="rounded-xl border-2 border-transparent p-2 transition-all hover:border-immich-primary/50 hover:bg-gray-200 hover:shadow-sm hover:dark:border-immich-dark-primary/25 dark:hover:bg-immich-dark-primary/20"
|
||||
>
|
||||
<PeopleCard
|
||||
{person}
|
||||
onMergePeople={() => handleMergePeople(person)}
|
||||
onHidePerson={() => handleHidePerson(person)}
|
||||
onToggleFavorite={() => handleToggleFavorite(person)}
|
||||
/>
|
||||
<PeopleCard {person} onMergePeople={() => handleMergePeople(person)} />
|
||||
|
||||
<input
|
||||
type="text"
|
||||
|
||||
@@ -1,51 +1,37 @@
|
||||
<script lang="ts">
|
||||
import { focusOutside } from '$lib/actions/focus-outside';
|
||||
import ActionMenuItem from '$lib/components/ActionMenuItem.svelte';
|
||||
import ButtonContextMenu from '$lib/components/shared-components/context-menu/ButtonContextMenu.svelte';
|
||||
import { Route } from '$lib/route';
|
||||
import { getPersonActions } from '$lib/services/person.service';
|
||||
import { getPeopleThumbnailUrl } from '$lib/utils';
|
||||
import { type PersonResponseDto } from '@immich/sdk';
|
||||
import { Icon } from '@immich/ui';
|
||||
import {
|
||||
mdiAccountMultipleCheckOutline,
|
||||
mdiDotsVertical,
|
||||
mdiEyeOffOutline,
|
||||
mdiHeart,
|
||||
mdiHeartMinusOutline,
|
||||
mdiHeartOutline,
|
||||
} from '@mdi/js';
|
||||
import { ContextMenuButton, Icon } from '@immich/ui';
|
||||
import { mdiAccountMultipleCheckOutline, mdiHeart } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import ImageThumbnail from '$lib/components/assets/thumbnail/ImageThumbnail.svelte';
|
||||
import MenuOption from '$lib/components/shared-components/context-menu/MenuOption.svelte';
|
||||
|
||||
type Props = {
|
||||
person: PersonResponseDto;
|
||||
onMergePeople: () => void;
|
||||
onHidePerson: () => void;
|
||||
onToggleFavorite: () => void;
|
||||
};
|
||||
|
||||
let { person, onMergePeople, onHidePerson, onToggleFavorite }: Props = $props();
|
||||
let { person, onMergePeople }: Props = $props();
|
||||
|
||||
let showVerticalDots = $state(false);
|
||||
const { SetDateOfBirth, HidePerson, Favorite, Unfavorite } = $derived(getPersonActions($t, person));
|
||||
|
||||
const { SetDateOfBirth } = $derived(getPersonActions($t, person));
|
||||
const items = $derived([
|
||||
HidePerson,
|
||||
SetDateOfBirth,
|
||||
{
|
||||
icon: mdiAccountMultipleCheckOutline,
|
||||
title: $t('merge_people'),
|
||||
onAction: onMergePeople,
|
||||
},
|
||||
Favorite,
|
||||
Unfavorite,
|
||||
]);
|
||||
</script>
|
||||
|
||||
<div
|
||||
id="people-card"
|
||||
class="relative"
|
||||
onmouseenter={() => (showVerticalDots = true)}
|
||||
onmouseleave={() => (showVerticalDots = false)}
|
||||
role="group"
|
||||
use:focusOutside={{ onFocusOut: () => (showVerticalDots = false) }}
|
||||
>
|
||||
<a
|
||||
href={Route.viewPerson(person, { previousRoute: Route.people() })}
|
||||
draggable="false"
|
||||
onfocus={() => (showVerticalDots = true)}
|
||||
>
|
||||
<div id="people-card" class="relative" role="group">
|
||||
<a href={Route.viewPerson(person, { previousRoute: Route.people() })} draggable="false" class="group">
|
||||
<div class="size-full rounded-xl brightness-95 filter">
|
||||
<ImageThumbnail
|
||||
shadow
|
||||
@@ -62,27 +48,14 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{#if showVerticalDots}
|
||||
<div class="absolute inset-e-2 top-2 z-1">
|
||||
<ButtonContextMenu
|
||||
buttonClass="icon-white-drop-shadow"
|
||||
color="secondary"
|
||||
size="medium"
|
||||
<div class="absolute inset-e-2 top-2 opacity-0 group-hover:opacity-100 focus-within:opacity-100">
|
||||
<ContextMenuButton
|
||||
variant="filled"
|
||||
icon={mdiDotsVertical}
|
||||
title={$t('show_person_options')}
|
||||
>
|
||||
<MenuOption onClick={onHidePerson} icon={mdiEyeOffOutline} text={$t('hide_person')} />
|
||||
<ActionMenuItem action={SetDateOfBirth} />
|
||||
<MenuOption onClick={onMergePeople} icon={mdiAccountMultipleCheckOutline} text={$t('merge_people')} />
|
||||
<MenuOption
|
||||
onClick={onToggleFavorite}
|
||||
icon={person.isFavorite ? mdiHeartMinusOutline : mdiHeartOutline}
|
||||
text={person.isFavorite ? $t('unfavorite') : $t('to_favorite')}
|
||||
/>
|
||||
</ButtonContextMenu>
|
||||
class="icon-white-drop-shadow"
|
||||
translations={{ open_menu: $t('show_person_options') }}
|
||||
{items}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user