From 0b745fef6f95f49616762d92f87c59ad23fb50b4 Mon Sep 17 00:00:00 2001 From: Varun Patil Date: Sat, 30 Sep 2023 13:38:46 -0700 Subject: [PATCH] nx: deduplicate confirmations for deletion Signed-off-by: Varun Patil --- src/components/SelectionManager.vue | 3 --- src/components/viewer/Viewer.vue | 2 -- src/native.ts | 12 ++++++++---- src/services/API.ts | 8 +++++++- src/services/dav/base.ts | 20 +++++++++++++++++--- 5 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/components/SelectionManager.vue b/src/components/SelectionManager.vue index 882f6db9..ec89f226 100644 --- a/src/components/SelectionManager.vue +++ b/src/components/SelectionManager.vue @@ -774,15 +774,12 @@ export default defineComponent({ * Delete the currently selected photos */ async deleteSelection(selection: Selection) { - if (!(await utils.dialogs.moveToTrash(selection.size))) return; - try { for await (const delIds of dav.deletePhotos(selection.photosNoDupFileId())) { this.deleteSelectedPhotosById(delIds, selection); } } catch (e) { console.error(e); - showError(this.t('memories', 'Failed to delete files')); } }, diff --git a/src/components/viewer/Viewer.vue b/src/components/viewer/Viewer.vue index f75874b0..324245f2 100644 --- a/src/components/viewer/Viewer.vue +++ b/src/components/viewer/Viewer.vue @@ -935,7 +935,6 @@ export default defineComponent({ /** Delete this photo and refresh */ async deleteCurrent() { if (this.routeIsPublic) return; - if (!(await utils.dialogs.moveToTrash(1))) return; let idx = this.photoswipe!.currIndex - this.globalAnchor; const photo = this.list[idx]; @@ -948,7 +947,6 @@ export default defineComponent({ if (!p[0]) return; } } catch { - showError(this.t('memories', 'Failed to delete photo')); return; } finally { this.updateLoading(-1); diff --git a/src/native.ts b/src/native.ts index 983a76f1..8ac13a98 100644 --- a/src/native.ts +++ b/src/native.ts @@ -1,6 +1,7 @@ import axios from '@nextcloud/axios'; import { generateUrl } from '@nextcloud/router'; -import type { IDay, IPhoto, IImageInfo } from './types'; +import type { IDay, IPhoto } from './types'; +import { API as SAPI } from './services/API'; const euc = encodeURIComponent; /** Access NativeX over localhost */ @@ -34,6 +35,7 @@ export const API = { * Delete files using local fileids. * @regex ^/api/image/delete/\d+(,\d+)*$ * @param fileIds List of AUIDs to delete + * @param dry (Query) Only check for confirmation and count of local files * @returns {void} * @throws Return an error code if the user denies the deletion. */ @@ -326,13 +328,15 @@ export async function extendDayWithLocal(dayId: number, photos: IPhoto[]) { /** * Request deletion of local photos wherever available. * @param photos List of photos to delete + * @returns The number of photos for which confirmation was received * @throws If the request fails */ -export async function deleteLocalPhotos(photos: IPhoto[]): Promise { - if (!has()) return; +export async function deleteLocalPhotos(photos: IPhoto[], dry: boolean = false): Promise { + if (!has()) return 0; const auids = photos.map((p) => p.auid).filter((a) => !!a) as number[]; - await axios.get(API.IMAGE_DELETE(auids)); + const res = await axios.get(SAPI.Q(API.IMAGE_DELETE(auids), { dry })); + return res.data.confirms ? res.data.count : 0; } /** diff --git a/src/services/API.ts b/src/services/API.ts index 4f2bfdf2..2d42d633 100644 --- a/src/services/API.ts +++ b/src/services/API.ts @@ -37,7 +37,7 @@ export enum DaysFilterType { } export class API { - static Q(url: string, query: Record): string { + static Q(url: string, query: Record): string { if (!query) return url; // Get everything as strings @@ -49,6 +49,12 @@ export class API { continue; } + if (typeof query[key] === 'boolean') { + if (!query[key]) continue; + records[key] = '1'; + continue; + } + records[key] = String(query[key]); } diff --git a/src/services/dav/base.ts b/src/services/dav/base.ts index 7d79904b..858d4d60 100644 --- a/src/services/dav/base.ts +++ b/src/services/dav/base.ts @@ -187,9 +187,10 @@ async function extendWithLivePhotos(photos: IPhoto[]) { * Delete all files in a given list of Ids * * @param photos list of photos to delete + * @param confirm whether to show a confirmation dialog (default true) * @returns list of file ids that were deleted */ -export async function* deletePhotos(photos: IPhoto[]) { +export async function* deletePhotos(photos: IPhoto[], confirm: boolean = true) { if (photos.length === 0) return; // Extend with Live Photos unless this is an album @@ -209,8 +210,21 @@ export async function* deletePhotos(photos: IPhoto[]) { // Check for locally available files and delete them. // For albums, we are not actually deleting. - if (nativex.has() && !routeIsAlbums) { - // Delete local files. This will throw if user cancels. + const hasNative = nativex.has() && !routeIsAlbums; + + // Check if native confirmation is available + if (hasNative) { + confirm &&= (await nativex.deleteLocalPhotos(photos, true)) !== photos.length; + } + + // Show confirmation dialog if required + if (confirm && !(await utils.dialogs.moveToTrash(photos.length))) { + throw new Error('User cancelled deletion'); + } + + // Delete local files. + if (hasNative) { + // Delete local files. await nativex.deleteLocalPhotos(photos); // Remove purely local files