A photo manager for VRChat.

add more shit

Changed files
+66 -9
src
src-tauri
src
+15 -3
src-tauri/src/main.rs
··· 43 43 44 44 // Scans all files under the "Pictures/VRChat" path 45 45 // then sends the list of photos to the frontend 46 + #[derive(Clone, serde::Serialize)] 47 + struct PhotosLoadedResponse{ 48 + photos: Vec<path::PathBuf>, 49 + size: usize 50 + } 51 + 46 52 #[tauri::command] 47 53 fn load_photos(window: tauri::Window) { 48 54 thread::spawn(move || { 49 55 let base_dir = dirs::home_dir().unwrap().join("Pictures\\VRChat"); 50 56 51 57 let mut photos: Vec<path::PathBuf> = Vec::new(); 58 + let mut size: usize = 0; 52 59 53 60 for folder in fs::read_dir(base_dir).unwrap() { 54 61 let f = folder.unwrap(); ··· 69 76 re2.is_match(p.file_name().to_str().unwrap()) 70 77 { 71 78 let path = fname.to_path_buf().clone(); 72 - let path = path.strip_prefix(dirs::home_dir().unwrap().join("Pictures\\VRChat")).unwrap().to_path_buf(); 79 + let metadata = fs::metadata(&path).unwrap(); 73 80 74 - photos.push(path); 81 + if metadata.is_file() { 82 + size += metadata.len() as usize; 83 + 84 + let path = path.strip_prefix(dirs::home_dir().unwrap().join("Pictures\\VRChat")).unwrap().to_path_buf(); 85 + photos.push(path); 86 + } 75 87 } 76 88 } 77 89 } 78 90 } 79 91 } 80 92 81 - window.emit("photos_loaded", photos).unwrap(); 93 + window.emit("photos_loaded", PhotosLoadedResponse{ photos, size }).unwrap(); 82 94 }); 83 95 } 84 96
+20 -3
src/Components/App.tsx
··· 20 20 let [ confirmationBoxText, setConfirmationBoxText ] = createSignal<string>(''); 21 21 let confirmationBoxCallback = () => {} 22 22 23 + let [ photoCount, setPhotoCount ] = createSignal(0); 24 + let [ photoSize, setPhotoSize ] = createSignal(0); 25 + 23 26 let setConfirmationBox = ( text: string, cb: () => void ) => { 24 27 setConfirmationBoxText(text); 25 28 confirmationBoxCallback = cb; ··· 138 141 return ( 139 142 <div class="container"> 140 143 <NavBar setLoadingType={setLoadingType} loggedIn={loggedIn} /> 141 - <PhotoList setCurrentPhotoView={setCurrentPhotoView} currentPhotoView={currentPhotoView} photoNavChoice={photoNavChoice} setPhotoNavChoice={setPhotoNavChoice} setConfirmationBox={setConfirmationBox} /> 142 - <PhotoViewer setPhotoNavChoice={setPhotoNavChoice} currentPhotoView={currentPhotoView} setCurrentPhotoView={setCurrentPhotoView} setConfirmationBox={setConfirmationBox} /> 144 + <PhotoList 145 + setCurrentPhotoView={setCurrentPhotoView} 146 + currentPhotoView={currentPhotoView} 147 + photoNavChoice={photoNavChoice} 148 + setPhotoNavChoice={setPhotoNavChoice} 149 + setConfirmationBox={setConfirmationBox} 150 + setPhotoCount={setPhotoCount} 151 + setPhotoSize={setPhotoSize} /> 143 152 144 - <SettingsMenu /> 153 + <PhotoViewer 154 + setPhotoNavChoice={setPhotoNavChoice} 155 + currentPhotoView={currentPhotoView} 156 + setCurrentPhotoView={setCurrentPhotoView} 157 + setConfirmationBox={setConfirmationBox} /> 158 + 159 + <SettingsMenu 160 + photoCount={photoCount} 161 + photoSize={photoSize} /> 145 162 146 163 <div class="copy-notif">Image Copied!</div> 147 164
+6 -1
src/Components/PhotoList.tsx
··· 11 11 12 12 class PhotoListProps{ 13 13 setCurrentPhotoView!: ( view: any ) => any; 14 + setPhotoCount!: ( value: any ) => any; 15 + setPhotoSize!: ( value: any ) => any; 14 16 currentPhotoView!: () => any; 15 17 photoNavChoice!: () => string; 16 18 setPhotoNavChoice!: ( view: any ) => any; ··· 317 319 invoke('load_photos') 318 320 319 321 listen('photos_loaded', ( event: any ) => { 320 - let photoPaths = event.payload.reverse(); 322 + let photoPaths = event.payload.photos.reverse(); 323 + 324 + props.setPhotoCount(photoPaths.length); 325 + props.setPhotoSize(event.payload.size); 321 326 322 327 photoPaths.forEach(( path: string ) => { 323 328 let photo = new Photo(path);
+10 -2
src/Components/SettingsMenu.tsx
··· 1 1 import { onMount } from "solid-js"; 2 + import { bytesToFormatted } from "../utils"; 2 3 import anime from "animejs"; 3 4 4 - let SettingsMenu = () => { 5 + class SettingsMenuProps{ 6 + photoCount!: () => number; 7 + photoSize!: () => number; 8 + } 9 + 10 + let SettingsMenu = ( props: SettingsMenuProps ) => { 5 11 let sliderBar: HTMLElement; 6 12 let settingsContainer: HTMLElement; 7 13 let currentButton = 0; ··· 130 136 <div class="settings"> 131 137 <div class="settings-container" ref={( el ) => settingsContainer = el}> 132 138 <div class="settings-block"> 133 - <h1>Program Settings</h1> 139 + <h1>Storage Settings</h1> 140 + 141 + <p>{ props.photoCount() } Photos ({ bytesToFormatted(props.photoSize(), 0) })</p> 134 142 </div> 135 143 <div class="settings-block"> 136 144 <h1>Account Settings</h1>
+15
src/utils.ts
··· 1 + let fileSize = [ 'B', 'KB', 'MB', 'GB', 'TB', 'PB' ]; 2 + 3 + let bytesToFormatted = ( bytes: number, stage: number ): string => { 4 + if(bytes >= 1000){ 5 + bytes = bytes / 1000; 6 + 7 + if(fileSize[stage + 1]) 8 + return bytesToFormatted(bytes, stage + 1); 9 + else 10 + return bytes.toFixed(2) + fileSize[stage]; 11 + } else 12 + return bytes.toFixed(2) + fileSize[stage]; 13 + } 14 + 15 + export { bytesToFormatted };