feat(server): trash asset (#4015)
* refactor(server): delete assets endpoint * fix: formatting * chore: cleanup * chore: open api * chore(mobile): replace DeleteAssetDTO with BulkIdsDTOs * feat: trash an asset * chore(server): formatting * chore: open api * chore: wording * chore: open-api * feat(server): add withDeleted to getAssets queries * WIP: mobile-recycle-bin * feat(server): recycle-bin to system config * feat(web): use recycle-bin system config * chore(server): domain assetcore removed * chore(server): rename recycle-bin to trash * chore(web): rename recycle-bin to trash * chore(server): always send soft deleted assets for getAllByUserId * chore(web): formatting * feat(server): permanent delete assets older than trashed period * feat(web): trash empty placeholder image * feat(server): empty trash * feat(web): empty trash * WIP: mobile-recycle-bin * refactor(server): empty / restore trash to separate endpoint * test(server): handle failures * test(server): fix e2e server-info test * test(server): deletion test refactor * feat(mobile): use map settings from server-config to enable / disable map * feat(mobile): trash asset * fix(server): operations on assets in trash * feat(web): show trash statistics * fix(web): handle trash enabled * fix(mobile): restore updates from trash * fix(server): ignore trashed assets for person * fix(server): add / remove search index when trashed / restored * chore(web): format * fix(server): asset service test * fix(server): include trashed assts for duplicates from uploads * feat(mobile): no dialog for trash, always dialog for permanent delete * refactor(mobile): use isar where instead of dart filter * refactor(mobile): asset provide - handle deletes in single db txn * chore(mobile): review changes * feat(web): confirmation before empty trash * server: review changes * fix(server): handle library changes * fix: filter external assets from getting trashed / deleted * fix(server): empty-bin * feat: broadcast config update events through ws * change order of trash button on mobile * styling * fix(mobile): do not show trashed toast for local only assets --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
16
web/src/routes/(user)/trash/+page.server.ts
Normal file
16
web/src/routes/(user)/trash/+page.server.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { AppRoute } from '$lib/constants';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load = (async ({ locals: { user } }) => {
|
||||
if (!user) {
|
||||
throw redirect(302, AppRoute.AUTH_LOGIN);
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
meta: {
|
||||
title: 'Trash',
|
||||
},
|
||||
};
|
||||
}) satisfies PageServerLoad;
|
||||
112
web/src/routes/(user)/trash/+page.svelte
Normal file
112
web/src/routes/(user)/trash/+page.svelte
Normal file
@@ -0,0 +1,112 @@
|
||||
<script lang="ts">
|
||||
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||
import DeleteAssets from '$lib/components/photos-page/actions/delete-assets.svelte';
|
||||
import RestoreAssets from '$lib/components/photos-page/actions/restore-assets.svelte';
|
||||
import SelectAllAssets from '$lib/components/photos-page/actions/select-all-assets.svelte';
|
||||
import AssetGrid from '$lib/components/photos-page/asset-grid.svelte';
|
||||
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
|
||||
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
|
||||
import { AppRoute } from '$lib/constants';
|
||||
import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import {
|
||||
NotificationType,
|
||||
notificationController,
|
||||
} from '$lib/components/shared-components/notification/notification';
|
||||
import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
|
||||
import { AssetStore } from '$lib/stores/assets.store';
|
||||
import { api, TimeBucketSize } from '@api';
|
||||
import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
|
||||
import HistoryOutline from 'svelte-material-icons/History.svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { featureFlags } from '$lib/stores/server-config.store';
|
||||
import { goto } from '$app/navigation';
|
||||
import empty3Url from '$lib/assets/empty-3.svg';
|
||||
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
$: $featureFlags.trash || goto(AppRoute.PHOTOS);
|
||||
|
||||
const assetStore = new AssetStore({ size: TimeBucketSize.Month, isTrashed: true });
|
||||
const assetInteractionStore = createAssetInteractionStore();
|
||||
const { isMultiSelectState, selectedAssets } = assetInteractionStore;
|
||||
let isShowEmptyConfirmation = false;
|
||||
|
||||
const handleEmptyTrash = async () => {
|
||||
isShowEmptyConfirmation = false;
|
||||
try {
|
||||
await api.assetApi.emptyTrash();
|
||||
|
||||
notificationController.show({
|
||||
message: `Empty trash initiated. Refresh the page to see the changes`,
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
} catch (e) {
|
||||
handleError(e, 'Error emptying trash');
|
||||
}
|
||||
};
|
||||
|
||||
const handleRestoreTrash = async () => {
|
||||
try {
|
||||
await api.assetApi.restoreTrash();
|
||||
|
||||
notificationController.show({
|
||||
message: `Restore trash initiated. Refresh the page to see the changes`,
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
} catch (e) {
|
||||
handleError(e, 'Error restoring trash');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if $isMultiSelectState}
|
||||
<AssetSelectControlBar assets={$selectedAssets} clearSelect={() => assetInteractionStore.clearMultiselect()}>
|
||||
<SelectAllAssets {assetStore} {assetInteractionStore} />
|
||||
<DeleteAssets force onAssetDelete={(assetId) => assetStore.removeAsset(assetId)} />
|
||||
<RestoreAssets onRestore={(ids) => assetStore.removeAssets(ids)} />
|
||||
</AssetSelectControlBar>
|
||||
{/if}
|
||||
|
||||
{#if $featureFlags.loaded && $featureFlags.trash}
|
||||
<UserPageLayout user={data.user} hideNavbar={$isMultiSelectState} title={data.meta.title}>
|
||||
<div class="flex place-items-center gap-2" slot="buttons">
|
||||
<LinkButton on:click={handleRestoreTrash}>
|
||||
<div class="flex place-items-center gap-2 text-sm">
|
||||
<HistoryOutline size="18" />
|
||||
Restore All
|
||||
</div>
|
||||
</LinkButton>
|
||||
<LinkButton on:click={() => (isShowEmptyConfirmation = true)}>
|
||||
<div class="flex place-items-center gap-2 text-sm">
|
||||
<DeleteOutline size="18" />
|
||||
Empty Trash
|
||||
</div>
|
||||
</LinkButton>
|
||||
</div>
|
||||
|
||||
<AssetGrid forceDelete {assetStore} {assetInteractionStore}>
|
||||
<EmptyPlaceholder
|
||||
text="Trashed photos and videos will show up here."
|
||||
alt="Empty trash can"
|
||||
slot="empty"
|
||||
src={empty3Url}
|
||||
/>
|
||||
</AssetGrid>
|
||||
</UserPageLayout>
|
||||
{/if}
|
||||
|
||||
{#if isShowEmptyConfirmation}
|
||||
<ConfirmDialogue
|
||||
title="Empty Trash"
|
||||
confirmText="Empty"
|
||||
on:confirm={handleEmptyTrash}
|
||||
on:cancel={() => (isShowEmptyConfirmation = false)}
|
||||
>
|
||||
<svelte:fragment slot="prompt">
|
||||
<p>Are you sure you want to empty the trash? This will remove all the assets in trash permanently from Immich.</p>
|
||||
<p><b>You cannot undo this action!</b></p>
|
||||
</svelte:fragment>
|
||||
</ConfirmDialogue>
|
||||
{/if}
|
||||
13
web/src/routes/(user)/trash/photos/[assetId]/+page.ts
Normal file
13
web/src/routes/(user)/trash/photos/[assetId]/+page.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { AppRoute } from '$lib/constants';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import type { PageLoad } from './$types';
|
||||
export const prerender = false;
|
||||
|
||||
export const load: PageLoad = async ({ parent }) => {
|
||||
const { user } = await parent();
|
||||
if (!user) {
|
||||
throw redirect(302, AppRoute.AUTH_LOGIN);
|
||||
}
|
||||
|
||||
throw redirect(302, AppRoute.TRASH);
|
||||
};
|
||||
@@ -19,6 +19,7 @@
|
||||
import ContentCopy from 'svelte-material-icons/ContentCopy.svelte';
|
||||
import Download from 'svelte-material-icons/Download.svelte';
|
||||
import type { PageData } from './$types';
|
||||
import TrashSettings from '$lib/components/admin-page/settings/trash-settings/trash-settings.svelte';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@@ -75,6 +76,10 @@
|
||||
<MapSettings disabled={$featureFlags.configFile} config={configs} />
|
||||
</SettingAccordion>
|
||||
|
||||
<SettingAccordion title="Trash Settings" subtitle="Manage trash settings">
|
||||
<TrashSettings disabled={$featureFlags.configFile} trashConfig={configs.trash} />
|
||||
</SettingAccordion>
|
||||
|
||||
<SettingAccordion title="OAuth Authentication" subtitle="Manage the login with OAuth settings">
|
||||
<OAuthSettings disabled={$featureFlags.configFile} oauthConfig={configs.oauth} />
|
||||
</SettingAccordion>
|
||||
|
||||
Reference in New Issue
Block a user