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}