blob: 64b0680c354fb166bd17b40a08c143aedff50df0 [file] [log] [blame]
Ishan Katpallydad5f1a2022-03-23 21:06:36 -07001import {Component, OnInit} from '@angular/core';
Filip Kujawac1ded372023-05-27 14:33:43 -07002import {Builder, ByteBuffer} from 'flatbuffers';
3import {ErrorResponse} from '../../webserver/requests/messages/error_response_generated';
Filip Kujawab5a16e32022-12-14 13:21:56 -08004import {
5 Ranking,
6 RequestAllDriverRankingsResponse,
Philipp Schraderd7efa2b2023-02-17 21:15:13 -08007} from '../../webserver/requests/messages/request_all_driver_rankings_response_generated';
Filip Kujawab5a16e32022-12-14 13:21:56 -08008import {
Emily Markova132e5be2023-03-25 13:43:05 -07009 Stats2023,
10 Request2023DataScoutingResponse,
11} from '../../webserver/requests/messages/request_2023_data_scouting_response_generated';
Emily Markova8e39f452023-12-23 12:17:30 -080012
13import {
14 PitImage,
15 RequestAllPitImagesResponse,
16} from '../../webserver/requests/messages/request_all_pit_images_response_generated';
17
Filip Kujawab5a16e32022-12-14 13:21:56 -080018import {
19 Note,
20 RequestAllNotesResponse,
Philipp Schraderd7efa2b2023-02-17 21:15:13 -080021} from '../../webserver/requests/messages/request_all_notes_response_generated';
Filip Kujawac1ded372023-05-27 14:33:43 -070022import {Delete2023DataScouting} from '../../webserver/requests/messages/delete_2023_data_scouting_generated';
23import {Delete2023DataScoutingResponse} from '../../webserver/requests/messages/delete_2023_data_scouting_response_generated';
Filip Kujawab5a16e32022-12-14 13:21:56 -080024
Philipp Schrader175a93c2023-02-19 13:13:40 -080025import {ViewDataRequestor} from '../rpc';
Filip Kujawab5a16e32022-12-14 13:21:56 -080026
Emily Markova8e39f452023-12-23 12:17:30 -080027type Source = 'Notes' | 'Stats2023' | 'PitImages' | 'DriverRanking';
Filip Kujawab5a16e32022-12-14 13:21:56 -080028
29//TODO(Filip): Deduplicate
30const COMP_LEVEL_LABELS = {
31 qm: 'Qualifications',
32 ef: 'Eighth Finals',
33 qf: 'Quarter Finals',
34 sf: 'Semi Finals',
35 f: 'Finals',
36};
Ishan Katpallydad5f1a2022-03-23 21:06:36 -070037
38@Component({
39 selector: 'app-view',
40 templateUrl: './view.ng.html',
Philipp Schrader175a93c2023-02-19 13:13:40 -080041 styleUrls: ['../app/common.css', './view.component.css'],
Ishan Katpallydad5f1a2022-03-23 21:06:36 -070042})
Filip Kujawab5a16e32022-12-14 13:21:56 -080043export class ViewComponent {
44 constructor(private readonly viewDataRequestor: ViewDataRequestor) {}
45
46 // Make COMP_LEVEL_LABELS available in view.ng.html.
47 readonly COMP_LEVEL_LABELS = COMP_LEVEL_LABELS;
48
49 // Progress and error messages to display to
50 // the user when fetching data.
51 progressMessage: string = '';
52 errorMessage: string = '';
53
54 // The current data source being displayed.
55 currentSource: Source = 'Notes';
56
57 // Current sort (ascending/descending match numbers).
58 // noteList is sorted based on team number until match
59 // number is added for note scouting.
60 ascendingSort = true;
61
62 // Stores the corresponding data.
63 noteList: Note[] = [];
64 driverRankingList: Ranking[] = [];
Emily Markova8e39f452023-12-23 12:17:30 -080065 pitImageList: PitImage[][] = [];
Emily Markova132e5be2023-03-25 13:43:05 -070066 statList: Stats2023[] = [];
Filip Kujawab5a16e32022-12-14 13:21:56 -080067
68 // Fetch notes on initialization.
69 ngOnInit() {
70 this.fetchCurrentSource();
71 }
72
73 // Called when a user changes the sort direction.
74 // Changes the data order between ascending/descending.
75 sortData() {
76 this.ascendingSort = !this.ascendingSort;
77 if (!this.ascendingSort) {
78 this.driverRankingList.sort((a, b) => b.matchNumber() - a.matchNumber());
Emily Markovae68b7632023-12-30 14:17:55 -080079 this.noteList.sort(function (a, b) {
80 return b[0]
81 .team()
82 .localeCompare(a[0].team(), undefined, {numeric: true});
83 });
Emily Markova8e39f452023-12-23 12:17:30 -080084 this.pitImageList.sort(function (a, b) {
85 return b[0]
86 .teamNumber()
87 .localeCompare(a[0].teamNumber(), undefined, {numeric: true});
88 });
Emily Markova132e5be2023-03-25 13:43:05 -070089 this.statList.sort((a, b) => b.matchNumber() - a.matchNumber());
Filip Kujawab5a16e32022-12-14 13:21:56 -080090 } else {
91 this.driverRankingList.sort((a, b) => a.matchNumber() - b.matchNumber());
Emily Markovae68b7632023-12-30 14:17:55 -080092 this.noteList.sort(function (a, b) {
Emily Markova8e39f452023-12-23 12:17:30 -080093 return b[0]
Emily Markovae68b7632023-12-30 14:17:55 -080094 .team()
Emily Markova8e39f452023-12-23 12:17:30 -080095 .localeCompare(a[0].team(), undefined, {numeric: true});
96 });
97 this.pitImageList.sort(function (a, b) {
98 return a[0]
99 .teamNumber()
100 .localeCompare(b[0].teamNumber(), undefined, {numeric: true});
Emily Markovae68b7632023-12-30 14:17:55 -0800101 });
Emily Markova132e5be2023-03-25 13:43:05 -0700102 this.statList.sort((a, b) => a.matchNumber() - b.matchNumber());
Filip Kujawab5a16e32022-12-14 13:21:56 -0800103 }
104 }
105
106 // Called when a user selects a new data source
107 // from the dropdown.
108 switchDataSource(target: Source) {
109 this.currentSource = target;
110 this.progressMessage = '';
111 this.errorMessage = '';
112 this.noteList = [];
113 this.driverRankingList = [];
114 this.statList = [];
Emily Markova8e39f452023-12-23 12:17:30 -0800115 this.pitImageList = [];
Filip Kujawab5a16e32022-12-14 13:21:56 -0800116 this.fetchCurrentSource();
117 }
118
119 // Call the method to fetch data for the current source.
120 fetchCurrentSource() {
121 switch (this.currentSource) {
122 case 'Notes': {
123 this.fetchNotes();
124 }
125
Emily Markova132e5be2023-03-25 13:43:05 -0700126 case 'Stats2023': {
127 this.fetchStats2023();
Filip Kujawab5a16e32022-12-14 13:21:56 -0800128 }
129
Emily Markova8e39f452023-12-23 12:17:30 -0800130 case 'PitImages': {
131 this.fetchPitImages();
132 }
133
Filip Kujawab5a16e32022-12-14 13:21:56 -0800134 case 'DriverRanking': {
135 this.fetchDriverRanking();
136 }
137 }
138 }
139
140 // TODO(Filip): Add delete functionality.
Filip Kujawac1ded372023-05-27 14:33:43 -0700141 // Gets called when a user clicks the delete icon (note scouting).
142 async deleteNoteData() {
Filip Kujawab5a16e32022-12-14 13:21:56 -0800143 const block_alerts = document.getElementById(
144 'block_alerts'
145 ) as HTMLInputElement;
Filip Kujawac1ded372023-05-27 14:33:43 -0700146 if (block_alerts.checked || window.confirm('Actually delete data?')) {
147 this.errorMessage = 'Deleting data has not been implemented yet.';
148 return;
149 }
150 }
151
152 // TODO(Filip): Add delete functionality.
153 // Gets called when a user clicks the delete icon (driver ranking).
154 async deleteDriverRankingData() {
155 const block_alerts = document.getElementById(
156 'block_alerts'
157 ) as HTMLInputElement;
158 if (block_alerts.checked || window.confirm('Actually delete data?')) {
159 this.errorMessage = 'Deleting data has not been implemented yet.';
160 return;
161 }
162 }
163
164 // Gets called when a user clicks the delete icon.
165 async deleteDataScouting(
166 compLevel: string,
167 matchNumber: number,
168 setNumber: number,
169 teamNumber: string
170 ) {
171 const block_alerts = document.getElementById(
172 'block_alerts'
173 ) as HTMLInputElement;
174 if (block_alerts.checked || window.confirm('Actually delete data?')) {
175 await this.requestDeleteDataScouting(
176 compLevel,
177 matchNumber,
178 setNumber,
179 teamNumber
180 );
181 await this.fetchStats2023();
182 }
183 }
184
185 async requestDeleteDataScouting(
186 compLevel: string,
187 matchNumber: number,
188 setNumber: number,
189 teamNumber: string
190 ) {
191 this.progressMessage = 'Deleting data. Please be patient.';
192 const builder = new Builder();
193 const compLevelData = builder.createString(compLevel);
194 const teamNumberData = builder.createString(teamNumber);
195
196 builder.finish(
197 Delete2023DataScouting.createDelete2023DataScouting(
198 builder,
199 compLevelData,
200 matchNumber,
201 setNumber,
202 teamNumberData
203 )
204 );
205
206 const buffer = builder.asUint8Array();
207 const res = await fetch('/requests/delete/delete_2023_data_scouting', {
208 method: 'POST',
209 body: buffer,
210 });
211
212 if (!res.ok) {
213 const resBuffer = await res.arrayBuffer();
214 const fbBuffer = new ByteBuffer(new Uint8Array(resBuffer));
215 const parsedResponse = ErrorResponse.getRootAsErrorResponse(fbBuffer);
216 const errorMessage = parsedResponse.errorMessage();
217 this.errorMessage = `Received ${res.status} ${res.statusText}: "${errorMessage}"`;
Filip Kujawab5a16e32022-12-14 13:21:56 -0800218 }
219 }
220
221 // Fetch all driver ranking data and store in driverRankingList.
222 async fetchDriverRanking() {
223 this.progressMessage = 'Fetching driver ranking data. Please be patient.';
224 this.errorMessage = '';
225
226 try {
227 this.driverRankingList =
228 await this.viewDataRequestor.fetchDriverRankingList();
229 this.progressMessage = 'Successfully fetched driver ranking data.';
230 } catch (e) {
231 this.errorMessage = e;
232 this.progressMessage = '';
233 }
234 }
235
Emily Markova8e39f452023-12-23 12:17:30 -0800236 // Fetch all pit image data and store in pitImageList.
237 async fetchPitImages() {
238 this.progressMessage = 'Fetching pit image list. Please be patient.';
239 this.errorMessage = '';
240
241 try {
242 const initialPitImageList =
243 await this.viewDataRequestor.fetchPitImagesList();
244 let officialPitImageList = [];
245 // Use iteration to make an array of arrays containing pit image data for individual teams.
246 // Ex. [ [ {971PitImageData} , {971PitImage2Data} ], [ {432PitImageData} ] , [ {213PitImageData} ] ]
247 for (let pitImage of initialPitImageList) {
248 let found = false;
249 for (let arr of officialPitImageList) {
250 if (arr[0].teamNumber() == pitImage.teamNumber()) {
251 arr.push(pitImage);
252 found = true;
253 }
254 }
255 if (!found) {
256 officialPitImageList.push([pitImage]);
257 }
258 }
259 // Sort the arrays based on image file names so their order is predictable.
260 for (let arr of officialPitImageList) {
261 arr.sort((a, b) => (a.imagePath() > b.imagePath() ? 1 : -1));
262 }
263 this.pitImageList = officialPitImageList;
264 this.sortData();
265 this.progressMessage = 'Successfully fetched pit image list.';
266 } catch (e) {
267 this.errorMessage = e;
268 this.progressMessage = '';
269 }
270 }
271
Filip Kujawab5a16e32022-12-14 13:21:56 -0800272 // Fetch all data scouting (stats) data and store in statList.
Emily Markova132e5be2023-03-25 13:43:05 -0700273 async fetchStats2023() {
Filip Kujawab5a16e32022-12-14 13:21:56 -0800274 this.progressMessage = 'Fetching stats list. Please be patient.';
275 this.errorMessage = '';
276
277 try {
Emily Markova132e5be2023-03-25 13:43:05 -0700278 this.statList = await this.viewDataRequestor.fetchStats2023List();
Filip Kujawab5a16e32022-12-14 13:21:56 -0800279 this.progressMessage = 'Successfully fetched stats list.';
280 } catch (e) {
281 this.errorMessage = e;
282 this.progressMessage = '';
283 }
284 }
285
286 // Fetch all notes data and store in noteList.
287 async fetchNotes() {
288 this.progressMessage = 'Fetching notes list. Please be patient.';
289 this.errorMessage = '';
290
291 try {
292 this.noteList = await this.viewDataRequestor.fetchNoteList();
293 this.progressMessage = 'Successfully fetched note list.';
294 } catch (e) {
295 this.errorMessage = e;
296 this.progressMessage = '';
297 }
298 }
299
300 // Parse all selected keywords for a note entry
301 // into one string to be displayed in the table.
302 parseKeywords(entry: Note) {
303 let parsedKeywords = '';
304
305 if (entry.goodDriving()) {
306 parsedKeywords += 'Good Driving ';
307 }
308 if (entry.badDriving()) {
309 parsedKeywords += 'Bad Driving ';
310 }
Filip Kujawa11dc4c92023-04-13 08:55:43 -0700311 if (entry.solidPlacing()) {
312 parsedKeywords += 'Solid Placing ';
Filip Kujawab5a16e32022-12-14 13:21:56 -0800313 }
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800314 if (entry.sketchyPlacing()) {
Filip Kujawa6f7f0b32023-03-30 13:26:08 -0700315 parsedKeywords += 'Sketchy Placing ';
Filip Kujawab5a16e32022-12-14 13:21:56 -0800316 }
317 if (entry.goodDefense()) {
318 parsedKeywords += 'Good Defense ';
319 }
320 if (entry.badDefense()) {
321 parsedKeywords += 'Bad Defense ';
322 }
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800323 if (entry.easilyDefended()) {
324 parsedKeywords += 'Easily Defended';
325 }
Filip Kujawab5a16e32022-12-14 13:21:56 -0800326
327 return parsedKeywords;
328 }
329}