1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2024-12-28 22:51:59 +00:00

feat(web,a11y): consolidate BaseModal into FullScreenModal (#8787)

* feat(web,a11y): FullScreenModal sticky buttons

* chore(web): combine BaseModal into FullScreenModal

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
Ben 2024-04-16 05:06:15 +00:00 committed by GitHub
parent 28f591d01b
commit bcdec25843
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 251 additions and 337 deletions

View file

@ -7,7 +7,6 @@
import EditAlbumForm from '$lib/components/forms/edit-album-form.svelte'; import EditAlbumForm from '$lib/components/forms/edit-album-form.svelte';
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte'; import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.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';
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
import { import {
NotificationType, NotificationType,
notificationController, notificationController,
@ -432,9 +431,12 @@
{#if allowEdit} {#if allowEdit}
<!-- Edit Modal --> <!-- Edit Modal -->
{#if albumToEdit} {#if albumToEdit}
<FullScreenModal id="edit-album-modal" title="Edit album" width="wide" onClose={() => (albumToEdit = null)}> <EditAlbumForm
<EditAlbumForm album={albumToEdit} onEditSuccess={successEditAlbumInfo} onCancel={() => (albumToEdit = null)} /> album={albumToEdit}
</FullScreenModal> onEditSuccess={successEditAlbumInfo}
onCancel={() => (albumToEdit = null)}
onClose={() => (albumToEdit = null)}
/>
{/if} {/if}
<!-- Share Modal --> <!-- Share Modal -->

View file

@ -5,12 +5,12 @@
import { getContextMenuPosition } from '../../utils/context-menu'; import { getContextMenuPosition } from '../../utils/context-menu';
import { handleError } from '../../utils/handle-error'; import { handleError } from '../../utils/handle-error';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte'; import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
import BaseModal from '../shared-components/base-modal.svelte';
import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte'; import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte';
import ContextMenu from '../shared-components/context-menu/context-menu.svelte'; import ContextMenu from '../shared-components/context-menu/context-menu.svelte';
import MenuOption from '../shared-components/context-menu/menu-option.svelte'; import MenuOption from '../shared-components/context-menu/menu-option.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 FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
export let album: AlbumResponseDto; export let album: AlbumResponseDto;
export let onClose: () => void; export let onClose: () => void;
@ -66,7 +66,7 @@
</script> </script>
{#if !selectedRemoveUser} {#if !selectedRemoveUser}
<BaseModal id="share-info-modal" title="Options" {onClose}> <FullScreenModal id="share-info-modal" title="Options" {onClose}>
<section class="immich-scrollbar max-h-[400px] overflow-y-auto pb-4"> <section class="immich-scrollbar max-h-[400px] overflow-y-auto pb-4">
<div class="flex w-full place-items-center justify-between gap-4 p-5"> <div class="flex w-full place-items-center justify-between gap-4 p-5">
<div class="flex place-items-center gap-4"> <div class="flex place-items-center gap-4">
@ -80,7 +80,7 @@
</div> </div>
{#each album.sharedUsers as user} {#each album.sharedUsers as user}
<div <div
class="flex w-full place-items-center justify-between gap-4 p-5 transition-colors hover:bg-gray-50 dark:hover:bg-gray-700" class="flex w-full place-items-center justify-between gap-4 p-5 rounded-xl transition-colors hover:bg-gray-50 dark:hover:bg-gray-700"
> >
<div class="flex place-items-center gap-4"> <div class="flex place-items-center gap-4">
<UserAvatar {user} size="md" /> <UserAvatar {user} size="md" />
@ -116,7 +116,7 @@
</div> </div>
{/each} {/each}
</section> </section>
</BaseModal> </FullScreenModal>
{/if} {/if}
{#if selectedRemoveUser && selectedRemoveUser?.id === currentUser?.id} {#if selectedRemoveUser && selectedRemoveUser?.id === currentUser?.id}

View file

@ -12,8 +12,8 @@
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 BaseModal from '../shared-components/base-modal.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';
export let album: AlbumResponseDto; export let album: AlbumResponseDto;
export let onClose: () => void; export let onClose: () => void;
@ -54,7 +54,7 @@
}; };
</script> </script>
<BaseModal 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 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>
@ -75,13 +75,13 @@
<div class="immich-scrollbar max-h-[500px] overflow-y-auto"> <div class="immich-scrollbar max-h-[500px] overflow-y-auto">
{#if users.length > 0} {#if users.length > 0}
<p class="px-5 text-xs font-medium">SUGGESTIONS</p> <p class="text-xs font-medium">SUGGESTIONS</p>
<div class="my-4"> <div class="my-4">
{#each users as user} {#each users as user}
<button <button
on:click={() => handleSelect(user)} on:click={() => handleSelect(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" 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 selectedUsers.includes(user)}
<div <div
@ -112,7 +112,7 @@
</div> </div>
{#if users.length > 0} {#if users.length > 0}
<div class="p-3"> <div class="py-3">
<Button <Button
size="sm" size="sm"
fullwidth fullwidth
@ -125,7 +125,7 @@
<hr /> <hr />
<div id="shared-buttons" class="my-4 flex place-content-center place-items-center justify-around"> <div id="shared-buttons" class="mt-4 flex place-content-center place-items-center justify-around">
<button <button
class="flex flex-col place-content-center place-items-center gap-2 hover:cursor-pointer" class="flex flex-col place-content-center place-items-center gap-2 hover:cursor-pointer"
on:click={() => dispatch('share')} on:click={() => dispatch('share')}
@ -144,4 +144,4 @@
</button> </button>
{/if} {/if}
</div> </div>
</BaseModal> </FullScreenModal>

View file

@ -29,7 +29,7 @@
<button <button
on:click={() => dispatch('album')} on:click={() => dispatch('album')}
class="flex w-full gap-4 px-6 py-2 text-left transition-colors hover:bg-gray-200 dark:hover:bg-gray-700" class="flex w-full gap-4 px-6 py-2 text-left transition-colors hover:bg-gray-200 dark:hover:bg-gray-700 rounded-xl"
> >
<div class="h-12 w-12 shrink-0 rounded-xl bg-slate-300"> <div class="h-12 w-12 shrink-0 rounded-xl bg-slate-300">
{#if album.albumThumbnailAssetId} {#if album.albumThumbnailAssetId}

View file

@ -27,6 +27,7 @@
export let fullwidth = false; export let fullwidth = false;
export let border = false; export let border = false;
export let title: string | undefined = ''; export let title: string | undefined = '';
export let form: string | undefined = undefined;
let className = ''; let className = '';
export { className as class }; export { className as class };
@ -65,6 +66,7 @@
{type} {type}
{disabled} {disabled}
{title} {title}
{form}
on:click on:click
on:focus on:focus
on:blur on:blur

View file

@ -102,8 +102,8 @@
<div class="flex px-4 pt-2"> <div class="flex px-4 pt-2">
<p class="text-sm text-gray-500 dark:text-gray-300">They will be merged together</p> <p class="text-sm text-gray-500 dark:text-gray-300">They will be merged together</p>
</div> </div>
<div class="mt-8 flex w-full gap-4 pb-4"> <svelte:fragment slot="sticky-bottom">
<Button fullwidth color="gray" on:click={() => dispatch('reject')}>No</Button> <Button fullwidth color="gray" on:click={() => dispatch('reject')}>No</Button>
<Button fullwidth on:click={() => dispatch('confirm', [personMerge1, personMerge2])}>Yes</Button> <Button fullwidth on:click={() => dispatch('confirm', [personMerge1, personMerge2])}>Yes</Button>
</div> </svelte:fragment>
</FullScreenModal> </FullScreenModal>

View file

@ -20,14 +20,14 @@
}; };
</script> </script>
<FullScreenModal id="set-birthday-modal" title="Set date of birth" icon={mdiCake} onClose={handleCancel}> <FullScreenModal id="set-birth-date-modal" title="Set date of birth" icon={mdiCake} onClose={handleCancel}>
<div class="text-immich-primary dark:text-immich-dark-primary"> <div class="text-immich-primary dark:text-immich-dark-primary">
<p class="text-sm dark:text-immich-dark-fg"> <p class="text-sm dark:text-immich-dark-fg">
Date of birth is used to calculate the age of this person at the time of a photo. Date of birth is used to calculate the age of this person at the time of a photo.
</p> </p>
</div> </div>
<form on:submit|preventDefault={() => handleSubmit()} autocomplete="off"> <form on:submit|preventDefault={() => handleSubmit()} autocomplete="off" id="set-birth-date-form">
<div class="my-4 flex flex-col gap-2"> <div class="my-4 flex flex-col gap-2">
<DateInput <DateInput
class="immich-form-input" class="immich-form-input"
@ -38,9 +38,9 @@
max={todayFormatted} max={todayFormatted}
/> />
</div> </div>
<div class="mt-8 flex w-full gap-4">
<Button color="gray" fullwidth on:click={() => handleCancel()}>Cancel</Button>
<Button type="submit" fullwidth>Set</Button>
</div>
</form> </form>
<svelte:fragment slot="sticky-bottom">
<Button color="gray" fullwidth on:click={() => handleCancel()}>Cancel</Button>
<Button type="submit" fullwidth form="set-birth-date-form">Set</Button>
</svelte:fragment>
</FullScreenModal> </FullScreenModal>

View file

@ -29,15 +29,14 @@
</script> </script>
<FullScreenModal id="api-key-modal" {title} icon={mdiKeyVariant} onClose={handleCancel}> <FullScreenModal id="api-key-modal" {title} icon={mdiKeyVariant} onClose={handleCancel}>
<form on:submit|preventDefault={handleSubmit} autocomplete="off"> <form on:submit|preventDefault={handleSubmit} autocomplete="off" id="api-key-form">
<div class="mb-4 flex flex-col gap-2"> <div class="mb-4 flex flex-col gap-2">
<label class="immich-form-label" for="name">Name</label> <label class="immich-form-label" for="name">Name</label>
<input class="immich-form-input" id="name" name="name" type="text" bind:value={apiKey.name} /> <input class="immich-form-input" id="name" name="name" type="text" bind:value={apiKey.name} />
</div> </div>
<div class="mt-8 flex w-full gap-4">
<Button color="gray" fullwidth on:click={handleCancel}>{cancelText}</Button>
<Button type="submit" fullwidth>{submitText}</Button>
</div>
</form> </form>
<svelte:fragment slot="sticky-bottom">
<Button color="gray" fullwidth on:click={handleCancel}>{cancelText}</Button>
<Button type="submit" fullwidth form="api-key-form">{submitText}</Button>
</svelte:fragment>
</FullScreenModal> </FullScreenModal>

View file

@ -31,10 +31,10 @@
<textarea class="immich-form-input" id="secret" name="secret" readonly={true} value={secret} /> <textarea class="immich-form-input" id="secret" name="secret" readonly={true} value={secret} />
</div> </div>
<div class="mt-8 flex w-full gap-4"> <svelte:fragment slot="sticky-bottom">
{#if canCopyImagesToClipboard} {#if canCopyImagesToClipboard}
<Button on:click={() => copyToClipboard(secret)} fullwidth>Copy to Clipboard</Button> <Button on:click={() => copyToClipboard(secret)} fullwidth>Copy to Clipboard</Button>
{/if} {/if}
<Button on:click={() => handleDone()} fullwidth>Done</Button> <Button on:click={() => handleDone()} fullwidth>Done</Button>
</div> </svelte:fragment>
</FullScreenModal> </FullScreenModal>

View file

@ -7,6 +7,9 @@
import Button from '../elements/buttons/button.svelte'; import Button from '../elements/buttons/button.svelte';
import PasswordField from '../shared-components/password-field.svelte'; import PasswordField from '../shared-components/password-field.svelte';
import Slider from '../elements/slider.svelte'; import Slider from '../elements/slider.svelte';
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
export let onClose: () => void;
let error: string; let error: string;
let success: string; let success: string;
@ -68,53 +71,55 @@
} }
</script> </script>
<form on:submit|preventDefault={registerUser} autocomplete="off"> <FullScreenModal id="create-new-user-modal" title="Create new user" showLogo {onClose}>
<div class="my-4 flex flex-col gap-2"> <form on:submit|preventDefault={registerUser} autocomplete="off" id="create-new-user-form">
<label class="immich-form-label" for="email">Email</label> <div class="my-4 flex flex-col gap-2">
<input class="immich-form-input" id="email" bind:value={email} type="email" required /> <label class="immich-form-label" for="email">Email</label>
</div> <input class="immich-form-input" id="email" bind:value={email} type="email" required />
</div>
<div class="my-4 flex flex-col gap-2"> <div class="my-4 flex flex-col gap-2">
<label class="immich-form-label" for="password">Password</label> <label class="immich-form-label" for="password">Password</label>
<PasswordField id="password" bind:password autocomplete="new-password" /> <PasswordField id="password" bind:password autocomplete="new-password" />
</div> </div>
<div class="my-4 flex flex-col gap-2"> <div class="my-4 flex flex-col gap-2">
<label class="immich-form-label" for="confirmPassword">Confirm Password</label> <label class="immich-form-label" for="confirmPassword">Confirm Password</label>
<PasswordField id="confirmPassword" bind:password={confirmPassword} autocomplete="new-password" /> <PasswordField id="confirmPassword" bind:password={confirmPassword} autocomplete="new-password" />
</div> </div>
<div class="my-4 flex place-items-center justify-between gap-2"> <div class="my-4 flex place-items-center justify-between gap-2">
<label class="text-sm dark:text-immich-dark-fg" for="require-password-change"> <label class="text-sm dark:text-immich-dark-fg" for="require-password-change">
Require user to change password on first login Require user to change password on first login
</label> </label>
<Slider id="require-password-change" bind:checked={shouldChangePassword} /> <Slider id="require-password-change" bind:checked={shouldChangePassword} />
</div> </div>
<div class="my-4 flex flex-col gap-2"> <div class="my-4 flex flex-col gap-2">
<label class="immich-form-label" for="name">Name</label> <label class="immich-form-label" for="name">Name</label>
<input class="immich-form-input" id="name" bind:value={name} type="text" required /> <input class="immich-form-input" id="name" bind:value={name} type="text" required />
</div> </div>
<div class="my-4 flex flex-col gap-2"> <div class="my-4 flex flex-col gap-2">
<label class="flex items-center gap-2 immich-form-label" for="quotaSize"> <label class="flex items-center gap-2 immich-form-label" for="quotaSize">
Quota Size (GiB) Quota Size (GiB)
{#if quotaSizeWarning} {#if quotaSizeWarning}
<p class="text-red-400 text-sm">You set a quota higher than the disk size</p> <p class="text-red-400 text-sm">You set a quota higher than the disk size</p>
{/if} {/if}
</label> </label>
<input class="immich-form-input" id="quotaSize" type="number" min="0" bind:value={quotaSize} /> <input class="immich-form-input" id="quotaSize" type="number" min="0" bind:value={quotaSize} />
</div> </div>
{#if error} {#if error}
<p class="text-sm text-red-400">{error}</p> <p class="text-sm text-red-400">{error}</p>
{/if} {/if}
{#if success} {#if success}
<p class="text-sm text-immich-primary">{success}</p> <p class="text-sm text-immich-primary">{success}</p>
{/if} {/if}
<div class="flex w-full gap-4 pt-4"> </form>
<svelte:fragment slot="sticky-bottom">
<Button color="gray" fullwidth on:click={() => dispatch('cancel')}>Cancel</Button> <Button color="gray" fullwidth on:click={() => dispatch('cancel')}>Cancel</Button>
<Button type="submit" disabled={isCreatingUser} fullwidth>Create</Button> <Button type="submit" disabled={isCreatingUser} fullwidth form="create-new-user-form">Create</Button>
</div> </svelte:fragment>
</form> </FullScreenModal>

View file

@ -3,10 +3,12 @@
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import Button from '$lib/components/elements/buttons/button.svelte'; import Button from '$lib/components/elements/buttons/button.svelte';
import AlbumCover from '$lib/components/album-page/album-cover.svelte'; import AlbumCover from '$lib/components/album-page/album-cover.svelte';
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
export let album: AlbumResponseDto; export let album: AlbumResponseDto;
export let onEditSuccess: ((album: AlbumResponseDto) => unknown) | undefined = undefined; export let onEditSuccess: ((album: AlbumResponseDto) => unknown) | undefined = undefined;
export let onCancel: (() => unknown) | undefined = undefined; export let onCancel: (() => unknown) | undefined = undefined;
export let onClose: () => void;
let albumName = album.albumName; let albumName = album.albumName;
let description = album.description; let description = album.description;
@ -34,29 +36,28 @@
}; };
</script> </script>
<form on:submit|preventDefault={handleUpdateAlbumInfo} autocomplete="off"> <FullScreenModal id="edit-album-modal" title="Edit album" width="wide" {onClose}>
<div class="flex items-center"> <form on:submit|preventDefault={handleUpdateAlbumInfo} autocomplete="off" id="edit-album-form">
<div class="hidden sm:flex"> <div class="flex items-center">
<AlbumCover {album} css="h-[200px] w-[200px] m-4 shadow-lg" /> <div class="hidden sm:flex">
</div> <AlbumCover {album} css="h-[200px] w-[200px] m-4 shadow-lg" />
<div class="flex-grow">
<div class="m-4 flex flex-col gap-2">
<label class="immich-form-label" for="name">Name</label>
<input class="immich-form-input" id="name" type="text" bind:value={albumName} />
</div> </div>
<div class="m-4 flex flex-col gap-2"> <div class="flex-grow">
<label class="immich-form-label" for="description">Description</label> <div class="m-4 flex flex-col gap-2">
<textarea class="immich-form-input" id="description" bind:value={description} /> <label class="immich-form-label" for="name">Name</label>
<input class="immich-form-input" id="name" type="text" bind:value={albumName} />
</div>
<div class="m-4 flex flex-col gap-2">
<label class="immich-form-label" for="description">Description</label>
<textarea class="immich-form-input" id="description" bind:value={description} />
</div>
</div> </div>
</div> </div>
</div> </form>
<svelte:fragment slot="sticky-bottom">
<div class="flex justify-center"> <Button color="gray" fullwidth on:click={() => onCancel?.()}>Cancel</Button>
<div class="mt-8 flex w-full sm:w-2/3 gap-4"> <Button type="submit" fullwidth disabled={isSubmitting} form="edit-album-form">OK</Button>
<Button color="gray" fullwidth on:click={() => onCancel?.()}>Cancel</Button> </svelte:fragment>
<Button type="submit" fullwidth disabled={isSubmitting}>OK</Button> </FullScreenModal>
</div>
</div>
</form>

View file

@ -7,10 +7,13 @@
import { updateUser, type UserResponseDto } from '@immich/sdk'; import { updateUser, type UserResponseDto } from '@immich/sdk';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import Button from '../elements/buttons/button.svelte'; import Button from '../elements/buttons/button.svelte';
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
import { mdiAccountEditOutline } from '@mdi/js';
export let user: UserResponseDto; export let user: UserResponseDto;
export let canResetPassword = true; export let canResetPassword = true;
export let newPassword: string; export let newPassword: string;
export let onClose: () => void;
let error: string; let error: string;
let success: string; let success: string;
@ -87,59 +90,63 @@
} }
</script> </script>
<form on:submit|preventDefault={editUser} autocomplete="off"> <FullScreenModal id="edit-user-modal" title="Edit user" icon={mdiAccountEditOutline} {onClose}>
<div class="my-4 flex flex-col gap-2"> <form on:submit|preventDefault={editUser} autocomplete="off" id="edit-user-form">
<label class="immich-form-label" for="email">Email</label> <div class="my-4 flex flex-col gap-2">
<input class="immich-form-input" id="email" name="email" type="email" bind:value={user.email} /> <label class="immich-form-label" for="email">Email</label>
</div> <input class="immich-form-input" id="email" name="email" type="email" bind:value={user.email} />
</div>
<div class="my-4 flex flex-col gap-2"> <div class="my-4 flex flex-col gap-2">
<label class="immich-form-label" for="name">Name</label> <label class="immich-form-label" for="name">Name</label>
<input class="immich-form-input" id="name" name="name" type="text" required bind:value={user.name} /> <input class="immich-form-input" id="name" name="name" type="text" required bind:value={user.name} />
</div> </div>
<div class="my-4 flex flex-col gap-2"> <div class="my-4 flex flex-col gap-2">
<label class="flex items-center gap-2 immich-form-label" for="quotaSize" <label class="flex items-center gap-2 immich-form-label" for="quotaSize"
>Quota Size (GiB) {#if quotaSizeWarning} >Quota Size (GiB) {#if quotaSizeWarning}
<p class="text-red-400 text-sm">You set a quota higher than the disk size</p> <p class="text-red-400 text-sm">You set a quota higher than the disk size</p>
{/if}</label {/if}</label
> >
<input class="immich-form-input" id="quotaSize" name="quotaSize" type="number" min="0" bind:value={quotaSize} /> <input class="immich-form-input" id="quotaSize" name="quotaSize" type="number" min="0" bind:value={quotaSize} />
<p>Note: Enter 0 for unlimited quota</p> <p>Note: Enter 0 for unlimited quota</p>
</div> </div>
<div class="my-4 flex flex-col gap-2"> <div class="my-4 flex flex-col gap-2">
<label class="immich-form-label" for="storage-label">Storage Label</label> <label class="immich-form-label" for="storage-label">Storage Label</label>
<input <input
class="immich-form-input" class="immich-form-input"
id="storage-label" id="storage-label"
name="storage-label" name="storage-label"
type="text" type="text"
bind:value={user.storageLabel} bind:value={user.storageLabel}
/> />
<p> <p>
Note: To apply the Storage Label to previously uploaded assets, run the Note: To apply the Storage Label to previously uploaded assets, run the
<a href={AppRoute.ADMIN_JOBS} class="text-immich-primary dark:text-immich-dark-primary"> Storage Migration Job</a> <a href={AppRoute.ADMIN_JOBS} class="text-immich-primary dark:text-immich-dark-primary">
</p> Storage Migration Job</a
</div> >
</p>
</div>
{#if error} {#if error}
<p class="ml-4 text-sm text-red-400">{error}</p> <p class="ml-4 text-sm text-red-400">{error}</p>
{/if} {/if}
{#if success} {#if success}
<p class="ml-4 text-sm text-immich-primary">{success}</p> <p class="ml-4 text-sm text-immich-primary">{success}</p>
{/if} {/if}
<div class="mt-8 flex w-full gap-4"> </form>
<svelte:fragment slot="sticky-bottom">
{#if canResetPassword} {#if canResetPassword}
<Button color="light-red" fullwidth on:click={() => (isShowResetPasswordConfirmation = true)} <Button color="light-red" fullwidth on:click={() => (isShowResetPasswordConfirmation = true)}
>Reset password</Button >Reset password</Button
> >
{/if} {/if}
<Button type="submit" fullwidth>Confirm</Button> <Button type="submit" fullwidth form="edit-user-form">Confirm</Button>
</div> </svelte:fragment>
</form> </FullScreenModal>
{#if isShowResetPasswordConfirmation} {#if isShowResetPasswordConfirmation}
<ConfirmDialogue <ConfirmDialogue

View file

@ -34,7 +34,7 @@
icon={mdiFolderRemove} icon={mdiFolderRemove}
onClose={handleCancel} onClose={handleCancel}
> >
<form on:submit|preventDefault={() => handleSubmit()} autocomplete="off"> <form on:submit|preventDefault={() => handleSubmit()} autocomplete="off" id="add-exclusion-pattern-form">
<p class="py-5 text-sm"> <p class="py-5 text-sm">
Exclusion patterns lets you ignore files and folders when scanning your library. This is useful if you have Exclusion patterns lets you ignore files and folders when scanning your library. This is useful if you have
folders that contain files you don't want to import, such as RAW files. folders that contain files you don't want to import, such as RAW files.
@ -52,18 +52,17 @@
bind:value={exclusionPattern} bind:value={exclusionPattern}
/> />
</div> </div>
<div class="mt-8 flex w-full gap-4">
<Button color="gray" fullwidth on:click={() => handleCancel()}>Cancel</Button>
{#if isEditing}
<Button color="red" fullwidth on:click={() => dispatch('delete')}>Delete</Button>
{/if}
<Button type="submit" disabled={!canSubmit} fullwidth>{submitText}</Button>
</div>
<div class="mt-8 flex w-full gap-4"> <div class="mt-8 flex w-full gap-4">
{#if isDuplicate} {#if isDuplicate}
<p class="text-red-500 text-sm">This exclusion pattern already exists.</p> <p class="text-red-500 text-sm">This exclusion pattern already exists.</p>
{/if} {/if}
</div> </div>
</form> </form>
<svelte:fragment slot="sticky-bottom">
<Button color="gray" fullwidth on:click={() => handleCancel()}>Cancel</Button>
{#if isEditing}
<Button color="red" fullwidth on:click={() => dispatch('delete')}>Delete</Button>
{/if}
<Button type="submit" disabled={!canSubmit} fullwidth form="add-exclusion-pattern-form">{submitText}</Button>
</svelte:fragment>
</FullScreenModal> </FullScreenModal>

View file

@ -31,7 +31,7 @@
</script> </script>
<FullScreenModal id="library-import-path-modal" {title} icon={mdiFolderSync} onClose={handleCancel}> <FullScreenModal id="library-import-path-modal" {title} icon={mdiFolderSync} onClose={handleCancel}>
<form on:submit|preventDefault={() => handleSubmit()} autocomplete="off"> <form on:submit|preventDefault={() => handleSubmit()} autocomplete="off" id="library-import-path-form">
<p class="py-5 text-sm"> <p class="py-5 text-sm">
Specify a folder to import. This folder, including subfolders, will be scanned for images and videos. Specify a folder to import. This folder, including subfolders, will be scanned for images and videos.
</p> </p>
@ -41,19 +41,17 @@
<input class="immich-form-input" id="path" name="path" type="text" bind:value={importPath} /> <input class="immich-form-input" id="path" name="path" type="text" bind:value={importPath} />
</div> </div>
<div class="mt-8 flex w-full gap-4">
<Button color="gray" fullwidth on:click={() => handleCancel()}>{cancelText}</Button>
{#if isEditing}
<Button color="red" fullwidth on:click={() => dispatch('delete')}>Delete</Button>
{/if}
<Button type="submit" disabled={!canSubmit} fullwidth>{submitText}</Button>
</div>
<div class="mt-8 flex w-full gap-4"> <div class="mt-8 flex w-full gap-4">
{#if isDuplicate} {#if isDuplicate}
<p class="text-red-500 text-sm">This import path already exists.</p> <p class="text-red-500 text-sm">This import path already exists.</p>
{/if} {/if}
</div> </div>
</form> </form>
<svelte:fragment slot="sticky-bottom">
<Button color="gray" fullwidth on:click={() => handleCancel()}>{cancelText}</Button>
{#if isEditing}
<Button color="red" fullwidth on:click={() => dispatch('delete')}>Delete</Button>
{/if}
<Button type="submit" disabled={!canSubmit} fullwidth form="library-import-path-form">{submitText}</Button>
</svelte:fragment>
</FullScreenModal> </FullScreenModal>

View file

@ -33,15 +33,13 @@
icon={mdiFolderSync} icon={mdiFolderSync}
onClose={handleCancel} onClose={handleCancel}
> >
<form on:submit|preventDefault={() => handleSubmit()} autocomplete="off"> <form on:submit|preventDefault={() => handleSubmit()} autocomplete="off" id="select-library-owner-form">
<p class="p-5 text-sm">NOTE: This cannot be changed later!</p> <p class="p-5 text-sm">NOTE: This cannot be changed later!</p>
<SettingSelect bind:value={ownerId} options={userOptions} name="user" /> <SettingSelect bind:value={ownerId} options={userOptions} name="user" />
<div class="mt-8 flex w-full gap-4">
<Button color="gray" fullwidth on:click={() => handleCancel()}>Cancel</Button>
<Button type="submit" fullwidth>Create</Button>
</div>
</form> </form>
<svelte:fragment slot="sticky-bottom">
<Button color="gray" fullwidth on:click={() => handleCancel()}>Cancel</Button>
<Button type="submit" fullwidth form="select-library-owner-form">Create</Button>
</svelte:fragment>
</FullScreenModal> </FullScreenModal>

View file

@ -25,6 +25,7 @@
<form <form
on:submit|preventDefault={() => dispatch('save', settings)} on:submit|preventDefault={() => dispatch('save', settings)}
class="flex flex-col gap-4 text-immich-primary dark:text-immich-dark-primary" class="flex flex-col gap-4 text-immich-primary dark:text-immich-dark-primary"
id="map-settings-form"
> >
<SettingSwitch id="allow-dark-mode" title="Allow dark mode" bind:checked={settings.allowDarkMode} /> <SettingSwitch id="allow-dark-mode" title="Allow dark mode" bind:checked={settings.allowDarkMode} />
<SettingSwitch id="only-favorites" title="Only favorites" bind:checked={settings.onlyFavorites} /> <SettingSwitch id="only-favorites" title="Only favorites" bind:checked={settings.onlyFavorites} />
@ -103,10 +104,9 @@
</div> </div>
</div> </div>
{/if} {/if}
<div class="mt-4 flex w-full gap-4">
<Button color="gray" size="sm" fullwidth on:click={handleClose}>Cancel</Button>
<Button type="submit" size="sm" fullwidth>Save</Button>
</div>
</form> </form>
<svelte:fragment slot="sticky-bottom">
<Button color="gray" size="sm" fullwidth on:click={handleClose}>Cancel</Button>
<Button type="submit" size="sm" fullwidth form="map-settings-form">Save</Button>
</svelte:fragment>
</FullScreenModal> </FullScreenModal>

View file

@ -4,8 +4,8 @@
import { mdiPlus } from '@mdi/js'; import { mdiPlus } from '@mdi/js';
import { createEventDispatcher, onMount } from 'svelte'; import { createEventDispatcher, onMount } from 'svelte';
import AlbumListItem from '../asset-viewer/album-list-item.svelte'; import AlbumListItem from '../asset-viewer/album-list-item.svelte';
import BaseModal from './base-modal.svelte';
import { normalizeSearchString } from '$lib/utils/string-utils'; import { normalizeSearchString } from '$lib/utils/string-utils';
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
let albums: AlbumResponseDto[] = []; let albums: AlbumResponseDto[] = [];
let recentAlbums: AlbumResponseDto[] = []; let recentAlbums: AlbumResponseDto[] = [];
@ -52,7 +52,7 @@
}; };
</script> </script>
<BaseModal id="album-selection-modal" title={getTitle()} {onClose}> <FullScreenModal id="album-selection-modal" title={getTitle()} {onClose}>
<div class="mb-2 flex max-h-[400px] flex-col"> <div class="mb-2 flex max-h-[400px] flex-col">
{#if loading} {#if loading}
{#each { length: 3 } as _} {#each { length: 3 } as _}
@ -76,7 +76,7 @@
<div class="immich-scrollbar overflow-y-auto"> <div class="immich-scrollbar overflow-y-auto">
<button <button
on:click={handleNew} on:click={handleNew}
class="flex w-full items-center gap-4 px-6 py-2 transition-colors hover:bg-gray-200 dark:hover:bg-gray-700" class="flex w-full items-center gap-4 px-6 py-2 transition-colors hover:bg-gray-200 dark:hover:bg-gray-700 rounded-xl"
> >
<div class="flex h-12 w-12 items-center justify-center"> <div class="flex h-12 w-12 items-center justify-center">
<Icon path={mdiPlus} size="30" /> <Icon path={mdiPlus} size="30" />
@ -110,4 +110,4 @@
</div> </div>
{/if} {/if}
</div> </div>
</BaseModal> </FullScreenModal>

View file

@ -1,78 +0,0 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
import { onMount, onDestroy } from 'svelte';
import { browser } from '$app/environment';
import { clickOutside } from '$lib/utils/click-outside';
import FocusTrap from '$lib/components/shared-components/focus-trap.svelte';
import ModalHeader from '$lib/components/shared-components/modal-header.svelte';
/**
* Unique identifier for the modal.
*/
export let id: string;
export let title: string;
export let onClose: () => void;
export let zIndex = 9999;
/**
* If true, the logo will be displayed next to the modal title.
*/
export let showLogo = false;
/**
* Optional icon to display next to the modal title, if `showLogo` is false.
*/
export let icon: string | undefined = undefined;
$: titleId = `${id}-title`;
onMount(() => {
if (browser) {
const scrollTop = document.documentElement.scrollTop;
const scrollLeft = document.documentElement.scrollLeft;
/* eslint-disable unicorn/prefer-add-event-listener */
window.onscroll = function () {
window.scrollTo(scrollLeft, scrollTop);
};
}
});
onDestroy(() => {
if (browser) {
/* eslint-disable unicorn/prefer-add-event-listener */
window.onscroll = null;
}
});
</script>
<FocusTrap>
<div
aria-modal="true"
aria-labelledby={titleId}
style:z-index={zIndex}
transition:fade={{ duration: 100, easing: quintOut }}
class="fixed left-0 top-0 flex h-full w-full place-content-center place-items-center overflow-hidden bg-black/50"
>
<div
use:clickOutside={{
onOutclick: onClose,
onEscape: onClose,
}}
class="min-h-[200px] w-[450px] overflow-y-auto rounded-3xl bg-immich-bg shadow-md dark:bg-immich-dark-gray dark:text-immich-dark-fg immich-scrollbar scroll-pb-20"
style="max-height: min(95vh, 800px);"
tabindex="-1"
>
<ModalHeader id={titleId} {title} {showLogo} {icon} {onClose} />
<div>
<slot />
</div>
{#if $$slots['sticky-bottom']}
<div class="sticky bottom-0 bg-immich-bg px-5 pb-5 pt-3 dark:bg-immich-dark-gray">
<slot name="sticky-bottom" />
</div>
{/if}
</div>
</div>
</FocusTrap>

View file

@ -65,7 +65,6 @@
<ConfirmDialogue <ConfirmDialogue
id="edit-date-time-modal" id="edit-date-time-modal"
confirmColor="primary" confirmColor="primary"
cancelColor="secondary"
title="Edit date and time" title="Edit date and time"
prompt="Please select a new date:" prompt="Please select a new date:"
disabled={!date.isValid} disabled={!date.isValid}

View file

@ -96,7 +96,6 @@
<ConfirmDialogue <ConfirmDialogue
id="change-location-modal" id="change-location-modal"
confirmColor="primary" confirmColor="primary"
cancelColor="secondary"
title="Change location" title="Change location"
width="wide" width="wide"
onConfirm={handleConfirm} onConfirm={handleConfirm}

View file

@ -9,7 +9,7 @@
export let confirmText = 'Confirm'; export let confirmText = 'Confirm';
export let confirmColor: Color = 'red'; export let confirmColor: Color = 'red';
export let cancelText = 'Cancel'; export let cancelText = 'Cancel';
export let cancelColor: Color = 'primary'; export let cancelColor: Color = 'secondary';
export let hideCancelButton = false; export let hideCancelButton = false;
export let disabled = false; export let disabled = false;
export let width: 'wide' | 'narrow' = 'narrow'; export let width: 'wide' | 'narrow' = 'narrow';
@ -31,7 +31,7 @@
</slot> </slot>
</div> </div>
<div class="mt-4 flex flex-col sm:flex-row w-full gap-4"> <svelte:fragment slot="sticky-bottom">
{#if !hideCancelButton} {#if !hideCancelButton}
<Button color={cancelColor} fullwidth on:click={onClose}> <Button color={cancelColor} fullwidth on:click={onClose}>
{cancelText} {cancelText}
@ -40,5 +40,5 @@
<Button color={confirmColor} fullwidth on:click={handleConfirm} disabled={disabled || isConfirmButtonDisabled}> <Button color={confirmColor} fullwidth on:click={handleConfirm} disabled={disabled || isConfirmButtonDisabled}>
{confirmText} {confirmText}
</Button> </Button>
</div> </svelte:fragment>
</FullScreenModal> </FullScreenModal>

View file

@ -8,12 +8,12 @@
import { SharedLinkType, createSharedLink, updateSharedLink, type SharedLinkResponseDto } from '@immich/sdk'; import { SharedLinkType, createSharedLink, updateSharedLink, type SharedLinkResponseDto } from '@immich/sdk';
import { mdiContentCopy, mdiLink } from '@mdi/js'; import { mdiContentCopy, mdiLink } from '@mdi/js';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import BaseModal from '../base-modal.svelte';
import type { ImmichDropDownOption } from '../dropdown-button.svelte'; import type { ImmichDropDownOption } from '../dropdown-button.svelte';
import DropdownButton from '../dropdown-button.svelte'; import DropdownButton from '../dropdown-button.svelte';
import { NotificationType, notificationController } from '../notification/notification'; import { NotificationType, notificationController } from '../notification/notification';
import SettingInputField, { SettingInputFieldType } from '../settings/setting-input-field.svelte'; import SettingInputField, { SettingInputFieldType } from '../settings/setting-input-field.svelte';
import SettingSwitch from '../settings/setting-switch.svelte'; import SettingSwitch from '../settings/setting-switch.svelte';
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
export let onClose: () => void; export let onClose: () => void;
export let albumId: string | undefined = undefined; export let albumId: string | undefined = undefined;
@ -159,8 +159,8 @@
}; };
</script> </script>
<BaseModal id="create-shared-link-modal" title={getTitle()} icon={mdiLink} {onClose}> <FullScreenModal id="create-shared-link-modal" title={getTitle()} icon={mdiLink} {onClose}>
<section class="mx-6 mb-6"> <section>
{#if shareType === SharedLinkType.Album} {#if shareType === SharedLinkType.Album}
{#if !editingLink} {#if !editingLink}
<div>Let anyone with the link see photos and people in this album.</div> <div>Let anyone with the link see photos and people in this album.</div>
@ -246,29 +246,22 @@
</div> </div>
</section> </section>
<hr /> <svelte:fragment slot="sticky-bottom">
<section slot="sticky-bottom">
{#if !sharedLink} {#if !sharedLink}
{#if editingLink} {#if editingLink}
<div class="flex justify-end"> <Button size="sm" fullwidth on:click={handleEditLink}>Confirm</Button>
<Button size="sm" on:click={handleEditLink}>Confirm</Button>
</div>
{:else} {:else}
<div class="flex justify-end"> <Button size="sm" fullwidth on:click={handleCreateSharedLink}>Create link</Button>
<Button size="sm" on:click={handleCreateSharedLink}>Create link</Button>
</div>
{/if} {/if}
{:else} {:else}
<div class="flex w-full gap-4"> <div class="flex w-full gap-2">
<input class="immich-form-input w-full" bind:value={sharedLink} disabled /> <input class="immich-form-input w-full" bind:value={sharedLink} disabled />
<LinkButton on:click={() => (sharedLink ? copyToClipboard(sharedLink) : '')}> <LinkButton on:click={() => (sharedLink ? copyToClipboard(sharedLink) : '')}>
<div class="flex place-items-center gap-2 text-sm"> <div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiContentCopy} size="18" /> <Icon path={mdiContentCopy} ariaLabel="Copy link to clipboard" size="18" />
</div> </div>
</LinkButton> </LinkButton>
</div> </div>
{/if} {/if}
</section> </svelte:fragment>
</BaseModal> </FullScreenModal>

View file

@ -29,6 +29,7 @@
export let width: 'wide' | 'narrow' | 'auto' = 'narrow'; export let width: 'wide' | 'narrow' | 'auto' = 'narrow';
$: titleId = `${id}-title`; $: titleId = `${id}-title`;
$: isStickyBottom = !!$$slots['sticky-bottom'];
let modalWidth: string; let modalWidth: string;
$: { $: {
@ -50,15 +51,25 @@
> >
<div <div
class="z-[9999] max-w-[95vw] max-h-[95vh] {modalWidth} overflow-y-auto rounded-3xl bg-immich-bg shadow-md dark:bg-immich-dark-gray dark:text-immich-dark-fg immich-scrollbar" class="z-[9999] max-w-[95vw] max-h-[95vh] {modalWidth} overflow-y-auto rounded-3xl bg-immich-bg shadow-md dark:bg-immich-dark-gray dark:text-immich-dark-fg immich-scrollbar"
style="max-height: min(95vh, 900px);"
use:clickOutside={{ onOutclick: onClose, onEscape: onClose }} use:clickOutside={{ onOutclick: onClose, onEscape: onClose }}
tabindex="-1" tabindex="-1"
aria-modal="true" aria-modal="true"
aria-labelledby={titleId} aria-labelledby={titleId}
class:scroll-pb-40={isStickyBottom}
class:sm:scroll-p-24={isStickyBottom}
> >
<ModalHeader id={titleId} {title} {showLogo} {icon} {onClose} /> <ModalHeader id={titleId} {title} {showLogo} {icon} {onClose} />
<div class="p-5 pt-0"> <div class="p-5 pt-0">
<slot /> <slot />
</div> </div>
{#if isStickyBottom}
<div
class="flex flex-col sm:flex-row justify-end w-full gap-2 sm:gap-4 sticky bottom-0 py-4 px-5 bg-immich-bg dark:bg-immich-dark-gray border-t border-gray-200 dark:border-gray-500 shadow"
>
<slot name="sticky-bottom" />
</div>
{/if}
</div> </div>
</section> </section>
</FocusTrap> </FocusTrap>

View file

@ -6,8 +6,8 @@
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import PhotoViewer from '../asset-viewer/photo-viewer.svelte'; import PhotoViewer from '../asset-viewer/photo-viewer.svelte';
import Button from '../elements/buttons/button.svelte'; import Button from '../elements/buttons/button.svelte';
import BaseModal from './base-modal.svelte';
import { NotificationType, notificationController } from './notification/notification'; import { NotificationType, notificationController } from './notification/notification';
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
export let asset: AssetResponseDto; export let asset: AssetResponseDto;
export let onClose: () => void; export let onClose: () => void;
@ -69,18 +69,15 @@
}; };
</script> </script>
<BaseModal id="profile-image-cropper" title="Set profile picture" {onClose}> <FullScreenModal id="profile-image-cropper" title="Set profile picture" width="auto" {onClose}>
<div class="flex place-items-center items-center justify-center"> <div class="flex place-items-center items-center justify-center">
<div <div
class="relative flex aspect-square w-1/2 overflow-hidden rounded-full border-4 border-immich-primary bg-immich-dark-primary dark:border-immich-dark-primary dark:bg-immich-primary" class="relative flex aspect-square w-[250px] overflow-hidden rounded-full border-4 border-immich-primary bg-immich-dark-primary dark:border-immich-dark-primary dark:bg-immich-primary"
> >
<PhotoViewer bind:element={imgElement} {asset} /> <PhotoViewer bind:element={imgElement} {asset} />
</div> </div>
</div> </div>
<span class="flex justify-end p-4"> <svelte:fragment slot="sticky-bottom">
<Button on:click={handleSetProfilePicture}> <Button fullwidth on:click={handleSetProfilePicture}>Set as profile picture</Button>
<p>Set as profile picture</p> </svelte:fragment>
</Button> </FullScreenModal>
</span>
<div class="mb-2 flex max-h-[400px] flex-col" />
</BaseModal>

View file

@ -53,8 +53,8 @@
<code>Latest Version: {releaseVersion}</code> <code>Latest Version: {releaseVersion}</code>
</div> </div>
<div class="mt-8 text-right"> <svelte:fragment slot="sticky-bottom">
<Button fullwidth on:click={onAcknowledge}>Acknowledge</Button> <Button fullwidth on:click={onAcknowledge}>Acknowledge</Button>
</div> </svelte:fragment>
</FullScreenModal> </FullScreenModal>
{/if} {/if}

View file

@ -46,6 +46,8 @@
min={1} min={1}
bind:value={$slideshowDelay} bind:value={$slideshowDelay}
/> />
<Button class="w-full" color="gray" on:click={onClose}>Done</Button>
</div> </div>
<svelte:fragment slot="sticky-bottom">
<Button fullwidth color="primary" on:click={onClose}>Done</Button>
</svelte:fragment>
</FullScreenModal> </FullScreenModal>

View file

@ -2,8 +2,8 @@
import { getAllUsers, getPartners, type UserResponseDto } from '@immich/sdk'; import { getAllUsers, getPartners, type UserResponseDto } from '@immich/sdk';
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 BaseModal from '../shared-components/base-modal.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';
export let user: UserResponseDto; export let user: UserResponseDto;
export let onClose: () => void; export let onClose: () => void;
@ -33,13 +33,13 @@
}; };
</script> </script>
<BaseModal id="partner-selection-modal" title="Add partner" showLogo {onClose}> <FullScreenModal id="partner-selection-modal" title="Add partner" showLogo {onClose}>
<div class="immich-scrollbar max-h-[300px] overflow-y-auto"> <div class="immich-scrollbar max-h-[300px] overflow-y-auto">
{#if availableUsers.length > 0} {#if availableUsers.length > 0}
{#each availableUsers as user} {#each availableUsers as user}
<button <button
on:click={() => selectUser(user)} on:click={() => selectUser(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" 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 selectedUsers.includes(user)}
<span <span
@ -61,15 +61,15 @@
</button> </button>
{/each} {/each}
{:else} {:else}
<p class="p-5 text-sm"> <p class="py-5 text-sm">
Looks like you shared your photos with all users or you don't have any user to share with. Looks like you shared your photos with all users or you don't have any user to share with.
</p> </p>
{/if} {/if}
{#if selectedUsers.length > 0} {#if selectedUsers.length > 0}
<div class="flex place-content-end p-5"> <div class="pt-5">
<Button size="sm" rounded="lg" on:click={() => dispatch('add-users', selectedUsers)}>Add</Button> <Button size="sm" fullwidth on:click={() => dispatch('add-users', selectedUsers)}>Add</Button>
</div> </div>
{/if} {/if}
</div> </div>
</BaseModal> </FullScreenModal>

View file

@ -465,24 +465,22 @@
{#if showChangeNameModal} {#if showChangeNameModal}
<FullScreenModal id="change-name-modal" title="Change name" onClose={() => (showChangeNameModal = false)}> <FullScreenModal id="change-name-modal" title="Change name" onClose={() => (showChangeNameModal = false)}>
<form on:submit|preventDefault={submitNameChange} autocomplete="off"> <form on:submit|preventDefault={submitNameChange} autocomplete="off" id="change-name-form">
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<label class="immich-form-label" for="name">Name</label> <label class="immich-form-label" for="name">Name</label>
<!-- svelte-ignore a11y-autofocus --> <input class="immich-form-input" id="name" name="name" type="text" bind:value={personName} />
<input class="immich-form-input" id="name" name="name" type="text" bind:value={personName} autofocus />
</div>
<div class="mt-8 flex w-full gap-4">
<Button
color="gray"
fullwidth
on:click={() => {
showChangeNameModal = false;
}}>Cancel</Button
>
<Button type="submit" fullwidth>Ok</Button>
</div> </div>
</form> </form>
<svelte:fragment slot="sticky-bottom">
<Button
color="gray"
fullwidth
on:click={() => {
showChangeNameModal = false;
}}>Cancel</Button
>
<Button type="submit" fullwidth form="change-name-form">Ok</Button>
</svelte:fragment>
</FullScreenModal> </FullScreenModal>
{/if} {/if}

View file

@ -9,7 +9,6 @@
import CreateUserForm from '$lib/components/forms/create-user-form.svelte'; import CreateUserForm from '$lib/components/forms/create-user-form.svelte';
import EditUserForm from '$lib/components/forms/edit-user-form.svelte'; import EditUserForm from '$lib/components/forms/edit-user-form.svelte';
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte'; import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
import { import {
NotificationType, NotificationType,
notificationController, notificationController,
@ -21,14 +20,7 @@
import { asByteUnitString } from '$lib/utils/byte-units'; import { asByteUnitString } from '$lib/utils/byte-units';
import { copyToClipboard } from '$lib/utils'; import { copyToClipboard } from '$lib/utils';
import { UserStatus, getAllUsers, type UserResponseDto } from '@immich/sdk'; import { UserStatus, getAllUsers, type UserResponseDto } from '@immich/sdk';
import { import { mdiClose, mdiContentCopy, mdiDeleteRestore, mdiPencilOutline, mdiTrashCanOutline } from '@mdi/js';
mdiAccountEditOutline,
mdiClose,
mdiContentCopy,
mdiDeleteRestore,
mdiPencilOutline,
mdiTrashCanOutline,
} from '@mdi/js';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import type { PageData } from './$types'; import type { PageData } from './$types';
@ -123,32 +115,22 @@
<section id="setting-content" class="flex place-content-center sm:mx-4"> <section id="setting-content" class="flex place-content-center sm:mx-4">
<section class="w-full pb-28 lg:w-[850px]"> <section class="w-full pb-28 lg:w-[850px]">
{#if shouldShowCreateUserForm} {#if shouldShowCreateUserForm}
<FullScreenModal <CreateUserForm
id="create-new-user-modal" on:submit={onUserCreated}
title="Create new user" on:cancel={() => (shouldShowCreateUserForm = false)}
showLogo
onClose={() => (shouldShowCreateUserForm = false)} onClose={() => (shouldShowCreateUserForm = false)}
> />
<CreateUserForm on:submit={onUserCreated} on:cancel={() => (shouldShowCreateUserForm = false)} />
</FullScreenModal>
{/if} {/if}
{#if shouldShowEditUserForm} {#if shouldShowEditUserForm}
<FullScreenModal <EditUserForm
id="edit-user-modal" user={selectedUser}
title="Edit user" bind:newPassword
icon={mdiAccountEditOutline} canResetPassword={selectedUser?.id !== $user.id}
on:editSuccess={onEditUserSuccess}
on:resetPasswordSuccess={onEditPasswordSuccess}
onClose={() => (shouldShowEditUserForm = false)} onClose={() => (shouldShowEditUserForm = false)}
> />
<EditUserForm
user={selectedUser}
bind:newPassword
canResetPassword={selectedUser?.id !== $user.id}
on:editSuccess={onEditUserSuccess}
on:resetPasswordSuccess={onEditPasswordSuccess}
on:close={() => (shouldShowEditUserForm = false)}
/>
</FullScreenModal>
{/if} {/if}
{#if shouldShowDeleteConfirmDialog} {#if shouldShowDeleteConfirmDialog}