Compare commits

..
2 Commits
Author SHA1 Message Date
Lncvrt 9369db6e97 Fix bg 2026-04-15 12:55:09 -07:00
Lncvrt 5a4be0ae84 Update binaries 2026-04-15 12:51:20 -07:00
10 changed files with 74 additions and 148 deletions

No files matched your search

-3
View File
@@ -15,7 +15,6 @@
"@tauri-apps/plugin-dialog": "~2",
"@tauri-apps/plugin-opener": "~2",
"@tauri-apps/plugin-shell": "~2",
"@tauri-apps/plugin-window-state": "~2",
"pretty-bytes": "^7.1.0",
"react": "19.2.5",
"react-dom": "19.2.5",
@@ -159,8 +158,6 @@
"@tauri-apps/plugin-shell": ["@tauri-apps/plugin-shell@2.3.5", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-jewtULhiQ7lI7+owCKAjc8tYLJr92U16bPOeAa472LHJdgaibLP83NcfAF2e+wkEcA53FxKQAZ7byDzs2eeizg=="],
"@tauri-apps/plugin-window-state": ["@tauri-apps/plugin-window-state@2.4.1", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-OuvdrzyY8Q5Dbzpj+GcrnV1iCeoZbcFdzMjanZMMcAEUNy/6PH5pxZPXpaZLOR7whlzXiuzx0L9EKZbH7zpdRw=="],
"@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=="],
-1
View File
@@ -20,7 +20,6 @@
"@tauri-apps/plugin-dialog": "~2",
"@tauri-apps/plugin-opener": "~2",
"@tauri-apps/plugin-shell": "~2",
"@tauri-apps/plugin-window-state": "~2",
"pretty-bytes": "^7.1.0",
"react": "19.2.5",
"react-dom": "19.2.5",
-5
View File
@@ -20,7 +20,6 @@ walkdir = "2.5.0"
tauri-plugin-shell = "2"
tauri-plugin-dialog = "2"
tauri-plugin-opener = "2"
tauri-plugin-prevent-default = "4.0.4"
[target.'cfg(any(target_os = "windows", target_os = "macos"))'.dependencies]
window-vibrancy = "0.7.1"
@@ -28,10 +27,6 @@ window-vibrancy = "0.7.1"
[target.'cfg(target_os = "windows")'.dependencies]
windows-version = "0.1.7"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-single-instance = "2"
tauri-plugin-window-state = "2"
[profile.release]
opt-level = 3
lto = "thin"
Binary file not shown.
Binary file not shown.
Binary file not shown.
+4 -10
View File
@@ -2,19 +2,13 @@
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"windows": [
"main"
],
"permissions": [
"core:default",
"shell:default",
"dialog:default",
"opener:default",
{
"identifier": "opener:allow-open-path",
"allow": [
{
"path": "**"
}
]
}
"opener:default"
]
}
-14
View File
@@ -1,14 +0,0 @@
{
"identifier": "desktop-capability",
"platforms": [
"macOS",
"windows",
"linux"
],
"windows": [
"main"
],
"permissions": [
"window-state:default"
]
}
+36 -61
View File
@@ -1,17 +1,13 @@
use std::{collections::HashMap, fs, path::PathBuf, sync::Mutex};
use std::{fs, path::PathBuf};
use serde::{Deserialize, Serialize};
use sysinfo::Disks;
use tauri::{Manager, State, WebviewUrl, WebviewWindowBuilder};
use tauri::{Emitter, WebviewUrl, WebviewWindowBuilder};
use tauri_plugin_shell::ShellExt;
#[cfg(target_os = "macos")]
use window_vibrancy::{NSVisualEffectMaterial, apply_vibrancy};
struct AppState {
files: Mutex<HashMap<String, Vec<FileInfo>>>,
}
#[derive(Serialize)]
struct DiskInfo {
name: String,
@@ -29,6 +25,13 @@ pub struct FileInfo {
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()
@@ -50,7 +53,6 @@ fn list_disks() -> Vec<DiskInfo> {
#[tauri::command]
async fn get_file_info_from_dirs(
app: tauri::AppHandle,
state: State<'_, AppState>,
path: String,
min_ratio: String,
) -> Result<(), String> {
@@ -76,18 +78,20 @@ async fn get_file_info_from_dirs(
let json: serde_json::Value =
serde_json::from_slice(&output.stdout).map_err(|e| e.to_string())?;
let mut local_map: HashMap<String, Vec<FileInfo>> = HashMap::new();
if let Some(tree) = json.get("tree") {
stream(tree, "", &mut local_map);
let mut batch = Vec::new();
stream(tree, "", &mut batch, &app);
}
*state.files.lock().unwrap() = local_map;
Ok(())
}
fn stream(node: &serde_json::Value, parent: &str, map: &mut HashMap<String, Vec<FileInfo>>) {
fn stream(
node: &serde_json::Value,
parent: &str,
batch: &mut Vec<FileInfo>,
app: &tauri::AppHandle,
) {
let name = match node.get("name").and_then(|v| v.as_str()) {
Some(n) => n,
None => return,
@@ -102,86 +106,58 @@ fn stream(node: &serde_json::Value, parent: &str, map: &mut HashMap<String, Vec<
format!("{}/{}", parent, name)
};
let is_dir = children.map_or(false, |c| !c.is_empty());
let is_dir = children.as_ref().map(|c| !c.is_empty()).unwrap_or(false);
map.entry(parent.to_string()).or_default().push(FileInfo {
batch.push(FileInfo {
name: name.to_string(),
path: path.clone(),
size,
is_dir,
});
if batch.len() >= 200 {
let _ = app.emit("scan-batch", batch.clone());
batch.clear();
}
if let Some(children) = children {
for c in children {
stream(c, &path, map);
stream(c, &path, batch, app);
}
}
if parent.is_empty() && !batch.is_empty() {
let _ = app.emit("scan-batch", batch.clone());
}
}
#[tauri::command]
fn get_files_in_dir(path: String, state: State<'_, AppState>) -> Vec<FileInfo> {
state
.files
.lock()
.unwrap()
.get(&path)
.cloned()
.unwrap_or_default()
}
fn remove_item(path: String) -> Result<(), String> {
let path = PathBuf::from(path);
#[tauri::command]
fn remove_item(path: String, state: State<'_, AppState>) -> Result<(), String> {
let path_buf = PathBuf::from(&path);
if !path_buf.exists() {
if !path.exists() {
return Err("path does not exist".to_string());
}
if path_buf.is_dir() {
fs::remove_dir_all(&path_buf).map_err(|e| e.to_string())?;
if path.is_dir() {
fs::remove_dir_all(&path).map_err(|e| e.to_string())?;
} else {
fs::remove_file(&path_buf).map_err(|e| e.to_string())?;
}
let parent = path_buf
.parent()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_default();
if let Some(files) = state.files.lock().unwrap().get_mut(&parent) {
files.retain(|f| f.path != path);
fs::remove_file(&path).map_err(|e| e.to_string())?;
}
Ok(())
}
#[tauri::command]
fn reset_scan(state: State<'_, AppState>) {
state.files.lock().unwrap().clear();
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
if let Some(window) = app.get_webview_window("main") {
let _ = window.set_focus();
}
}))
.plugin(tauri_plugin_window_state::Builder::new().build())
.plugin(tauri_plugin_prevent_default::init())
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_shell::init())
.manage(AppState {
files: Mutex::new(HashMap::new()),
})
.invoke_handler(tauri::generate_handler![
list_disks,
get_file_info_from_dirs,
get_files_in_dir,
remove_item,
reset_scan
remove_item
])
.setup(|app| {
#[allow(unused)]
@@ -215,7 +191,6 @@ pub fn run() {
)
.title("DiskSense")
.inner_size(800.0, 600.0)
.min_inner_size(600.0, 400.0)
.transparent(transparency)
.visible(false)
.build()?;
+34 -54
View File
@@ -12,7 +12,8 @@ import {
import { v5 as uuidv5 } from 'uuid'
import prettyBytes from 'pretty-bytes'
import { ask, message, open } from '@tauri-apps/plugin-dialog'
import { openPath, revealItemInDir } from '@tauri-apps/plugin-opener'
import { listen } from '@tauri-apps/api/event'
import { revealItemInDir } from '@tauri-apps/plugin-opener'
interface Disk {
name: string
@@ -43,23 +44,16 @@ export default function App () {
})()
}, [])
useEffect(() => {
;(async () => {
if (currentPath && !scanning) {
const result = await invoke<FileInfo[]>('get_files_in_dir', {
path: currentPath
})
setFiles(result)
}
})()
}, [currentPath, scanning])
const handleScan = async (path: string) => {
setRootPath(path)
setCurrentPath(path)
setFiles([])
setScanning(true)
const unlisten = await listen<FileInfo[]>('scan-batch', e => {
setFiles(prev => [...prev, ...e.payload])
})
try {
await invoke('get_file_info_from_dirs', {
path,
@@ -71,6 +65,7 @@ export default function App () {
}
setScanning(false)
unlisten()
}
const breadcrumbs = useMemo(() => {
@@ -83,13 +78,13 @@ export default function App () {
]
}, [rootPath, currentPath])
const goBackToDisks = async () => {
setScanning(false)
setRootPath(null)
setCurrentPath('')
setFiles([])
await invoke('reset_scan')
}
const visibleFiles = useMemo(() => {
if (!rootPath) return []
return files.filter(f => {
const parent = f.path.split('/').slice(0, -1).join('/')
return parent === currentPath
})
}, [files, currentPath, rootPath])
return !rootPath ? (
<>
@@ -109,10 +104,9 @@ export default function App () {
className='icon-large -ml-2'
/>
<div className='flex flex-col -ml-2 w-full'>
<p hidden={d.name.length == 0}>
<p>
{d.name} <span className='text-sm'>({d.mount_point})</span>
</p>
<p hidden={d.name.length != 0}>{d.mount_point}</p>
<p>{prettyBytes(d.total_space)} total</p>
<div className='w-full h-2 bg-(--col6) rounded overflow-hidden my-1'>
<div
@@ -160,64 +154,48 @@ export default function App () {
</p>
) : (
<div className='p-3'>
<div className='flex flex-wrap gap-1 mb-3 relative'>
<div className='flex flex-nowrap gap-1 overflow-x-auto mr-14'>
{breadcrumbs.map((p, i) => (
<button
key={p + i}
className='cursor-pointer bg-(--col2)/50 hover:bg-(--col4)/50 border border-(--col4)/50 hover:border-(--col6)/50 px-2 py-1 rounded-lg transition-colors text-white'
onClick={() => setCurrentPath(p)}
>
{i == 0 ? p : p.split('/').pop()}
</button>
))}
</div>
<button
className='cursor-pointer bg-(--col2)/50 hover:bg-(--col4)/50 border border-(--col4)/50 hover:border-(--col6)/50 px-2 py-1 rounded-lg transition-colors text-white absolute right-0'
onClick={() => goBackToDisks()}
>
Back
</button>
<div className='flex flex-wrap gap-1 mb-3'>
{breadcrumbs.map((p, i) => (
<button
key={p + i}
className='cursor-pointer bg-(--col2)/50 hover:bg-(--col4)/50 border border-(--col4)/50 hover:border-(--col6)/50 px-2 py-1 rounded-lg transition-colors text-white/60 hover:text-white/75'
onClick={() => setCurrentPath(p)}
>
{i == 0 ? p : p.split('/').pop()}
</button>
))}
</div>
<div className='flex flex-col gap-1'>
{files.map(f => (
{visibleFiles.map(f => (
<div
key={f.path}
className='flex justify-between px-2 py-1 cursor-pointer bg-(--col2)/50 hover:bg-(--col4)/50 border border-(--col4)/50 hover:border-(--col6)/50 transition-colors rounded-lg'
onClick={() => {
if (f.is_dir) setCurrentPath(f.path)
else openPath(f.path)
}}
title={f.is_dir ? undefined : 'Click to open the file'}
>
<p>
<FontAwesomeIcon
icon={f.is_dir ? faFolder : faFile}
className='text-white transition-all hover:scale-110'
className='text-white/60 hover:text-white/75 transition-all hover:scale-110'
/>{' '}
{f.name}
</p>
<div className='flex flex-row gap-2 items-center'>
<div
className='w-4 h-6 flex justify-center items-center'
title={'Click to show the ' + f.is_dir ? 'folder' : 'file'}
>
<div className='w-4 h-6 flex justify-center items-center'>
<FontAwesomeIcon
icon={faFolderOpen}
className='text-white transition-all hover:scale-110'
className='text-white/60 hover:text-white/75 transition-all hover:scale-110'
onClick={async e => {
e.stopPropagation()
revealItemInDir(f.path)
}}
/>
</div>
<div
className='w-4 h-6 flex justify-center items-center'
title={'Click to delete the ' + f.is_dir ? 'folder' : 'file'}
>
<div className='w-4 h-6 flex justify-center items-center'>
<FontAwesomeIcon
icon={faTrashCan}
className='text-red-500 transition-all hover:scale-110'
className='text-red-500/60 hover:text-red-500/75 transition-all hover:scale-110'
onClick={async e => {
e.stopPropagation()
@@ -237,7 +215,9 @@ export default function App () {
if (!response) return
await invoke('remove_item', { path: f.path })
setFiles(prev => prev.filter(x => x.path !== f.path))
setFiles(prev =>
prev.filter(x => !x.path.startsWith(f.path))
)
}}
/>
</div>