mirror of
https://github.com/immich-app/immich.git
synced 2025-05-31 12:15:47 -04:00
refactor: activity manager (#17943)
This commit is contained in:
parent
be5cc2cdf5
commit
436cff72b5
@ -1,32 +1,24 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
|
||||||
import { AppRoute, timeBeforeShowLoadingSpinner } from '$lib/constants';
|
|
||||||
import { getAssetThumbnailUrl, handlePromiseError } from '$lib/utils';
|
|
||||||
import { getAssetType } from '$lib/utils/asset-utils';
|
|
||||||
import { autoGrowHeight } from '$lib/actions/autogrow';
|
import { autoGrowHeight } from '$lib/actions/autogrow';
|
||||||
|
import { shortcut } from '$lib/actions/shortcut';
|
||||||
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
|
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
||||||
|
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
||||||
|
import { AppRoute, timeBeforeShowLoadingSpinner } from '$lib/constants';
|
||||||
|
import { activityManager } from '$lib/managers/activity-manager.svelte';
|
||||||
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
|
import { getAssetThumbnailUrl } from '$lib/utils';
|
||||||
|
import { getAssetType } from '$lib/utils/asset-utils';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { isTenMinutesApart } from '$lib/utils/timesince';
|
import { isTenMinutesApart } from '$lib/utils/timesince';
|
||||||
import {
|
import { ReactionType, type ActivityResponseDto, type AssetTypeEnum, type UserResponseDto } from '@immich/sdk';
|
||||||
ReactionType,
|
import { mdiClose, mdiDeleteOutline, mdiDotsVertical, mdiHeart, mdiSend } from '@mdi/js';
|
||||||
createActivity,
|
|
||||||
deleteActivity,
|
|
||||||
getActivities,
|
|
||||||
type ActivityResponseDto,
|
|
||||||
type AssetTypeEnum,
|
|
||||||
type UserResponseDto,
|
|
||||||
} from '@immich/sdk';
|
|
||||||
import { mdiClose, mdiDotsVertical, mdiHeart, mdiSend, mdiDeleteOutline } from '@mdi/js';
|
|
||||||
import * as luxon from 'luxon';
|
import * as luxon from 'luxon';
|
||||||
import { onMount } from 'svelte';
|
import { t } from 'svelte-i18n';
|
||||||
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
|
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
|
||||||
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
||||||
import { NotificationType, notificationController } from '../shared-components/notification/notification';
|
import { NotificationType, notificationController } from '../shared-components/notification/notification';
|
||||||
import UserAvatar from '../shared-components/user-avatar.svelte';
|
import UserAvatar from '../shared-components/user-avatar.svelte';
|
||||||
import { locale } from '$lib/stores/preferences.store';
|
|
||||||
import { shortcut } from '$lib/actions/shortcut';
|
|
||||||
import { t } from 'svelte-i18n';
|
|
||||||
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
|
||||||
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
|
||||||
|
|
||||||
const units: Intl.RelativeTimeFormatUnit[] = ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'];
|
const units: Intl.RelativeTimeFormatUnit[] = ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'];
|
||||||
|
|
||||||
@ -48,34 +40,16 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
reactions: ActivityResponseDto[];
|
|
||||||
user: UserResponseDto;
|
user: UserResponseDto;
|
||||||
assetId?: string | undefined;
|
assetId?: string | undefined;
|
||||||
albumId: string;
|
albumId: string;
|
||||||
assetType?: AssetTypeEnum | undefined;
|
assetType?: AssetTypeEnum | undefined;
|
||||||
albumOwnerId: string;
|
albumOwnerId: string;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
isLiked: ActivityResponseDto | null;
|
|
||||||
onDeleteComment: () => void;
|
|
||||||
onDeleteLike: () => void;
|
|
||||||
onAddComment: () => void;
|
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let { user, assetId = undefined, albumId, assetType = undefined, albumOwnerId, disabled, onClose }: Props = $props();
|
||||||
reactions = $bindable(),
|
|
||||||
user,
|
|
||||||
assetId = undefined,
|
|
||||||
albumId,
|
|
||||||
assetType = undefined,
|
|
||||||
albumOwnerId,
|
|
||||||
disabled,
|
|
||||||
isLiked,
|
|
||||||
onDeleteComment,
|
|
||||||
onDeleteLike,
|
|
||||||
onAddComment,
|
|
||||||
onClose,
|
|
||||||
}: Props = $props();
|
|
||||||
|
|
||||||
let innerHeight: number = $state(0);
|
let innerHeight: number = $state(0);
|
||||||
let activityHeight: number = $state(0);
|
let activityHeight: number = $state(0);
|
||||||
@ -85,36 +59,18 @@
|
|||||||
let message = $state('');
|
let message = $state('');
|
||||||
let isSendingMessage = $state(false);
|
let isSendingMessage = $state(false);
|
||||||
|
|
||||||
onMount(async () => {
|
const timeOptions: Intl.DateTimeFormatOptions = {
|
||||||
await getReactions();
|
|
||||||
});
|
|
||||||
|
|
||||||
const getReactions = async () => {
|
|
||||||
try {
|
|
||||||
reactions = await getActivities({ assetId, albumId });
|
|
||||||
} catch (error) {
|
|
||||||
handleError(error, $t('errors.unable_to_load_asset_activity'));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const timeOptions = {
|
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: '2-digit',
|
month: '2-digit',
|
||||||
day: '2-digit',
|
day: '2-digit',
|
||||||
hour: '2-digit',
|
hour: '2-digit',
|
||||||
minute: '2-digit',
|
minute: '2-digit',
|
||||||
hour12: false,
|
hour12: false,
|
||||||
} as Intl.DateTimeFormatOptions;
|
};
|
||||||
|
|
||||||
const handleDeleteReaction = async (reaction: ActivityResponseDto, index: number) => {
|
const handleDeleteReaction = async (reaction: ActivityResponseDto, index: number) => {
|
||||||
try {
|
try {
|
||||||
await deleteActivity({ id: reaction.id });
|
await activityManager.deleteActivity(reaction, index);
|
||||||
reactions.splice(index, 1);
|
|
||||||
if (isLiked && reaction.type === ReactionType.Like && reaction.id == isLiked.id) {
|
|
||||||
onDeleteLike();
|
|
||||||
} else {
|
|
||||||
onDeleteComment();
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteMessages: Record<ReactionType, string> = {
|
const deleteMessages: Record<ReactionType, string> = {
|
||||||
[ReactionType.Comment]: $t('comment_deleted'),
|
[ReactionType.Comment]: $t('comment_deleted'),
|
||||||
@ -135,13 +91,9 @@
|
|||||||
}
|
}
|
||||||
const timeout = setTimeout(() => (isSendingMessage = true), timeBeforeShowLoadingSpinner);
|
const timeout = setTimeout(() => (isSendingMessage = true), timeBeforeShowLoadingSpinner);
|
||||||
try {
|
try {
|
||||||
const data = await createActivity({
|
await activityManager.addActivity({ albumId, assetId, type: ReactionType.Comment, comment: message });
|
||||||
activityCreateDto: { albumId, assetId, type: ReactionType.Comment, comment: message },
|
|
||||||
});
|
|
||||||
reactions.push(data);
|
|
||||||
|
|
||||||
message = '';
|
message = '';
|
||||||
onAddComment();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, $t('errors.unable_to_add_comment'));
|
handleError(error, $t('errors.unable_to_add_comment'));
|
||||||
} finally {
|
} finally {
|
||||||
@ -156,7 +108,6 @@
|
|||||||
});
|
});
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (assetId && previousAssetId != assetId) {
|
if (assetId && previousAssetId != assetId) {
|
||||||
handlePromiseError(getReactions());
|
|
||||||
previousAssetId = assetId;
|
previousAssetId = assetId;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -184,7 +135,7 @@
|
|||||||
class="overflow-y-auto immich-scrollbar relative w-full px-2"
|
class="overflow-y-auto immich-scrollbar relative w-full px-2"
|
||||||
style="height: {divHeight}px;padding-bottom: {chatHeight}px"
|
style="height: {divHeight}px;padding-bottom: {chatHeight}px"
|
||||||
>
|
>
|
||||||
{#each reactions as reaction, index (reaction.id)}
|
{#each activityManager.activities as reaction, index (reaction.id)}
|
||||||
{#if reaction.type === ReactionType.Comment}
|
{#if reaction.type === ReactionType.Comment}
|
||||||
<div class="flex dark:bg-gray-800 bg-gray-200 py-3 ps-3 mt-3 rounded-lg gap-4 justify-start">
|
<div class="flex dark:bg-gray-800 bg-gray-200 py-3 ps-3 mt-3 rounded-lg gap-4 justify-start">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
@ -221,7 +172,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if (index != reactions.length - 1 && !shouldGroup(reactions[index].createdAt, reactions[index + 1].createdAt)) || index === reactions.length - 1}
|
{#if (index != activityManager.activities.length - 1 && !shouldGroup(activityManager.activities[index].createdAt, activityManager.activities[index + 1].createdAt)) || index === activityManager.activities.length - 1}
|
||||||
<div
|
<div
|
||||||
class="pt-1 px-2 text-right w-full text-sm text-gray-500 dark:text-gray-300"
|
class="pt-1 px-2 text-right w-full text-sm text-gray-500 dark:text-gray-300"
|
||||||
title={new Date(reaction.createdAt).toLocaleDateString(undefined, timeOptions)}
|
title={new Date(reaction.createdAt).toLocaleDateString(undefined, timeOptions)}
|
||||||
@ -273,7 +224,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if (index != reactions.length - 1 && isTenMinutesApart(reactions[index].createdAt, reactions[index + 1].createdAt)) || index === reactions.length - 1}
|
{#if (index != activityManager.activities.length - 1 && isTenMinutesApart(activityManager.activities[index].createdAt, activityManager.activities[index + 1].createdAt)) || index === activityManager.activities.length - 1}
|
||||||
<div
|
<div
|
||||||
class="pt-1 px-2 text-right w-full text-sm text-gray-500 dark:text-gray-300"
|
class="pt-1 px-2 text-right w-full text-sm text-gray-500 dark:text-gray-300"
|
||||||
title={new Date(reaction.createdAt).toLocaleDateString(navigator.language, timeOptions)}
|
title={new Date(reaction.createdAt).toLocaleDateString(navigator.language, timeOptions)}
|
||||||
|
@ -5,8 +5,8 @@
|
|||||||
import NextAssetAction from '$lib/components/asset-viewer/actions/next-asset-action.svelte';
|
import NextAssetAction from '$lib/components/asset-viewer/actions/next-asset-action.svelte';
|
||||||
import PreviousAssetAction from '$lib/components/asset-viewer/actions/previous-asset-action.svelte';
|
import PreviousAssetAction from '$lib/components/asset-viewer/actions/previous-asset-action.svelte';
|
||||||
import { AssetAction, ProjectionType } from '$lib/constants';
|
import { AssetAction, ProjectionType } from '$lib/constants';
|
||||||
|
import { activityManager } from '$lib/managers/activity-manager.svelte';
|
||||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||||
import { updateNumberOfComments } from '$lib/stores/activity.store';
|
|
||||||
import { closeEditorCofirm } from '$lib/stores/asset-editor.store';
|
import { closeEditorCofirm } from '$lib/stores/asset-editor.store';
|
||||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
import { isShowDetail } from '$lib/stores/preferences.store';
|
import { isShowDetail } from '$lib/stores/preferences.store';
|
||||||
@ -19,15 +19,9 @@
|
|||||||
import {
|
import {
|
||||||
AssetJobName,
|
AssetJobName,
|
||||||
AssetTypeEnum,
|
AssetTypeEnum,
|
||||||
ReactionType,
|
|
||||||
createActivity,
|
|
||||||
deleteActivity,
|
|
||||||
getActivities,
|
|
||||||
getActivityStatistics,
|
|
||||||
getAllAlbums,
|
getAllAlbums,
|
||||||
getStack,
|
getStack,
|
||||||
runAssetJobs,
|
runAssetJobs,
|
||||||
type ActivityResponseDto,
|
|
||||||
type AlbumResponseDto,
|
type AlbumResponseDto,
|
||||||
type AssetResponseDto,
|
type AssetResponseDto,
|
||||||
type PersonResponseDto,
|
type PersonResponseDto,
|
||||||
@ -61,7 +55,6 @@
|
|||||||
person?: PersonResponseDto | null;
|
person?: PersonResponseDto | null;
|
||||||
preAction?: PreAction | undefined;
|
preAction?: PreAction | undefined;
|
||||||
onAction?: OnAction | undefined;
|
onAction?: OnAction | undefined;
|
||||||
reactions?: ActivityResponseDto[];
|
|
||||||
showCloseButton?: boolean;
|
showCloseButton?: boolean;
|
||||||
onClose: (dto: { asset: AssetResponseDto }) => void;
|
onClose: (dto: { asset: AssetResponseDto }) => void;
|
||||||
onNext: () => Promise<HasAsset>;
|
onNext: () => Promise<HasAsset>;
|
||||||
@ -80,7 +73,6 @@
|
|||||||
person = null,
|
person = null,
|
||||||
preAction = undefined,
|
preAction = undefined,
|
||||||
onAction = undefined,
|
onAction = undefined,
|
||||||
reactions = $bindable([]),
|
|
||||||
showCloseButton,
|
showCloseButton,
|
||||||
onClose,
|
onClose,
|
||||||
onNext,
|
onNext,
|
||||||
@ -107,8 +99,6 @@
|
|||||||
let previewStackedAsset: AssetResponseDto | undefined = $state();
|
let previewStackedAsset: AssetResponseDto | undefined = $state();
|
||||||
let isShowActivity = $state(false);
|
let isShowActivity = $state(false);
|
||||||
let isShowEditor = $state(false);
|
let isShowEditor = $state(false);
|
||||||
let isLiked: ActivityResponseDto | null = $state(null);
|
|
||||||
let numberOfComments = $state(0);
|
|
||||||
let fullscreenElement = $state<Element>();
|
let fullscreenElement = $state<Element>();
|
||||||
let unsubscribes: (() => void)[] = [];
|
let unsubscribes: (() => void)[] = [];
|
||||||
let selectedEditType: string = $state('');
|
let selectedEditType: string = $state('');
|
||||||
@ -136,59 +126,20 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddComment = () => {
|
|
||||||
numberOfComments++;
|
|
||||||
updateNumberOfComments(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRemoveComment = () => {
|
|
||||||
numberOfComments--;
|
|
||||||
updateNumberOfComments(-1);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFavorite = async () => {
|
const handleFavorite = async () => {
|
||||||
if (album && album.isActivityEnabled) {
|
if (album && album.isActivityEnabled) {
|
||||||
try {
|
try {
|
||||||
if (isLiked) {
|
await activityManager.toggleLike();
|
||||||
const activityId = isLiked.id;
|
|
||||||
await deleteActivity({ id: activityId });
|
|
||||||
reactions = reactions.filter((reaction) => reaction.id !== activityId);
|
|
||||||
isLiked = null;
|
|
||||||
} else {
|
|
||||||
const data = await createActivity({
|
|
||||||
activityCreateDto: { albumId: album.id, assetId: asset.id, type: ReactionType.Like },
|
|
||||||
});
|
|
||||||
|
|
||||||
isLiked = data;
|
|
||||||
reactions = [...reactions, isLiked];
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, $t('errors.unable_to_change_favorite'));
|
handleError(error, $t('errors.unable_to_change_favorite'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFavorite = async () => {
|
const updateComments = async () => {
|
||||||
if (album && $user) {
|
|
||||||
try {
|
|
||||||
const data = await getActivities({
|
|
||||||
userId: $user.id,
|
|
||||||
assetId: asset.id,
|
|
||||||
albumId: album.id,
|
|
||||||
$type: ReactionType.Like,
|
|
||||||
});
|
|
||||||
isLiked = data.length > 0 ? data[0] : null;
|
|
||||||
} catch (error) {
|
|
||||||
handleError(error, $t('errors.unable_to_load_liked_status'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getNumberOfComments = async () => {
|
|
||||||
if (album) {
|
if (album) {
|
||||||
try {
|
try {
|
||||||
const { comments } = await getActivityStatistics({ assetId: asset.id, albumId: album.id });
|
await activityManager.refreshActivities(album.id, asset.id);
|
||||||
numberOfComments = comments;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, $t('errors.unable_to_get_comments_number'));
|
handleError(error, $t('errors.unable_to_get_comments_number'));
|
||||||
}
|
}
|
||||||
@ -227,6 +178,10 @@
|
|||||||
if (!sharedLink) {
|
if (!sharedLink) {
|
||||||
await handleGetAllAlbums();
|
await handleGetAllAlbums();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (album) {
|
||||||
|
activityManager.init(album.id, asset.id);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
@ -241,6 +196,8 @@
|
|||||||
for (const unsubscribe of unsubscribes) {
|
for (const unsubscribe of unsubscribes) {
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
activityManager.reset();
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleGetAllAlbums = async () => {
|
const handleGetAllAlbums = async () => {
|
||||||
@ -402,14 +359,13 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (album && !album.isActivityEnabled && numberOfComments === 0) {
|
if (album && !album.isActivityEnabled && activityManager.commentCount === 0) {
|
||||||
isShowActivity = false;
|
isShowActivity = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (isShared && asset.id) {
|
if (isShared && asset.id) {
|
||||||
handlePromiseError(getFavorite());
|
handlePromiseError(updateComments());
|
||||||
handlePromiseError(getNumberOfComments());
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
@ -547,12 +503,12 @@
|
|||||||
onVideoStarted={handleVideoStarted}
|
onVideoStarted={handleVideoStarted}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $slideshowState === SlideshowState.None && isShared && ((album && album.isActivityEnabled) || numberOfComments > 0)}
|
{#if $slideshowState === SlideshowState.None && isShared && ((album && album.isActivityEnabled) || activityManager.commentCount > 0)}
|
||||||
<div class="z-[9999] absolute bottom-0 end-0 mb-20 me-8">
|
<div class="z-[9999] absolute bottom-0 end-0 mb-20 me-8">
|
||||||
<ActivityStatus
|
<ActivityStatus
|
||||||
disabled={!album?.isActivityEnabled}
|
disabled={!album?.isActivityEnabled}
|
||||||
{isLiked}
|
isLiked={activityManager.isLiked}
|
||||||
{numberOfComments}
|
numberOfComments={activityManager.commentCount}
|
||||||
onFavorite={handleFavorite}
|
onFavorite={handleFavorite}
|
||||||
onOpenActivityTab={handleOpenActivity}
|
onOpenActivityTab={handleOpenActivity}
|
||||||
/>
|
/>
|
||||||
@ -642,11 +598,6 @@
|
|||||||
albumOwnerId={album.ownerId}
|
albumOwnerId={album.ownerId}
|
||||||
albumId={album.id}
|
albumId={album.id}
|
||||||
assetId={asset.id}
|
assetId={asset.id}
|
||||||
{isLiked}
|
|
||||||
bind:reactions
|
|
||||||
onAddComment={handleAddComment}
|
|
||||||
onDeleteComment={handleRemoveComment}
|
|
||||||
onDeleteLike={() => (isLiked = null)}
|
|
||||||
onClose={() => (isShowActivity = false)}
|
onClose={() => (isShowActivity = false)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
113
web/src/lib/managers/activity-manager.svelte.ts
Normal file
113
web/src/lib/managers/activity-manager.svelte.ts
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import { user } from '$lib/stores/user.store';
|
||||||
|
import { handlePromiseError } from '$lib/utils';
|
||||||
|
import {
|
||||||
|
createActivity,
|
||||||
|
deleteActivity,
|
||||||
|
getActivities,
|
||||||
|
getActivityStatistics,
|
||||||
|
ReactionLevel,
|
||||||
|
ReactionType,
|
||||||
|
type ActivityCreateDto,
|
||||||
|
type ActivityResponseDto,
|
||||||
|
} from '@immich/sdk';
|
||||||
|
import { get } from 'svelte/store';
|
||||||
|
|
||||||
|
class ActivityManager {
|
||||||
|
#albumId = $state<string | undefined>();
|
||||||
|
#assetId = $state<string | undefined>();
|
||||||
|
#activities = $state<ActivityResponseDto[]>([]);
|
||||||
|
#commentCount = $state(0);
|
||||||
|
#isLiked = $state<ActivityResponseDto | null>(null);
|
||||||
|
|
||||||
|
get activities() {
|
||||||
|
return this.#activities;
|
||||||
|
}
|
||||||
|
|
||||||
|
get commentCount() {
|
||||||
|
return this.#commentCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isLiked() {
|
||||||
|
return this.#isLiked;
|
||||||
|
}
|
||||||
|
|
||||||
|
init(albumId: string, assetId?: string) {
|
||||||
|
this.#albumId = albumId;
|
||||||
|
this.#assetId = assetId;
|
||||||
|
}
|
||||||
|
|
||||||
|
async addActivity(dto: ActivityCreateDto) {
|
||||||
|
if (this.#albumId === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const activity = await createActivity({ activityCreateDto: dto });
|
||||||
|
this.#activities = [...this.#activities, activity];
|
||||||
|
|
||||||
|
if (activity.type === ReactionType.Comment) {
|
||||||
|
this.#commentCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePromiseError(this.refreshActivities(this.#albumId, this.#assetId));
|
||||||
|
return activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteActivity(activity: ActivityResponseDto, index?: number) {
|
||||||
|
if (!this.#albumId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activity.type === ReactionType.Comment) {
|
||||||
|
this.#commentCount--;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#activities = index
|
||||||
|
? this.#activities.splice(index, 1)
|
||||||
|
: this.#activities.filter(({ id }) => id !== activity.id);
|
||||||
|
|
||||||
|
await deleteActivity({ id: activity.id });
|
||||||
|
handlePromiseError(this.refreshActivities(this.#albumId, this.#assetId));
|
||||||
|
}
|
||||||
|
|
||||||
|
async toggleLike() {
|
||||||
|
if (!this.#albumId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.#isLiked) {
|
||||||
|
await this.deleteActivity(this.#isLiked);
|
||||||
|
this.#isLiked = null;
|
||||||
|
} else {
|
||||||
|
this.#isLiked = (await this.addActivity({
|
||||||
|
albumId: this.#albumId,
|
||||||
|
assetId: this.#assetId,
|
||||||
|
type: ReactionType.Like,
|
||||||
|
}))!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async refreshActivities(albumId: string, assetId?: string) {
|
||||||
|
this.#activities = await getActivities({ albumId, assetId });
|
||||||
|
|
||||||
|
const [liked] = await getActivities({
|
||||||
|
albumId,
|
||||||
|
assetId,
|
||||||
|
userId: get(user).id,
|
||||||
|
$type: ReactionType.Like,
|
||||||
|
level: assetId ? undefined : ReactionLevel.Album,
|
||||||
|
});
|
||||||
|
this.#isLiked = liked ?? null;
|
||||||
|
|
||||||
|
const { comments } = await getActivityStatistics({ albumId, assetId });
|
||||||
|
this.#commentCount = comments;
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.#albumId = undefined;
|
||||||
|
this.#assetId = undefined;
|
||||||
|
this.#activities = [];
|
||||||
|
this.#commentCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const activityManager = new ActivityManager();
|
@ -1,11 +0,0 @@
|
|||||||
import { writable } from 'svelte/store';
|
|
||||||
|
|
||||||
export const numberOfComments = writable<number>(0);
|
|
||||||
|
|
||||||
export const setNumberOfComments = (number: number) => {
|
|
||||||
numberOfComments.set(number);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const updateNumberOfComments = (addOrRemove: 1 | -1) => {
|
|
||||||
numberOfComments.update((n) => n + addOrRemove);
|
|
||||||
};
|
|
@ -22,9 +22,10 @@
|
|||||||
import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte';
|
import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte';
|
||||||
import RemoveFromAlbum from '$lib/components/photos-page/actions/remove-from-album.svelte';
|
import RemoveFromAlbum from '$lib/components/photos-page/actions/remove-from-album.svelte';
|
||||||
import SelectAllAssets from '$lib/components/photos-page/actions/select-all-assets.svelte';
|
import SelectAllAssets from '$lib/components/photos-page/actions/select-all-assets.svelte';
|
||||||
|
import TagAction from '$lib/components/photos-page/actions/tag-action.svelte';
|
||||||
import AssetGrid from '$lib/components/photos-page/asset-grid.svelte';
|
import AssetGrid from '$lib/components/photos-page/asset-grid.svelte';
|
||||||
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
|
||||||
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
|
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
|
||||||
|
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
||||||
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
||||||
import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
|
import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
|
||||||
import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte';
|
import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte';
|
||||||
@ -33,14 +34,16 @@
|
|||||||
notificationController,
|
notificationController,
|
||||||
} from '$lib/components/shared-components/notification/notification';
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
|
import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
|
||||||
import { AppRoute, AlbumPageViewMode } from '$lib/constants';
|
import { AlbumPageViewMode, AppRoute } from '$lib/constants';
|
||||||
import { numberOfComments, setNumberOfComments, updateNumberOfComments } from '$lib/stores/activity.store';
|
import { activityManager } from '$lib/managers/activity-manager.svelte';
|
||||||
|
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
import { AssetStore } from '$lib/stores/assets-store.svelte';
|
import { AssetStore } from '$lib/stores/assets-store.svelte';
|
||||||
import { SlideshowNavigation, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
|
import { SlideshowNavigation, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
|
||||||
import { preferences, user } from '$lib/stores/user.store';
|
import { preferences, user } from '$lib/stores/user.store';
|
||||||
import { handlePromiseError } from '$lib/utils';
|
import { handlePromiseError } from '$lib/utils';
|
||||||
import { downloadAlbum, cancelMultiselect } from '$lib/utils/asset-utils';
|
import { confirmAlbumDelete } from '$lib/utils/album-utils';
|
||||||
|
import { cancelMultiselect, downloadAlbum } from '$lib/utils/asset-utils';
|
||||||
import { openFileUploadDialog } from '$lib/utils/file-uploader';
|
import { openFileUploadDialog } from '$lib/utils/file-uploader';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import {
|
import {
|
||||||
@ -53,18 +56,11 @@
|
|||||||
import {
|
import {
|
||||||
AlbumUserRole,
|
AlbumUserRole,
|
||||||
AssetOrder,
|
AssetOrder,
|
||||||
ReactionLevel,
|
|
||||||
ReactionType,
|
|
||||||
addAssetsToAlbum,
|
addAssetsToAlbum,
|
||||||
addUsersToAlbum,
|
addUsersToAlbum,
|
||||||
createActivity,
|
|
||||||
deleteActivity,
|
|
||||||
deleteAlbum,
|
deleteAlbum,
|
||||||
getActivities,
|
|
||||||
getActivityStatistics,
|
|
||||||
getAlbumInfo,
|
getAlbumInfo,
|
||||||
updateAlbumInfo,
|
updateAlbumInfo,
|
||||||
type ActivityResponseDto,
|
|
||||||
type AlbumUserAddDto,
|
type AlbumUserAddDto,
|
||||||
} from '@immich/sdk';
|
} from '@immich/sdk';
|
||||||
import {
|
import {
|
||||||
@ -80,13 +76,10 @@
|
|||||||
mdiPresentationPlay,
|
mdiPresentationPlay,
|
||||||
mdiShareVariantOutline,
|
mdiShareVariantOutline,
|
||||||
} from '@mdi/js';
|
} from '@mdi/js';
|
||||||
|
import { onDestroy } from 'svelte';
|
||||||
|
import { t } from 'svelte-i18n';
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import { t } from 'svelte-i18n';
|
|
||||||
import { onDestroy } from 'svelte';
|
|
||||||
import { confirmAlbumDelete } from '$lib/utils/album-utils';
|
|
||||||
import TagAction from '$lib/components/photos-page/actions/tag-action.svelte';
|
|
||||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: PageData;
|
data: PageData;
|
||||||
@ -103,8 +96,6 @@
|
|||||||
let viewMode: AlbumPageViewMode = $state(AlbumPageViewMode.VIEW);
|
let viewMode: AlbumPageViewMode = $state(AlbumPageViewMode.VIEW);
|
||||||
let isCreatingSharedAlbum = $state(false);
|
let isCreatingSharedAlbum = $state(false);
|
||||||
let isShowActivity = $state(false);
|
let isShowActivity = $state(false);
|
||||||
let isLiked: ActivityResponseDto | null = $state(null);
|
|
||||||
let reactions: ActivityResponseDto[] = $state([]);
|
|
||||||
let albumOrder: AssetOrder | undefined = $state(data.album.order);
|
let albumOrder: AssetOrder | undefined = $state(data.album.order);
|
||||||
|
|
||||||
const assetInteraction = new AssetInteraction();
|
const assetInteraction = new AssetInteraction();
|
||||||
@ -154,44 +145,15 @@
|
|||||||
|
|
||||||
const handleFavorite = async () => {
|
const handleFavorite = async () => {
|
||||||
try {
|
try {
|
||||||
if (isLiked) {
|
await activityManager.toggleLike();
|
||||||
const activityId = isLiked.id;
|
|
||||||
await deleteActivity({ id: activityId });
|
|
||||||
reactions = reactions.filter((reaction) => reaction.id !== activityId);
|
|
||||||
isLiked = null;
|
|
||||||
} else {
|
|
||||||
isLiked = await createActivity({
|
|
||||||
activityCreateDto: { albumId: album.id, type: ReactionType.Like },
|
|
||||||
});
|
|
||||||
reactions = [...reactions, isLiked];
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, $t('errors.cant_change_asset_favorite'));
|
handleError(error, $t('errors.cant_change_asset_favorite'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFavorite = async () => {
|
const updateComments = async () => {
|
||||||
if ($user) {
|
|
||||||
try {
|
|
||||||
const data = await getActivities({
|
|
||||||
userId: $user.id,
|
|
||||||
albumId: album.id,
|
|
||||||
$type: ReactionType.Like,
|
|
||||||
level: ReactionLevel.Album,
|
|
||||||
});
|
|
||||||
if (data.length > 0) {
|
|
||||||
isLiked = data[0];
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
handleError(error, $t('errors.unable_to_load_liked_status'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getNumberOfComments = async () => {
|
|
||||||
try {
|
try {
|
||||||
const { comments } = await getActivityStatistics({ albumId: album.id });
|
await activityManager.refreshActivities(album.id);
|
||||||
setNumberOfComments(comments);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, $t('errors.cant_get_number_of_comments'));
|
handleError(error, $t('errors.cant_get_number_of_comments'));
|
||||||
}
|
}
|
||||||
@ -398,7 +360,7 @@
|
|||||||
let albumId = $derived(album.id);
|
let albumId = $derived(album.id);
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!album.isActivityEnabled && $numberOfComments === 0) {
|
if (!album.isActivityEnabled && activityManager.commentCount === 0) {
|
||||||
isShowActivity = false;
|
isShowActivity = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -412,7 +374,16 @@
|
|||||||
void assetStore.updateOptions({ isArchived: false, withPartners: true, timelineAlbumId: albumId });
|
void assetStore.updateOptions({ isArchived: false, withPartners: true, timelineAlbumId: albumId });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
onDestroy(() => assetStore.destroy());
|
|
||||||
|
$effect(() => {
|
||||||
|
activityManager.reset();
|
||||||
|
activityManager.init(album.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
activityManager.reset();
|
||||||
|
assetStore.destroy();
|
||||||
|
});
|
||||||
// let timelineStore = new AssetStore();
|
// let timelineStore = new AssetStore();
|
||||||
// $effect(() => void timelineStore.updateOptions({ isArchived: false, withPartners: true, timelineAlbumId: albumId }));
|
// $effect(() => void timelineStore.updateOptions({ isArchived: false, withPartners: true, timelineAlbumId: albumId }));
|
||||||
// onDestroy(() => timelineStore.destroy());
|
// onDestroy(() => timelineStore.destroy());
|
||||||
@ -420,7 +391,7 @@
|
|||||||
let isOwned = $derived($user.id == album.ownerId);
|
let isOwned = $derived($user.id == album.ownerId);
|
||||||
|
|
||||||
let showActivityStatus = $derived(
|
let showActivityStatus = $derived(
|
||||||
album.albumUsers.length > 0 && !$showAssetViewer && (album.isActivityEnabled || $numberOfComments > 0),
|
album.albumUsers.length > 0 && !$showAssetViewer && (album.isActivityEnabled || activityManager.commentCount > 0),
|
||||||
);
|
);
|
||||||
let isEditor = $derived(
|
let isEditor = $derived(
|
||||||
album.albumUsers.find(({ user: { id } }) => id === $user.id)?.role === AlbumUserRole.Editor ||
|
album.albumUsers.find(({ user: { id } }) => id === $user.id)?.role === AlbumUserRole.Editor ||
|
||||||
@ -430,8 +401,7 @@
|
|||||||
let albumHasViewers = $derived(album.albumUsers.some(({ role }) => role === AlbumUserRole.Viewer));
|
let albumHasViewers = $derived(album.albumUsers.some(({ role }) => role === AlbumUserRole.Viewer));
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (album.albumUsers.length > 0) {
|
if (album.albumUsers.length > 0) {
|
||||||
handlePromiseError(getFavorite());
|
handlePromiseError(updateComments());
|
||||||
handlePromiseError(getNumberOfComments());
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const isShared = $derived(viewMode === AlbumPageViewMode.SELECT_ASSETS ? false : album.albumUsers.length > 0);
|
const isShared = $derived(viewMode === AlbumPageViewMode.SELECT_ASSETS ? false : album.albumUsers.length > 0);
|
||||||
@ -711,8 +681,8 @@
|
|||||||
<div class="absolute z-[2] bottom-0 end-0 mb-6 me-6 justify-self-end">
|
<div class="absolute z-[2] bottom-0 end-0 mb-6 me-6 justify-self-end">
|
||||||
<ActivityStatus
|
<ActivityStatus
|
||||||
disabled={!album.isActivityEnabled}
|
disabled={!album.isActivityEnabled}
|
||||||
{isLiked}
|
isLiked={activityManager.isLiked}
|
||||||
numberOfComments={$numberOfComments}
|
numberOfComments={activityManager.commentCount}
|
||||||
onFavorite={handleFavorite}
|
onFavorite={handleFavorite}
|
||||||
onOpenActivityTab={handleOpenAndCloseActivityTab}
|
onOpenActivityTab={handleOpenAndCloseActivityTab}
|
||||||
/>
|
/>
|
||||||
@ -733,11 +703,6 @@
|
|||||||
disabled={!album.isActivityEnabled}
|
disabled={!album.isActivityEnabled}
|
||||||
albumOwnerId={album.ownerId}
|
albumOwnerId={album.ownerId}
|
||||||
albumId={album.id}
|
albumId={album.id}
|
||||||
{isLiked}
|
|
||||||
bind:reactions
|
|
||||||
onAddComment={() => updateNumberOfComments(1)}
|
|
||||||
onDeleteComment={() => updateNumberOfComments(-1)}
|
|
||||||
onDeleteLike={() => (isLiked = null)}
|
|
||||||
onClose={handleOpenAndCloseActivityTab}
|
onClose={handleOpenAndCloseActivityTab}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user