feat(web,a11y): standardize the FullScreenModal UI (#8566)

* feat(web,a11y): standardize the FullScreenModal look

* consistent header, padding, close button, and radius as BaseModal
* vertically stacking ConfirmDialogue CTA buttons in narrow screens
* adding aria-modal tags for screen reader
* add viewport-specific height limits on modals, to enable scrolling
* prevent focus from being hidden under sticky content in modals
* standardize FullScreenModal widths using a Prop

* wip: consistent padding with header

* fix: alignment on "create user" and "edit user" modals

* fix: horizontal modal content alignment

* fix: create user CTA buttons

* chore: remove unnecessary warning

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
Ben 2024-04-08 21:02:09 +00:00 committed by GitHub
parent d43daaee81
commit 796c933fb8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 749 additions and 853 deletions

View file

@ -1,5 +1,4 @@
<script lang="ts">
import Icon from '$lib/components/elements/icon.svelte';
import type { ApiKeyResponseDto } from '@immich/sdk';
import { mdiKeyVariant } from '@mdi/js';
import { createEventDispatcher } from 'svelte';
@ -8,7 +7,7 @@
import { NotificationType, notificationController } from '../shared-components/notification/notification';
export let apiKey: Partial<ApiKeyResponseDto>;
export let title = 'API Key';
export let title: string;
export let cancelText = 'Cancel';
export let submitText = 'Save';
@ -29,29 +28,16 @@
};
</script>
<FullScreenModal onClose={handleCancel}>
<div
class="w-[500px] max-w-[95vw] rounded-3xl border bg-immich-bg p-4 py-8 shadow-sm dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-fg"
>
<div
class="flex flex-col place-content-center place-items-center gap-4 px-4 text-immich-primary dark:text-immich-dark-primary"
>
<Icon path={mdiKeyVariant} size="4em" />
<h1 class="text-2xl font-medium text-immich-primary dark:text-immich-dark-primary">
{title}
</h1>
<FullScreenModal id="api-key-modal" {title} icon={mdiKeyVariant} onClose={handleCancel}>
<form on:submit|preventDefault={handleSubmit} autocomplete="off">
<div class="mb-4 flex flex-col gap-2">
<label class="immich-form-label" for="name">Name</label>
<input class="immich-form-input" id="name" name="name" type="text" bind:value={apiKey.name} />
</div>
<form on:submit|preventDefault={handleSubmit} autocomplete="off">
<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" name="name" type="text" bind:value={apiKey.name} />
</div>
<div class="mt-8 flex w-full gap-4 px-4">
<Button color="gray" fullwidth on:click={handleCancel}>{cancelText}</Button>
<Button type="submit" fullwidth>{submitText}</Button>
</div>
</form>
</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>
</FullScreenModal>

View file

