Skip to content

Commit

Permalink
Re-implement Import Dropzone (macOS + Linux only)
Browse files Browse the repository at this point in the history
  • Loading branch information
martpie committed Dec 30, 2024
1 parent 4ee69c0 commit 7f67921
Show file tree
Hide file tree
Showing 10 changed files with 115 additions and 66 deletions.
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@tanstack/react-virtual": "3.11.1",
"@tauri-apps/api": "2.1.1",
"@tauri-apps/plugin-dialog": "2.2.0",
"@tauri-apps/plugin-fs": "^2.2.0",
"@tauri-apps/plugin-log": "2.2.0",
"@tauri-apps/plugin-notification": "2.2.0",
"@tauri-apps/plugin-opener": "^2.2.1",
Expand Down
1 change: 1 addition & 0 deletions src-tauri/Cargo.lock

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

1 change: 1 addition & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ tauri = { version = "2.1.1", features = [
"devtools",
] }
tauri-plugin-dialog = "2.2.0"
tauri-plugin-fs = "2.2.0"
tauri-plugin-log = { version = "2.2.0", features = ["colored"] }
tauri-plugin-notification = "2.2.0"
tauri-plugin-opener = "2.2.1"
Expand Down
1 change: 1 addition & 0 deletions src-tauri/capabilities/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"shell:allow-open",
"dialog:allow-open",
"dialog:allow-ask",
"fs:allow-lstat",
"opener:allow-open-url",
"opener:allow-reveal-item-in-dir",
"os:allow-os-type",
Expand Down
10 changes: 8 additions & 2 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ fn main() {
.plugin(plugins::sleepblocker::init())
// Tauri integrations with the Operating System
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_notification::init())
Expand Down Expand Up @@ -77,16 +78,21 @@ fn main() {
.min_inner_size(900.0, 550.0)
.fullscreen(false)
.resizable(true)
.disable_drag_drop_handler() // TODO: Windows drag-n-drop on windows does not work :| https://github.com/tauri-apps/wry/issues/904
.zoom_hotkeys_enabled(true);

// TODO: Windows drag-n-drop on windows does not work :|
// https://github.com/tauri-apps/wry/issues/904
#[cfg(target_os = "windows")]
window_builder.disable_drag_drop_handler().build()?;

// On macOS, we hide the native frame and use overlay controls as they're nicer
#[cfg(target_os = "macos")]
window_builder
.hidden_title(true)
.title_bar_style(tauri::TitleBarStyle::Overlay)
.build()?;

#[cfg(not(target_os = "macos"))]
#[cfg(target_os = "linux")]
window_builder.build()?;

info!("Main window built");
Expand Down
130 changes: 82 additions & 48 deletions src/components/DropzoneImport.tsx
Original file line number Diff line number Diff line change
@@ -1,64 +1,98 @@
import { getCurrentWindow } from '@tauri-apps/api/window';
import { lstat } from '@tauri-apps/plugin-fs';
import cx from 'classnames';
// import { getCurrent } from '@tauri-apps/api/window';
import { /** useEffect,*/ useState } from 'react';
// import { getCurrent } from '@tauri-apps/api/window';

// import { logAndNotifyError } from '../../lib/utils';
import { useEffect, useState } from 'react';

import useInvalidate from '../hooks/useInvalidate';
import { plural } from '../lib/localization';
import { logAndNotifyError } from '../lib/utils';
import { useLibraryAPI } from '../stores/useLibraryStore';
import { useToastsAPI } from '../stores/useToastsStore';
import styles from './DropzoneImport.module.css';

export default function DropzoneImport() {
const [isShown, _setIsShown] = useState(false);

// const unlisten = await getCurrent().onFileDropEvent((event) => {
// if (event.payload.type === 'hover') {
// console.log('User hovering', event.payload.paths);
// } else if (event.payload.type === 'drop') {
// console.log('User dropped', event.payload.paths);
// } else {
// console.log('File drop cancelled');
// }
// });

// useEffect(() => {
// async function attachFileDropEvent() {
// await getCurrent()
// .onFileDropEvent((event) => {
// if (event.payload.type === 'hover') {
// setIsShown(true);
// } else if (event.payload.type === 'drop') {
// console.log(event.payload.paths);
// setIsShown(false);
// } else {
// setIsShown(false);
// }
// })
// .catch(logAndNotifyError);
// }

// attachFileDropEvent().catch(logAndNotifyError);

// return getCurrent().clearEffects;
// }, []);
const libraryAPI = useLibraryAPI();
const toastsAPI = useToastsAPI();

const [isShown, setIsShown] = useState(false);
const invalidate = useInvalidate();

// Simplification welcome
useEffect(() => {
async function attachFileDropEvent() {
const unlisten = getCurrentWindow()
.onDragDropEvent(async (event) => {
if (event.payload.type === 'over') {
setIsShown(true);
} else if (event.payload.type === 'drop') {
setIsShown(false);

// Museeks does not deal in terms of files anymore, so we need to only retain folders.
// Why? Because in case a user imports a specific file from within a folder, it should
// ignore all other files, but it cannot do that as of today.
const fileInfos = await Promise.all(
event.payload.paths.map(async (path) => {
return {
...(await lstat(path)),
path,
};
}),
);

const folders = fileInfos
.filter((fileOrFolder) => fileOrFolder.isDirectory)
.map((folderInfo) => folderInfo.path);

const skippedItemsCount =
event.payload.paths.length - folders.length;

if (skippedItemsCount !== 0) {
toastsAPI.add(
'warning',
`${skippedItemsCount} non-folder ${plural('item', skippedItemsCount)} ignored`,
);
}

if (folders.length > 0) {
await libraryAPI.addLibraryFolders(folders);
toastsAPI.add(
'success',
`${folders.length} ${plural('folder', folders.length)} added to the library`,
);

await libraryAPI.refresh();

invalidate();
}
} else {
setIsShown(false);
}
})
.catch(logAndNotifyError);

return unlisten;
}

const unlisten = attachFileDropEvent().catch(logAndNotifyError);

return function cleanup() {
unlisten.then((u) => (u ? u() : null));
};
}, [
libraryAPI.addLibraryFolders,
libraryAPI.refresh,
toastsAPI.add,
invalidate,
]);

const classes = cx(styles.dropzone, {
[styles.shown]: isShown,
});

// TODO: Fix this, drop files from TAURI instead
// const files = item.files.map((file) => file.path);
// libraryAPI
// .add(files)
// .then((/* _importedTracks */) => {
// // TODO: Import to playlist here
// })
// .catch((err) => {
// logger.warn(err);
// });
return (
<div className={classes}>
<div className={styles.dropzoneTitle}>Add music to the library</div>
<span>Drop files or folders anywhere</span>
<span>Drop folders anywhere</span>
</div>
);
}
6 changes: 2 additions & 4 deletions src/routes/Root.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Suspense, useEffect } from 'react';
import { useEffect } from 'react';
import { Outlet } from 'react-router';

