A photo manager for VRChat.
1import { PhotoListPhoto } from "../Structs/PhotoListElements/PhotoListPhoto"; 2import { PhotoListText } from "../Structs/PhotoListElements/PhotoListText"; 3import { PhotoListElementType } from "../Structs/PhotoListElementType"; 4import { PhotoListRow } from "../Structs/PhotoListRow"; 5 6const MONTHS = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ]; 7 8export class PhotoListRenderingManager{ 9 private _layout: PhotoListRow[] = []; 10 private _canvas!: HTMLCanvasElement; 11 12 private _isLoading = false; 13 14 constructor(){} 15 16 public SetCanvas( canvas: HTMLCanvasElement ){ 17 this._canvas = canvas; 18 } 19 20 public ComputeLayout(){ 21 this._layout = []; 22 23 let lastDateString = null; 24 let row = new PhotoListRow(); 25 row.Height = 0; 26 27 for (let i = 0; i < window.PhotoManager.FilteredPhotos.length; i++) { 28 let photo = window.PhotoManager.FilteredPhotos[i]; 29 30 // If date string has changed since the last photo, we should label the correct date above it 31 if(lastDateString !== photo.dateString){ 32 this._layout.push(row); 33 row = new PhotoListRow(); 34 35 row.Height = 50; 36 37 let dateParts = photo.dateString.split('-'); 38 lastDateString = photo.dateString; 39 40 row.Elements = [ new PhotoListText(dateParts[2] + ' ' + MONTHS[parseInt(dateParts[1]) - 1] + ' ' + dateParts[0]) ]; 41 42 this._layout.push(row); 43 row = new PhotoListRow(); 44 } 45 46 // Check if the current row width plus another photo is too big to fit, push this row to the 47 // layout and add the photo to the next row instead 48 if(row.Width + photo.scaledWidth! + 10 > this._canvas.width - 100){ 49 this._layout.push(row); 50 row = new PhotoListRow(); 51 } 52 53 // We should now add this photo to the current row 54 row.Elements.push(new PhotoListPhoto(photo)); 55 row.Width += photo.scaledWidth! + 10; 56 } 57 58 this._layout.push(row); 59 } 60 61 public Render( ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, scroll: number ){ 62 let currentY = 0; 63 64 // Loop through each row 65 for (let i = 0; i < this._layout.length; i++) { 66 let row = this._layout[i]; 67 68 // Cull rows that are out of frame 69 if(currentY - scroll > canvas.height){ 70 // Reset frames for out of frame rows so they fade back in 71 row.Elements.forEach(el => { 72 if(el.Type === PhotoListElementType.PHOTO){ 73 (el as PhotoListPhoto).Photo.frames = 0; 74 (el as PhotoListPhoto).Photo.shown = false; 75 } 76 }); 77 78 return; 79 } 80 81 if(currentY - scroll < -row.Height){ 82 // Reset frames for out of frame rows so they fade back in 83 row.Elements.forEach(el => { 84 if(el.Type === PhotoListElementType.PHOTO){ 85 (el as PhotoListPhoto).Photo.frames = 0; 86 (el as PhotoListPhoto).Photo.shown = false; 87 } 88 }); 89 90 currentY += row.Height + 10; 91 continue; 92 } 93 94 // === DEBUG === 95 // ctx.strokeStyle = '#f00'; 96 // ctx.strokeRect((canvas.width / 2) - row.Width / 2, currentY - 5 - scroll, row.Width, row.Height + 10); 97 98 // Loop through all elements in the row 99 let rowXPos = 10; 100 for (let j = 0; j < row.Elements.length; j++) { 101 let el = row.Elements[j]; 102 103 switch(el.Type){ 104 case PhotoListElementType.TEXT: 105 // If it is a text element we should centre the text in the middle of the canvas 106 // and then render that text 107 108 // === DEBUG === 109 // ctx.strokeStyle = '#f00'; 110 // ctx.strokeRect(0, currentY - scroll, canvas.width, row.Height); 111 112 ctx.textAlign = 'center'; 113 ctx.textBaseline = 'middle'; 114 ctx.globalAlpha = 1; 115 ctx.fillStyle = '#fff'; 116 ctx.font = '30px Rubik'; 117 118 ctx.fillText((el as PhotoListText).Text, canvas.width / 2, currentY - scroll + 25); 119 break; 120 case PhotoListElementType.PHOTO: 121 let photo = (el as PhotoListPhoto).Photo; 122 123 // === DEBUG === 124 // ctx.strokeStyle = '#f00'; 125 // ctx.strokeRect((rowXPos - row.Width / 2) + canvas.width / 2, currentY - scroll, photo.scaledWidth!, row.Height); 126 127 if(!photo.loaded) 128 // If the photo is not loaded, start a new task and load it in that task 129 setTimeout(() => photo.loadImage(), 1); 130 else{ 131 photo.shown = true; 132 133 photo.x = (rowXPos - row.Width / 2) + canvas.width / 2; 134 photo.y = currentY - scroll; 135 136 // Photo is already loaded so we should draw it on the application 137 ctx.globalAlpha = photo.frames / 100; 138 ctx.drawImage(photo.image!, (rowXPos - row.Width / 2) + canvas.width / 2, currentY - scroll, photo.scaledWidth!, photo.scaledHeight!); 139 140 if(photo.frames < 100) 141 photo.frames += 10; 142 } 143 144 rowXPos += photo.scaledWidth! + 10; 145 break; 146 } 147 } 148 149 currentY += row.Height + 10; 150 } 151 152 if(!this._isLoading){ 153 console.log('Loading more photos...'); 154 this._isLoading = true; 155 156 window.PhotoManager.LoadSomeAndReloadFilters() 157 .then(() => { 158 this._isLoading = false; 159 }); 160 } 161 } 162}