From 711e830c9472b623a1b27eaf9704e115a1ed28ae Mon Sep 17 00:00:00 2001 From: Arnab Chakraborty <11457760+Rocky43007@users.noreply.github.com> Date: Tue, 28 May 2024 21:41:15 +0300 Subject: [PATCH] Add Delete Style Choice to Settings --- core/src/api/mod.rs | 4 +- core/src/api/nodes.rs | 7 ++- core/src/node/config.rs | 28 +++++++++++ .../$libraryId/settings/client/general.tsx | 40 ++++++++++++++-- interface/hooks/useKeyDeleteFile.tsx | 47 +++++++++++++++---- interface/locales/en/common.json | 3 ++ packages/client/src/core.ts | 8 +++- 7 files changed, 120 insertions(+), 17 deletions(-) diff --git a/core/src/api/mod.rs b/core/src/api/mod.rs index 3ad1625e1..cf3db2ba4 100644 --- a/core/src/api/mod.rs +++ b/core/src/api/mod.rs @@ -1,7 +1,7 @@ use crate::{ invalidate_query, node::{ - config::{NodeConfig, NodeConfigP2P, NodePreferences}, + config::{DeletePreferences, DeletePromptOptions, NodeConfig, NodeConfigP2P, NodePreferences}, get_hardware_model_name, HardwareModel, }, old_job::JobProgressEvent, @@ -94,6 +94,7 @@ pub struct SanitisedNodeConfig { pub features: Vec, pub preferences: NodePreferences, pub image_labeler_version: Option, + pub delete_prompt: DeletePreferences, } impl From for SanitisedNodeConfig { @@ -106,6 +107,7 @@ impl From for SanitisedNodeConfig { features: value.features, preferences: value.preferences, image_labeler_version: value.image_labeler_version, + delete_prompt: value.delete_prompt, } } } diff --git a/core/src/api/nodes.rs b/core/src/api/nodes.rs index b5be20f3d..4250b0f79 100644 --- a/core/src/api/nodes.rs +++ b/core/src/api/nodes.rs @@ -1,6 +1,6 @@ use crate::{ invalidate_query, - node::config::{P2PDiscoveryState, Port}, + node::config::{DeletePromptOptions, P2PDiscoveryState, Port}, }; use sd_prisma::prisma::{instance, location}; @@ -25,6 +25,7 @@ pub(crate) fn mount() -> AlphaRouter { pub p2p_discovery: Option, pub p2p_remote_access: Option, pub image_labeler_version: Option, + pub delete_prompt: Option, } R.mutation(|node, args: ChangeNodeNameArgs| async move { if let Some(name) = &args.name { @@ -61,6 +62,10 @@ pub(crate) fn mount() -> AlphaRouter { config.p2p.remote_access = remote_access; }; + if let Some(delete_prompt) = args.delete_prompt { + config.delete_prompt.set_option(delete_prompt); + } + #[cfg(feature = "ai")] if let Some(version) = args.image_labeler_version { if config diff --git a/core/src/node/config.rs b/core/src/node/config.rs index 25e225af7..152983b2c 100644 --- a/core/src/node/config.rs +++ b/core/src/node/config.rs @@ -124,6 +124,9 @@ pub struct NodeConfig { pub preferences: NodePreferences, // Model version for the image labeler pub image_labeler_version: Option, + // Delete preferences + #[serde(default)] + pub delete_prompt: DeletePreferences, version: NodeConfigVersion, } @@ -152,6 +155,30 @@ mod identity_serde { } } +#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Type, Default)] +pub enum DeletePromptOptions { + #[default] + ShowPrompt, + SendTrash, + DeleteInstantly, +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Type, Default)] +pub struct DeletePreferences { + option: DeletePromptOptions, +} + +impl DeletePreferences { + pub fn delete_prompt(&self) -> DeletePromptOptions { + self.option + } + + pub fn set_option(&mut self, mut delete_prompt: DeletePromptOptions) -> &mut Self { + self.option = delete_prompt; + self + } +} + #[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Type)] pub struct NodePreferences { pub thumbnailer: ThumbnailerPreferences, @@ -201,6 +228,7 @@ impl ManagedVersion for NodeConfig { sd_api_origin: None, preferences: NodePreferences::default(), image_labeler_version, + delete_prompt: DeletePreferences::default(), }) } } diff --git a/interface/app/$libraryId/settings/client/general.tsx b/interface/app/$libraryId/settings/client/general.tsx index da16765d9..789b4c64a 100644 --- a/interface/app/$libraryId/settings/client/general.tsx +++ b/interface/app/$libraryId/settings/client/general.tsx @@ -32,6 +32,9 @@ export const Component = () => { const { t } = useLocale(); + console.log(node.data); + // console.log(node.data?.delete_prompt); + const form = useZodForm({ schema: z .object({ @@ -57,7 +60,12 @@ export const Component = () => { }) .int() .nonnegative() - .lte(100) + .lte(100), + delete_prompt: z.union([ + z.literal('ShowPrompt'), + z.literal('SendTrash'), + z.literal('DeleteInstantly') + ]) }) .strict(), reValidateMode: 'onChange', @@ -70,7 +78,8 @@ export const Component = () => { p2p_remote_access: node.data?.p2p.remote_access || false, image_labeler_version: node.data?.image_labeler_version ?? undefined, background_processing_percentage: - node.data?.preferences.thumbnailer.background_processing_percentage || 50 + node.data?.preferences.thumbnailer.background_processing_percentage || 50, + delete_prompt: node.data?.delete_prompt.option || 'ShowPrompt' } }); const p2p_port = form.watch('p2p_port'); @@ -87,7 +96,8 @@ export const Component = () => { p2p_ipv6_enabled: value.p2p_ipv6_enabled ?? null, p2p_discovery: value.p2p_discovery ?? null, p2p_remote_access: value.p2p_remote_access ?? null, - image_labeler_version: value.image_labeler_version ?? null + image_labeler_version: value.image_labeler_version ?? null, + delete_prompt: value.delete_prompt ?? "ShowPrompt" }); if (value.background_processing_percentage != undefined) { @@ -406,6 +416,30 @@ export const Component = () => { ) : null} +
+

{t('delete_settings')}

+ + + {t('delete_show_prompt_description')} +

+ } + > + +
+
); }; diff --git a/interface/hooks/useKeyDeleteFile.tsx b/interface/hooks/useKeyDeleteFile.tsx index 6062285d3..18cd48b75 100644 --- a/interface/hooks/useKeyDeleteFile.tsx +++ b/interface/hooks/useKeyDeleteFile.tsx @@ -1,4 +1,10 @@ -import { useItemsAsEphemeralPaths, useItemsAsFilePaths, type ExplorerItem } from '@sd/client'; +import { + useBridgeQuery, + useItemsAsEphemeralPaths, + useItemsAsFilePaths, + useLibraryMutation, + type ExplorerItem +} from '@sd/client'; import { dialogManager } from '@sd/ui'; import DeleteDialog from '~/app/$libraryId/Explorer/FilePath/DeleteDialog'; import { isNonEmpty } from '~/util'; @@ -8,6 +14,11 @@ import { useShortcut } from './useShortcut'; export const useKeyDeleteFile = (selectedItems: Set, locationId?: number | null) => { const filePaths = useItemsAsFilePaths([...selectedItems]); const ephemeralPaths = useItemsAsEphemeralPaths([...selectedItems]); + const node = useBridgeQuery(['nodeState']); + const deleteFile = useLibraryMutation('files.deleteFiles'); + const deleteEphemeralFile = useLibraryMutation('ephemeralFiles.deleteFiles'); + const moveToTrashFile = useLibraryMutation('files.moveToTrash'); + const moveToTrashEphemeralFile = useLibraryMutation('ephemeralFiles.moveToTrash'); const deleteHandler = (e: KeyboardEvent) => { e.preventDefault(); @@ -30,15 +41,31 @@ export const useKeyDeleteFile = (selectedItems: Set, locationId?: fileCount += entry.is_dir ? 0 : 1; } - dialogManager.create((dp) => ( - - )); + if (node.data?.delete_prompt.option === 'ShowPrompt') { + dialogManager.create((dp) => ( + + )); + } else if (node.data?.delete_prompt.option === 'SendTrash') { + if (locationId != null && isNonEmpty(filePaths)) { + moveToTrashFile.mutate({ location_id: locationId, file_path_ids: filePaths.map((p) => p.id) }); + } else if (isNonEmpty(ephemeralPaths)) { + const { paths } = ephemeralArgs!; + moveToTrashEphemeralFile.mutate(paths); + } + } else { + if (locationId != null && isNonEmpty(filePaths)) { + deleteFile.mutate({ location_id: locationId, file_path_ids: filePaths.map((p) => p.id) }); + } else if (isNonEmpty(ephemeralPaths)) { + const { paths } = ephemeralArgs!; + deleteEphemeralFile.mutate(paths); + } + } }; useShortcut('delItem', deleteHandler); diff --git a/interface/locales/en/common.json b/interface/locales/en/common.json index 8cd4b92dc..1da95fbaa 100644 --- a/interface/locales/en/common.json +++ b/interface/locales/en/common.json @@ -142,6 +142,9 @@ "default": "Default", "default_settings": "Default Settings", "delete": "Delete", + "delete_settings": "Delete Functionality", + "delete_show_prompt": "Delete Prompt", + "delete_show_prompt_description": "Choose if you would like to automatically send files to the trash, delete them forever, or show a prompt before choosing an option.", "delete_dialog_title": "Delete {{prefix}} {{type}}", "delete_forever": "Delete Forever", "delete_info": "This will not delete the actual folder on disk. Preview media will be deleted.", diff --git a/packages/client/src/core.ts b/packages/client/src/core.ts index 5c9c249a7..5cdd67ff7 100644 --- a/packages/client/src/core.ts +++ b/packages/client/src/core.ts @@ -168,7 +168,7 @@ export type CRDTOperationData = { c: { [key in string]: JsonValue } } | { u: { f export type CameraData = { device_make: string | null; device_model: string | null; color_space: string | null; color_profile: ColorProfile | null; focal_length: number | null; shutter_speed: number | null; flash: Flash | null; orientation: Orientation; lens_make: string | null; lens_model: string | null; bit_depth: number | null; zoom: number | null; iso: number | null; software: string | null; serial_number: string | null; lens_serial_number: string | null; contrast: number | null; saturation: number | null; sharpness: number | null; composite: Composite | null } -export type ChangeNodeNameArgs = { name: string | null; p2p_port: Port | null; p2p_ipv4_enabled: boolean | null; p2p_ipv6_enabled: boolean | null; p2p_discovery: P2PDiscoveryState | null; p2p_remote_access: boolean | null; image_labeler_version: string | null } +export type ChangeNodeNameArgs = { name: string | null; p2p_port: Port | null; p2p_ipv4_enabled: boolean | null; p2p_ipv6_enabled: boolean | null; p2p_discovery: P2PDiscoveryState | null; p2p_remote_access: boolean | null; image_labeler_version: string | null; delete_prompt: DeletePromptOptions | null } export type Chapter = { id: number; start: [number, number]; end: [number, number]; time_base_den: number; time_base_num: number; metadata: Metadata } @@ -224,6 +224,10 @@ export type CursorOrderItem = { order: SortOrder; data: T } export type DefaultLocations = { desktop: boolean; documents: boolean; downloads: boolean; pictures: boolean; music: boolean; videos: boolean } +export type DeletePreferences = { option: DeletePromptOptions } + +export type DeletePromptOptions = "ShowPrompt" | "SendTrash" | "DeleteInstantly" + /** * The method used for the discovery of this peer. * *Technically* you can have multiple under the hood but this simplifies things for the UX. @@ -484,7 +488,7 @@ id: string; /** * name is the display name of the current node. This is set by the user and is shown in the UI. // TODO: Length validation so it can fit in DNS record */ -name: string; identity: RemoteIdentity; p2p: NodeConfigP2P; features: BackendFeature[]; preferences: NodePreferences; image_labeler_version: string | null }) & { data_path: string; device_model: string | null } +name: string; identity: RemoteIdentity; p2p: NodeConfigP2P; features: BackendFeature[]; preferences: NodePreferences; image_labeler_version: string | null; delete_prompt: DeletePreferences }) & { data_path: string; device_model: string | null } export type NonIndexedPathItem = { path: string; name: string; extension: string; kind: number; is_dir: boolean; date_created: string; date_modified: string; size_in_bytes_bytes: number[]; hidden: boolean }