@ -1,5 +1,4 @@
<script lang="ts">
import Icon from '$lib/components/elements/icon.svelte';
import { copyToClipboard } from '$lib/utils';
import { mdiKeyVariant } from '@mdi/js';
import { createEventDispatcher, onMount } from 'svelte';
@ -20,31 +19,22 @@
});
</script>
<FullScreenModal>
<div
class="w-[500px] max-w-[95vw] rounded-3xl border bg-immich-bg p-4 py-8 shadow-sm dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-fg"
>
<div
class="flex flex-col place-content-center place-items-center gap-4 px-4 text-immich-primary dark:text-immich-dark-primary"
>
<Icon path={mdiKeyVariant} size="4em" />
<h1 class="text-2xl font-medium text-immich-primary dark:text-immich-dark-primary">API Key</h1>
<FullScreenModal id="api-key-secret-modal" title="API key" icon={mdiKeyVariant} onClose={() => handleDone()}>
<div class="text-immich-primary dark:text-immich-dark-primary">
<p class="text-sm dark:text-immich-dark-fg">
This value will only be shown once. Please be sure to copy it before closing the window.
</p>
</div>
<p class="text-sm dark:text-immich-dark-fg">
This value will only be shown once. Please be sure to copy it before closing the window.
</p>
</div>
<div class="my-4 flex flex-col gap-2">
<!-- <label class="immich-form-label" for="secret">API Key</label> -->
<textarea class="immich-form-input" id="secret" name="secret" readonly={true} value={secret} />
</div>
<div class="m-4 flex flex-col gap-2">
<!-- <label class="immich-form-label" for="secret">API Key</label> -->
<textarea class="immich-form-input" id="secret" name="secret" readonly={true} value={secret} />
</div>
<div class="mt-8 flex w-full gap-4 px-4">
{#if canCopyImagesToClipboard}
<Button on:click={() => copyToClipboard(secret)} fullwidth>Copy to Clipboard</Button>
{/if}
<Button on:click={() => handleDone()} fullwidth>Done</Button>
</div>
<div class="mt-8 flex w-full gap-4">
{#if canCopyImagesToClipboard}
<Button on:click={() => copyToClipboard(secret)} fullwidth>Copy to Clipboard</Button>
{/if}
<Button on:click={() => handleDone()} fullwidth>Done</Button>
</div>
</FullScreenModal>

View file

@ -5,7 +5,6 @@
import { createUser } from '@immich/sdk';
import { createEventDispatcher } from 'svelte';
import Button from '../elements/buttons/button.svelte';
import ImmichLogo from '../shared-components/immich-logo.svelte';
import PasswordField from '../shared-components/password-field.svelte';
import Slider from '../elements/slider.svelte';
@ -69,62 +68,53 @@
}
</script>
<div
class="max-h-screen w-[500px] max-w-[95vw] overflow-y-auto immich-scrollbar rounded-3xl border bg-immich-bg p-4 py-8 shadow-sm dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-fg"
>
<div class="flex flex-col place-content-center place-items-center gap-4 px-4">
<ImmichLogo noText class="text-center" height="75" width="75" />
<h1 class="text-2xl font-medium text-immich-primary dark:text-immich-dark-primary">Create new user</h1>
<form on:submit|preventDefault={registerUser} autocomplete="off">
<div class="my-4 flex flex-col gap-2">
<label class="immich-form-label" for="email">Email</label>
<input class="immich-form-input" id="email" bind:value={email} type="email" required />
</div>
<form on:submit|preventDefault={registerUser} autocomplete="off">
<div class="m-4 flex flex-col gap-2">
<label class="immich-form-label" for="email">Email</label>
<input class="immich-form-input" id="email" bind:value={email} type="email" required />
</div>
<div class="my-4 flex flex-col gap-2">
<label class="immich-form-label" for="password">Password</label>
<PasswordField id="password" bind:password autocomplete="new-password" />
</div>
<div class="m-4 flex flex-col gap-2">
<label class="immich-form-label" for="password">Password</label>
<PasswordField id="password" bind:password autocomplete="new-password" />
</div>
<div class="my-4 flex flex-col gap-2">
<label class="immich-form-label" for="confirmPassword">Confirm Password</label>
<PasswordField id="confirmPassword" bind:password={confirmPassword} autocomplete="new-password" />
</div>
<div class="m-4 flex flex-col gap-2">
<label class="immich-form-label" for="confirmPassword">Confirm Password</label>
<PasswordField id="confirmPassword" bind:password={confirmPassword} autocomplete="new-password" />
</div>
<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">
Require user to change password on first login
</label>
<Slider id="require-password-change" bind:checked={shouldChangePassword} />
</div>
<div class="m-4 flex place-items-center justify-between gap-2">
<label class="text-sm dark:text-immich-dark-fg" for="require-password-change">
Require user to change password on first login
</label>
<Slider id="require-password-change" bind:checked={shouldChangePassword} />
</div>
<div class="my-4 flex flex-col gap-2">
<label class="immich-form-label" for="name">Name</label>
<input class="immich-form-input" id="name" bind:value={name} type="text" required />
</div>
<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" bind:value={name} type="text" required />
</div>
<div class="my-4 flex flex-col gap-2">
<label class="flex items-center gap-2 immich-form-label" for="quotaSize">
Quota Size (GiB)
{#if quotaSizeWarning}
<p class="text-red-400 text-sm">You set a quota higher than the disk size</p>
{/if}
</label>
<input class="immich-form-input" id="quotaSize" type="number" min="0" bind:value={quotaSize} />
</div>
<div class="m-4 flex flex-col gap-2">
<label class="flex items-center gap-2 immich-form-label" for="quotaSize">
Quota Size (GiB)
{#if quotaSizeWarning}
<p class="text-red-400 text-sm">You set a quota higher than the disk size</p>
{/if}
</label>
<input class="immich-form-input" id="quotaSize" type="number" min="0" bind:value={quotaSize} />
</div>
{#if error}
<p class="text-sm text-red-400">{error}</p>
{/if}
{#if error}
<p class="ml-4 text-sm text-red-400">{error}</p>
{/if}
{#if success}
<p class="ml-4 text-sm text-immich-primary">{success}</p>
{/if}
<div class="flex w-full gap-4 p-4">
<Button color="gray" fullwidth on:click={() => dispatch('cancel')}>Cancel</Button>
<Button type="submit" disabled={isCreatingUser} fullwidth>Create</Button>
</div>
</form>
</div>
{#if success}
<p class="text-sm text-immich-primary">{success}</p>
{/if}
<div class="flex w-full gap-4 pt-4">
<Button color="gray" fullwidth on:click={() => dispatch('cancel')}>Cancel</Button>
<Button type="submit" disabled={isCreatingUser} fullwidth>Create</Button>
</div>
</form>

View file

@ -34,39 +34,29 @@
};
</script>
<div
class="max-h-screen w-[700px] max-w-[95vw] overflow-y-auto rounded-3xl border bg-immich-bg p-4 py-8 shadow-sm dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-fg"
>
<div
class="flex flex-col place-content-center place-items-center gap-4 px-4 mb-4 text-immich-primary dark:text-immich-dark-primary"
>
<h1 class="text-2xl font-medium text-immich-primary dark:text-immich-dark-primary">Edit Album</h1>
<form on:submit|preventDefault={handleUpdateAlbumInfo} autocomplete="off">
<div class="flex items-center">
<div class="hidden sm:flex">
<AlbumCover {album} css="h-[200px] w-[200px] m-4 shadow-lg" />
</div>
<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 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>
<form on:submit|preventDefault={handleUpdateAlbumInfo} autocomplete="off">
<div class="flex items-center">
<div class="hidden sm:flex">
<AlbumCover {album} css="h-[200px] w-[200px] m-4 shadow-lg" />
</div>
<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 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 class="flex justify-center">
<div class="mt-8 flex w-full sm:w-2/3 gap-4">
<Button color="gray" fullwidth on:click={() => onCancel?.()}>Cancel</Button>
<Button type="submit" fullwidth disabled={isSubmitting}>OK</Button>
</div>
<div class="flex justify-center">
<div class="mt-8 flex w-full sm:w-2/3 gap-4 px-4">
<Button color="gray" fullwidth on:click={() => onCancel?.()}>Cancel</Button>
<Button type="submit" fullwidth disabled={isSubmitting}>OK</Button>
</div>
</div>
</form>
</div>
</div>
</form>

View file

@ -1,16 +1,12 @@
<script lang="ts">
import Icon from '$lib/components/elements/icon.svelte';
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
import { AppRoute } from '$lib/constants';
import { serverInfo } from '$lib/stores/server-info.store';
import { convertFromBytes, convertToBytes } from '$lib/utils/byte-converter';
import { handleError } from '$lib/utils/handle-error';
import { updateUser, type UserResponseDto } from '@immich/sdk';
import { mdiAccountEditOutline, mdiClose } from '@mdi/js';
import { createEventDispatcher } from 'svelte';
import Button from '../elements/buttons/button.svelte';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
import FocusTrap from '$lib/components/shared-components/focus-trap.svelte';
export let user: UserResponseDto;
export let canResetPassword = true;
@ -91,82 +87,64 @@
}
</script>
<FocusTrap>
<div
class="relative max-h-screen w-[500px] max-w-[95vw] overflow-y-auto rounded-3xl border bg-immich-bg p-4 py-8 shadow-sm dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-fg"
>
<div class="absolute top-0 right-0 px-2 py-2 h-fit">
<CircleIconButton title="Close" icon={mdiClose} on:click={() => dispatch('close')} />
</div>
<div
class="flex flex-col place-content-center place-items-center gap-4 px-4 text-immich-primary dark:text-immich-dark-primary"
>
<Icon path={mdiAccountEditOutline} size="4em" />
<h1 class="text-2xl font-medium text-immich-primary dark:text-immich-dark-primary">Edit user</h1>
</div>
<form on:submit|preventDefault={editUser} autocomplete="off">
<div class="m-4 flex flex-col gap-2">
<label class="immich-form-label" for="email">Email</label>
<input class="immich-form-input" id="email" name="email" type="email" bind:value={user.email} />
</div>
<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" name="name" type="text" required bind:value={user.name} />
</div>
<div class="m-4 flex flex-col gap-2">
<label class="flex items-center gap-2 immich-form-label" for="quotaSize"
>Quota Size (GiB) {#if quotaSizeWarning}
<p class="text-red-400 text-sm">You set a quota higher than the disk size</p>
{/if}</label
>
<input class="immich-form-input" id="quotaSize" name="quotaSize" type="number" min="0" bind:value={quotaSize} />
<p>Note: Enter 0 for unlimited quota</p>
</div>
<div class="m-4 flex flex-col gap-2">
<label class="immich-form-label" for="storage-label">Storage Label</label>
<input
class="immich-form-input"
id="storage-label"
name="storage-label"
type="text"
bind:value={user.storageLabel}
/>
<p>
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
>
</p>
</div>
{#if error}
<p class="ml-4 text-sm text-red-400">{error}</p>
{/if}
{#if success}
<p class="ml-4 text-sm text-immich-primary">{success}</p>
{/if}
<div class="mt-8 flex w-full gap-4 px-4">
{#if canResetPassword}
<Button color="light-red" fullwidth on:click={() => (isShowResetPasswordConfirmation = true)}
>Reset password</Button
>
{/if}
<Button type="submit" fullwidth>Confirm</Button>
</div>
</form>
<form on:submit|preventDefault={editUser} autocomplete="off">
<div class="my-4 flex flex-col gap-2">
<label class="immich-form-label" for="email">Email</label>
<input class="immich-form-input" id="email" name="email" type="email" bind:value={user.email} />
</div>
</FocusTrap>
<div class="my-4 flex flex-col gap-2">
<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} />
</div>
<div class="my-4 flex flex-col gap-2">
<label class="flex items-center gap-2 immich-form-label" for="quotaSize"
>Quota Size (GiB) {#if quotaSizeWarning}
<p class="text-red-400 text-sm">You set a quota higher than the disk size</p>
{/if}</label
>
<input class="immich-form-input" id="quotaSize" name="quotaSize" type="number" min="0" bind:value={quotaSize} />
<p>Note: Enter 0 for unlimited quota</p>
</div>
<div class="my-4 flex flex-col gap-2">
<label class="immich-form-label" for="storage-label">Storage Label</label>
<input
class="immich-form-input"
id="storage-label"
name="storage-label"
type="text"
bind:value={user.storageLabel}
/>
<p>
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>
</p>
</div>
{#if error}
<p class="ml-4 text-sm text-red-400">{error}</p>
{/if}
{#if success}
<p class="ml-4 text-sm text-immich-primary">{success}</p>
{/if}
<div class="mt-8 flex w-full gap-4">
{#if canResetPassword}
<Button color="light-red" fullwidth on:click={() => (isShowResetPasswordConfirmation = true)}
>Reset password</Button
>
{/if}
<Button type="submit" fullwidth>Confirm</Button>
</div>
</form>
{#if isShowResetPasswordConfirmation}
<ConfirmDialogue
title="Reset Password"
id="reset-password-modal"
title="Reset password"
confirmText="Reset"
onConfirm={resetPassword}
onClose={() => (isShowResetPasswordConfirmation = false)}

View file

@ -2,7 +2,6 @@
import { createEventDispatcher } from 'svelte';
import Button from '../elements/buttons/button.svelte';
import FullScreenModal from '../shared-components/full-screen-modal.svelte';
import Icon from '$lib/components/elements/icon.svelte';
import { mdiFolderRemove } from '@mdi/js';
import { onMount } from 'svelte';
@ -29,48 +28,42 @@
const handleSubmit = () => dispatch('submit', { excludePattern: exclusionPattern });
</script>
<FullScreenModal onClose={handleCancel}>
<div
class="w-[500px] max-w-[95vw] rounded-3xl border bg-immich-bg p-4 py-8 shadow-sm dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-fg"
>
<div
class="flex flex-col place-content-center place-items-center gap-4 px-4 text-immich-primary dark:text-immich-dark-primary"
>
<Icon path={mdiFolderRemove} size="4em" />
<h1 class="text-2xl font-medium text-immich-primary dark:text-immich-dark-primary">Add Exclusion pattern</h1>
<FullScreenModal
id="add-exclusion-pattern-modal"
title="Add exclusion pattern"
icon={mdiFolderRemove}
onClose={handleCancel}
>
<form on:submit|preventDefault={() => handleSubmit()} autocomplete="off">
<p class="py-5 text-sm">
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.
<br /><br />
Add exclusion patterns. Globbing using *, **, and ? is supported. To ignore all files in any directory named "Raw",
use "**/Raw/**". To ignore all files ending in ".tif", use "**/*.tif". To ignore an absolute path, use "/path/to/ignore".
</p>
<div class="my-4 flex flex-col gap-2">
<label class="immich-form-label" for="exclusionPattern">Pattern</label>
<input
class="immich-form-input"
id="exclusionPattern"
name="exclusionPattern"
type="text"
bind:value={exclusionPattern}
/>
</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}
<form on:submit|preventDefault={() => handleSubmit()} autocomplete="off">
<p class="p-5 text-sm">
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.
<br /><br />
Add exclusion patterns. Globbing using *, **, and ? is supported. To ignore all files in any directory named "Raw",
use "**/Raw/**". To ignore all files ending in ".tif", use "**/*.tif". To ignore an absolute path, use "/path/to/ignore".
</p>
<div class="m-4 flex flex-col gap-2">
<label class="immich-form-label" for="exclusionPattern">Pattern</label>
<input
class="immich-form-input"
id="exclusionPattern"
name="exclusionPattern"
type="text"
bind:value={exclusionPattern}
/>
</div>
<div class="mt-8 flex w-full gap-4 px-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 px-4">
{#if isDuplicate}
<p class="text-red-500 text-sm">This exclusion pattern already exists.</p>
{/if}
</div>
</form>
</div>
<Button type="submit" disabled={!canSubmit} fullwidth>{submitText}</Button>
</div>
<div class="mt-8 flex w-full gap-4">
{#if isDuplicate}
<p class="text-red-500 text-sm">This exclusion pattern already exists.</p>
{/if}
</div>
</form>
</FullScreenModal>

View file

@ -1,6 +1,5 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import Icon from '$lib/components/elements/icon.svelte';
import Button from '../elements/buttons/button.svelte';
import FullScreenModal from '../shared-components/full-screen-modal.svelte';
import { mdiFolderSync } from '@mdi/js';
@ -31,45 +30,30 @@
const handleSubmit = () => dispatch('submit', { importPath });
</script>
<FullScreenModal onClose={handleCancel}>
<div
class="w-[500px] max-w-[95vw] rounded-3xl border bg-immich-bg p-4 py-8 shadow-sm dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-fg"
>
<div
class="flex flex-col place-content-center place-items-center gap-4 px-4 text-immich-primary dark:text-immich-dark-primary"
>
<Icon path={mdiFolderSync} size="4em" />
<h1 class="text-2xl font-medium text-immich-primary dark:text-immich-dark-primary">
{title}
</h1>
<FullScreenModal id="library-import-path-modal" {title} icon={mdiFolderSync} onClose={handleCancel}>
<form on:submit|preventDefault={() => handleSubmit()} autocomplete="off">
<p class="py-5 text-sm">
Specify a folder to import. This folder, including subfolders, will be scanned for images and videos.
</p>
<div class="my-4 flex flex-col gap-2">
<label class="immich-form-label" for="path">Path</label>
<input class="immich-form-input" id="path" name="path" type="text" bind:value={importPath} />
</div>
<form on:submit|preventDefault={() => handleSubmit()} autocomplete="off">
<p class="p-5 text-sm">
Specify a folder to import. This folder, including subfolders, will be scanned for images and videos. Note that
you are only allowed to import paths inside of your account's external path, configured in the administrative
settings.
</p>
<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}
<div class="m-4 flex flex-col gap-2">
<label class="immich-form-label" for="path">Path</label>
<input class="immich-form-input" id="path" name="path" type="text" bind:value={importPath} />
</div>
<Button type="submit" disabled={!canSubmit} fullwidth>{submitText}</Button>
</div>
<div class="mt-8 flex w-full gap-4 px-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 px-4">
{#if isDuplicate}
<p class="text-red-500 text-sm">This import path already exists.</p>
{/if}
</div>
</form>
</div>
<div class="mt-8 flex w-full gap-4">
{#if isDuplicate}
<p class="text-red-500 text-sm">This import path already exists.</p>
{/if}
</div>
</form>
</FullScreenModal>

View file

@ -152,7 +152,7 @@
{#if addImportPath}
<LibraryImportPathForm
title="Add Import Path"
title="Add import path"
submitText="Add"
bind:importPath={importPathToAdd}
{importPaths}
@ -166,7 +166,7 @@
{#if editImportPath != undefined}
<LibraryImportPathForm
title="Edit Import Path"
title="Edit import path"
submitText="Save"
isEditing={true}
bind:importPath={editedImportPath}

View file

@ -169,7 +169,7 @@
size="sm"
on:click={() => {
addExclusionPattern = true;
}}>Add Exclusion Pattern</Button
}}>Add exclusion pattern</Button
></td
></tr
>

View file

@ -1,6 +1,5 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import Icon from '$lib/components/elements/icon.svelte';
import Button from '../elements/buttons/button.svelte';
import FullScreenModal from '../shared-components/full-screen-modal.svelte';
import { mdiFolderSync } from '@mdi/js';
@ -28,27 +27,21 @@
const handleSubmit = () => dispatch('submit', { ownerId });
</script>
<FullScreenModal onClose={handleCancel}>
<div
class="w-[500px] max-w-[95vw] rounded-3xl border bg-immich-bg p-4 py-8 shadow-sm dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-fg"
>
<div
class="flex flex-col place-content-center place-items-center gap-4 px-4 text-immich-primary dark:text-immich-dark-primary"
>
<Icon path={mdiFolderSync} size="4em" />
<h1 class="text-2xl font-medium text-immich-primary dark:text-immich-dark-primary">Select library owner</h1>
<FullScreenModal
id="select-library-owner-modal"
title="Select library owner"
icon={mdiFolderSync}
onClose={handleCancel}
>
<form on:submit|preventDefault={() => handleSubmit()} autocomplete="off">
<p class="p-5 text-sm">NOTE: This cannot be changed later!</p>
<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 on:submit|preventDefault={() => handleSubmit()} autocomplete="off">
<p class="p-5 text-sm">NOTE: This cannot be changed later!</p>
<SettingSelect bind:value={ownerId} options={userOptions} name="user" />
<div class="mt-8 flex w-full gap-4 px-4">
<Button color="gray" fullwidth on:click={() => handleCancel()}>Cancel</Button>
<Button type="submit" fullwidth>Create</Button>
</div>
</form>
</div>
</form>
</FullScreenModal>