merge main
This commit is contained in:
parent
6faa597aaf
commit
ee4120c5f7
12 changed files with 475 additions and 275 deletions
|
@ -40,7 +40,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||
progressInPercentage: 0,
|
||||
cancelToken: CancellationToken(),
|
||||
autoBackup: Store.get(StoreKey.autoBackup, false),
|
||||
backgroundBackup: false,
|
||||
backgroundBackup: Store.get(StoreKey.backgroundBackup, false),
|
||||
backupRequireWifi: Store.get(StoreKey.backupRequireWifi, true),
|
||||
backupRequireCharging:
|
||||
Store.get(StoreKey.backupRequireCharging, false),
|
||||
|
@ -171,6 +171,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||
state.backupRequireCharging,
|
||||
);
|
||||
await Store.put(StoreKey.backupTriggerDelay, state.backupTriggerDelay);
|
||||
await Store.put(StoreKey.backgroundBackup, state.backgroundBackup);
|
||||
} else {
|
||||
state = state.copyWith(
|
||||
backgroundBackup: wasEnabled,
|
||||
|
@ -383,6 +384,9 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||
final isEnabled = await _backgroundService.isBackgroundBackupEnabled();
|
||||
|
||||
state = state.copyWith(backgroundBackup: isEnabled);
|
||||
if (isEnabled != Store.get(StoreKey.backgroundBackup, !isEnabled)) {
|
||||
Store.put(StoreKey.backgroundBackup, isEnabled);
|
||||
}
|
||||
|
||||
if (state.backupProgress != BackUpProgressEnum.inBackground) {
|
||||
await _getBackupAlbumsInfo();
|
||||
|
|
|
@ -156,6 +156,7 @@ enum StoreKey<T> {
|
|||
accessToken<String>(11, type: String),
|
||||
serverEndpoint<String>(12, type: String),
|
||||
autoBackup<bool>(13, type: bool),
|
||||
backgroundBackup<bool>(14, type: bool),
|
||||
// user settings from [AppSettingsEnum] below:
|
||||
loadPreview<bool>(100, type: bool),
|
||||
loadOriginal<bool>(101, type: bool),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Colorspace, SystemConfigKey } from '@app/infra/entities';
|
||||
import { AssetFaceEntity, Colorspace, SystemConfigKey } from '@app/infra/entities';
|
||||
import { BadRequestException, NotFoundException } from '@nestjs/common';
|
||||
import {
|
||||
IAccessRepositoryMock,
|
||||
|
@ -449,6 +449,23 @@ describe(PersonService.name, () => {
|
|||
expect(machineLearningMock.detectFaces).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should skip it the asset has already been processed', async () => {
|
||||
assetMock.getByIds.mockResolvedValue([
|
||||
{
|
||||
...assetStub.noResizePath,
|
||||
faces: [
|
||||
{
|
||||
id: 'asset-face-1',
|
||||
assetId: assetStub.noResizePath.id,
|
||||
personId: faceStub.face1.personId,
|
||||
} as AssetFaceEntity,
|
||||
],
|
||||
},
|
||||
]);
|
||||
await sut.handleRecognizeFaces({ id: assetStub.noResizePath.id });
|
||||
expect(machineLearningMock.detectFaces).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle no results', async () => {
|
||||
machineLearningMock.detectFaces.mockResolvedValue([]);
|
||||
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
||||
|
|
|
@ -217,7 +217,7 @@ export class PersonService {
|
|||
}
|
||||
|
||||
const [asset] = await this.assetRepository.getByIds([id]);
|
||||
if (!asset || !asset.resizePath) {
|
||||
if (!asset || !asset.resizePath || asset.faces?.length > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
33
web/src/lib/components/asset-viewer/activity-status.svelte
Normal file
33
web/src/lib/components/asset-viewer/activity-status.svelte
Normal file
|
@ -0,0 +1,33 @@
|
|||
<script lang="ts">
|
||||
import { mdiCommentOutline, mdiHeart, mdiHeartOutline } from '@mdi/js';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import Icon from '../elements/icon.svelte';
|
||||
import type { ActivityResponseDto } from '@api';
|
||||
|
||||
export let isLiked: ActivityResponseDto | null;
|
||||
export let numberOfComments: number | undefined;
|
||||
export let isShowActivity: boolean | undefined;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="w-full h-14 flex p-4 text-white items-center justify-center rounded-full gap-4 bg-immich-dark-bg bg-opacity-60"
|
||||
>
|
||||
<button on:click={() => dispatch('favorite')}>
|
||||
<!-- svelte-ignore missing-declaration -->
|
||||
<div class="items-center justify-center">
|
||||
<Icon path={isLiked ? mdiHeart : mdiHeartOutline} size={24} />
|
||||
</div>
|
||||
</button>
|
||||
<button on:click={() => dispatch('openActivityTab')}>
|
||||
<div class="flex gap-2 items-center justify-center">
|
||||
<Icon path={mdiCommentOutline} class="scale-x-[-1]" size={24} />
|
||||
{#if numberOfComments}
|
||||
<div class="text-xl">{numberOfComments}</div>
|
||||
{:else if !isShowActivity}
|
||||
<div class="text-lg">Say something</div>
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
|
@ -1,9 +1,9 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { createEventDispatcher, onMount } from 'svelte';
|
||||
import UserAvatar from '../shared-components/user-avatar.svelte';
|
||||
import { mdiClose, mdiHeart, mdiSend, mdiDotsVertical } from '@mdi/js';
|
||||
import Icon from '$lib/components/elements/icon.svelte';
|
||||
import { ActivityResponseDto, api, AssetTypeEnum, ReactionType, type UserResponseDto } from '@api';
|
||||
import { ActivityResponseDto, api, AssetTypeEnum, ReactionType, ThumbnailFormat, type UserResponseDto } from '@api';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { isTenMinutesApart } from '$lib/utils/timesince';
|
||||
import { clickOutside } from '$lib/utils/click-outside';
|
||||
|
@ -15,6 +15,13 @@
|
|||
|
||||
const units: Intl.RelativeTimeFormatUnit[] = ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'];
|
||||
|
||||
const shouldGroup = (currentDate: string, nextDate: string): boolean => {
|
||||
const currentDateTime = luxon.DateTime.fromISO(currentDate);
|
||||
const nextDateTime = luxon.DateTime.fromISO(nextDate);
|
||||
|
||||
return currentDateTime.hasSame(nextDateTime, 'hour') || currentDateTime.toRelative() === nextDateTime.toRelative();
|
||||
};
|
||||
|
||||
const timeSince = (dateTime: luxon.DateTime) => {
|
||||
const diff = dateTime.diffNow().shiftTo(...units);
|
||||
const unit = units.find((unit) => diff.get(unit) !== 0) || 'second';
|
||||
|
@ -27,9 +34,9 @@
|
|||
|
||||
export let reactions: ActivityResponseDto[];
|
||||
export let user: UserResponseDto;
|
||||
export let assetId: string;
|
||||
export let assetId: string | undefined = undefined;
|
||||
export let albumId: string;
|
||||
export let assetType: AssetTypeEnum;
|
||||
export let assetType: AssetTypeEnum | undefined = undefined;
|
||||
export let albumOwnerId: string;
|
||||
|
||||
let textArea: HTMLTextAreaElement;
|
||||
|
@ -37,7 +44,7 @@
|
|||
let activityHeight: number;
|
||||
let chatHeight: number;
|
||||
let divHeight: number;
|
||||
let previousAssetId: string | null;
|
||||
let previousAssetId: string | undefined = assetId;
|
||||
let message = '';
|
||||
let isSendingMessage = false;
|
||||
|
||||
|
@ -51,11 +58,14 @@
|
|||
}
|
||||
|
||||
$: {
|
||||
if (previousAssetId != assetId) {
|
||||
if (assetId && previousAssetId != assetId) {
|
||||
getReactions();
|
||||
previousAssetId = assetId;
|
||||
}
|
||||
}
|
||||
onMount(async () => {
|
||||
await getReactions();
|
||||
});
|
||||
|
||||
const getReactions = async () => {
|
||||
try {
|
||||
|
@ -161,11 +171,20 @@
|
|||
{#each reactions as reaction, index (reaction.id)}
|
||||
{#if reaction.type === 'comment'}
|
||||
<div class="flex dark:bg-gray-800 bg-gray-200 p-3 mx-2 mt-3 rounded-lg gap-4 justify-start">
|
||||
<div>
|
||||
<div class="flex items-center">
|
||||
<UserAvatar user={reaction.user} size="sm" />
|
||||
</div>
|
||||
|
||||
<div class="w-full leading-4 overflow-hidden self-center break-words text-sm">{reaction.comment}</div>
|
||||
{#if assetId === undefined && reaction.assetId}
|
||||
<div class="aspect-square w-[75px] h-[75px]">
|
||||
<img
|
||||
class="rounded-lg w-[75px] h-[75px] object-cover"
|
||||
src={api.getAssetThumbnailUrl(reaction.assetId, ThumbnailFormat.Webp)}
|
||||
alt="comment-thumbnail"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if reaction.user.id === user.id || albumOwnerId === user.id}
|
||||
<div class="flex items-start w-fit pt-[5px]" title="Delete comment">
|
||||
<button on:click={() => (!showDeleteReaction[index] ? showOptionsMenu(index) : '')}>
|
||||
|
@ -176,17 +195,18 @@
|
|||
<div>
|
||||
{#if showDeleteReaction[index]}
|
||||
<button
|
||||
class="absolute right-6 rounded-xl items-center bg-gray-300 dark:bg-slate-100 py-2 px-6 text-left text-sm font-medium text-immich-fg hover:bg-red-300 focus:outline-none focus:ring-2 focus:ring-inset dark:text-immich-dark-bg dark:hover:bg-red-300 transition-colors"
|
||||
class="absolute right-6 rounded-xl items-center bg-gray-300 dark:bg-slate-100 py-3 px-6 text-left text-sm font-medium text-immich-fg hover:bg-red-300 focus:outline-none focus:ring-2 focus:ring-inset dark:text-immich-dark-bg dark:hover:bg-red-100 transition-colors"
|
||||
use:clickOutside
|
||||
on:outclick={() => (showDeleteReaction[index] = false)}
|
||||
on:click={() => handleDeleteReaction(reaction, index)}
|
||||
>
|
||||
Delete
|
||||
Remove
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if (index != reactions.length - 1 && isTenMinutesApart(reactions[index].createdAt, reactions[index + 1].createdAt)) || index === reactions.length - 1}
|
||||
|
||||
{#if (index != reactions.length - 1 && !shouldGroup(reactions[index].createdAt, reactions[index + 1].createdAt)) || index === reactions.length - 1}
|
||||
<div
|
||||
class=" px-2 text-right w-full text-sm text-gray-500 dark:text-gray-300"
|
||||
title={new Date(reaction.createdAt).toLocaleDateString(undefined, timeOptions)}
|
||||
|
@ -196,17 +216,26 @@
|
|||
{/if}
|
||||
{:else if reaction.type === 'like'}
|
||||
<div class="relative">
|
||||
<div class="flex p-2 mx-2 mt-2 rounded-full gap-2 items-center text-sm">
|
||||
<div class="flex p-3 mx-2 mt-3 rounded-full gap-4 items-center text-sm">
|
||||
<div class="text-red-600"><Icon path={mdiHeart} size={20} /></div>
|
||||
|
||||
<div
|
||||
class="w-full"
|
||||
title={`${reaction.user.firstName} ${reaction.user.lastName} (${reaction.user.email})`}
|
||||
>
|
||||
{`${reaction.user.firstName} ${reaction.user.lastName} liked this ${getAssetType(
|
||||
assetType,
|
||||
).toLowerCase()}`}
|
||||
{`${reaction.user.firstName} ${reaction.user.lastName} liked ${
|
||||
assetType ? `this ${getAssetType(assetType).toLowerCase()}` : 'it'
|
||||
}`}
|
||||
</div>
|
||||
{#if assetId === undefined && reaction.assetId}
|
||||
<div class="aspect-square w-[75px] h-[75px]">
|
||||
<img
|
||||
class="rounded-lg w-[75px] h-[75px] object-cover"
|
||||
src={api.getAssetThumbnailUrl(reaction.assetId, ThumbnailFormat.Webp)}
|
||||
alt="like-thumbnail"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if reaction.user.id === user.id || albumOwnerId === user.id}
|
||||
<div class="flex items-start w-fit" title="Delete like">
|
||||
<button on:click={() => (!showDeleteReaction[index] ? showOptionsMenu(index) : '')}>
|
||||
|
@ -217,12 +246,12 @@
|
|||
<div>
|
||||
{#if showDeleteReaction[index]}
|
||||
<button
|
||||
class="absolute top-2 right-6 rounded-xl items-center bg-gray-300 dark:bg-slate-100 p-3 text-left text-sm font-medium text-immich-fg hover:bg-gray-400 focus:outline-none focus:ring-2 focus:ring-inset dark:text-immich-dark-bg"
|
||||
class="absolute right-6 rounded-xl items-center bg-gray-300 dark:bg-slate-100 py-3 px-6 text-left text-sm font-medium text-immich-fg hover:bg-red-300 focus:outline-none focus:ring-2 focus:ring-inset dark:text-immich-dark-bg dark:hover:bg-red-100 transition-colors"
|
||||
use:clickOutside
|
||||
on:outclick={() => (showDeleteReaction[index] = false)}
|
||||
on:click={() => handleDeleteReaction(reaction, index)}
|
||||
>
|
||||
Delete Like
|
||||
Remove
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -266,8 +295,8 @@
|
|||
</div>
|
||||
</div>
|
||||
{:else if message}
|
||||
<div class="flex items-end w-fit ml-0 text-immich-primary dark:text-white">
|
||||
<CircleIconButton size="15" icon={mdiSend} />
|
||||
<div class="flex items-end w-fit ml-0">
|
||||
<CircleIconButton size="15" icon={mdiSend} iconColor={'dark'} hoverColor={'rgb(173,203,250)'} />
|
||||
</div>
|
||||
{/if}
|
||||
</form>
|
||||
|
|
|
@ -33,18 +33,13 @@
|
|||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||
import { SlideshowHistory } from '$lib/utils/slideshow-history';
|
||||
import { featureFlags } from '$lib/stores/server-config.store';
|
||||
import {
|
||||
mdiHeartOutline,
|
||||
mdiHeart,
|
||||
mdiCommentOutline,
|
||||
mdiChevronLeft,
|
||||
mdiChevronRight,
|
||||
mdiImageBrokenVariant,
|
||||
} from '@mdi/js';
|
||||
import { mdiChevronLeft, mdiChevronRight, mdiImageBrokenVariant } from '@mdi/js';
|
||||
import Icon from '$lib/components/elements/icon.svelte';
|
||||
import Thumbnail from '../assets/thumbnail/thumbnail.svelte';
|
||||
import { stackAssetsStore } from '$lib/stores/stacked-asset.store';
|
||||
import ActivityViewer from './activity-viewer.svelte';
|
||||
import ActivityStatus from './activity-status.svelte';
|
||||
import { updateNumberOfComments } from '$lib/stores/activity.store';
|
||||
import { SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
|
||||
import SlideshowBar from './slideshow-bar.svelte';
|
||||
|
||||
|
@ -55,7 +50,7 @@
|
|||
$: isTrashEnabled = $featureFlags.trash;
|
||||
export let force = false;
|
||||
export let withStacked = false;
|
||||
export let isShared = true;
|
||||
export let isShared = false;
|
||||
export let user: UserResponseDto | null = null;
|
||||
export let album: AlbumResponseDto | null = null;
|
||||
|
||||
|
@ -109,6 +104,16 @@
|
|||
}
|
||||
}
|
||||
|
||||
const handleAddComment = () => {
|
||||
numberOfComments++;
|
||||
updateNumberOfComments(1);
|
||||
};
|
||||
|
||||
const handleRemoveComment = () => {
|
||||
numberOfComments--;
|
||||
updateNumberOfComments(-1);
|
||||
};
|
||||
|
||||
const handleFavorite = async () => {
|
||||
if (album) {
|
||||
try {
|
||||
|
@ -658,25 +663,13 @@
|
|||
{/if}
|
||||
{#if $slideshowState === SlideshowState.None && isShared}
|
||||
<div class="z-[9999] absolute bottom-0 right-0 mb-6 mr-6 justify-self-end">
|
||||
<div
|
||||
class="w-full h-14 flex p-4 text-white items-center justify-center rounded-full gap-4 bg-immich-dark-bg bg-opacity-60"
|
||||
>
|
||||
<button on:click={handleFavorite}>
|
||||
<div class="items-center justify-center">
|
||||
<Icon path={isLiked ? mdiHeart : mdiHeartOutline} size={24} />
|
||||
</div>
|
||||
</button>
|
||||
<button on:click={handleOpenActivity}>
|
||||
<div class="flex gap-2 items-center justify-center">
|
||||
<Icon path={mdiCommentOutline} class="scale-x-[-1]" size={24} />
|
||||
{#if numberOfComments}
|
||||
<div class="text-xl">{numberOfComments}</div>
|
||||
{:else if !isShowActivity && !$isShowDetail}
|
||||
<div class="text-lg">Say something</div>
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<ActivityStatus
|
||||
{isLiked}
|
||||
{numberOfComments}
|
||||
{isShowActivity}
|
||||
on:favorite={handleFavorite}
|
||||
on:openActivityTab={handleOpenActivity}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/key}
|
||||
|
@ -746,7 +739,7 @@
|
|||
<div
|
||||
transition:fly={{ duration: 150 }}
|
||||
id="activity-panel"
|
||||
class="z-[1002] row-start-1 row-span-5 w-[460px] overflow-y-auto bg-immich-bg transition-all dark:border-l dark:border-l-immich-dark-gray dark:bg-immich-dark-bg pl-4"
|
||||
class="z-[1002] row-start-1 row-span-5 w-[360px] md:w-[460px] overflow-y-auto bg-immich-bg transition-all dark:border-l dark:border-l-immich-dark-gray dark:bg-immich-dark-bg pl-4"
|
||||
translate="yes"
|
||||
>
|
||||
<ActivityViewer
|
||||
|
@ -756,8 +749,8 @@
|
|||
albumId={album.id}
|
||||
assetId={asset.id}
|
||||
bind:reactions
|
||||
on:addComment={() => numberOfComments++}
|
||||
on:deleteComment={() => numberOfComments--}
|
||||
on:addComment={handleAddComment}
|
||||
on:deleteComment={handleRemoveComment}
|
||||
on:deleteLike={() => (isLiked = null)}
|
||||
on:close={() => (isShowActivity = false)}
|
||||
/>
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
export let isOpacity = false;
|
||||
export let forceDark = false;
|
||||
export let hideMobile = false;
|
||||
export let iconColor = 'currentColor';
|
||||
</script>
|
||||
|
||||
<button
|
||||
|
@ -23,7 +24,7 @@
|
|||
{hideMobile && 'hidden sm:flex'}"
|
||||
on:click
|
||||
>
|
||||
<Icon path={icon} {size} />
|
||||
<Icon path={icon} {size} color={iconColor} />
|
||||
<slot />
|
||||
</button>
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<div in:fly={{ y: 10, duration: 200 }} class="fixed top-0 z-[100] w-full bg-transparent">
|
||||
<div in:fly={{ y: 10, duration: 200 }} class="absolute top-0 w-full z-[100] bg-transparent">
|
||||
<div
|
||||
id="asset-selection-app-bar"
|
||||
class={`grid grid-cols-[10%_80%_10%] justify-between md:grid-cols-[20%_60%_20%] lg:grid-cols-3 ${appBarBorder} mx-2 mt-2 place-items-center rounded-lg p-2 transition-all ${tailwindClasses} dark:bg-immich-dark-gray ${
|
||||
|
|
|
@ -93,7 +93,7 @@
|
|||
{#if $assetStore.timelineHeight > height}
|
||||
<div
|
||||
id="immich-scrubbable-scrollbar"
|
||||
class="fixed right-0 z-[1] select-none bg-immich-bg hover:cursor-row-resize"
|
||||
class="absolute right-0 z-[1] select-none bg-immich-bg hover:cursor-row-resize"
|
||||
style:width={isDragging ? '100vw' : '60px'}
|
||||
style:height={height + 'px'}
|
||||
style:background-color={isDragging ? 'transparent' : 'transparent'}
|
||||
|
|
11
web/src/lib/stores/activity.store.ts
Normal file
11
web/src/lib/stores/activity.store.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { writable } from 'svelte/store';
|
||||
|
||||
export const numberOfComments = writable<number | undefined>(undefined);
|
||||
|
||||
export const setNumberOfComments = (number: number) => {
|
||||
numberOfComments.set(number);
|
||||
};
|
||||
|
||||
export const updateNumberOfComments = (addOrRemove: 1 | -1) => {
|
||||
numberOfComments.update((n) => (n ? n + addOrRemove : undefined));
|
||||
};
|
|
@ -35,7 +35,7 @@
|
|||
import { downloadArchive } from '$lib/utils/asset-utils';
|
||||
import { openFileUploadDialog } from '$lib/utils/file-uploader';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { UserResponseDto, api } from '@api';
|
||||
import { ActivityResponseDto, ReactionType, UserResponseDto, api } from '@api';
|
||||
import Icon from '$lib/components/elements/icon.svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { clickOutside } from '$lib/utils/click-outside';
|
||||
|
@ -45,11 +45,16 @@
|
|||
mdiDotsVertical,
|
||||
mdiArrowLeft,
|
||||
mdiFileImagePlusOutline,
|
||||
mdiShareVariantOutline,
|
||||
mdiDeleteOutline,
|
||||
mdiFolderDownloadOutline,
|
||||
mdiLink,
|
||||
mdiShareVariantOutline,
|
||||
mdiDeleteOutline,
|
||||
} from '@mdi/js';
|
||||
import { onMount } from 'svelte';
|
||||
import { fly } from 'svelte/transition';
|
||||
import ActivityViewer from '$lib/components/asset-viewer/activity-viewer.svelte';
|
||||
import ActivityStatus from '$lib/components/asset-viewer/activity-status.svelte';
|
||||
import { numberOfComments, setNumberOfComments, updateNumberOfComments } from '$lib/stores/activity.store';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
|
@ -77,6 +82,12 @@
|
|||
let isCreatingSharedAlbum = false;
|
||||
let currentAlbumName = '';
|
||||
let contextMenuPosition: { x: number; y: number } = { x: 0, y: 0 };
|
||||
let isShowActivity = false;
|
||||
let isLiked: ActivityResponseDto | null = null;
|
||||
let reactions: ActivityResponseDto[] = [];
|
||||
let user = data.user;
|
||||
let globalWidth: number;
|
||||
let assetGridWidth: number;
|
||||
|
||||
const assetStore = new AssetStore({ albumId: album.id });
|
||||
const assetInteractionStore = createAssetInteractionStore();
|
||||
|
@ -89,6 +100,13 @@
|
|||
$: isOwned = data.user.id == album.ownerId;
|
||||
$: isAllUserOwned = Array.from($selectedAssets).every((asset) => asset.ownerId === data.user.id);
|
||||
$: isAllFavorite = Array.from($selectedAssets).every((asset) => asset.isFavorite);
|
||||
$: {
|
||||
if (isShowActivity) {
|
||||
assetGridWidth = globalWidth - (globalWidth < 768 ? 360 : 460);
|
||||
} else {
|
||||
assetGridWidth = globalWidth;
|
||||
}
|
||||
}
|
||||
|
||||
afterNavigate(({ from }) => {
|
||||
assetViewingStore.showAssetViewer(false);
|
||||
|
@ -110,6 +128,63 @@
|
|||
}
|
||||
});
|
||||
|
||||
const handleFavorite = async () => {
|
||||
try {
|
||||
if (isLiked) {
|
||||
const activityId = isLiked.id;
|
||||
await api.activityApi.deleteActivity({ id: activityId });
|
||||
reactions = reactions.filter((reaction) => reaction.id !== activityId);
|
||||
isLiked = null;
|
||||
} else {
|
||||
const { data } = await api.activityApi.createActivity({
|
||||
activityCreateDto: { albumId: album.id, type: ReactionType.Like },
|
||||
});
|
||||
|
||||
isLiked = data;
|
||||
reactions = [...reactions, isLiked];
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error, "Can't change favorite for asset");
|
||||
}
|
||||
};
|
||||
|
||||
const getFavorite = async () => {
|
||||
if (user) {
|
||||
try {
|
||||
const { data } = await api.activityApi.getActivities({
|
||||
userId: user.id,
|
||||
albumId: album.id,
|
||||
type: ReactionType.Like,
|
||||
});
|
||||
if (data.length > 0) {
|
||||
isLiked = data[0];
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error, "Can't get Favorite");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getNumberOfComments = async () => {
|
||||
try {
|
||||
const { data } = await api.activityApi.getActivityStatistics({ albumId: album.id });
|
||||
setNumberOfComments(data.comments);
|
||||
} catch (error) {
|
||||
handleError(error, "Can't get number of comments");
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenAndCloseActivityTab = () => {
|
||||
isShowActivity = !isShowActivity;
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
if (album.sharedUsers.length > 0) {
|
||||
getFavorite();
|
||||
getNumberOfComments();
|
||||
}
|
||||
});
|
||||
|
||||
const handleStartSlideshow = async () => {
|
||||
const asset = $slideshowShuffle ? await assetStore.getRandomAsset() : assetStore.assets[0];
|
||||
if (asset) {
|
||||
|
@ -321,239 +396,275 @@
|
|||
};
|
||||
</script>
|
||||
|
||||
<header>
|
||||
{#if $isMultiSelectState}
|
||||
<AssetSelectControlBar assets={$selectedAssets} clearSelect={() => assetInteractionStore.clearMultiselect()}>
|
||||
<CreateSharedLink />
|
||||
<SelectAllAssets {assetStore} {assetInteractionStore} />
|
||||
<AssetSelectContextMenu icon={mdiPlus} title="Add">
|
||||
<AddToAlbum />
|
||||
<AddToAlbum shared />
|
||||
</AssetSelectContextMenu>
|
||||
<AssetSelectContextMenu icon={mdiDotsVertical} title="Menu">
|
||||
{#if isAllUserOwned}
|
||||
<FavoriteAction menuItem removeFavorite={isAllFavorite} />
|
||||
{/if}
|
||||
<ArchiveAction menuItem />
|
||||
<DownloadAction menuItem filename="{album.albumName}.zip" />
|
||||
{#if isOwned || isAllUserOwned}
|
||||
<RemoveFromAlbum menuItem bind:album onRemove={(assetIds) => handleRemoveAssets(assetIds)} />
|
||||
{/if}
|
||||
{#if isAllUserOwned}
|
||||
<DeleteAssets menuItem onAssetDelete={(assetId) => assetStore.removeAsset(assetId)} />
|
||||
{/if}
|
||||
</AssetSelectContextMenu>
|
||||
</AssetSelectControlBar>
|
||||
{:else}
|
||||
{#if viewMode === ViewMode.VIEW || viewMode === ViewMode.ALBUM_OPTIONS}
|
||||
<ControlAppBar showBackButton backIcon={mdiArrowLeft} on:close-button-click={() => goto(backUrl)}>
|
||||
<svelte:fragment slot="trailing">
|
||||
<CircleIconButton
|
||||
title="Add Photos"
|
||||
on:click={() => (viewMode = ViewMode.SELECT_ASSETS)}
|
||||
icon={mdiFileImagePlusOutline}
|
||||
/>
|
||||
|
||||
{#if isOwned}
|
||||
<CircleIconButton
|
||||
title="Share"
|
||||
on:click={() => (viewMode = ViewMode.SELECT_USERS)}
|
||||
icon={mdiShareVariantOutline}
|
||||
/>
|
||||
<CircleIconButton
|
||||
title="Delete album"
|
||||
on:click={() => (viewMode = ViewMode.CONFIRM_DELETE)}
|
||||
icon={mdiDeleteOutline}
|
||||
/>
|
||||
<div class="flex overflow-hidden" bind:clientWidth={globalWidth}>
|
||||
<div class="relative w-full shrink">
|
||||
{#if $isMultiSelectState}
|
||||
<AssetSelectControlBar assets={$selectedAssets} clearSelect={() => assetInteractionStore.clearMultiselect()}>
|
||||
<CreateSharedLink />
|
||||
<SelectAllAssets {assetStore} {assetInteractionStore} />
|
||||
<AssetSelectContextMenu icon={mdiPlus} title="Add">
|
||||
<AddToAlbum />
|
||||
<AddToAlbum shared />
|
||||
</AssetSelectContextMenu>
|
||||
<AssetSelectContextMenu icon={mdiDotsVertical} title="Menu">
|
||||
{#if isAllUserOwned}
|
||||
<FavoriteAction menuItem removeFavorite={isAllFavorite} />
|
||||
{/if}
|
||||
|
||||
{#if album.assetCount > 0}
|
||||
<CircleIconButton title="Download" on:click={handleDownloadAlbum} icon={mdiFolderDownloadOutline} />
|
||||
<ArchiveAction menuItem />
|
||||
<DownloadAction menuItem filename="{album.albumName}.zip" />
|
||||
{#if isOwned || isAllUserOwned}
|
||||
<RemoveFromAlbum menuItem bind:album onRemove={(assetIds) => handleRemoveAssets(assetIds)} />
|
||||
{/if}
|
||||
{#if isAllUserOwned}
|
||||
<DeleteAssets menuItem onAssetDelete={(assetId) => assetStore.removeAsset(assetId)} />
|
||||
{/if}
|
||||
</AssetSelectContextMenu>
|
||||
</AssetSelectControlBar>
|
||||
{:else}
|
||||
{#if viewMode === ViewMode.VIEW || viewMode === ViewMode.ALBUM_OPTIONS}
|
||||
<ControlAppBar showBackButton backIcon={mdiArrowLeft} on:close-button-click={() => goto(backUrl)}>
|
||||
<svelte:fragment slot="trailing">
|
||||
<CircleIconButton
|
||||
title="Add Photos"
|
||||
on:click={() => (viewMode = ViewMode.SELECT_ASSETS)}
|
||||
icon={mdiFileImagePlusOutline}
|
||||
/>
|
||||
|
||||
{#if isOwned}
|
||||
<div use:clickOutside on:outclick={() => (viewMode = ViewMode.VIEW)}>
|
||||
<CircleIconButton title="Album options" on:click={handleOpenAlbumOptions} icon={mdiDotsVertical}>
|
||||
{#if viewMode === ViewMode.ALBUM_OPTIONS}
|
||||
<ContextMenu {...contextMenuPosition}>
|
||||
{#if album.assetCount !== 0}
|
||||
<MenuOption on:click={handleStartSlideshow} text="Slideshow" />
|
||||
{/if}
|
||||
<MenuOption on:click={() => (viewMode = ViewMode.SELECT_THUMBNAIL)} text="Set album cover" />
|
||||
</ContextMenu>
|
||||
{/if}
|
||||
</CircleIconButton>
|
||||
</div>
|
||||
<CircleIconButton
|
||||
title="Share"
|
||||
on:click={() => (viewMode = ViewMode.SELECT_USERS)}
|
||||
icon={mdiShareVariantOutline}
|
||||
/>
|
||||
<CircleIconButton
|
||||
title="Delete album"
|
||||
on:click={() => (viewMode = ViewMode.CONFIRM_DELETE)}
|
||||
icon={mdiDeleteOutline}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if isCreatingSharedAlbum && album.sharedUsers.length === 0}
|
||||
<Button
|
||||
size="sm"
|
||||
rounded="lg"
|
||||
disabled={album.assetCount == 0}
|
||||
on:click={() => (viewMode = ViewMode.SELECT_USERS)}
|
||||
>
|
||||
Share
|
||||
</Button>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</ControlAppBar>
|
||||
{/if}
|
||||
|
||||
{#if viewMode === ViewMode.SELECT_ASSETS}
|
||||
<ControlAppBar on:close-button-click={handleCloseSelectAssets}>
|
||||
<svelte:fragment slot="leading">
|
||||
<p class="text-lg dark:text-immich-dark-fg">
|
||||
{#if $timelineSelected.size == 0}
|
||||
Add to album
|
||||
{:else}
|
||||
{$timelineSelected.size.toLocaleString($locale)} selected
|
||||
{/if}
|
||||
</p>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="trailing">
|
||||
<button
|
||||
on:click={handleSelectFromComputer}
|
||||
class="rounded-lg px-6 py-2 text-sm font-medium text-immich-primary transition-all hover:bg-immich-primary/10 dark:text-immich-dark-primary dark:hover:bg-immich-dark-primary/25"
|
||||
>
|
||||
Select from computer
|
||||
</button>
|
||||
<Button size="sm" rounded="lg" disabled={$timelineSelected.size === 0} on:click={handleAddAssets}>Done</Button
|
||||
>
|
||||
</svelte:fragment>
|
||||
</ControlAppBar>
|
||||
{/if}
|
||||
|
||||
{#if viewMode === ViewMode.SELECT_THUMBNAIL}
|
||||
<ControlAppBar on:close-button-click={() => (viewMode = ViewMode.VIEW)}>
|
||||
<svelte:fragment slot="leading">Select Album Cover</svelte:fragment>
|
||||
</ControlAppBar>
|
||||
{/if}
|
||||
{/if}
|
||||
</header>
|
||||
|
||||
<main
|
||||
class="relative h-screen overflow-hidden bg-immich-bg px-6 pt-[var(--navbar-height)] dark:bg-immich-dark-bg sm:px-12 md:px-24 lg:px-40"
|
||||
>
|
||||
{#if viewMode === ViewMode.SELECT_ASSETS}
|
||||
<AssetGrid
|
||||
user={data.user}
|
||||
assetStore={timelineStore}
|
||||
assetInteractionStore={timelineInteractionStore}
|
||||
isSelectionMode={true}
|
||||
/>
|
||||
{:else}
|
||||
<AssetGrid
|
||||
{album}
|
||||
user={data.user}
|
||||
{assetStore}
|
||||
{assetInteractionStore}
|
||||
isShared={album.sharedUsers.length > 0}
|
||||
isSelectionMode={viewMode === ViewMode.SELECT_THUMBNAIL}
|
||||
singleSelect={viewMode === ViewMode.SELECT_THUMBNAIL}
|
||||
on:select={({ detail: asset }) => handleUpdateThumbnail(asset.id)}
|
||||
on:escape={handleEscape}
|
||||
>
|
||||
{#if viewMode !== ViewMode.SELECT_THUMBNAIL}
|
||||
<!-- ALBUM TITLE -->
|
||||
<section class="pt-24">
|
||||
<input
|
||||
on:keydown={(e) => e.key == 'Enter' && titleInput.blur()}
|
||||
on:blur={handleUpdateName}
|
||||
class="w-[99%] border-b-2 border-transparent text-6xl text-immich-primary outline-none transition-all dark:text-immich-dark-primary {isOwned
|
||||
? 'hover:border-gray-400'
|
||||
: 'hover:border-transparent'} bg-immich-bg focus:border-b-2 focus:border-immich-primary focus:outline-none dark:bg-immich-dark-bg dark:focus:border-immich-dark-primary dark:focus:bg-immich-dark-gray"
|
||||
type="text"
|
||||
bind:value={album.albumName}
|
||||
disabled={!isOwned}
|
||||
bind:this={titleInput}
|
||||
title="Edit Title"
|
||||
/>
|
||||
|
||||
<!-- ALBUM SUMMARY -->
|
||||
{#if album.assetCount > 0}
|
||||
<span class="my-4 flex gap-2 text-sm font-medium text-gray-500" data-testid="album-details">
|
||||
<p class="">{getDateRange()}</p>
|
||||
<p>·</p>
|
||||
<p>{album.assetCount} items</p>
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
<!-- ALBUM SHARING -->
|
||||
{#if album.sharedUsers.length > 0 || (album.hasSharedLink && isOwned)}
|
||||
<div class="my-6 flex gap-x-1">
|
||||
<!-- link -->
|
||||
{#if album.hasSharedLink && isOwned}
|
||||
<CircleIconButton
|
||||
backgroundColor="#d3d3d3"
|
||||
forceDark
|
||||
size="20"
|
||||
icon={mdiLink}
|
||||
on:click={() => (viewMode = ViewMode.LINK_SHARING)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<!-- owner -->
|
||||
<button on:click={() => (viewMode = ViewMode.VIEW_USERS)}>
|
||||
<UserAvatar user={album.owner} size="md" />
|
||||
</button>
|
||||
|
||||
<!-- users -->
|
||||
{#each album.sharedUsers as user (user.id)}
|
||||
<button on:click={() => (viewMode = ViewMode.VIEW_USERS)}>
|
||||
<UserAvatar {user} size="md" />
|
||||
</button>
|
||||
{/each}
|
||||
{#if album.assetCount > 0}
|
||||
<CircleIconButton title="Download" on:click={handleDownloadAlbum} icon={mdiFolderDownloadOutline} />
|
||||
|
||||
{#if isOwned}
|
||||
<CircleIconButton
|
||||
backgroundColor="#d3d3d3"
|
||||
forceDark
|
||||
size="20"
|
||||
icon={mdiPlus}
|
||||
on:click={() => (viewMode = ViewMode.SELECT_USERS)}
|
||||
title="Add more users"
|
||||
/>
|
||||
<div use:clickOutside on:outclick={() => (viewMode = ViewMode.VIEW)}>
|
||||
<CircleIconButton title="Album options" on:click={handleOpenAlbumOptions} icon={mdiDotsVertical}>
|
||||
{#if viewMode === ViewMode.ALBUM_OPTIONS}
|
||||
<ContextMenu {...contextMenuPosition}>
|
||||
{#if album.assetCount !== 0}
|
||||
<MenuOption on:click={handleStartSlideshow} text="Slideshow" />
|
||||
{/if}
|
||||
<MenuOption on:click={() => (viewMode = ViewMode.SELECT_THUMBNAIL)} text="Set album cover" />
|
||||
</ContextMenu>
|
||||
{/if}
|
||||
</CircleIconButton>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<!-- ALBUM DESCRIPTION -->
|
||||
{#if isOwned || album.description}
|
||||
<button
|
||||
class="mb-12 mt-6 w-full border-b-2 border-transparent pb-2 text-left text-lg font-medium transition-colors hover:border-b-2 dark:text-gray-300"
|
||||
on:click={() => (isEditingDescription = true)}
|
||||
class:hover:border-gray-400={isOwned}
|
||||
disabled={!isOwned}
|
||||
title="Edit description"
|
||||
>
|
||||
{album.description || 'Add description'}
|
||||
</button>
|
||||
{/if}
|
||||
</section>
|
||||
{#if isCreatingSharedAlbum && album.sharedUsers.length === 0}
|
||||
<Button
|
||||
size="sm"
|
||||
rounded="lg"
|
||||
disabled={album.assetCount == 0}
|
||||
on:click={() => (viewMode = ViewMode.SELECT_USERS)}
|
||||
>
|
||||
Share
|
||||
</Button>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</ControlAppBar>
|
||||
{/if}
|
||||
|
||||
{#if album.assetCount === 0}
|
||||
<section id="empty-album" class=" mt-[200px] flex place-content-center place-items-center">
|
||||
<div class="w-[300px]">
|
||||
<p class="text-xs dark:text-immich-dark-fg">ADD PHOTOS</p>
|
||||
{#if viewMode === ViewMode.SELECT_ASSETS}
|
||||
<ControlAppBar on:close-button-click={handleCloseSelectAssets}>
|
||||
<svelte:fragment slot="leading">
|
||||
<p class="text-lg dark:text-immich-dark-fg">
|
||||
{#if $timelineSelected.size === 0}
|
||||
Add to album
|
||||
{:else}
|
||||
{$timelineSelected.size.toLocaleString($locale)} selected
|
||||
{/if}
|
||||
</p>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="trailing">
|
||||
<button
|
||||
on:click={() => (viewMode = ViewMode.SELECT_ASSETS)}
|
||||
class="mt-5 flex w-full place-items-center gap-6 rounded-md border bg-immich-bg px-8 py-8 text-immich-fg transition-all hover:bg-gray-100 hover:text-immich-primary dark:border-none dark:bg-immich-dark-gray dark:text-immich-dark-fg dark:hover:text-immich-dark-primary"
|
||||
on:click={handleSelectFromComputer}
|
||||
class="rounded-lg px-6 py-2 text-sm font-medium text-immich-primary transition-all hover:bg-immich-primary/10 dark:text-immich-dark-primary dark:hover:bg-immich-dark-primary/25"
|
||||
>
|
||||
<span class="text-text-immich-primary dark:text-immich-dark-primary"
|
||||
><Icon path={mdiPlus} size="24" />
|
||||
</span>
|
||||
<span class="text-lg">Select photos</span>
|
||||
Select from computer
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
<Button size="sm" rounded="lg" disabled={$timelineSelected.size === 0} on:click={handleAddAssets}
|
||||
>Done</Button
|
||||
>
|
||||
</svelte:fragment>
|
||||
</ControlAppBar>
|
||||
{/if}
|
||||
</AssetGrid>
|
||||
|
||||
{#if viewMode === ViewMode.SELECT_THUMBNAIL}
|
||||
<ControlAppBar on:close-button-click={() => (viewMode = ViewMode.VIEW)}>
|
||||
<svelte:fragment slot="leading">Select Album Cover</svelte:fragment>
|
||||
</ControlAppBar>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<main
|
||||
class="relative h-screen overflow-hidden bg-immich-bg px-6 pt-[var(--navbar-height)] dark:bg-immich-dark-bg sm:px-12 md:px-24 lg:px-40"
|
||||
style={`width:${assetGridWidth}px`}
|
||||
>
|
||||
{#if viewMode === ViewMode.SELECT_ASSETS}
|
||||
<AssetGrid
|
||||
user={data.user}
|
||||
assetStore={timelineStore}
|
||||
assetInteractionStore={timelineInteractionStore}
|
||||
isSelectionMode={true}
|
||||
/>
|
||||
{:else}
|
||||
<AssetGrid
|
||||
{album}
|
||||
user={data.user}
|
||||
{assetStore}
|
||||
{assetInteractionStore}
|
||||
isShared={album.sharedUsers.length > 0}
|
||||
isSelectionMode={viewMode === ViewMode.SELECT_THUMBNAIL}
|
||||
singleSelect={viewMode === ViewMode.SELECT_THUMBNAIL}
|
||||
on:select={({ detail: asset }) => handleUpdateThumbnail(asset.id)}
|
||||
on:escape={handleEscape}
|
||||
>
|
||||
{#if viewMode !== ViewMode.SELECT_THUMBNAIL}
|
||||
<!-- ALBUM TITLE -->
|
||||
<section class="pt-24">
|
||||
<input
|
||||
on:keydown={(e) => e.key === 'Enter' && titleInput.blur()}
|
||||
on:blur={handleUpdateName}
|
||||
class="w-[99%] border-b-2 border-transparent text-6xl text-immich-primary outline-none transition-all dark:text-immich-dark-primary {isOwned
|
||||
? 'hover:border-gray-400'
|
||||
: 'hover:border-transparent'} bg-immich-bg focus:border-b-2 focus:border-immich-primary focus:outline-none dark:bg-immich-dark-bg dark:focus:border-immich-dark-primary dark:focus:bg-immich-dark-gray"
|
||||
type="text"
|
||||
bind:value={album.albumName}
|
||||
disabled={!isOwned}
|
||||
bind:this={titleInput}
|
||||
title="Edit Title"
|
||||
/>
|
||||
|
||||
<!-- ALBUM SUMMARY -->
|
||||
{#if album.assetCount > 0}
|
||||
<span class="my-4 flex gap-2 text-sm font-medium text-gray-500" data-testid="album-details">
|
||||
<p class="">{getDateRange()}</p>
|
||||
<p>·</p>
|
||||
<p>{album.assetCount} items</p>
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
<!-- ALBUM SHARING -->
|
||||
{#if album.sharedUsers.length > 0 || (album.hasSharedLink && isOwned)}
|
||||
<div class="my-6 flex gap-x-1">
|
||||
<!-- link -->
|
||||
{#if album.hasSharedLink && isOwned}
|
||||
<CircleIconButton
|
||||
backgroundColor="#d3d3d3"
|
||||
forceDark
|
||||
size="20"
|
||||
icon={mdiLink}
|
||||
on:click={() => (viewMode = ViewMode.LINK_SHARING)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<!-- owner -->
|
||||
<button on:click={() => (viewMode = ViewMode.VIEW_USERS)}>
|
||||
<UserAvatar user={album.owner} size="md" />
|
||||
</button>
|
||||
|
||||
<!-- users -->
|
||||
{#each album.sharedUsers as user (user.id)}
|
||||
<button on:click={() => (viewMode = ViewMode.VIEW_USERS)}>
|
||||
<UserAvatar {user} size="md" />
|
||||
</button>
|
||||
{/each}
|
||||
|
||||
{#if isOwned}
|
||||
<CircleIconButton
|
||||
backgroundColor="#d3d3d3"
|
||||
forceDark
|
||||
size="20"
|
||||
icon={mdiPlus}
|
||||
on:click={() => (viewMode = ViewMode.SELECT_USERS)}
|
||||
title="Add more users"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- ALBUM DESCRIPTION -->
|
||||
{#if isOwned || album.description}
|
||||
<button
|
||||
class="mb-12 mt-6 w-full border-b-2 border-transparent pb-2 text-left text-lg font-medium transition-colors hover:border-b-2 dark:text-gray-300"
|
||||
on:click={() => (isEditingDescription = true)}
|
||||
class:hover:border-gray-400={isOwned}
|
||||
disabled={!isOwned}
|
||||
title="Edit description"
|
||||
>
|
||||
{album.description || 'Add description'}
|
||||
</button>
|
||||
{/if}
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
{#if album.assetCount === 0}
|
||||
<section id="empty-album" class=" mt-[200px] flex place-content-center place-items-center">
|
||||
<div class="w-[300px]">
|
||||
<p class="text-xs dark:text-immich-dark-fg">ADD PHOTOS</p>
|
||||
<button
|
||||
on:click={() => (viewMode = ViewMode.SELECT_ASSETS)}
|
||||
class="mt-5 flex w-full place-items-center gap-6 rounded-md border bg-immich-bg px-8 py-8 text-immich-fg transition-all hover:bg-gray-100 hover:text-immich-primary dark:border-none dark:bg-immich-dark-gray dark:text-immich-dark-fg dark:hover:text-immich-dark-primary"
|
||||
>
|
||||
<span class="text-text-immich-primary dark:text-immich-dark-primary"
|
||||
><Icon path={mdiPlus} size="24" />
|
||||
</span>
|
||||
<span class="text-lg">Select photos</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
</AssetGrid>
|
||||
{/if}
|
||||
|
||||
{#if album.sharedUsers.length > 0 && !$showAssetViewer}
|
||||
<div class="absolute z-[2] bottom-0 right-0 mb-6 mr-6 justify-self-end">
|
||||
<ActivityStatus
|
||||
{isLiked}
|
||||
numberOfComments={$numberOfComments}
|
||||
{isShowActivity}
|
||||
on:favorite={handleFavorite}
|
||||
on:openActivityTab={handleOpenAndCloseActivityTab}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</main>
|
||||
</div>
|
||||
{#if album.sharedUsers.length > 0 && album && isShowActivity && user && !$showAssetViewer}
|
||||
<div class="flex">
|
||||
<div
|
||||
transition:fly={{ duration: 150 }}
|
||||
id="activity-panel"
|
||||
class="z-[1002] w-[360px] md:w-[460px] overflow-y-auto bg-immich-bg transition-all dark:border-l dark:border-l-immich-dark-gray dark:bg-immich-dark-bg pl-4"
|
||||
translate="yes"
|
||||
>
|
||||
<ActivityViewer
|
||||
{user}
|
||||
albumOwnerId={album.ownerId}
|
||||
albumId={album.id}
|
||||
bind:reactions
|
||||
on:addComment={() => updateNumberOfComments(1)}
|
||||
on:deleteComment={() => updateNumberOfComments(-1)}
|
||||
on:deleteLike={() => (isLiked = null)}
|
||||
on:close={handleOpenAndCloseActivityTab}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</main>
|
||||
|
||||
</div>
|
||||
{#if viewMode === ViewMode.SELECT_USERS}
|
||||
<UserSelectionModal
|
||||
{album}
|
||||
|
|
Loading…
Reference in a new issue