add UI for adding editors

This commit is contained in:
mgabor
2024-04-24 12:29:56 +02:00
parent 5bb3ddb1f5
commit 5a887069da
3 changed files with 41 additions and 21 deletions
@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { groupBy, orderBy } from 'lodash-es'; import { groupBy, orderBy } from 'lodash-es';
import { addUsersToAlbum, deleteAlbum, type UserResponseDto, type AlbumResponseDto } from '@immich/sdk'; import { addUsersToAlbum, deleteAlbum, type AddUserDto, type AlbumResponseDto } from '@immich/sdk';
import { mdiDeleteOutline, mdiShareVariantOutline, mdiFolderDownloadOutline, mdiRenameOutline } from '@mdi/js'; import { mdiDeleteOutline, mdiShareVariantOutline, mdiFolderDownloadOutline, mdiRenameOutline } from '@mdi/js';
import Icon from '$lib/components/elements/icon.svelte'; import Icon from '$lib/components/elements/icon.svelte';
import EditAlbumForm from '$lib/components/forms/edit-album-form.svelte'; import EditAlbumForm from '$lib/components/forms/edit-album-form.svelte';
@@ -328,7 +328,7 @@
updateAlbumInfo(album); updateAlbumInfo(album);
}; };
const handleAddUsers = async (users: UserResponseDto[]) => { const handleAddUsers = async (users: AddUserDto[]) => {
if (!albumToShare) { if (!albumToShare) {
return; return;
} }
@@ -8,20 +8,22 @@
type AlbumResponseDto, type AlbumResponseDto,
type SharedLinkResponseDto, type SharedLinkResponseDto,
type UserResponseDto, type UserResponseDto,
AlbumUserRole, type AddUserDto,
} from '@immich/sdk'; } from '@immich/sdk';
import { mdiCheck, mdiLink, mdiShareCircle } from '@mdi/js'; import { mdiCheck, mdiLink, mdiShareCircle } from '@mdi/js';
import { createEventDispatcher, onMount } from 'svelte'; import { createEventDispatcher, onMount } from 'svelte';
import Button from '../elements/buttons/button.svelte'; import Button from '../elements/buttons/button.svelte';
import UserAvatar from '../shared-components/user-avatar.svelte'; import UserAvatar from '../shared-components/user-avatar.svelte';
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte'; import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
import Dropdown from '$lib/components/elements/dropdown.svelte';
export let album: AlbumResponseDto; export let album: AlbumResponseDto;
export let onClose: () => void; export let onClose: () => void;
let users: UserResponseDto[] = []; let users: UserResponseDto[] = [];
let selectedUsers: UserResponseDto[] = []; let selectedUsers: Record<string, { user: UserResponseDto, role: AlbumUserRole }> = {}
const dispatch = createEventDispatcher<{ const dispatch = createEventDispatcher<{
select: UserResponseDto[]; select: AddUserDto[];
share: void; share: void;
}>(); }>();
let sharedLinks: SharedLinkResponseDto[] = []; let sharedLinks: SharedLinkResponseDto[] = [];
@@ -43,26 +45,29 @@
sharedLinks = data.filter((link) => link.album?.id === album.id); sharedLinks = data.filter((link) => link.album?.id === album.id);
}; };
const handleSelect = (user: UserResponseDto) => { const handleToggle = (user: UserResponseDto) => {
selectedUsers = selectedUsers.includes(user) if (Object.keys(selectedUsers).includes(user.id)) {
? selectedUsers.filter((selectedUser) => selectedUser.id !== user.id) delete selectedUsers[user.id];
: [...selectedUsers, user]; selectedUsers = selectedUsers
} else {
selectedUsers[user.id] = { user, role: AlbumUserRole.Editor };
}
}; };
const handleUnselect = (user: UserResponseDto) => { const handleChangeRole = (user: UserResponseDto, role: AlbumUserRole) => {
selectedUsers = selectedUsers.filter((selectedUser) => selectedUser.id !== user.id); selectedUsers[user.id].role = role;
}; };
</script> </script>
<FullScreenModal id="user-selection-modal" title="Invite to album" showLogo {onClose}> <FullScreenModal id="user-selection-modal" title="Invite to album" showLogo {onClose}>
{#if selectedUsers.length > 0} {#if Object.keys(selectedUsers).length > 0}
<div class="mb-2 flex flex-wrap place-items-center gap-4 overflow-x-auto px-5 py-2 sticky"> <div class="mb-2 flex flex-wrap place-items-center gap-4 overflow-x-auto px-5 py-2 sticky">
<p class="font-medium">To</p> <p class="font-medium">To</p>
{#each selectedUsers as user} {#each Object.values(selectedUsers) as { user }}
{#key user.id} {#key user.id}
<button <button
on:click={() => handleUnselect(user)} on:click={() => handleToggle(user)}
class="flex place-items-center gap-1 rounded-full border border-gray-500 p-2 transition-colors hover:bg-gray-200 dark:hover:bg-gray-700" class="flex place-items-center gap-1 rounded-full border border-gray-500 p-2 transition-colors hover:bg-gray-200 dark:hover:bg-gray-700"
> >
<UserAvatar {user} size="sm" /> <UserAvatar {user} size="sm" />
@@ -80,10 +85,10 @@
<div class="my-4"> <div class="my-4">
{#each users as user} {#each users as user}
<button <button
on:click={() => handleSelect(user)} on:click={() => handleToggle(user)}
class="flex w-full place-items-center gap-4 px-5 py-4 transition-all hover:bg-gray-200 dark:hover:bg-gray-700 rounded-xl" class="flex w-full place-items-center gap-4 px-5 py-4 transition-all hover:bg-gray-200 dark:hover:bg-gray-700 rounded-xl"
> >
{#if selectedUsers.includes(user)} {#if Object.keys(selectedUsers).includes(user.id)}
<div <div
class="flex h-10 w-10 items-center justify-center rounded-full border bg-immich-primary text-3xl text-white dark:border-immich-dark-gray dark:bg-immich-dark-primary dark:text-immich-dark-bg" class="flex h-10 w-10 items-center justify-center rounded-full border bg-immich-primary text-3xl text-white dark:border-immich-dark-gray dark:bg-immich-dark-primary dark:text-immich-dark-bg"
> >
@@ -93,7 +98,7 @@
<UserAvatar {user} size="md" /> <UserAvatar {user} size="md" />
{/if} {/if}
<div class="text-left"> <div class="text-left flex-grow">
<p class="text-immich-fg dark:text-immich-dark-fg"> <p class="text-immich-fg dark:text-immich-dark-fg">
{user.name} {user.name}
</p> </p>
@@ -101,6 +106,21 @@
{user.email} {user.email}
</p> </p>
</div> </div>
{#if Object.keys(selectedUsers).includes(user.id)}
<div on:click={(e) => e.stopPropagation()}>
<Dropdown
title="Role"
options={[
{ title: 'Editor', value: AlbumUserRole.Editor },
{ title: 'Viewer', value: AlbumUserRole.Viewer },
]}
selectedOption={{ title: 'Editor', value: AlbumUserRole.Editor }}
render={({ title }) => title}
on:select={({detail: {value}}) => handleChangeRole(user, value)}
/>
</div>
{/if}
</button> </button>
{/each} {/each}
</div> </div>
@@ -117,8 +137,8 @@
size="sm" size="sm"
fullwidth fullwidth
rounded="full" rounded="full"
disabled={selectedUsers.length === 0} disabled={Object.keys(selectedUsers).length === 0}
on:click={() => dispatch('select', selectedUsers)}>Add</Button on:click={() => dispatch('select', Object.values(selectedUsers).map(({user, ...rest}) => ({userId: user.id, ...rest})))}>Add</Button
> >
</div> </div>
{/if} {/if}
@@ -63,7 +63,7 @@
ReactionLevel, ReactionLevel,
ReactionType, ReactionType,
updateAlbumInfo, updateAlbumInfo,
type UserResponseDto, type AddUserDto,
} from '@immich/sdk'; } from '@immich/sdk';
import { import {
mdiArrowLeft, mdiArrowLeft,
@@ -316,12 +316,12 @@
viewMode = ViewMode.VIEW; viewMode = ViewMode.VIEW;
}; };
const handleAddUsers = async (users: UserResponseDto[]) => { const handleAddUsers = async (albumUsers: AddUserDto[]) => {
try { try {
album = await addUsersToAlbum({ album = await addUsersToAlbum({
id: album.id, id: album.id,
addUsersDto: { addUsersDto: {
sharedUserIds: [...users].map(({ id }) => id), albumUsers,
}, },
}); });