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>
This commit is contained in:
Arnab Chakraborty 2024-04-22 15:46:10 -04:00 committed by GitHub
parent 745399ecab
commit b4037d6537
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 104 additions and 29 deletions

View file

@ -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<Node>>) -> 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,

View file

@ -41,6 +41,17 @@ export const commands = {
async requestFdaMacos(): Promise<null> {
return await TAURI_INVOKE('plugin:tauri-specta|request_fda_macos');
},
async openTrashInOsExplorer(): Promise<__Result__<null, null>> {
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[]

View file

@ -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;

View file

@ -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<T>`: 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<T>`: 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<T>`'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<T>`'s to reflect the updated data.
When the cache changes (from another query, invalidation, etc), we can reconstruct _all_ queries using their `Reference<T>`'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

View file

@ -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.

View file

@ -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)

View file

@ -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 }) => (
</Button>
);
const OpenToButton = ({ className }: { className?: string; what_is_opening?: string }) => (
<Button className={clsx('absolute right-[2px] !p-[5px]', className)} variant="subtle">
<ArrowRight size={18} className="size-3 opacity-70" />
</Button>
);
const SidebarIcon = ({ name }: { name: IconName }) => {
return <Icon name={name} size={20} className="mr-1" />;
};
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 (
<Section name={t('local')}>
<SeeMore>
@ -137,6 +146,17 @@ export default function LocalSection() {
</EphemeralLocation>
);
})}
{platform.openTrashInOsExplorer && (
// eslint-disable-next-line tailwindcss/migration-from-tailwind-2
<div
className={`max-w relative flex grow flex-row items-center gap-0.5 truncate rounded border border-transparent ${os === 'macOS' ? 'bg-opacity-90' : ''} px-2 py-1 text-sm font-medium text-sidebar-inkDull outline-none ring-0 ring-inset ring-transparent ring-offset-0 focus:ring-1 focus:ring-accent focus:ring-offset-0`}
onClick={() => platform.openTrashInOsExplorer?.()}
>
<SidebarIcon name="Trash" />
<Name>{t('trash')}</Name>
<OpenToButton />
</div>
)}
</SeeMore>
</Section>
);

View file

@ -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));
}

View file

@ -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.",

View file

@ -36,6 +36,7 @@ export type Platform = {
showDevtools?(): void;
openPath?(path: string): void;
openLogsDir?(): void;
openTrashInOsExplorer?(): void;
userHomeDir?(): Promise<string>;
// Opens a file path with a given ID
openFilePaths?(library: string, ids: number[]): any;