import AppEvents from '../components/AppEvents';
Expand Down Expand Up @@ -43,9 +43,7 @@ export default function ViewRoot() {
</main>
<Footer />
<Toasts />
<Suspense fallback={null}>
<DropzoneImport />
</Suspense>
<DropzoneImport />
</div>
);
}
Expand Down
17 changes: 16 additions & 1 deletion src/routes/settings-library.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import SettingsAPI from '../stores/SettingsAPI';
import useLibraryStore, { useLibraryAPI } from '../stores/useLibraryStore';
import type { SettingsLoaderData } from './settings';

import { open } from '@tauri-apps/plugin-dialog';
import { useCallback } from 'react';
import styles from './settings-library.module.css';

export default function ViewSettingsLibrary() {
Expand All @@ -17,6 +19,19 @@ export default function ViewSettingsLibrary() {
const { config } = useLoaderData() as SettingsLoaderData;
const invalidate = useInvalidate();

const addLibraryFolders = useCallback(async () => {
const paths = await open({
directory: true,
multiple: true,
});

if (paths == null) {
return;
}

await libraryAPI.addLibraryFolders(paths);
}, [libraryAPI.addLibraryFolders]);

return (
<div className="setting settings-musicfolder">
<Setting.Section>
Expand Down Expand Up @@ -52,7 +67,7 @@ export default function ViewSettingsLibrary() {
<Flexbox gap={4}>
<Button
disabled={isLibraryRefreshing}
onClick={useInvalidateCallback(libraryAPI.addLibraryFolder)}
onClick={useInvalidateCallback(addLibraryFolders)}
>
Add folder
</Button>
Expand Down
14 changes: 3 additions & 11 deletions src/stores/useLibraryStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type LibraryState = API<{
search: (value: string) => void;
sort: (sortBy: SortBy) => void;
add: () => Promise<void>;
addLibraryFolder: () => Promise<void>;
addLibraryFolders: (paths: Array<string>) => Promise<void>;
removeLibraryFolder: (path: string) => Promise<void>;
refresh: () => Promise<void>;
remove: (tracksIDs: string[]) => Promise<void>;
Expand Down Expand Up @@ -147,20 +147,12 @@ const useLibraryStore = createLibraryStore<LibraryState>((set, get) => ({
}
},

addLibraryFolder: async () => {
addLibraryFolders: async (paths: Array<string>) => {
try {
const path = await open({
directory: true,
});

if (path == null) {
return;
}

const musicFolders = await config.get('library_folders');
const newFolders = removeRedundantFolders([
...musicFolders,
path,
...paths,
]).sort();
await config.set('library_folders', newFolders);
} catch (err) {
Expand Down

0 comments on commit 7f67921

Please sign in to comment.