refactor(web): migrate away from event dispatcher (#12802)

This commit is contained in:
Jason Rasmussen 2024-09-19 18:20:09 -04:00 committed by GitHub
parent cfc575d89c
commit 94fc1f213a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 76 additions and 123 deletions

View File

@ -1,25 +1,21 @@
<script lang="ts">
import Checkbox from '$lib/components/elements/checkbox.svelte';
import FormatMessage from '$lib/components/i18n/format-message.svelte';
import ConfirmDialog from '$lib/components/shared-components/dialog/confirm-dialog.svelte';
import { serverConfig } from '$lib/stores/server-config.store';
import { handleError } from '$lib/utils/handle-error';
import { deleteUserAdmin, type UserResponseDto } from '@immich/sdk';
import { serverConfig } from '$lib/stores/server-config.store';
import { createEventDispatcher } from 'svelte';
import Checkbox from '$lib/components/elements/checkbox.svelte';
import { t } from 'svelte-i18n';
import FormatMessage from '$lib/components/i18n/format-message.svelte';
export let user: UserResponseDto;
export let onSuccess: () => void;
export let onFail: () => void;
export let onCancel: () => void;
let forceDelete = false;
let deleteButtonDisabled = false;
let userIdInput: string = '';
const dispatch = createEventDispatcher<{
success: void;
fail: void;
cancel: void;
}>();
const handleDeleteUser = async () => {
try {
const { deletedAt } = await deleteUserAdmin({
@ -28,13 +24,13 @@
});
if (deletedAt == undefined) {
dispatch('fail');
onFail();
} else {
dispatch('success');
onSuccess();
}
} catch (error) {
handleError(error, $t('errors.unable_to_delete_user'));
dispatch('fail');
onFail();
}
};
@ -48,7 +44,7 @@
title={$t('delete_user')}
confirmText={forceDelete ? $t('permanently_delete') : $t('delete')}
onConfirm={handleDeleteUser}
onCancel={() => dispatch('cancel')}
{onCancel}
disabled={deleteButtonDisabled}
>
<svelte:fragment slot="prompt">

View File

@ -1,5 +1,6 @@
<script lang="ts">
import Badge from '$lib/components/elements/badge.svelte';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import Icon from '$lib/components/elements/icon.svelte';
import { locale } from '$lib/stores/preferences.store';
import { JobCommand, type JobCommandDto, type JobCountsDto, type QueueStatusDto } from '@immich/sdk';
@ -12,11 +13,10 @@
mdiPlay,
mdiSelectionSearch,
} from '@mdi/js';
import { createEventDispatcher, type ComponentType } from 'svelte';
import { type ComponentType } from 'svelte';
import { t } from 'svelte-i18n';
import JobTileButton from './job-tile-button.svelte';
import JobTileStatus from './job-tile-status.svelte';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import { t } from 'svelte-i18n';
export let title: string;
export let subtitle: string | undefined;
@ -29,13 +29,12 @@
export let allText: string;
export let missingText: string;
export let onCommand: (command: JobCommandDto) => void;
$: waitingCount = jobCounts.waiting + jobCounts.paused + jobCounts.delayed;
$: isIdle = !queueStatus.isActive && !queueStatus.isPaused;
const commonClasses = 'flex place-items-center justify-between w-full py-2 sm:py-4 pr-4 pl-6';
const dispatch = createEventDispatcher<{ command: JobCommandDto }>();
</script>
<div
@ -66,7 +65,7 @@
title={$t('clear_message')}
size="12"
padding="1"
on:click={() => dispatch('command', { command: JobCommand.ClearFailed, force: false })}
on:click={() => onCommand({ command: JobCommand.ClearFailed, force: false })}
/>
</div>
</Badge>
@ -117,54 +116,42 @@
<JobTileButton
disabled={true}
color="light-gray"
on:click={() => dispatch('command', { command: JobCommand.Start, force: false })}
on:click={() => onCommand({ command: JobCommand.Start, force: false })}
>
<Icon path={mdiAlertCircle} size="36" />
{$t('disabled').toUpperCase()}
</JobTileButton>
{:else if !isIdle}
{#if waitingCount > 0}
<JobTileButton color="gray" on:click={() => dispatch('command', { command: JobCommand.Empty, force: false })}>
<JobTileButton color="gray" on:click={() => onCommand({ command: JobCommand.Empty, force: false })}>
<Icon path={mdiClose} size="24" />
{$t('clear').toUpperCase()}
</JobTileButton>
{/if}
{#if queueStatus.isPaused}
{@const size = waitingCount > 0 ? '24' : '48'}
<JobTileButton
color="light-gray"
on:click={() => dispatch('command', { command: JobCommand.Resume, force: false })}
>
<JobTileButton color="light-gray" on:click={() => onCommand({ command: JobCommand.Resume, force: false })}>
<!-- size property is not reactive, so have to use width and height -->
<Icon path={mdiFastForward} {size} />
{$t('resume').toUpperCase()}
</JobTileButton>
{:else}
<JobTileButton
color="light-gray"
on:click={() => dispatch('command', { command: JobCommand.Pause, force: false })}
>
<JobTileButton color="light-gray" on:click={() => onCommand({ command: JobCommand.Pause, force: false })}>
<Icon path={mdiPause} size="24" />
{$t('pause').toUpperCase()}
</JobTileButton>
{/if}
{:else if allowForceCommand}
<JobTileButton color="gray" on:click={() => dispatch('command', { command: JobCommand.Start, force: true })}>
<JobTileButton color="gray" on:click={() => onCommand({ command: JobCommand.Start, force: true })}>
<Icon path={mdiAllInclusive} size="24" />
{allText}
</JobTileButton>
<JobTileButton
color="light-gray"
on:click={() => dispatch('command', { command: JobCommand.Start, force: false })}
>
<JobTileButton color="light-gray" on:click={() => onCommand({ command: JobCommand.Start, force: false })}>
<Icon path={mdiSelectionSearch} size="24" />
{missingText}
</JobTileButton>
{:else}
<JobTileButton
color="light-gray"
on:click={() => dispatch('command', { command: JobCommand.Start, force: false })}
>
<JobTileButton color="light-gray" on:click={() => onCommand({ command: JobCommand.Start, force: false })}>
<Icon path={mdiPlay} size="48" />
{$t('start').toUpperCase()}
</JobTileButton>

View File

@ -163,7 +163,7 @@
{allowForceCommand}
{jobCounts}
{queueStatus}
on:command={({ detail }) => (handleCommandOverride || handleCommand)(jobName, detail)}
onCommand={(command) => (handleCommandOverride || handleCommand)(jobName, command)}
/>
{/each}
</div>

View File

@ -3,28 +3,24 @@
import ConfirmDialog from '$lib/components/shared-components/dialog/confirm-dialog.svelte';
import { handleError } from '$lib/utils/handle-error';
import { restoreUserAdmin, type UserResponseDto } from '@immich/sdk';
import { createEventDispatcher } from 'svelte';
import { t } from 'svelte-i18n';
export let user: UserResponseDto;
const dispatch = createEventDispatcher<{
success: void;
fail: void;
cancel: void;
}>();
export let onSuccess: () => void;
export let onFail: () => void;
export let onCancel: () => void;
const handleRestoreUser = async () => {
try {
const { deletedAt } = await restoreUserAdmin({ id: user.id });
if (deletedAt == undefined) {
dispatch('success');
onSuccess();
} else {
dispatch('fail');
onFail();
}
} catch (error) {
handleError(error, $t('errors.unable_to_restore_user'));
dispatch('fail');
onFail();
}
};
</script>
@ -34,7 +30,7 @@
confirmText={$t('continue')}
confirmColor="green"
onConfirm={handleRestoreUser}
onCancel={() => dispatch('cancel')}
{onCancel}
>
<svelte:fragment slot="prompt">
<p>

View File

@ -1,6 +1,4 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
/**
* Unique identifier for the checkbox element, used to associate labels with the input element.
*/
@ -11,9 +9,9 @@
export let ariaDescribedBy: string | undefined = undefined;
export let checked = false;
export let disabled = false;
export let onToggle: ((checked: boolean) => void) | undefined = undefined;
const dispatch = createEventDispatcher<{ toggle: boolean }>();
const onToggle = (event: Event) => dispatch('toggle', (event.target as HTMLInputElement).checked);
const handleToggle = (event: Event) => onToggle?.((event.target as HTMLInputElement).checked);
</script>
<label class="relative inline-block h-[10px] w-[36px] flex-none">
@ -22,7 +20,7 @@
class="disabled::cursor-not-allowed h-0 w-0 opacity-0 peer"
type="checkbox"
bind:checked
on:click={onToggle}
on:click={handleToggle}
{disabled}
aria-describedby={ariaDescribedBy}
/>

View File

@ -1,15 +1,15 @@
<script lang="ts">
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
import { featureFlags } from '$lib/stores/server-config.store';
import { serverInfo } from '$lib/stores/server-info.store';
import { ByteUnit, convertToBytes } from '$lib/utils/byte-units';
import { handleError } from '$lib/utils/handle-error';
import { createUserAdmin } from '@immich/sdk';
import { createEventDispatcher } from 'svelte';
import Button from '../elements/buttons/button.svelte';
import PasswordField from '../shared-components/password-field.svelte';
import Slider from '../elements/slider.svelte';
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
import { featureFlags } from '$lib/stores/server-config.store';
import { t } from 'svelte-i18n';
import { ByteUnit, convertToBytes } from '$lib/utils/byte-units';
import Button from '../elements/buttons/button.svelte';
import Slider from '../elements/slider.svelte';
import PasswordField from '../shared-components/password-field.svelte';
export let onClose: () => void;

View File

@ -760,7 +760,7 @@
{/if}
{#if showShortcuts}
<ShowShortcuts on:close={() => (showShortcuts = !showShortcuts)} />
<ShowShortcuts onClose={() => (showShortcuts = !showShortcuts)} />
{/if}
{#if assetStore.buckets.length > 0}
<Scrubber

View File

@ -1,4 +1,5 @@
<script lang="ts">
import { page } from '$app/stores';
import { focusTrap } from '$lib/actions/focus-trap';
import Button from '$lib/components/elements/buttons/button.svelte';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
@ -9,12 +10,11 @@
import { deleteProfileImage, updateMyPreferences, type UserAvatarColor } from '@immich/sdk';
import { mdiCog, mdiLogout, mdiPencil, mdiWrench } from '@mdi/js';
import { createEventDispatcher } from 'svelte';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
import { NotificationType, notificationController } from '../notification/notification';
import UserAvatar from '../user-avatar.svelte';
import AvatarSelector from './avatar-selector.svelte';
import { t } from 'svelte-i18n';
import { page } from '$app/stores';
let isShowSelectAvatar = false;
@ -120,9 +120,5 @@
</div>
{#if isShowSelectAvatar}
<AvatarSelector
user={$user}
on:close={() => (isShowSelectAvatar = false)}
on:choose={({ detail: color }) => handleSaveProfile(color)}
/>
<AvatarSelector user={$user} onClose={() => (isShowSelectAvatar = false)} onChoose={handleSaveProfile} />
{/if}

View File

@ -1,24 +1,21 @@
<script lang="ts">
import { UserAvatarColor, type UserResponseDto } from '@immich/sdk';
import { createEventDispatcher } from 'svelte';
import { t } from 'svelte-i18n';
import FullScreenModal from '../full-screen-modal.svelte';
import UserAvatar from '../user-avatar.svelte';
import { t } from 'svelte-i18n';
export let user: UserResponseDto;
export let onClose: () => void;
export let onChoose: (color: UserAvatarColor) => void;
const dispatch = createEventDispatcher<{
close: void;
choose: UserAvatarColor;
}>();
const colors: UserAvatarColor[] = Object.values(UserAvatarColor);
</script>
<FullScreenModal title={$t('select_avatar_color')} width="auto" onClose={() => dispatch('close')}>
<FullScreenModal title={$t('select_avatar_color')} width="auto" {onClose}>
<div class="flex items-center justify-center mt-4">
<div class="grid grid-cols-2 md:grid-cols-5 gap-4">
{#each colors as color}
<button type="button" on:click={() => dispatch('choose', color)}>
<button type="button" on:click={() => onChoose(color)}>
<UserAvatar label={color} {user} {color} size="xl" showProfileImage={false} />
</button>
{/each}

View File

@ -43,11 +43,5 @@
<slot />
</div>
<Slider
id={sliderId}
bind:checked
{disabled}
on:toggle={({ detail }) => onToggle(detail)}
ariaDescribedBy={subtitleId}
/>
<Slider id={sliderId} bind:checked {disabled} {onToggle} ariaDescribedBy={subtitleId} />
</div>

View File

@ -1,9 +1,8 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import FullScreenModal from './full-screen-modal.svelte';
import { mdiInformationOutline } from '@mdi/js';
import Icon from '../elements/icon.svelte';
import { t } from 'svelte-i18n';
import Icon from '../elements/icon.svelte';
import FullScreenModal from './full-screen-modal.svelte';
interface Shortcuts {
general: ExplainedShortcut[];
@ -16,6 +15,8 @@
info?: string;
}
export let onClose: () => void;
export let shortcuts: Shortcuts = {
general: [
{ key: ['←', '→'], action: $t('previous_or_next_photo') },
@ -33,12 +34,9 @@
{ key: ['Del'], action: $t('trash_delete_asset'), info: $t('shift_to_permanent_delete') },
],
};
const dispatch = createEventDispatcher<{
close: void;
}>();
</script>
<FullScreenModal title={$t('keyboard_shortcuts')} width="auto" onClose={() => dispatch('close')}>
<FullScreenModal title={$t('keyboard_shortcuts')} width="auto" {onClose}>
<div class="grid grid-cols-1 gap-4 px-4 pb-4 md:grid-cols-2">
{#if shortcuts.general.length > 0}
<div class="p-4">

View File

@ -15,14 +15,10 @@
mdiUbuntu,
} from '@mdi/js';
import { DateTime, type ToRelativeCalendarOptions } from 'luxon';
import { createEventDispatcher } from 'svelte';
import { t } from 'svelte-i18n';
export let device: SessionResponseDto;
const dispatcher = createEventDispatcher<{
delete: void;
}>();
export let onDelete: (() => void) | undefined = undefined;
const options: ToRelativeCalendarOptions = {
unit: 'days',
@ -68,14 +64,14 @@
</span>
</div>
</div>
{#if !device.current}
{#if !device.current && onDelete}
<div>
<CircleIconButton
color="primary"
icon={mdiTrashCanOutline}
title={$t('log_out')}
size="16"
on:click={() => dispatcher('delete')}
on:click={onDelete}
/>
</div>
{/if}

View File

@ -68,7 +68,7 @@
{$t('other_devices').toUpperCase()}
</h3>
{#each otherDevices as device, index}
<DeviceCard {device} on:delete={() => handleDelete(device)} />
<DeviceCard {device} onDelete={() => handleDelete(device)} />
{#if index !== otherDevices.length - 1}
<hr class="my-3" />
{/if}

View File

@ -1,19 +1,18 @@
<script lang="ts">
import { searchUsers, getPartners, type UserResponseDto, PartnerDirection } from '@immich/sdk';
import { createEventDispatcher, onMount } from 'svelte';
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
import { getPartners, PartnerDirection, searchUsers, type UserResponseDto } from '@immich/sdk';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';
import Button from '../elements/buttons/button.svelte';
import UserAvatar from '../shared-components/user-avatar.svelte';
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
import { t } from 'svelte-i18n';
export let user: UserResponseDto;
export let onClose: () => void;
export let onAddUsers: (users: UserResponseDto[]) => void;
let availableUsers: UserResponseDto[] = [];
let selectedUsers: UserResponseDto[] = [];
const dispatch = createEventDispatcher<{ 'add-users': UserResponseDto[] }>();
onMount(async () => {
let users = await searchUsers();
@ -69,7 +68,7 @@
{#if selectedUsers.length > 0}
<div class="pt-5">
<Button size="sm" fullwidth on:click={() => dispatch('add-users', selectedUsers)}>{$t('add')}</Button>
<Button size="sm" fullwidth on:click={() => onAddUsers(selectedUsers)}>{$t('add')}</Button>
</div>
{/if}
</div>

View File

@ -191,9 +191,5 @@
</section>
{#if createPartnerFlag}
<PartnerSelectionModal
{user}
onClose={() => (createPartnerFlag = false)}
on:add-users={(event) => handleCreatePartners(event.detail)}
/>
<PartnerSelectionModal {user} onClose={() => (createPartnerFlag = false)} onAddUsers={handleCreatePartners} />
{/if}

View File

@ -27,5 +27,5 @@
</UserPageLayout>
{#if isShowKeyboardShortcut}
<ShowShortcuts on:close={() => (isShowKeyboardShortcut = false)} />
<ShowShortcuts onClose={() => (isShowKeyboardShortcut = false)} />
{/if}

View File

@ -196,5 +196,5 @@
</UserPageLayout>
{#if isShowKeyboardShortcut}
<ShowShortcuts shortcuts={duplicateShortcuts} on:close={() => (isShowKeyboardShortcut = false)} />
<ShowShortcuts shortcuts={duplicateShortcuts} onClose={() => (isShowKeyboardShortcut = false)} />
{/if}

View File

@ -1,6 +1,5 @@
<script lang="ts">
import { page } from '$app/stores';
import ConfirmDialog from '$lib/components/shared-components/dialog/confirm-dialog.svelte';
import DeleteConfirmDialog from '$lib/components/admin-page/delete-confirm-dialogue.svelte';
import RestoreDialogue from '$lib/components/admin-page/restore-dialogue.svelte';
import Button from '$lib/components/elements/buttons/button.svelte';
@ -10,6 +9,7 @@
import CreateUserForm from '$lib/components/forms/create-user-form.svelte';
import EditUserForm from '$lib/components/forms/edit-user-form.svelte';
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
import ConfirmDialog from '$lib/components/shared-components/dialog/confirm-dialog.svelte';
import {
NotificationType,
notificationController,
@ -21,11 +21,11 @@
import { copyToClipboard } from '$lib/utils';
import { getByteUnitString } from '$lib/utils/byte-units';
import { UserStatus, searchUsersAdmin, type UserAdminResponseDto } from '@immich/sdk';
import { mdiInfinity, mdiContentCopy, mdiDeleteRestore, mdiPencilOutline, mdiTrashCanOutline } from '@mdi/js';
import { mdiContentCopy, mdiDeleteRestore, mdiInfinity, mdiPencilOutline, mdiTrashCanOutline } from '@mdi/js';
import { DateTime } from 'luxon';
import { onMount } from 'svelte';
import type { PageData } from './$types';
import { t } from 'svelte-i18n';
import type { PageData } from './$types';
export let data: PageData;
@ -130,18 +130,18 @@
{#if shouldShowDeleteConfirmDialog}
<DeleteConfirmDialog
user={selectedUser}
on:success={onUserDelete}
on:fail={onUserDelete}
on:cancel={() => (shouldShowDeleteConfirmDialog = false)}
onSuccess={onUserDelete}
onFail={onUserDelete}
onCancel={() => (shouldShowDeleteConfirmDialog = false)}
/>
{/if}
{#if shouldShowRestoreDialog}
<RestoreDialogue
user={selectedUser}
on:success={onUserRestore}
on:fail={onUserRestore}
on:cancel={() => (shouldShowRestoreDialog = false)}
onSuccess={onUserRestore}
onFail={onUserRestore}
onCancel={() => (shouldShowRestoreDialog = false)}
/>
{/if}