Skip to content

Commit

Permalink
fix: prevent trashing of trashed assets
Browse files Browse the repository at this point in the history
Motivation
----------
This will improve user experience by hiding a pointless action.

You can not trash a trashed asset again. It won't get any trashier than it already is.

How to test
-----------
1. Visit route `/trash`
2. Click on an asset
3. Press "Delete" on your keyboard
4. Nothing happens
5. Try to find the trash button in the top right
6. You can't find it
  • Loading branch information
roschaefer committed Jun 7, 2024
1 parent c8f2d99 commit 0d8daa4
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 13 deletions.
55 changes: 55 additions & 0 deletions web/src/lib/components/asset-viewer/asset-viewer-nav-bar.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { resetSavedUser, user as userStore } from '$lib/stores/user.store';
import { assetFactory } from '@test-data/factories/asset-factory';
import { userAdminFactory } from '@test-data/factories/user-factory';
import '@testing-library/jest-dom';
import { render } from '@testing-library/svelte';
import { init } from 'svelte-i18n';
import AssetViewerNavBar from './asset-viewer-nav-bar.svelte';

describe('AssetViewerNavBar component', () => {
const additionalProps = {
showCopyButton: false,
showZoomButton: false,
showDetailButton: false,
showDownloadButton: false,
showMotionPlayButton: false,
showShareButton: false,
};

beforeAll(async () => {
await init({ fallbackLocale: 'en-US' });
});

afterEach(() => {
vi.resetAllMocks();
resetSavedUser();
});

it('shows back button', () => {
const asset = assetFactory.build({ isTrashed: false });
const { getByTitle } = render(AssetViewerNavBar, { asset, ...additionalProps });
expect(getByTitle('go_back')).toBeInTheDocument();
});

describe('if the current user owns the asset', () => {
it('shows trash button', () => {
const ownerId = 'id-of-the-user';
const user = userAdminFactory.build({ id: ownerId });
const asset = assetFactory.build({ ownerId, isTrashed: false });
userStore.set(user);
const { getByTitle } = render(AssetViewerNavBar, { asset, ...additionalProps });
expect(getByTitle('delete')).toBeInTheDocument();
});

describe('but if the asset is already trashed', () => {
it('hides trash button', () => {
const ownerId = 'id-of-the-user';
const user = userAdminFactory.build({ id: ownerId });
const asset = assetFactory.build({ ownerId, isTrashed: true });
userStore.set(user);
const { queryByTitle } = render(AssetViewerNavBar, { asset, ...additionalProps });
expect(queryByTitle('delete')).toBeNull();
});
});
});
});
14 changes: 8 additions & 6 deletions web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -190,12 +190,14 @@
{/if}

{#if isOwner}
<CircleIconButton
color="opaque"
icon={mdiDeleteOutline}
on:click={() => dispatch('delete')}
title={$t('delete')}
/>
{#if !asset.isTrashed}
<CircleIconButton
color="opaque"
icon={mdiDeleteOutline}
on:click={() => dispatch('delete')}
title={$t('delete')}
/>
{/if}
<div
use:clickOutside={{
onOutclick: () => (isShowAssetOptions = false),
Expand Down
16 changes: 10 additions & 6 deletions web/src/lib/components/asset-viewer/asset-viewer.svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script lang="ts">
import type { ShortcutOptions } from '$lib/actions/shortcut';
import Icon from '$lib/components/elements/icon.svelte';
import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte';
import FocusTrap from '$lib/components/shared-components/focus-trap.svelte';
Expand Down Expand Up @@ -538,21 +539,24 @@
$: if (!$user) {
shouldShowShareModal = false;
}
</script>
<svelte:window
use:shortcuts={[
let shortcutOptions: ShortcutOptions<unknown>[] = [
{ shortcut: { key: 'a', shift: true }, onShortcut: toggleAssetArchive },
{ shortcut: { key: 'ArrowLeft' }, onShortcut: () => navigateAsset('previous') },
{ shortcut: { key: 'ArrowRight' }, onShortcut: () => navigateAsset('next') },
{ shortcut: { key: 'd', shift: true }, onShortcut: () => downloadFile(asset) },
{ shortcut: { key: 'Delete' }, onShortcut: () => trashOrDelete(false) },
{ shortcut: { key: 'Delete', shift: true }, onShortcut: () => trashOrDelete(true) },
{ shortcut: { key: 'Escape' }, onShortcut: closeViewer },
{ shortcut: { key: 'f' }, onShortcut: toggleFavorite },
{ shortcut: { key: 'i' }, onShortcut: toggleDetailPanel },
]}
/>
];
if (!asset.isTrashed) {
shortcutOptions.push({ shortcut: { key: 'Delete' }, onShortcut: () => trashOrDelete(false) });
}
</script>

<svelte:window use:shortcuts={shortcutOptions} />

<svelte:document bind:fullscreenElement />

Expand Down
20 changes: 19 additions & 1 deletion web/src/test-data/factories/user-factory.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { faker } from '@faker-js/faker';
import { UserAvatarColor, type UserResponseDto } from '@immich/sdk';
import { UserAvatarColor, UserStatus, type UserAdminResponseDto, type UserResponseDto } from '@immich/sdk';
import { Sync } from 'factory.ts';

export const userFactory = Sync.makeFactory<UserResponseDto>({
Expand All @@ -9,3 +9,21 @@ export const userFactory = Sync.makeFactory<UserResponseDto>({
profileImagePath: '',
avatarColor: UserAvatarColor.Primary,
});

export const userAdminFactory = Sync.makeFactory<UserAdminResponseDto>({
id: Sync.each(() => faker.string.uuid()),
email: Sync.each(() => faker.internet.email()),
name: Sync.each(() => faker.person.fullName()),
profileImagePath: '',
avatarColor: UserAvatarColor.Primary,
isAdmin: true,
createdAt: Sync.each(() => faker.date.recent().toISOString()),
updatedAt: Sync.each(() => faker.date.recent().toISOString()),
deletedAt: null,
oauthId: '',
quotaUsageInBytes: 0,
quotaSizeInBytes: 1000,
shouldChangePassword: false,
status: UserStatus.Active,
storageLabel: null,
});

0 comments on commit 0d8daa4

Please sign in to comment.