From b4037d65371205f36cd9f60b89dab76966badb0c Mon Sep 17 00:00:00 2001 From: Arnab Chakraborty <11457760+Rocky43007@users.noreply.github.com> Date: Mon, 22 Apr 2024 15:46:10 -0400 Subject: [PATCH] Open Trash from the application (#2338) * Open Trash from the application * Working Trash Sidebar Button * Small UI fixes * Update common.json * Move openTrash to Tauri Command instead of RSPC * format and remove type assertion --------- Co-authored-by: Utku Bakir <74243531+utkubakir@users.noreply.github.com> --- apps/desktop/src-tauri/src/main.rs | 43 +++++++++++++++++++ apps/desktop/src/commands.ts | 11 +++++ core/src/api/search/mod.rs | 4 +- .../technology/normalised-cache.mdx | 8 ++-- docs/developers/technology/rspc.mdx | 20 ++++----- docs/product/getting-started/setup.mdx | 7 +-- .../Layout/Sidebar/sections/Local/index.tsx | 24 ++++++++++- interface/hooks/useMouseItemResize.ts | 14 +++--- interface/locales/en/common.json | 1 + interface/util/Platform.tsx | 1 + 10 files changed, 104 insertions(+), 29 deletions(-) diff --git a/apps/desktop/src-tauri/src/main.rs b/apps/desktop/src-tauri/src/main.rs index c925b7d9503d..a3c5e1d0cf35 100644 --- a/apps/desktop/src-tauri/src/main.rs +++ b/apps/desktop/src-tauri/src/main.rs @@ -7,6 +7,7 @@ use std::{ collections::HashMap, fs, path::PathBuf, + process::Command, sync::{Arc, Mutex, PoisonError}, time::Duration, }; @@ -149,6 +150,47 @@ async fn open_logs_dir(node: tauri::State<'_, Arc>) -> Result<(), ()> { }) } +#[tauri::command(async)] +#[specta::specta] +async fn open_trash_in_os_explorer() -> Result<(), ()> { + #[cfg(target_os = "macos")] + { + let full_path = format!("{}/.Trash/", std::env::var("HOME").unwrap()); + + Command::new("open") + .arg(full_path) + .spawn() + .map_err(|err| error!("Error opening trash: {err:#?}"))? + .wait() + .map_err(|err| error!("Error opening trash: {err:#?}"))?; + + return Ok(()); + } + + #[cfg(target_os = "windows")] + { + Command::new("explorer") + .arg("shell:RecycleBinFolder") + .spawn() + .map_err(|err| error!("Error opening trash: {err:#?}"))? + .wait() + .map_err(|err| error!("Error opening trash: {err:#?}"))?; + return Ok(()); + } + + #[cfg(target_os = "linux")] + { + Command::new("xdg-open") + .arg("~/.local/share/Trash/") + .spawn() + .map_err(|err| error!("Error opening trash: {err:#?}"))? + .wait() + .map_err(|err| error!("Error opening trash: {err:#?}"))?; + + return Ok(()); + } +} + #[derive(Debug, Clone, Serialize, Deserialize, specta::Type, tauri_specta::Event)] #[serde(tag = "type")] pub enum DragAndDropEvent { @@ -218,6 +260,7 @@ async fn main() -> tauri::Result<()> { reload_webview, set_menu_bar_item_state, request_fda_macos, + open_trash_in_os_explorer, file::open_file_paths, file::open_ephemeral_files, file::get_file_path_open_with_apps, diff --git a/apps/desktop/src/commands.ts b/apps/desktop/src/commands.ts index 662011a6aa8d..360b8c8a74dc 100644 --- a/apps/desktop/src/commands.ts +++ b/apps/desktop/src/commands.ts @@ -41,6 +41,17 @@ export const commands = { async requestFdaMacos(): Promise { return await TAURI_INVOKE('plugin:tauri-specta|request_fda_macos'); }, + async openTrashInOsExplorer(): Promise<__Result__> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('plugin:tauri-specta|open_trash_in_os_explorer') + }; + } catch (e) { + if (e instanceof Error) throw e; + else return { status: 'error', error: e as any }; + } + }, async openFilePaths( library: string, ids: number[] diff --git a/core/src/api/search/mod.rs b/core/src/api/search/mod.rs index 0c78c8343842..895c8d8f013f 100644 --- a/core/src/api/search/mod.rs +++ b/core/src/api/search/mod.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, path::PathBuf}; +use std::{collections::HashMap, path::PathBuf, process::Command}; use crate::{ api::{locations::ExplorerItem, utils::library}, @@ -28,7 +28,7 @@ use futures::StreamExt; use rspc::{alpha::AlphaRouter, ErrorCode}; use serde::{Deserialize, Serialize}; use specta::Type; -use tracing::{error, warn}; +use tracing::{error, info, warn}; pub mod file_path; pub mod media_data; diff --git a/docs/developers/technology/normalised-cache.mdx b/docs/developers/technology/normalised-cache.mdx index 47fdc5e693aa..cdf90ecf499c 100644 --- a/docs/developers/technology/normalised-cache.mdx +++ b/docs/developers/technology/normalised-cache.mdx @@ -17,8 +17,8 @@ This means the queries will always render the newest version of the model. ## Terminology - - `CacheNode`: A node in the cache - this contains the data and can be identified by the model's name and unique ID within the data (eg. database primary key). - - `Reference`: A reference to a node in the cache - This contains the model's name and unique ID. +- `CacheNode`: A node in the cache - this contains the data and can be identified by the model's name and unique ID within the data (eg. database primary key). +- `Reference`: A reference to a node in the cache - This contains the model's name and unique ID. ## High level overview @@ -26,8 +26,7 @@ We turn the data on the backend into a list of `CacheNode`'s and a list of `Refe We insert the `CacheNode`'s into a global cache on the frontend and then use the `Reference`'s to reconstruct the data by looking up the `CacheNode`'s. -When the cache changes (from another query, invalidation, etc), we can reconstruct *all* queries using their `Reference`'s to reflect the updated data. - +When the cache changes (from another query, invalidation, etc), we can reconstruct _all_ queries using their `Reference`'s to reflect the updated data. ## Rust usage @@ -129,7 +128,6 @@ const filePaths = useCache(query.data?.file_paths); This is only possible because `useNodes` and `useCache` take in a specific key, instead of the whole `data` object, so you can tell it where to look. - ## Known issues ### Specta support diff --git a/docs/developers/technology/rspc.mdx b/docs/developers/technology/rspc.mdx index 1885135f9657..23056d19f8a8 100644 --- a/docs/developers/technology/rspc.mdx +++ b/docs/developers/technology/rspc.mdx @@ -7,16 +7,17 @@ We use a fork based on [rspc 0.1.4](https://docs.rs/rspc) which contains heavy m ## What's different? - - A super pre-release version of rspc v1's procedure syntax. - - Upgrade to Specta v2 prelease - - Add `Router::sd_patch_types_dangerously` - - Expose internal type maps for the invalidation system. - - All procedures must return a result - - `Procedure::with2` which is a hack to properly support the middleware mapper API - - Legacy executor system - Will require major changes to the React Native link. +- A super pre-release version of rspc v1's procedure syntax. +- Upgrade to Specta v2 prelease +- Add `Router::sd_patch_types_dangerously` +- Expose internal type maps for the invalidation system. +- All procedures must return a result +- `Procedure::with2` which is a hack to properly support the middleware mapper API +- Legacy executor system - Will require major changes to the React Native link. Removed features relied on by Spacedrive: - - Argument middleware mapper API has been removed upstream + +- Argument middleware mapper API has been removed upstream ## Basic usage @@ -83,9 +84,8 @@ Minus batching HTTP requests are run in parallel. ### Websocket reconnect -If the websocket connection is dropped (due to network disruption) all subscriptions *will not* restart upon reconnecting. +If the websocket connection is dropped (due to network disruption) all subscriptions _will not_ restart upon reconnecting. This will cause the invalidation system to break and potentially other parts of the app that rely on subscriptions. Queries and mutations done during the network disruption will hang indefinitely. - diff --git a/docs/product/getting-started/setup.mdx b/docs/product/getting-started/setup.mdx index 70f470928613..0e7a596e5971 100644 --- a/docs/product/getting-started/setup.mdx +++ b/docs/product/getting-started/setup.mdx @@ -44,9 +44,10 @@ docker run -d --name spacedrive -p 8080:8080 -e SD_AUTH=admin:spacedrive -v /var When using the Spacedrive server you can use the `SD_AUTH` environment variable to configure authentication. Valid values: - - `SD_AUTH=disabled` - Disables authentication. - - `SD_AUTH=username:password` - Enables authentication for a single user. - - `SD_AUTH=username:password,username1:password1` - Enables authentication with multiple users (you can add as many users as you want). + +- `SD_AUTH=disabled` - Disables authentication. +- `SD_AUTH=username:password` - Enables authentication for a single user. +- `SD_AUTH=username:password,username1:password1` - Enables authentication with multiple users (you can add as many users as you want). ### Mobile (Preview) diff --git a/interface/app/$libraryId/Layout/Sidebar/sections/Local/index.tsx b/interface/app/$libraryId/Layout/Sidebar/sections/Local/index.tsx index 3bb747444f4c..aa0726744968 100644 --- a/interface/app/$libraryId/Layout/Sidebar/sections/Local/index.tsx +++ b/interface/app/$libraryId/Layout/Sidebar/sections/Local/index.tsx @@ -1,11 +1,12 @@ -import { EjectSimple } from '@phosphor-icons/react'; +import { ArrowRight, EjectSimple } from '@phosphor-icons/react'; import clsx from 'clsx'; import { PropsWithChildren, useMemo } from 'react'; import { useBridgeQuery, useCache, useLibraryQuery, useNodes } from '@sd/client'; import { Button, toast, tw } from '@sd/ui'; import { Icon, IconName } from '~/components'; -import { useLocale } from '~/hooks'; +import { useLocale, useOperatingSystem } from '~/hooks'; import { useHomeDir } from '~/hooks/useHomeDir'; +import { usePlatform } from '~/util/Platform'; import { useExplorerDroppable } from '../../../../Explorer/useExplorerDroppable'; import { useExplorerSearchParams } from '../../../../Explorer/util'; @@ -26,11 +27,18 @@ const EjectButton = ({ className }: { className?: string }) => ( ); +const OpenToButton = ({ className }: { className?: string; what_is_opening?: string }) => ( + +); + const SidebarIcon = ({ name }: { name: IconName }) => { return ; }; export default function LocalSection() { + const platform = usePlatform(); const locationsQuery = useLibraryQuery(['locations.list']); useNodes(locationsQuery.data?.nodes); const locations = useCache(locationsQuery.data?.items); @@ -76,6 +84,7 @@ export default function LocalSection() { ) ); + const os = useOperatingSystem(); return (
@@ -137,6 +146,17 @@ export default function LocalSection() { ); })} + {platform.openTrashInOsExplorer && ( + // eslint-disable-next-line tailwindcss/migration-from-tailwind-2 +
platform.openTrashInOsExplorer?.()} + > + + {t('trash')} + +
+ )}
); diff --git a/interface/hooks/useMouseItemResize.ts b/interface/hooks/useMouseItemResize.ts index 1fce52e1483c..4f9b0323604d 100644 --- a/interface/hooks/useMouseItemResize.ts +++ b/interface/hooks/useMouseItemResize.ts @@ -1,9 +1,9 @@ import { useCallback, useEffect } from 'react'; import { useExplorerContext } from '~/app/$libraryId/Explorer/Context'; +import { getSizes } from '~/app/$libraryId/Explorer/OptionsPanel/ListView/util'; import { LIST_VIEW_ICON_SIZES } from '~/app/$libraryId/Explorer/View/ListView/useTable'; import { useOperatingSystem } from './useOperatingSystem'; -import { getSizes } from '~/app/$libraryId/Explorer/OptionsPanel/ListView/util'; /** * Hook that allows resizing of items in the Explorer views for GRID and LIST only - using the mouse wheel. @@ -19,11 +19,11 @@ export const useMouseItemResize = () => { const isList = layoutMode === 'list'; const deltaYModifier = isList ? Math.sign(e.deltaY) : e.deltaY / 10; // Sensitivity adjustment const newSize = - Number( - isList - ? explorer.settingsStore.listViewIconSize - : explorer.settingsStore.gridItemSize - ) + deltaYModifier; + Number( + isList + ? explorer.settingsStore.listViewIconSize + : explorer.settingsStore.gridItemSize + ) + deltaYModifier; const minSize = isList ? 0 : 60; const maxSize = isList ? 2 : 200; @@ -31,7 +31,7 @@ export const useMouseItemResize = () => { if (isList) { const listSizes = getSizes(LIST_VIEW_ICON_SIZES); - explorer.settingsStore.listViewIconSize = listSizes.sizeMap.get(clampedSize) ?? "0" + explorer.settingsStore.listViewIconSize = listSizes.sizeMap.get(clampedSize) ?? '0'; } else if (layoutMode === 'grid') { explorer.settingsStore.gridItemSize = Number(clampedSize.toFixed(0)); } diff --git a/interface/locales/en/common.json b/interface/locales/en/common.json index 212ddd23f9c9..a73c138d323a 100644 --- a/interface/locales/en/common.json +++ b/interface/locales/en/common.json @@ -471,6 +471,7 @@ "toggle_metadata": "Toggle metadata", "toggle_path_bar": "Toggle path bar", "toggle_quick_preview": "Toggle quick preview", + "trash": "Trash", "type": "Type", "ui_animations": "UI Animations", "ui_animations_description": "Dialogs and other UI elements will animate when opening and closing.", diff --git a/interface/util/Platform.tsx b/interface/util/Platform.tsx index 2c10c50b8d22..f70bb596008a 100644 --- a/interface/util/Platform.tsx +++ b/interface/util/Platform.tsx @@ -36,6 +36,7 @@ export type Platform = { showDevtools?(): void; openPath?(path: string): void; openLogsDir?(): void; + openTrashInOsExplorer?(): void; userHomeDir?(): Promise; // Opens a file path with a given ID openFilePaths?(library: string, ids: number[]): any;