A photo manager for VRChat.

Fixed photo ordering & Fixed automatic updates

phaz.uk d38313ef 7818bec9

verified
Changed files
+40 -20
src
Components
src-tauri
src
frontend_calls
+3 -1
changelog
··· 109 109 - Fixed "Open in folder" not selecting files on linux 110 110 - Remove all sync stuff 111 111 - Fixed scroll to top button not animating out 112 - - Fixed scroll to top button being ontop of filters menu 112 + - Fixed scroll to top button being ontop of filters menu 113 + - Fixed photo ordering 114 + - Fixed automatic updates
+3 -1
src-tauri/src/frontend_calls/load_photos.rs
··· 36 36 let name = name.to_str().unwrap(); 37 37 38 38 let re1_match = // This is the current format used by VRChat 39 - Regex::new(r"(?m)VRChat_[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}.[0-9]{3}_[0-9]{3,4}x[0-9]{3,4}.png").unwrap().is_match(name); 39 + Regex::new(r"(?m)VRChat_[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}.[0-9]{3}_[0-9]{3,4}x[0-9]{3,4}.png").unwrap().is_match(name) || 40 + Regex::new(r"(?m)VRChat_[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}.[0-9]{3}_[0-9]{3,4}x[0-9]{3,4}_Player.png").unwrap().is_match(name) || 41 + Regex::new(r"(?m)VRChat_[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}.[0-9]{3}_[0-9]{3,4}x[0-9]{3,4}_Environment.png").unwrap().is_match(name); 40 42 41 43 let re2_match = // This is the format VRCX uses if you enable renaming photos 42 44 Regex::new(r"(?m)VRChat_[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}.[0-9]{3}_[0-9]{3,4}x[0-9]{3,4}_wrld_[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}.png").unwrap().is_match(name);
+8 -6
src-tauri/src/main.rs
··· 98 98 let path = event.paths.first().unwrap(); 99 99 let name = path.file_name().unwrap().to_str().unwrap().to_owned(); 100 100 101 - let re1 = Regex::new(r"(?m)VRChat_[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}.[0-9]{3}_[0-9]{4}x[0-9]{4}.png").unwrap(); 102 - let re2 = Regex::new(r"(?m)/VRChat_[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}.[0-9]{3}_[0-9]{4}x[0-9]{4}_wrld_[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}.png/gm").unwrap(); 101 + let re1_match = // This is the current format used by VRChat 102 + Regex::new(r"(?m)VRChat_[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}.[0-9]{3}_[0-9]{3,4}x[0-9]{3,4}.png").unwrap().is_match(&name) || 103 + Regex::new(r"(?m)VRChat_[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}.[0-9]{3}_[0-9]{3,4}x[0-9]{3,4}_Player.png").unwrap().is_match(&name) || 104 + Regex::new(r"(?m)VRChat_[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}.[0-9]{3}_[0-9]{3,4}x[0-9]{3,4}_Environment.png").unwrap().is_match(&name); 103 105 104 - if 105 - re1.is_match(&name) || 106 - re2.is_match(&name) 107 - { 106 + let re2_match = // This is the format VRCX uses if you enable renaming photos 107 + Regex::new(r"(?m)VRChat_[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}.[0-9]{3}_[0-9]{3,4}x[0-9]{3,4}_wrld_[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}.png").unwrap().is_match(&name); 108 + 109 + if re1_match || re2_match{ 108 110 sender.send((2, path.strip_prefix(get_photo_path()).unwrap().to_str().unwrap().to_owned())).unwrap(); 109 111 } 110 112 },
+10 -11
src/Components/Managers/PhotoManager.tsx
··· 61 61 }) 62 62 63 63 this.Photos = MergeSort(this.Photos); 64 + console.log(this.Photos); 64 65 65 66 console.log(this.Photos.length + ' Photos found.'); 66 67 if(this.Photos.length === 0 || photoPaths.length > Vars.MAX_PHOTOS_BULK_LOAD){ ··· 116 117 117 118 photo.onMetaLoaded = () => this.ReloadFilters(); 118 119 photo.loadMeta(); 119 - 120 - if(!window.SyncManager.IsSyncing()){ 121 - window.SyncManager.TriggerSync(); 122 - } 123 120 }) 124 - 121 + 125 122 listen('photo_remove', ( event: any ) => { 126 123 this.Photos = this.Photos.filter(x => x.path !== event.payload); 127 - this.FilteredPhotos = this.FilteredPhotos.filter(x => x.path !== event.payload); 128 - 124 + 129 125 if(event.payload === window.PhotoViewerManager.CurrentPhoto()?.path) 130 126 window.PhotoViewerManager.Close() 131 127 ··· 168 164 public ReloadFilters(){ 169 165 this.FilteredPhotos = []; 170 166 167 + if(this._filter === ''){ 168 + this.FilteredPhotos = this.Photos; 169 + window.PhotoListRenderingManager.ComputeLayout(); 170 + 171 + return; 172 + } 173 + 171 174 switch(this._filterType){ 172 175 case FilterType.USER: 173 - if(this._filter === '')return this.FilteredPhotos = this.Photos; 174 - 175 176 this.Photos.map(p => { 176 177 if(p.metadata){ 177 178 try{ ··· 187 188 }) 188 189 break; 189 190 case FilterType.WORLD: 190 - if(this._filter === '')return this.FilteredPhotos = this.Photos; 191 - 192 191 this.Photos.map(p => { 193 192 if(p.metadata){ 194 193 try{
+5 -1
src/Components/PhotoViewer.tsx
··· 272 272 let id = doc.getElementsByTagName('xmp:Author')[0]!.innerHTML; 273 273 274 274 authorProfileButton!.style.display = 'flex'; 275 - authorProfileButton!.onclick = () => 275 + authorProfileButton!.onclick = () => { 276 + console.log(id); 276 277 invoke('open_url', { url: 'https://vrchat.com/home/user/' + id }); 278 + } 277 279 } catch(e){ 278 280 console.error(e); 279 281 console.log('Couldn\'t decode metadata') ··· 286 288 } 287 289 } else{ 288 290 trayButton.style.display = 'none'; 291 + authorProfileButton!.style.display = 'none'; 292 + 289 293 closeTray(); 290 294 } 291 295 }
+11
src/Components/Structs/Photo.ts
··· 43 43 else 44 44 this.dateString = split[1]; 45 45 46 + let timeString; 47 + if(this.legacy) 48 + timeString = split[3]; 49 + else 50 + timeString = split[2]; 51 + 46 52 let splitDateString = this.dateString.split('-'); 53 + let splitTimeString = timeString.split('-'); 47 54 48 55 this.date = new Date(); 49 56 50 57 this.date.setFullYear(parseInt(splitDateString[0])); 51 58 this.date.setMonth(parseInt(splitDateString[1])); 52 59 this.date.setDate(parseInt(splitDateString[2])); 60 + 61 + this.date.setHours(parseInt(splitTimeString[0])); 62 + this.date.setMinutes(parseInt(splitTimeString[1])); 63 + this.date.setSeconds(parseInt(splitTimeString[2])); 53 64 54 65 let resSplit = split[3].split('x'); 55 66