Get file scanning working & focus window on show
This commit is contained in:
10 files changed
+193
-16
No files matched your search
@@ -12,7 +12,9 @@
|
||||
"@fortawesome/react-fontawesome": "^3.3.0",
|
||||
"@tailwindcss/vite": "4.2.2",
|
||||
"@tauri-apps/api": "2.10.1",
|
||||
"@tauri-apps/plugin-dialog": "~2",
|
||||
"@tauri-apps/plugin-opener": "2.5.3",
|
||||
"@tauri-apps/plugin-shell": "~2",
|
||||
"pretty-bytes": "^7.1.0",
|
||||
"react": "19.2.5",
|
||||
"react-dom": "19.2.5",
|
||||
@@ -150,8 +152,12 @@
|
||||
|
||||
"@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.10.1", "", { "os": "win32", "cpu": "x64" }, "sha512-6Cn7YpPFwzChy0ERz6djKEmUehWrYlM+xTaNzGPgZocw3BD7OfwfWHKVWxXzdjEW2KfKkHddfdxK1XXTYqBRLg=="],
|
||||
|
||||
"@tauri-apps/plugin-dialog": ["@tauri-apps/plugin-dialog@2.7.0", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-4nS/hfGMGCXiAS3LtVjH9AgsSAPJeG/7R+q8agTFqytjnMa4Zq95Bq8WzVDkckpanX+yyRHXnRtrKXkANKDHvw=="],
|
||||
|
||||
"@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.5.3", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-CCcUltXMOfUEArbf3db3kCE7Ggy1ExBEBl51Ko2ODJ6GDYHRp1nSNlQm5uNCFY5k7/ufaK5Ib3Du/Zir19IYQQ=="],
|
||||
|
||||
"@tauri-apps/plugin-shell": ["@tauri-apps/plugin-shell@2.3.5", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-jewtULhiQ7lI7+owCKAjc8tYLJr92U16bPOeAa472LHJdgaibLP83NcfAF2e+wkEcA53FxKQAZ7byDzs2eeizg=="],
|
||||
|
||||
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||
|
||||
"@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
"@fortawesome/react-fontawesome": "^3.3.0",
|
||||
"@tailwindcss/vite": "4.2.2",
|
||||
"@tauri-apps/api": "2.10.1",
|
||||
"@tauri-apps/plugin-dialog": "~2",
|
||||
"@tauri-apps/plugin-opener": "2.5.3",
|
||||
"@tauri-apps/plugin-shell": "~2",
|
||||
"pretty-bytes": "^7.1.0",
|
||||
"react": "19.2.5",
|
||||
"react-dom": "19.2.5",
|
||||
|
||||
@@ -16,6 +16,9 @@ tauri = { version = "2.10.3", features = ["macos-private-api"] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.149"
|
||||
sysinfo = "0.38.4"
|
||||
walkdir = "2.5.0"
|
||||
tauri-plugin-shell = "2"
|
||||
tauri-plugin-dialog = "2"
|
||||
|
||||
[target.'cfg(any(target_os = "windows", target_os = "macos"))'.dependencies]
|
||||
window-vibrancy = "0.7.1"
|
||||
|
||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -2,8 +2,12 @@
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "Capability for the main window",
|
||||
"windows": ["main"],
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"core:default"
|
||||
"core:default",
|
||||
"shell:default",
|
||||
"dialog:default"
|
||||
]
|
||||
}
|
||||
}
|
||||
+108
-8
@@ -1,9 +1,35 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sysinfo::Disks;
|
||||
use tauri::{WebviewUrl, WebviewWindowBuilder};
|
||||
|
||||
use tauri_plugin_shell::ShellExt;
|
||||
#[cfg(target_os = "macos")]
|
||||
use window_vibrancy::{NSVisualEffectMaterial, apply_vibrancy};
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct DiskInfo {
|
||||
name: String,
|
||||
mount_point: String,
|
||||
total_space: u64,
|
||||
available_space: u64,
|
||||
is_removable: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct FileInfo {
|
||||
pub name: String,
|
||||
pub path: String,
|
||||
pub size: u64,
|
||||
pub is_dir: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct DirScanResult {
|
||||
pub items: Vec<FileInfo>,
|
||||
pub total_size: u64,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn list_disks() -> Vec<DiskInfo> {
|
||||
Disks::new_with_refreshed_list()
|
||||
@@ -22,19 +48,92 @@ fn list_disks() -> Vec<DiskInfo> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
struct DiskInfo {
|
||||
name: String,
|
||||
mount_point: String,
|
||||
total_space: u64,
|
||||
available_space: u64,
|
||||
is_removable: bool,
|
||||
#[tauri::command]
|
||||
async fn get_file_info_from_dirs(
|
||||
app: tauri::AppHandle,
|
||||
path: String,
|
||||
min_ratio: String,
|
||||
) -> Result<DirScanResult, String> {
|
||||
let cmd = app
|
||||
.shell()
|
||||
.sidecar("pdu")
|
||||
.map_err(|e| e.to_string())?
|
||||
.args([
|
||||
"--json-output",
|
||||
"--min-ratio",
|
||||
&min_ratio,
|
||||
"--max-depth",
|
||||
"inf",
|
||||
&path,
|
||||
]);
|
||||
|
||||
let output = cmd.output().await.map_err(|e| e.to_string())?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(format!(
|
||||
"pdu failed: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
}
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
|
||||
let json: serde_json::Value =
|
||||
serde_json::from_str(&stdout).map_err(|e| format!("JSON parse failed: {}", e))?;
|
||||
|
||||
let mut items = Vec::new();
|
||||
if let Some(tree) = json.get("tree") {
|
||||
extract_files(tree, "", &mut items);
|
||||
}
|
||||
|
||||
let total_size = items.iter().map(|i| i.size).sum();
|
||||
|
||||
Ok(DirScanResult {
|
||||
items,
|
||||
total_size,
|
||||
error: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_files(node: &serde_json::Value, parent_path: &str, items: &mut Vec<FileInfo>) {
|
||||
if let Some(name) = node.get("name").and_then(|v| v.as_str()) {
|
||||
let size = node.get("size").and_then(|v| v.as_u64()).unwrap_or(0);
|
||||
let children = node.get("children").and_then(|v| v.as_array());
|
||||
|
||||
let is_dir = children.as_ref().map(|c| !c.is_empty()).unwrap_or(false);
|
||||
|
||||
let path = if parent_path.is_empty() {
|
||||
name.to_string()
|
||||
} else {
|
||||
format!("{}/{}", parent_path, name)
|
||||
};
|
||||
|
||||
items.push(FileInfo {
|
||||
name: name.to_string(),
|
||||
path: path.clone(),
|
||||
size,
|
||||
is_dir,
|
||||
});
|
||||
|
||||
if let Some(children_array) = children {
|
||||
if !children_array.is_empty() {
|
||||
for child in children_array {
|
||||
extract_files(child, &path, items);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.invoke_handler(tauri::generate_handler![list_disks])
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
list_disks,
|
||||
get_file_info_from_dirs
|
||||
])
|
||||
.setup(|app| {
|
||||
#[allow(unused)]
|
||||
let mut transparency = false;
|
||||
@@ -82,6 +181,7 @@ pub fn run() {
|
||||
}
|
||||
|
||||
window.show().unwrap();
|
||||
window.set_focus().unwrap();
|
||||
|
||||
Ok(())
|
||||
})
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
],
|
||||
"macOS": {
|
||||
"minimumSystemVersion": "13.0"
|
||||
}
|
||||
},
|
||||
"externalBin": ["binaries/pdu"]
|
||||
}
|
||||
}
|
||||
+43
-4
@@ -5,6 +5,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faHardDrive } from '@fortawesome/free-solid-svg-icons'
|
||||
import { v5 as uuidv5 } from 'uuid'
|
||||
import prettyBytes from 'pretty-bytes'
|
||||
import { FileInfo, scanDir } from './scanlib'
|
||||
import { message, open } from '@tauri-apps/plugin-dialog'
|
||||
|
||||
interface Disk {
|
||||
name: string
|
||||
@@ -16,6 +18,9 @@ interface Disk {
|
||||
|
||||
export default function App () {
|
||||
const [disks, setDisks] = useState<Array<Disk>>([])
|
||||
const [selectedFolder, setSelectedFolder] = useState<string | null>(null)
|
||||
const [scanning, setScanning] = useState<boolean>(false)
|
||||
const [files, setFiles] = useState<FileInfo[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
;(async () => {
|
||||
@@ -23,8 +28,26 @@ export default function App () {
|
||||
})()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div>
|
||||
const handleFolderSelect = async (path: string) => {
|
||||
setSelectedFolder(path)
|
||||
setScanning(true)
|
||||
console.log('scanning', path)
|
||||
const scanResult = await scanDir(path)
|
||||
console.log('done', scanResult)
|
||||
if (scanResult.error) {
|
||||
await message(`Error: ${scanResult.error}`, {
|
||||
title: 'Tauri',
|
||||
kind: 'error'
|
||||
})
|
||||
setSelectedFolder(null)
|
||||
} else {
|
||||
setFiles(scanResult.items)
|
||||
}
|
||||
setScanning(false)
|
||||
}
|
||||
|
||||
return !selectedFolder ? (
|
||||
<>
|
||||
<p className='text-center text-2xl mt-2'>Disks</p>
|
||||
<div className='flex flex-col gap-2 p-2'>
|
||||
{disks.map(d => {
|
||||
@@ -34,6 +57,7 @@ export default function App () {
|
||||
<div
|
||||
key={uuidv5(JSON.stringify(d), uuidv5.DNS)}
|
||||
className='w-full bg-(--col2)/50 hover:bg-(--col4)/50 border border-(--col4)/50 hover:border-(--col6)/50 rounded-lg px-3 py-1.5 flex flex-row gap-4 items-center relative cursor-pointer transition-colors'
|
||||
onClick={() => handleFolderSelect(d.mount_point)}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faHardDrive}
|
||||
@@ -69,10 +93,25 @@ export default function App () {
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<p className='w-full bg-(--col2)/50 hover:bg-(--col4)/50 border border-(--col4)/50 hover:border-(--col6)/50 rounded-lg px-3 py-1.5 cursor-pointer transition-colors'>
|
||||
<p
|
||||
className='w-full bg-(--col2)/50 hover:bg-(--col4)/50 border border-(--col4)/50 hover:border-(--col6)/50 rounded-lg px-3 py-1.5 cursor-pointer transition-colors'
|
||||
onClick={async () => {
|
||||
const folder = await open({
|
||||
multiple: false,
|
||||
directory: true
|
||||
})
|
||||
if (!folder) return
|
||||
handleFolderSelect(folder)
|
||||
}}
|
||||
>
|
||||
Or select a folder to scan
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p>{selectedFolder}</p>
|
||||
<p>{JSON.stringify(files)}</p>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
|
||||
export interface FileInfo {
|
||||
name: string
|
||||
path: string
|
||||
size: number
|
||||
is_dir: boolean
|
||||
}
|
||||
|
||||
export interface DirScanResult {
|
||||
items: FileInfo[]
|
||||
total_size: number
|
||||
error: string | null
|
||||
}
|
||||
|
||||
export const scanDir = async (path: string) => {
|
||||
const result: DirScanResult = await invoke('get_file_info_from_dirs', {
|
||||
path,
|
||||
minRatio: '0'
|
||||
})
|
||||
return result
|
||||
}
|
||||
Reference in new issue
Block a user