A photo manager for VRChat.

AAAAAAA

Changed files
+60 -36
src
src-tauri
src
+4 -1
changelog
··· 64 64 - Fixed world cache not being saved to the config file 65 65 66 66 v0.2.3: 67 - - Finally replaced the awful render function in the frontend 67 + - Finally replaced the awful render function in the frontend, ( should use less resources when app is open ) 68 + - Fixed photos not being lined up 69 + - Fixed filters not updating photo list 70 + - Fixed adding / removing photos not updating the photo list 68 71 69 72 Dev Stuff: 70 73 - Split frontend up into many smaller files for easier editing
+10 -9
src-tauri/src/main.rs
··· 12 12 use notify::{EventKind, RecursiveMode, Watcher}; 13 13 use pngmeta::PNGImage; 14 14 use regex::Regex; 15 - use util::cache::Cache; 15 + use util::{cache::Cache, get_photo_path::get_photo_path}; 16 16 use std::{env, fs, thread}; 17 17 use tauri::{Emitter, Manager, State, WindowEvent}; 18 18 use tauri_plugin_deep_link::DeepLinkExt; ··· 92 92 // Listen for file updates, store each update in an mpsc channel and send to the frontend 93 93 let (sender, receiver) = std::sync::mpsc::channel(); 94 94 let mut watcher = notify::recommended_watcher(move | res: Result<notify::Event, notify::Error> | { 95 - // TODO: Fix this, why does it not work?? it does work??? 96 95 match res { 97 - Ok(event) => { 96 + Ok(event) => { 98 97 match event.kind{ 99 98 EventKind::Remove(_) => { 100 99 let path = event.paths.first().unwrap(); 100 + let name = path.file_name().unwrap().to_str().unwrap().to_owned(); 101 101 102 102 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(); 103 103 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(); 104 104 105 105 if 106 - re1.is_match(path.to_str().unwrap()) || 107 - re2.is_match(path.to_str().unwrap()) 106 + re1.is_match(&name) || 107 + re2.is_match(&name) 108 108 { 109 - sender.send((2, path.clone().strip_prefix(util::get_photo_path::get_photo_path()).unwrap().to_path_buf())).unwrap(); 109 + sender.send((2, path.strip_prefix(get_photo_path()).unwrap().to_str().unwrap().to_owned())).unwrap(); 110 110 } 111 111 }, 112 112 EventKind::Create(_) => { 113 113 let path = event.paths.first().unwrap(); 114 + let name = path.file_name().unwrap().to_str().unwrap().to_owned(); 114 115 115 116 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(); 116 117 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(); 117 118 118 119 if 119 - re1.is_match(path.to_str().unwrap()) || 120 - re2.is_match(path.to_str().unwrap()) 120 + re1.is_match(&name) || 121 + re2.is_match(&name) 121 122 { 122 123 thread::sleep(time::Duration::from_millis(1000)); 123 - sender.send((1, path.clone().strip_prefix(util::get_photo_path::get_photo_path()).unwrap().to_path_buf())).unwrap(); 124 + sender.send((1, path.strip_prefix(get_photo_path()).unwrap().to_str().unwrap().to_owned())).unwrap(); 124 125 } 125 126 }, 126 127 _ => {}
+19 -8
src/Components/Managers/PhotoListRenderingManager.tsx
··· 7 7 8 8 export class PhotoListRenderingManager{ 9 9 private _layout: PhotoListRow[] = []; 10 + private _canvas!: HTMLCanvasElement; 10 11 11 12 constructor(){} 12 13 13 - public ComputeLayout( canvas: HTMLCanvasElement ){ 14 + public SetCanvas( canvas: HTMLCanvasElement ){ 15 + this._canvas = canvas; 16 + } 17 + 18 + public ComputeLayout(){ 19 + this._layout = []; 20 + 14 21 let lastDateString = null; 15 22 let row = new PhotoListRow(); 16 23 row.Height = 100; ··· 36 43 37 44 // Check if the current row width plus another photo is too big to fit, push this row to the 38 45 // layout and add the photo to the next row instead 39 - if(row.Width + photo.scaledWidth! + 10 > canvas.width - 100){ 46 + if(row.Width + photo.scaledWidth! + 10 > this._canvas.width - 100){ 40 47 this._layout.push(row); 41 48 row = new PhotoListRow(); 42 49 } 43 50 44 51 // We should now add this photo to the current row 45 52 row.Elements.push(new PhotoListPhoto(photo)); 46 - row.Width += photo.scaledWidth!; 53 + row.Width += photo.scaledWidth! + 10; 47 54 } 48 55 49 56 this._layout.push(row); ··· 81 88 currentY += row.Height + 10; 82 89 continue; 83 90 } 91 + 92 + // === DEBUG === 93 + // ctx.strokeStyle = '#f00'; 94 + // ctx.strokeRect((canvas.width / 2) - row.Width / 2, currentY - 5 - scroll, row.Width, row.Height + 10); 84 95 85 96 // Loop through all elements in the row 86 - let rowXPos = 0; 97 + let rowXPos = 10; 87 98 for (let j = 0; j < row.Elements.length; j++) { 88 99 let el = row.Elements[j]; 89 100 ··· 93 104 // and then render that text 94 105 95 106 // === DEBUG === 96 - ctx.strokeStyle = '#f00'; 97 - ctx.strokeRect(0, currentY - scroll, canvas.width, row.Height); 107 + // ctx.strokeStyle = '#f00'; 108 + // ctx.strokeRect(0, currentY - scroll, canvas.width, row.Height); 98 109 99 110 ctx.textAlign = 'center'; 100 111 ctx.textBaseline = 'middle'; ··· 108 119 let photo = (el as PhotoListPhoto).Photo; 109 120 110 121 // === DEBUG === 111 - ctx.strokeStyle = '#f00'; 112 - ctx.strokeRect((rowXPos - row.Width / 2) + canvas.width / 2, currentY - scroll, photo.scaledWidth!, row.Height); 122 + // ctx.strokeStyle = '#f00'; 123 + // ctx.strokeRect((rowXPos - row.Width / 2) + canvas.width / 2, currentY - scroll, photo.scaledWidth!, row.Height); 113 124 114 125 if(!photo.loaded) 115 126 // If the photo is not loaded, start a new task and load it in that task
+21 -14
src/Components/Managers/PhotoManager.tsx
··· 69 69 let data: PhotoMetadata = event.payload; 70 70 71 71 let photo = this.Photos.find(x => x.path === data.path); 72 - if(!photo)return; 72 + if(!photo)return console.error('Cannot find photo.', data); 73 73 74 74 photo.width = data.width; 75 75 photo.height = data.height; ··· 84 84 85 85 photo.metaLoaded = true; 86 86 photo.onMetaLoaded(); 87 - 88 - // this.ReloadFilters(); 89 - 90 - console.log(this._amountLoaded, this.Photos.length); 91 - if(this._amountLoaded === this.Photos.length && !this.HasFirstLoaded){ 87 + 88 + if(this._amountLoaded === this.Photos.length - 1 && !this.HasFirstLoaded){ 92 89 this.FilteredPhotos = this.Photos; 93 90 this.HasFirstLoaded = true; 94 91 ··· 100 97 let photo = new Photo(event.payload); 101 98 102 99 this.Photos.splice(0, 0, photo); 100 + 101 + photo.onMetaLoaded = () => this.ReloadFilters(); 103 102 photo.loadMeta(); 104 103 105 104 if(!window.SyncManager.IsSyncing() && window.AccountManager.Storage()?.isSyncing){ ··· 113 112 114 113 if(event.payload === window.PhotoViewerManager.CurrentPhoto()?.path) 115 114 window.PhotoViewerManager.Close() 115 + 116 + this.ReloadFilters(); 116 117 }) 117 118 } 118 119 ··· 133 134 case FilterType.USER: 134 135 this.Photos.map(p => { 135 136 if(p.metadata){ 136 - let meta = JSON.parse(p.metadata); 137 - let photo = meta.players.find(( y: any ) => y.displayName.toLowerCase().includes(this._filter) || y.id === this._filter); 138 - 139 - if(photo)this.FilteredPhotos.push(p); 137 + try{ 138 + let meta = JSON.parse(p.metadata); 139 + let photo = meta.players.find(( y: any ) => y.displayName.toLowerCase().includes(this._filter) || y.id === this._filter); 140 + 141 + if(photo)this.FilteredPhotos.push(p); 142 + } catch(e){} 140 143 } 141 144 }) 142 145 break; 143 146 case FilterType.WORLD: 144 147 this.Photos.map(p => { 145 148 if(p.metadata){ 146 - let meta = JSON.parse(p.metadata); 147 - let photo = meta.world.name.toLowerCase().includes(this._filter) || meta.world.id === this._filter; 148 - 149 - if(photo)this.FilteredPhotos.push(p); 149 + try{ 150 + let meta = JSON.parse(p.metadata); 151 + let photo = meta.world.name.toLowerCase().includes(this._filter) || meta.world.id === this._filter; 152 + 153 + if(photo)this.FilteredPhotos.push(p); 154 + } catch(e){} 150 155 } 151 156 }) 152 157 break; 153 158 } 159 + 160 + window.PhotoListRenderingManager.ComputeLayout(); 154 161 } 155 162 156 163 public Load(){
+4 -2
src/Components/PhotoList.tsx
··· 129 129 easing: 'easeInOutQuad' 130 130 }) 131 131 132 - window.PhotoListRenderingManager.ComputeLayout(photoContainer!); 132 + window.PhotoListRenderingManager.SetCanvas(photoContainer!); 133 + window.PhotoListRenderingManager.ComputeLayout(); 134 + 133 135 render(); 134 136 }); 135 137 ··· 163 165 photoContainerBG.width = window.innerWidth; 164 166 photoContainerBG.height = window.innerHeight; 165 167 166 - window.PhotoListRenderingManager.ComputeLayout(photoContainer!); 168 + window.PhotoListRenderingManager.ComputeLayout(); 167 169 }) 168 170 169 171 photoContainer.addEventListener('click', ( e: MouseEvent ) => {
+1 -1
src/Components/Structs/Photo.ts
··· 55 55 loadImage(){ 56 56 if(this.loading || this.loaded || imagesLoading >= Vars.MAX_IMAGE_LOAD)return; 57 57 58 - this.loadMeta(); 58 + // this.loadMeta(); 59 59 if(!this.metaLoaded)return; 60 60 61 61 this.loading = true;
+1 -1
src/Components/Structs/PhotoListRow.ts
··· 4 4 export class PhotoListRow{ 5 5 public Elements: PhotoListElement[] = []; 6 6 public Height: number = Vars.PHOTO_HEIGHT; 7 - public Width: number = 0; 7 + public Width: number = 10; 8 8 }