Skip to content

Commit

Permalink
Drag & Drop into/out of Spacedrive (#2849)
Browse files Browse the repository at this point in the history
* Better drag & drop into the os

* Update drag.rs

* Drag & Drop into Spacedrive from OS

* Re-enable Supertokes & change Drag-rs pointer

* Autoformat
  • Loading branch information
Rocky43007 authored Jan 10, 2025
1 parent b225dcd commit 645b412
Show file tree
Hide file tree
Showing 16 changed files with 153 additions and 58 deletions.
28 changes: 23 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions apps/desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ sd-prisma = { path = "../../../crates/prisma" }
# Workspace dependencies
axum = { workspace = true, features = ["query"] }
axum-extra = { workspace = true, features = ["typed-header"] }
base64 = { workspace = true }
futures = { workspace = true }
http = { workspace = true }
hyper = { workspace = true }
Expand All @@ -32,25 +33,24 @@ thiserror = { workspace = true }
tokio = { workspace = true, features = ["sync"] }
tracing = { workspace = true }
uuid = { workspace = true, features = ["serde"] }
base64 = { workspace = true }

# Specific Desktop dependencies
# WARNING: Do NOT enable default features, as that vendors dbus (see below)
drag = { git = "https://github.com/spacedriveapp/drag-rs", rev = "157b2cd9" }
opener = { version = "0.7.1", features = ["reveal"], default-features = false }
specta-typescript = "=0.0.7"
tauri-plugin-clipboard-manager = "=2.0.1"
tauri-plugin-cors-fetch = { path = "../../../crates/tauri-plugin-cors-fetch" }
tauri-plugin-deep-link = "=2.0.1"
tauri-plugin-dialog = "=2.0.3"
tauri-plugin-drag = "2.0.0"
tauri-plugin-http = "=2.0.3"
tauri-plugin-os = "=2.0.1"
tauri-plugin-shell = "=2.0.2"
tauri-plugin-updater = "=2.0.2"
tauri-plugin-drag = "2.0.0"
drag = "2.0.0"

# memory allocator
mimalloc = { workspace = true }
mimalloc = { workspace = true }

[dependencies.tauri]
features = ["linux-libxdo", "macos-private-api", "native-tls-vendored", "unstable"]
Expand Down
12 changes: 9 additions & 3 deletions apps/desktop/src-tauri/src/drag.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// Import required dependencies for drag and drop operations, serialization, and async functionality
use drag::{DragItem, Image, Options};
use serde::{Deserialize, Serialize};
use specta::Type;
Expand Down Expand Up @@ -162,6 +161,8 @@ pub async fn start_drag(
let is_completed = is_completed_clone.clone();
let cancel_flag_clone = cancel_flag.clone();
let window_for_drag = window_owned.clone();
let drag_session = Arc::new(Mutex::new(None));
let drag_session_clone = drag_session.clone();

// Execute drag operation on main thread
app_handle_owned
Expand All @@ -176,7 +177,7 @@ pub async fn start_drag(
Image::File(PathBuf::from(&icon_path_for_drag));

// Start the drag operation
if let Ok(_) = drag::start_drag(
if let Ok(session) = drag::start_drag(
&window_for_drag,
item,
preview_icon,
Expand All @@ -190,9 +191,14 @@ pub async fn start_drag(
is_completed.store(true, Ordering::SeqCst);
TRACKING.store(false, Ordering::SeqCst);
},
Options::default(),
Options {
skip_animatation_on_cancel_or_failure: false,
mode: drag::DragMode::Move,
},
) {
println!("Drag operation started");
// Store drag session for cancellation
*drag_session_clone.lock().unwrap() = Some(session);
}
} else {
println!("Cursor returned to window");
Expand Down
14 changes: 12 additions & 2 deletions apps/desktop/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
createRoutes,
DeeplinkEvent,
ErrorPage,
FileDropEvent,
KeybindEvent,
PlatformProvider,
SpacedriveInterfaceRoot,
Expand Down Expand Up @@ -99,7 +100,9 @@ function useDragAndDrop() {
}

const file_path =
'path' in data ? data.path : await libraryClient.query(['files.getPath', data.id]);
'path' in data
? data.path
: await libraryClient.query(['files.getPath', data.id]);

console.log('Resolved file path:', file_path);
return {
Expand Down Expand Up @@ -134,6 +137,8 @@ function useDragAndDrop() {
y: payload.cursorPos.y,
screen: window.screen
});
// Refetch explorer files after successful drop
queryClient.invalidateQueries({ queryKey: ['search.paths'] });
}

explorerStore.drag = null;
Expand Down Expand Up @@ -181,10 +186,14 @@ export default function App() {
if (!url) return;
document.dispatchEvent(new DeeplinkEvent(url));
});
const fileDropListener = listen('tauri://drag-drop', async (data) => {
document.dispatchEvent(new FileDropEvent((data.payload as { paths: string[] }).paths));
});

return () => {
keybindListener.then((unlisten) => unlisten());
deeplinkListener.then((unlisten) => unlisten());
fileDropListener.then((unlisten) => unlisten());
};
}, []);

