mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-04 13:23:28 +00:00
Add Delete Style Choice to Settings
This commit is contained in:
parent
d64b21357b
commit
711e830c94
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
invalidate_query,
|
invalidate_query,
|
||||||
node::{
|
node::{
|
||||||
config::{NodeConfig, NodeConfigP2P, NodePreferences},
|
config::{DeletePreferences, DeletePromptOptions, NodeConfig, NodeConfigP2P, NodePreferences},
|
||||||
get_hardware_model_name, HardwareModel,
|
get_hardware_model_name, HardwareModel,
|
||||||
},
|
},
|
||||||
old_job::JobProgressEvent,
|
old_job::JobProgressEvent,
|
||||||
|
@ -94,6 +94,7 @@ pub struct SanitisedNodeConfig {
|
||||||
pub features: Vec<BackendFeature>,
|
pub features: Vec<BackendFeature>,
|
||||||
pub preferences: NodePreferences,
|
pub preferences: NodePreferences,
|
||||||
pub image_labeler_version: Option<String>,
|
pub image_labeler_version: Option<String>,
|
||||||
|
pub delete_prompt: DeletePreferences,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<NodeConfig> for SanitisedNodeConfig {
|
impl From<NodeConfig> for SanitisedNodeConfig {
|
||||||
|
@ -106,6 +107,7 @@ impl From<NodeConfig> for SanitisedNodeConfig {
|
||||||
features: value.features,
|
features: value.features,
|
||||||
preferences: value.preferences,
|
preferences: value.preferences,
|
||||||
image_labeler_version: value.image_labeler_version,
|
image_labeler_version: value.image_labeler_version,
|
||||||
|
delete_prompt: value.delete_prompt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
invalidate_query,
|
invalidate_query,
|
||||||
node::config::{P2PDiscoveryState, Port},
|
node::config::{DeletePromptOptions, P2PDiscoveryState, Port},
|
||||||
};
|
};
|
||||||
|
|
||||||
use sd_prisma::prisma::{instance, location};
|
use sd_prisma::prisma::{instance, location};
|
||||||
|
@ -25,6 +25,7 @@ pub(crate) fn mount() -> AlphaRouter<Ctx> {
|
||||||
pub p2p_discovery: Option<P2PDiscoveryState>,
|
pub p2p_discovery: Option<P2PDiscoveryState>,
|
||||||
pub p2p_remote_access: Option<bool>,
|
pub p2p_remote_access: Option<bool>,
|
||||||
pub image_labeler_version: Option<String>,
|
pub image_labeler_version: Option<String>,
|
||||||
|
pub delete_prompt: Option<DeletePromptOptions>,
|
||||||
}
|
}
|
||||||
R.mutation(|node, args: ChangeNodeNameArgs| async move {
|
R.mutation(|node, args: ChangeNodeNameArgs| async move {
|
||||||
if let Some(name) = &args.name {
|
if let Some(name) = &args.name {
|
||||||
|
@ -61,6 +62,10 @@ pub(crate) fn mount() -> AlphaRouter<Ctx> {
|
||||||
config.p2p.remote_access = remote_access;
|
config.p2p.remote_access = remote_access;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Some(delete_prompt) = args.delete_prompt {
|
||||||
|
config.delete_prompt.set_option(delete_prompt);
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ai")]
|
#[cfg(feature = "ai")]
|
||||||
if let Some(version) = args.image_labeler_version {
|
if let Some(version) = args.image_labeler_version {
|
||||||
if config
|
if config
|
||||||
|
|
|
@ -124,6 +124,9 @@ pub struct NodeConfig {
|
||||||
pub preferences: NodePreferences,
|
pub preferences: NodePreferences,
|
||||||
// Model version for the image labeler
|
// Model version for the image labeler
|
||||||
pub image_labeler_version: Option<String>,
|
pub image_labeler_version: Option<String>,
|
||||||
|
// Delete preferences
|
||||||
|
#[serde(default)]
|
||||||
|
pub delete_prompt: DeletePreferences,
|
||||||
|
|
||||||
version: NodeConfigVersion,
|
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)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Type)]
|
||||||
pub struct NodePreferences {
|
pub struct NodePreferences {
|
||||||
pub thumbnailer: ThumbnailerPreferences,
|
pub thumbnailer: ThumbnailerPreferences,
|
||||||
|
@ -201,6 +228,7 @@ impl ManagedVersion<NodeConfigVersion> for NodeConfig {
|
||||||
sd_api_origin: None,
|
sd_api_origin: None,
|
||||||
preferences: NodePreferences::default(),
|
preferences: NodePreferences::default(),
|
||||||
image_labeler_version,
|
image_labeler_version,
|
||||||
|
delete_prompt: DeletePreferences::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,9 @@ export const Component = () => {
|
||||||
|
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
|
|
||||||
|
console.log(node.data);
|
||||||
|
// console.log(node.data?.delete_prompt);
|
||||||
|
|
||||||
const form = useZodForm({
|
const form = useZodForm({
|
||||||
schema: z
|
schema: z
|
||||||
.object({
|
.object({
|
||||||
|
@ -57,7 +60,12 @@ export const Component = () => {
|
||||||
})
|
})
|
||||||
.int()
|
.int()
|
||||||
.nonnegative()
|
.nonnegative()
|
||||||
.lte(100)
|
.lte(100),
|
||||||
|
delete_prompt: z.union([
|
||||||
|
z.literal('ShowPrompt'),
|
||||||
|
z.literal('SendTrash'),
|
||||||
|
z.literal('DeleteInstantly')
|
||||||
|
])
|
||||||
})
|
})
|
||||||
.strict(),
|
.strict(),
|
||||||
reValidateMode: 'onChange',
|
reValidateMode: 'onChange',
|
||||||
|
@ -70,7 +78,8 @@ export const Component = () => {
|
||||||
p2p_remote_access: node.data?.p2p.remote_access || false,
|
p2p_remote_access: node.data?.p2p.remote_access || false,
|
||||||
image_labeler_version: node.data?.image_labeler_version ?? undefined,
|
image_labeler_version: node.data?.image_labeler_version ?? undefined,
|
||||||
background_processing_percentage:
|
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');
|
const p2p_port = form.watch('p2p_port');
|
||||||
|
@ -87,7 +96,8 @@ export const Component = () => {
|
||||||
p2p_ipv6_enabled: value.p2p_ipv6_enabled ?? null,
|
p2p_ipv6_enabled: value.p2p_ipv6_enabled ?? null,
|
||||||
p2p_discovery: value.p2p_discovery ?? null,
|
p2p_discovery: value.p2p_discovery ?? null,
|
||||||
p2p_remote_access: value.p2p_remote_access ?? 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) {
|
if (value.background_processing_percentage != undefined) {
|
||||||
|
@ -406,6 +416,30 @@ export const Component = () => {
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<h1 className="mb-3 text-lg font-bold text-ink">{t('delete_settings')}</h1>
|
||||||
|
|
||||||
|
<Setting
|
||||||
|
mini
|
||||||
|
title={t('delete_show_prompt')}
|
||||||
|
description={
|
||||||
|
<p className="text-sm text-gray-400">
|
||||||
|
{t('delete_show_prompt_description')}
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
value={form.watch('delete_prompt') || 'ShowPrompt'}
|
||||||
|
containerClassName="h-[30px]"
|
||||||
|
className="h-full"
|
||||||
|
onChange={(type) => form.setValue('delete_prompt', type)}
|
||||||
|
>
|
||||||
|
<SelectOption value="ShowPrompt">{'Show Prompt'}</SelectOption>
|
||||||
|
<SelectOption value="SendTrash">{'Send to Trash'}</SelectOption>
|
||||||
|
<SelectOption value="DeleteInstantly">{'Delete Instantly'}</SelectOption>
|
||||||
|
</Select>
|
||||||
|
</Setting>
|
||||||
|
</div>
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 { dialogManager } from '@sd/ui';
|
||||||
import DeleteDialog from '~/app/$libraryId/Explorer/FilePath/DeleteDialog';
|
import DeleteDialog from '~/app/$libraryId/Explorer/FilePath/DeleteDialog';
|
||||||
import { isNonEmpty } from '~/util';
|
import { isNonEmpty } from '~/util';
|
||||||
|
@ -8,6 +14,11 @@ import { useShortcut } from './useShortcut';
|
||||||
export const useKeyDeleteFile = (selectedItems: Set<ExplorerItem>, locationId?: number | null) => {
|
export const useKeyDeleteFile = (selectedItems: Set<ExplorerItem>, locationId?: number | null) => {
|
||||||
const filePaths = useItemsAsFilePaths([...selectedItems]);
|
const filePaths = useItemsAsFilePaths([...selectedItems]);
|
||||||
const ephemeralPaths = useItemsAsEphemeralPaths([...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) => {
|
const deleteHandler = (e: KeyboardEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -30,6 +41,7 @@ export const useKeyDeleteFile = (selectedItems: Set<ExplorerItem>, locationId?:
|
||||||
fileCount += entry.is_dir ? 0 : 1;
|
fileCount += entry.is_dir ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (node.data?.delete_prompt.option === 'ShowPrompt') {
|
||||||
dialogManager.create((dp) => (
|
dialogManager.create((dp) => (
|
||||||
<DeleteDialog
|
<DeleteDialog
|
||||||
{...dp}
|
{...dp}
|
||||||
|
@ -39,6 +51,21 @@ export const useKeyDeleteFile = (selectedItems: Set<ExplorerItem>, locationId?:
|
||||||
fileCount={fileCount}
|
fileCount={fileCount}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
} 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);
|
useShortcut('delItem', deleteHandler);
|
||||||
|
|
|
@ -142,6 +142,9 @@
|
||||||
"default": "Default",
|
"default": "Default",
|
||||||
"default_settings": "Default Settings",
|
"default_settings": "Default Settings",
|
||||||
"delete": "Delete",
|
"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_dialog_title": "Delete {{prefix}} {{type}}",
|
||||||
"delete_forever": "Delete Forever",
|
"delete_forever": "Delete Forever",
|
||||||
"delete_info": "This will not delete the actual folder on disk. Preview media will be deleted.",
|
"delete_info": "This will not delete the actual folder on disk. Preview media will be deleted.",
|
||||||
|
|
|
@ -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 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 }
|
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<T> = { order: SortOrder; data: T }
|
||||||
|
|
||||||
export type DefaultLocations = { desktop: boolean; documents: boolean; downloads: boolean; pictures: boolean; music: boolean; videos: boolean }
|
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.
|
* The method used for the discovery of this peer.
|
||||||
* *Technically* you can have multiple under the hood but this simplifies things for the UX.
|
* *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 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 }
|
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 }
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue