mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	refactor(server,web): add/remove album users (#2681)
* refactor(server,web): add/remove album users * fix(web): bug fixes for multiple users * fix: linting
This commit is contained in:
		@@ -42,6 +42,7 @@
 | 
			
		||||
	import ShareInfoModal from './share-info-modal.svelte';
 | 
			
		||||
	import ThumbnailSelection from './thumbnail-selection.svelte';
 | 
			
		||||
	import UserSelectionModal from './user-selection-modal.svelte';
 | 
			
		||||
	import { handleError } from '../../utils/handle-error';
 | 
			
		||||
 | 
			
		||||
	export let album: AlbumResponseDto;
 | 
			
		||||
	export let sharedLink: SharedLinkResponseDto | undefined = undefined;
 | 
			
		||||
@@ -195,19 +196,16 @@
 | 
			
		||||
		if (userId == 'me') {
 | 
			
		||||
			isShowShareInfoModal = false;
 | 
			
		||||
			goto(backUrl);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		try {
 | 
			
		||||
			const { data } = await api.albumApi.getAlbumInfo({ id: album.id });
 | 
			
		||||
 | 
			
		||||
			album = data;
 | 
			
		||||
			isShowShareInfoModal = false;
 | 
			
		||||
			isShowShareInfoModal = data.sharedUsers.length >= 1;
 | 
			
		||||
		} catch (e) {
 | 
			
		||||
			console.error('Error [sharedUserDeletedHandler] ', e);
 | 
			
		||||
			notificationController.show({
 | 
			
		||||
				type: NotificationType.Error,
 | 
			
		||||
				message: 'Error deleting share users, check console for more details'
 | 
			
		||||
			});
 | 
			
		||||
			handleError(e, 'Error deleting share users');
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
	import { createEventDispatcher, onMount } from 'svelte';
 | 
			
		||||
	import { AlbumResponseDto, api, UserResponseDto } from '@api';
 | 
			
		||||
	import { clickOutside } from '$lib/utils/click-outside';
 | 
			
		||||
	import BaseModal from '../shared-components/base-modal.svelte';
 | 
			
		||||
	import UserAvatar from '../shared-components/user-avatar.svelte';
 | 
			
		||||
	import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
 | 
			
		||||
@@ -12,15 +11,18 @@
 | 
			
		||||
		notificationController,
 | 
			
		||||
		NotificationType
 | 
			
		||||
	} from '../shared-components/notification/notification';
 | 
			
		||||
	import { handleError } from '../../utils/handle-error';
 | 
			
		||||
	import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte';
 | 
			
		||||
 | 
			
		||||
	export let album: AlbumResponseDto;
 | 
			
		||||
 | 
			
		||||
	const dispatch = createEventDispatcher();
 | 
			
		||||
 | 
			
		||||
	let currentUser: UserResponseDto;
 | 
			
		||||
	let isShowMenu = false;
 | 
			
		||||
	let position = { x: 0, y: 0 };
 | 
			
		||||
	let targetUserId: string;
 | 
			
		||||
	let selectedMenuUser: UserResponseDto | null = null;
 | 
			
		||||
	let selectedRemoveUser: UserResponseDto | null = null;
 | 
			
		||||
 | 
			
		||||
	$: isOwned = currentUser?.id == album.ownerId;
 | 
			
		||||
 | 
			
		||||
	onMount(async () => {
 | 
			
		||||
@@ -28,16 +30,12 @@
 | 
			
		||||
			const { data } = await api.userApi.getMyUserInfo();
 | 
			
		||||
			currentUser = data;
 | 
			
		||||
		} catch (e) {
 | 
			
		||||
			console.error('Error [share-info-modal] [getAllUsers]', e);
 | 
			
		||||
			notificationController.show({
 | 
			
		||||
				message: 'Error getting user info, check console for more details',
 | 
			
		||||
				type: NotificationType.Error
 | 
			
		||||
			});
 | 
			
		||||
			handleError(e, 'Unable to refresh user');
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	const showContextMenu = (userId: string) => {
 | 
			
		||||
		const iconButton = document.getElementById('icon-' + userId);
 | 
			
		||||
	const showContextMenu = (user: UserResponseDto) => {
 | 
			
		||||
		const iconButton = document.getElementById('icon-' + user.id);
 | 
			
		||||
 | 
			
		||||
		if (iconButton) {
 | 
			
		||||
			position = {
 | 
			
		||||
@@ -46,69 +44,101 @@
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		targetUserId = userId;
 | 
			
		||||
		isShowMenu = !isShowMenu;
 | 
			
		||||
		selectedMenuUser = user;
 | 
			
		||||
		selectedRemoveUser = null;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const removeUser = async (userId: string) => {
 | 
			
		||||
		if (window.confirm('Do you want to remove selected user from the album?')) {
 | 
			
		||||
			try {
 | 
			
		||||
				await api.albumApi.removeUserFromAlbum({ id: album.id, userId });
 | 
			
		||||
				dispatch('user-deleted', { userId });
 | 
			
		||||
			} catch (e) {
 | 
			
		||||
				console.error('Error [share-info-modal] [removeUser]', e);
 | 
			
		||||
				notificationController.show({
 | 
			
		||||
					message: 'Error removing user, check console for more details',
 | 
			
		||||
					type: NotificationType.Error
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
	const handleMenuRemove = () => {
 | 
			
		||||
		selectedRemoveUser = selectedMenuUser;
 | 
			
		||||
		selectedMenuUser = null;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const handleRemoveUser = async () => {
 | 
			
		||||
		if (!selectedRemoveUser) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const userId = selectedRemoveUser.id === currentUser?.id ? 'me' : selectedRemoveUser.id;
 | 
			
		||||
 | 
			
		||||
		try {
 | 
			
		||||
			await api.albumApi.removeUserFromAlbum({ id: album.id, userId });
 | 
			
		||||
			dispatch('user-deleted', { userId });
 | 
			
		||||
			const message =
 | 
			
		||||
				userId === 'me' ? `Left ${album.albumName}` : `Removed ${selectedRemoveUser.firstName}`;
 | 
			
		||||
			notificationController.show({ type: NotificationType.Info, message });
 | 
			
		||||
		} catch (e) {
 | 
			
		||||
			handleError(e, 'Unable to remove user');
 | 
			
		||||
		} finally {
 | 
			
		||||
			selectedRemoveUser = null;
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<BaseModal on:close={() => dispatch('close')}>
 | 
			
		||||
	<svelte:fragment slot="title">
 | 
			
		||||
		<span class="flex gap-2 place-items-center">
 | 
			
		||||
			<p class="font-medium text-immich-fg dark:text-immich-dark-fg">Options</p>
 | 
			
		||||
		</span>
 | 
			
		||||
	</svelte:fragment>
 | 
			
		||||
{#if !selectedRemoveUser}
 | 
			
		||||
	<BaseModal on:close={() => dispatch('close')}>
 | 
			
		||||
		<svelte:fragment slot="title">
 | 
			
		||||
			<span class="flex gap-2 place-items-center">
 | 
			
		||||
				<p class="font-medium text-immich-fg dark:text-immich-dark-fg">Options</p>
 | 
			
		||||
			</span>
 | 
			
		||||
		</svelte:fragment>
 | 
			
		||||
 | 
			
		||||
	<section class="max-h-[400px] overflow-y-auto immich-scrollbar pb-4">
 | 
			
		||||
		{#each album.sharedUsers as user}
 | 
			
		||||
			<div
 | 
			
		||||
				class="flex gap-4 p-5 place-items-center justify-between w-full transition-colors hover:bg-gray-50 dark:hover:bg-gray-700"
 | 
			
		||||
			>
 | 
			
		||||
				<div class="flex gap-4 place-items-center">
 | 
			
		||||
					<UserAvatar {user} size="md" autoColor />
 | 
			
		||||
					<p class="font-medium text-sm">{user.firstName} {user.lastName}</p>
 | 
			
		||||
				</div>
 | 
			
		||||
		<section class="max-h-[400px] overflow-y-auto immich-scrollbar pb-4">
 | 
			
		||||
			{#each album.sharedUsers as user}
 | 
			
		||||
				<div
 | 
			
		||||
					class="flex gap-4 p-5 place-items-center justify-between w-full transition-colors hover:bg-gray-50 dark:hover:bg-gray-700"
 | 
			
		||||
				>
 | 
			
		||||
					<div class="flex gap-4 place-items-center">
 | 
			
		||||
						<UserAvatar {user} size="md" autoColor />
 | 
			
		||||
						<p class="font-medium text-sm">{user.firstName} {user.lastName}</p>
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
				<div id={`icon-${user.id}`} class="flex place-items-center">
 | 
			
		||||
					{#if isOwned}
 | 
			
		||||
						<div use:clickOutside on:outclick={() => (isShowMenu = false)}>
 | 
			
		||||
							<CircleIconButton
 | 
			
		||||
								on:click={() => showContextMenu(user.id)}
 | 
			
		||||
								logo={DotsVertical}
 | 
			
		||||
								backgroundColor={'transparent'}
 | 
			
		||||
								hoverColor={'#e2e7e9'}
 | 
			
		||||
								size={'20'}
 | 
			
		||||
							>
 | 
			
		||||
								{#if isShowMenu}
 | 
			
		||||
									<ContextMenu {...position}>
 | 
			
		||||
										<MenuOption on:click={() => removeUser(targetUserId)} text="Remove" />
 | 
			
		||||
					<div id={`icon-${user.id}`} class="flex place-items-center">
 | 
			
		||||
						{#if isOwned}
 | 
			
		||||
							<div>
 | 
			
		||||
								<CircleIconButton
 | 
			
		||||
									on:click={() => showContextMenu(user)}
 | 
			
		||||
									logo={DotsVertical}
 | 
			
		||||
									backgroundColor="transparent"
 | 
			
		||||
									hoverColor="#e2e7e9"
 | 
			
		||||
									size="20"
 | 
			
		||||
								/>
 | 
			
		||||
 | 
			
		||||
								{#if selectedMenuUser === user}
 | 
			
		||||
									<ContextMenu {...position} on:outclick={() => (selectedMenuUser = null)}>
 | 
			
		||||
										<MenuOption on:click={handleMenuRemove} text="Remove" />
 | 
			
		||||
									</ContextMenu>
 | 
			
		||||
								{/if}
 | 
			
		||||
							</CircleIconButton>
 | 
			
		||||
						</div>
 | 
			
		||||
					{:else if user.id == currentUser?.id}
 | 
			
		||||
						<button
 | 
			
		||||
							on:click={() => removeUser('me')}
 | 
			
		||||
							class="text-sm text-immich-primary dark:text-immich-dark-primary font-medium transition-colors hover:text-immich-primary/75"
 | 
			
		||||
							>Leave</button
 | 
			
		||||
						>
 | 
			
		||||
					{/if}
 | 
			
		||||
							</div>
 | 
			
		||||
						{:else if user.id == currentUser?.id}
 | 
			
		||||
							<button
 | 
			
		||||
								on:click={() => (selectedRemoveUser = user)}
 | 
			
		||||
								class="text-sm text-immich-primary dark:text-immich-dark-primary font-medium transition-colors hover:text-immich-primary/75"
 | 
			
		||||
								>Leave</button
 | 
			
		||||
							>
 | 
			
		||||
						{/if}
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		{/each}
 | 
			
		||||
	</section>
 | 
			
		||||
</BaseModal>
 | 
			
		||||
			{/each}
 | 
			
		||||
		</section>
 | 
			
		||||
	</BaseModal>
 | 
			
		||||
{/if}
 | 
			
		||||
 | 
			
		||||
{#if selectedRemoveUser && selectedRemoveUser?.id === currentUser?.id}
 | 
			
		||||
	<ConfirmDialogue
 | 
			
		||||
		title="Leave Album?"
 | 
			
		||||
		prompt="Are you sure you want to leave {album.albumName}?"
 | 
			
		||||
		confirmText="Leave"
 | 
			
		||||
		on:confirm={handleRemoveUser}
 | 
			
		||||
		on:cancel={() => (selectedRemoveUser = null)}
 | 
			
		||||
	/>
 | 
			
		||||
{/if}
 | 
			
		||||
 | 
			
		||||
{#if selectedRemoveUser && selectedRemoveUser?.id !== currentUser?.id}
 | 
			
		||||
	<ConfirmDialogue
 | 
			
		||||
		title="Remove User?"
 | 
			
		||||
		prompt="Are you sure you want to remove {selectedRemoveUser.firstName} {selectedRemoveUser.lastName}"
 | 
			
		||||
		confirmText="Remove"
 | 
			
		||||
		on:confirm={handleRemoveUser}
 | 
			
		||||
		on:cancel={() => (selectedRemoveUser = null)}
 | 
			
		||||
	/>
 | 
			
		||||
{/if}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user