Expand Down Expand Up @@ -379,7 +388,8 @@ function AppInner() {
new Promise((res) => {
startTransition(() => {
setTabs((tabs) => {
const { pathname, search } = selectedTab.router.state.location;
const { pathname, search } =
selectedTab.router.state.location;
const newTab = createTab({ pathname, search });
const newTabs = [...tabs, newTab];

Expand Down
2 changes: 1 addition & 1 deletion apps/landing/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"@fortawesome/free-brands-svg-icons": "^6.6.0",
"@fortawesome/react-fontawesome": "^0.2.2",
"@octokit/webhooks": "^12.0.3",
"@phosphor-icons/react": "^2.0.14",
"@phosphor-icons/react": "^2.1.0",
"@radix-ui/react-dialog": "^1.0.5",
"@react-three/drei": "^9.88.13",
"@react-three/fiber": "^8.15.11",
Expand Down
17 changes: 11 additions & 6 deletions interface/app/$libraryId/Explorer/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FolderNotchOpen } from '@phosphor-icons/react';
import { CSSProperties, type PropsWithChildren, type ReactNode } from 'react';
import { CSSProperties, useEffect, type PropsWithChildren, type ReactNode } from 'react';
import {
explorerLayout,
useExplorerLayoutStore,
Expand Down Expand Up @@ -87,8 +87,6 @@ export default function Explorer(props: PropsWithChildren<Props>) {
explorer.settingsStore.showHiddenFiles = !explorer.settingsStore.showHiddenFiles;
});

window.useDragAndDrop();

useKeyRevealFinder();

useExplorerDnd();
Expand Down Expand Up @@ -118,13 +116,18 @@ export default function Explorer(props: PropsWithChildren<Props>) {
contextMenu={props.contextMenu ? props.contextMenu() : <ContextMenu />}
emptyNotice={
props.emptyNotice ?? (
<EmptyNotice icon={FolderNotchOpen} message="This folder is empty" />
<EmptyNotice
icon={FolderNotchOpen}
message="This folder is empty"
/>
)
}
listViewOptions={{ hideHeaderBorder: true }}
scrollPadding={{
top: topBar.topBarHeight,
bottom: showPathBar ? PATH_BAR_HEIGHT + (showTagBar ? TAG_BAR_HEIGHT : 0) : undefined
bottom: showPathBar
? PATH_BAR_HEIGHT + (showTagBar ? TAG_BAR_HEIGHT : 0)
: undefined
}}
/>
</div>
Expand All @@ -144,7 +147,9 @@ export default function Explorer(props: PropsWithChildren<Props>) {
)}
style={{
paddingTop: topBar.topBarHeight + 12,
bottom: showPathBar ? PATH_BAR_HEIGHT + (showTagBar ? TAG_BAR_HEIGHT : 0) : 0
bottom: showPathBar
? PATH_BAR_HEIGHT + (showTagBar ? TAG_BAR_HEIGHT : 0)
: 0
}}
/>
)}
Expand Down
17 changes: 1 addition & 16 deletions interface/app/$libraryId/Explorer/useExplorerDnd.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { useAssignItemsToTag } from '../settings/library/tags/CreateDialog';
import { useExplorerContext } from './Context';
import { explorerStore } from './store';
import { explorerDroppableSchema } from './useExplorerDroppable';
import { useExplorerSearchParams } from './util';
import { getPathIdsPerLocation, useExplorerSearchParams } from './util';

export const getPaths = async (items: ExplorerItem[]) => {
const paths = items.map(async (item) => {
Expand All @@ -27,21 +27,6 @@ export const getPaths = async (items: ExplorerItem[]) => {
return (await Promise.all(paths)).filter((path): path is string => Boolean(path));
};

const getPathIdsPerLocation = (items: ExplorerItem[]) => {
return items.reduce(
(items, item) => {
const path = getIndexedItemFilePath(item);
if (!path || path.location_id === null) return items;

return {
...items,
[path.location_id]: [...(items[path.location_id] ?? []), path.id]
};
},
{} as Record<number, number[]>
);
};

export const useExplorerDnd = () => {
const explorer = useExplorerContext();

Expand Down
17 changes: 16 additions & 1 deletion interface/app/$libraryId/Explorer/util.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import dayjs from 'dayjs';
import { type ExplorerItem } from '@sd/client';
import { getIndexedItemFilePath, type ExplorerItem } from '@sd/client';
import i18n from '~/app/I18n';
import { ExplorerParamsSchema } from '~/app/route-schemas';
import { useZodSearchParams } from '~/hooks';
Expand Down Expand Up @@ -200,3 +200,18 @@ export function fetchAccessToken(): string {
.split(';')[0] || '';
return accessToken;
}

export const getPathIdsPerLocation = (items: ExplorerItem[]) => {
return items.reduce(
(items, item) => {
const path = getIndexedItemFilePath(item);
if (!path || path.location_id === null) return items;

return {
...items,
[path.location_id]: [...(items[path.location_id] ?? []), path.id]
};
},
{} as Record<number, number[]>
);
};
4 changes: 4 additions & 0 deletions interface/app/$libraryId/Layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { LibraryIdParamsSchema } from '~/app/route-schemas';
import ErrorFallback, { BetterErrorBoundary } from '~/ErrorFallback';
import {
useDeeplinkEventHandler,
useFileDropEventHandler,
useKeybindEventHandler,
useOperatingSystem,
useRedirectToNewLocation,
Expand All @@ -42,6 +43,9 @@ const Layout = () => {

useKeybindEventHandler(library?.uuid);
useDeeplinkEventHandler();
useFileDropEventHandler(library?.uuid);

window.useDragAndDrop();

const layoutRef = useRef<HTMLDivElement>(null);

Expand Down
1 change: 1 addition & 0 deletions interface/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export * from './useIsDark';
export * from './useKeyDeleteFile';
export * from './useKeybind';
export * from './useKeybindEventHandler';
export * from './useFileDropEventHandler';
export * from './useOperatingSystem';
export * from './useScrolled';
// export * from './useSearchStore';
Expand Down
44 changes: 44 additions & 0 deletions interface/hooks/useFileDropEventHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useEffect } from 'react';
import { useLocation, useNavigate } from 'react-router';
import { libraryClient } from '@sd/client';
import { getPathIdsPerLocation, useExplorerSearchParams } from '~/app/$libraryId/Explorer/util';
import { isNonEmptyObject } from '~/util';
import { FileDropEvent } from '~/util/events';

import { useQuickRescan } from './useQuickRescan';

export const useFileDropEventHandler = (libraryId?: string) => {
const navigate = useNavigate();
const rescan = useQuickRescan();
const regex = new RegExp(
'/[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}/location/'
);
const id = parseInt(useLocation().pathname.replace(regex, ''));
const [{ path }] = useExplorerSearchParams();

useEffect(() => {
const handler = async (e: FileDropEvent) => {
e.preventDefault();
const paths = e.detail.paths;

if (libraryId && path) {
libraryClient.mutation([
'ephemeralFiles.cutFiles',
{ sources: paths, target_dir: path! }
]);
} else if (libraryId) {
// Get Materialized Path using the location id
const locationId = id;
const location = await libraryClient.query(['locations.get', locationId]);
const locationPath = location!.path;
libraryClient.mutation([
'ephemeralFiles.cutFiles',
{ sources: paths, target_dir: locationPath! }
]);
}
};

document.addEventListener('filedrop', handler);
return () => document.removeEventListener('filedrop', handler);
}, [navigate, libraryId, rescan, id, path]);
};
4 changes: 2 additions & 2 deletions interface/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"@dnd-kit/utilities": "^3.2.2",
"@headlessui/react": "^1.7.17",
"@icons-pack/react-simple-icons": "^9.1.0",
"@phosphor-icons/react": "^2.0.13",
"@phosphor-icons/react": "^2.1.0",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-progress": "^1.0.1",
Expand Down Expand Up @@ -87,4 +87,4 @@
"vite": "^5.4.9",
"vite-plugin-svgr": "^3.3.0"
}
}
}
Loading

0 comments on commit 645b412

Please sign in to comment.