blob: d88c25aa838432847797a62d517f2f053376e651 [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';
Philipp Schradere5d13942024-03-17 15:44:35 -07003import {ErrorResponse} from '@org_frc971/scouting/webserver/requests/messages/error_response_generated';
Filip Kujawab5a16e32022-12-14 13:21:56 -08004import {
5 Ranking,
6 RequestAllDriverRankingsResponse,
Philipp Schradere5d13942024-03-17 15:44:35 -07007} from '@org_frc971/scouting/webserver/requests/messages/request_all_driver_rankings_response_generated';
Filip Kujawab5a16e32022-12-14 13:21:56 -08008import {
Emily Markovadcadcb62024-02-03 13:07:17 -08009 Stats2024,
10 Request2024DataScoutingResponse,
Philipp Schradere5d13942024-03-17 15:44:35 -070011} from '@org_frc971/scouting/webserver/requests/messages/request_2024_data_scouting_response_generated';
Emily Markova8e39f452023-12-23 12:17:30 -080012
13import {
14 PitImage,
15 RequestAllPitImagesResponse,
Philipp Schradere5d13942024-03-17 15:44:35 -070016} from '@org_frc971/scouting/webserver/requests/messages/request_all_pit_images_response_generated';
Emily Markova8e39f452023-12-23 12:17:30 -080017
Filip Kujawab5a16e32022-12-14 13:21:56 -080018import {
19 Note,
20 RequestAllNotesResponse,
Philipp Schradere5d13942024-03-17 15:44:35 -070021} from '@org_frc971/scouting/webserver/requests/messages/request_all_notes_response_generated';
22import {Delete2024DataScouting} from '@org_frc971/scouting/webserver/requests/messages/delete_2024_data_scouting_generated';
23import {Delete2024DataScoutingResponse} from '@org_frc971/scouting/webserver/requests/messages/delete_2024_data_scouting_response_generated';
Filip Kujawab5a16e32022-12-14 13:21:56 -080024
Philipp Schradere5d13942024-03-17 15:44:35 -070025import {
26 MatchListRequestor,
27 ViewDataRequestor,
28} from '@org_frc971/scouting/www/rpc';
Filip Kujawab5a16e32022-12-14 13:21:56 -080029
Emily Markovadcadcb62024-02-03 13:07:17 -080030type Source = 'Notes' | 'Stats2024' | 'PitImages' | 'DriverRanking';
Filip Kujawab5a16e32022-12-14 13:21:56 -080031
32//TODO(Filip): Deduplicate
33const COMP_LEVEL_LABELS = {
34 qm: 'Qualifications',
35 ef: 'Eighth Finals',
36 qf: 'Quarter Finals',
37 sf: 'Semi Finals',
38 f: 'Finals',
39};
Ishan Katpallydad5f1a2022-03-23 21:06:36 -070040
41@Component({
42 selector: 'app-view',
43 templateUrl: './view.ng.html',
Philipp Schrader175a93c2023-02-19 13:13:40 -080044 styleUrls: ['../app/common.css', './view.component.css'],
Ishan Katpallydad5f1a2022-03-23 21:06:36 -070045})
Filip Kujawab5a16e32022-12-14 13:21:56 -080046export class ViewComponent {
47 constructor(private readonly viewDataRequestor: ViewDataRequestor) {}
48
49 // Make COMP_LEVEL_LABELS available in view.ng.html.
50 readonly COMP_LEVEL_LABELS = COMP_LEVEL_LABELS;
51
52 // Progress and error messages to display to
53 // the user when fetching data.
54 progressMessage: string = '';
55 errorMessage: string = '';
56
57 // The current data source being displayed.
58 currentSource: Source = 'Notes';
59
60 // Current sort (ascending/descending match numbers).
61 // noteList is sorted based on team number until match
62 // number is added for note scouting.
63 ascendingSort = true;
64
65 // Stores the corresponding data.
66 noteList: Note[] = [];
67 driverRankingList: Ranking[] = [];
Emily Markova8e39f452023-12-23 12:17:30 -080068 pitImageList: PitImage[][] = [];
Emily Markovadcadcb62024-02-03 13:07:17 -080069 statList: Stats2024[] = [];
Filip Kujawab5a16e32022-12-14 13:21:56 -080070
71 // Fetch notes on initialization.
72 ngOnInit() {
73 this.fetchCurrentSource();
74 }
75
76 // Called when a user changes the sort direction.
77 // Changes the data order between ascending/descending.
78 sortData() {
79 this.ascendingSort = !this.ascendingSort;
80 if (!this.ascendingSort) {
81 this.driverRankingList.sort((a, b) => b.matchNumber() - a.matchNumber());
Emily Markovae68b7632023-12-30 14:17:55 -080082 this.noteList.sort(function (a, b) {
Emily Markovacf893f42024-03-13 19:03:10 -070083 return b.team().localeCompare(a.team(), undefined, {numeric: true});
Emily Markovae68b7632023-12-30 14:17:55 -080084 });
Emily Markova8e39f452023-12-23 12:17:30 -080085 this.pitImageList.sort(function (a, b) {
86 return b[0]
87 .teamNumber()
88 .localeCompare(a[0].teamNumber(), undefined, {numeric: true});
89 });
Emily Markova132e5be2023-03-25 13:43:05 -070090 this.statList.sort((a, b) => b.matchNumber() - a.matchNumber());
Filip Kujawab5a16e32022-12-14 13:21:56 -080091 } else {
92 this.driverRankingList.sort((a, b) => a.matchNumber() - b.matchNumber());
Emily Markovae68b7632023-12-30 14:17:55 -080093 this.noteList.sort(function (a, b) {
Emily Markovacf893f42024-03-13 19:03:10 -070094 return a.team().localeCompare(b.team(), undefined, {numeric: true});
Emily Markova8e39f452023-12-23 12:17:30 -080095 });
96 this.pitImageList.sort(function (a, b) {
97 return a[0]
98 .teamNumber()
99 .localeCompare(b[0].teamNumber(), undefined, {numeric: true});
Emily Markovae68b7632023-12-30 14:17:55 -0800100 });
Emily Markova132e5be2023-03-25 13:43:05 -0700101 this.statList.sort((a, b) => a.matchNumber() - b.matchNumber());
Filip Kujawab5a16e32022-12-14 13:21:56 -0800102 }
103 }
104
105 // Called when a user selects a new data source
106 // from the dropdown.
107 switchDataSource(target: Source) {
108 this.currentSource = target;
109 this.progressMessage = '';
110 this.errorMessage = '';
111 this.noteList = [];
112 this.driverRankingList = [];
113 this.statList = [];
Emily Markova8e39f452023-12-23 12:17:30 -0800114 this.pitImageList = [];
Filip Kujawab5a16e32022-12-14 13:21:56 -0800115 this.fetchCurrentSource();
116 }
117
118 // Call the method to fetch data for the current source.
119 fetchCurrentSource() {
120 switch (this.currentSource) {
121 case 'Notes': {
122 this.fetchNotes();
123 }
124
Emily Markovadcadcb62024-02-03 13:07:17 -0800125 case 'Stats2024': {
126 this.fetchStats2024();
Filip Kujawab5a16e32022-12-14 13:21:56 -0800127 }
128
Emily Markova8e39f452023-12-23 12:17:30 -0800129 case 'PitImages': {
130 this.fetchPitImages();
131 }
132
Filip Kujawab5a16e32022-12-14 13:21:56 -0800133 case 'DriverRanking': {
134 this.fetchDriverRanking();
135 }
136 }
137 }
138
139 // TODO(Filip): Add delete functionality.
Filip Kujawac1ded372023-05-27 14:33:43 -0700140 // Gets called when a user clicks the delete icon (note scouting).
141 async deleteNoteData() {
Filip Kujawab5a16e32022-12-14 13:21:56 -0800142 const block_alerts = document.getElementById(
143 'block_alerts'
144 ) as HTMLInputElement;
Filip Kujawac1ded372023-05-27 14:33:43 -0700145 if (block_alerts.checked || window.confirm('Actually delete data?')) {
146 this.errorMessage = 'Deleting data has not been implemented yet.';
147 return;
148 }
149 }
150
151 // TODO(Filip): Add delete functionality.
152 // Gets called when a user clicks the delete icon (driver ranking).
153 async deleteDriverRankingData() {
154 const block_alerts = document.getElementById(
155 'block_alerts'
156 ) as HTMLInputElement;
157 if (block_alerts.checked || window.confirm('Actually delete data?')) {
158 this.errorMessage = 'Deleting data has not been implemented yet.';
159 return;
160 }
161 }
162
163 // Gets called when a user clicks the delete icon.
Emily Markovadcadcb62024-02-03 13:07:17 -0800164 async delete2024DataScouting(
Filip Kujawac1ded372023-05-27 14:33:43 -0700165 compLevel: string,
166 matchNumber: number,
167 setNumber: number,
168 teamNumber: string
169 ) {
170 const block_alerts = document.getElementById(
171 'block_alerts'
172 ) as HTMLInputElement;
173 if (block_alerts.checked || window.confirm('Actually delete data?')) {
Emily Markovadcadcb62024-02-03 13:07:17 -0800174 await this.requestDelete2024DataScouting(
Filip Kujawac1ded372023-05-27 14:33:43 -0700175 compLevel,
176 matchNumber,
177 setNumber,
178 teamNumber
179 );
Emily Markovadcadcb62024-02-03 13:07:17 -0800180 await this.fetchStats2024();
Filip Kujawac1ded372023-05-27 14:33:43 -0700181 }
182 }
183
Emily Markovadcadcb62024-02-03 13:07:17 -0800184 async requestDelete2024DataScouting(
Filip Kujawac1ded372023-05-27 14:33:43 -0700185 compLevel: string,
186 matchNumber: number,
187 setNumber: number,
188 teamNumber: string
189 ) {
190 this.progressMessage = 'Deleting data. Please be patient.';
191 const builder = new Builder();
192 const compLevelData = builder.createString(compLevel);
193 const teamNumberData = builder.createString(teamNumber);
194
195 builder.finish(
Emily Markovadcadcb62024-02-03 13:07:17 -0800196 Delete2024DataScouting.createDelete2024DataScouting(
Filip Kujawac1ded372023-05-27 14:33:43 -0700197 builder,
198 compLevelData,
199 matchNumber,
200 setNumber,
201 teamNumberData
202 )
203 );
204
205 const buffer = builder.asUint8Array();
Emily Markovadcadcb62024-02-03 13:07:17 -0800206 const res = await fetch('/requests/delete/delete_2024_data_scouting', {
Filip Kujawac1ded372023-05-27 14:33:43 -0700207 method: 'POST',
208 body: buffer,
209 });
210
211 if (!res.ok) {
212 const resBuffer = await res.arrayBuffer();
213 const fbBuffer = new ByteBuffer(new Uint8Array(resBuffer));
214 const parsedResponse = ErrorResponse.getRootAsErrorResponse(fbBuffer);
215 const errorMessage = parsedResponse.errorMessage();
216 this.errorMessage = `Received ${res.status} ${res.statusText}: "${errorMessage}"`;
Filip Kujawab5a16e32022-12-14 13:21:56 -0800217 }
218 }
219
220 // Fetch all driver ranking data and store in driverRankingList.
221 async fetchDriverRanking() {
222 this.progressMessage = 'Fetching driver ranking data. Please be patient.';
223 this.errorMessage = '';
224
225 try {
226 this.driverRankingList =
227 await this.viewDataRequestor.fetchDriverRankingList();
228 this.progressMessage = 'Successfully fetched driver ranking data.';
229 } catch (e) {
230 this.errorMessage = e;
231 this.progressMessage = '';
232 }
233 }
234
Emily Markova8e39f452023-12-23 12:17:30 -0800235 // Fetch all pit image data and store in pitImageList.
236 async fetchPitImages() {
237 this.progressMessage = 'Fetching pit image list. Please be patient.';
238 this.errorMessage = '';
239
240 try {
241 const initialPitImageList =
242 await this.viewDataRequestor.fetchPitImagesList();
243 let officialPitImageList = [];
244 // Use iteration to make an array of arrays containing pit image data for individual teams.
245 // Ex. [ [ {971PitImageData} , {971PitImage2Data} ], [ {432PitImageData} ] , [ {213PitImageData} ] ]
246 for (let pitImage of initialPitImageList) {
247 let found = false;
248 for (let arr of officialPitImageList) {
249 if (arr[0].teamNumber() == pitImage.teamNumber()) {
250 arr.push(pitImage);
251 found = true;
252 }
253 }
254 if (!found) {
255 officialPitImageList.push([pitImage]);
256 }
257 }
258 // Sort the arrays based on image file names so their order is predictable.
259 for (let arr of officialPitImageList) {
260 arr.sort((a, b) => (a.imagePath() > b.imagePath() ? 1 : -1));
261 }
262 this.pitImageList = officialPitImageList;
263 this.sortData();
264 this.progressMessage = 'Successfully fetched pit image list.';
265 } catch (e) {
266 this.errorMessage = e;
267 this.progressMessage = '';
268 }
269 }
270
Filip Kujawab5a16e32022-12-14 13:21:56 -0800271 // Fetch all data scouting (stats) data and store in statList.
Emily Markovadcadcb62024-02-03 13:07:17 -0800272 async fetchStats2024() {
Filip Kujawab5a16e32022-12-14 13:21:56 -0800273 this.progressMessage = 'Fetching stats list. Please be patient.';
274 this.errorMessage = '';
275
276 try {
Emily Markovadcadcb62024-02-03 13:07:17 -0800277 this.statList = await this.viewDataRequestor.fetchStats2024List();
Filip Kujawab5a16e32022-12-14 13:21:56 -0800278 this.progressMessage = 'Successfully fetched stats list.';
279 } catch (e) {
280 this.errorMessage = e;
281 this.progressMessage = '';
282 }
283 }
284
285 // Fetch all notes data and store in noteList.
286 async fetchNotes() {
287 this.progressMessage = 'Fetching notes list. Please be patient.';
288 this.errorMessage = '';
289
290 try {
291 this.noteList = await this.viewDataRequestor.fetchNoteList();
292 this.progressMessage = 'Successfully fetched note list.';
293 } catch (e) {
294 this.errorMessage = e;
295 this.progressMessage = '';
296 }
297 }
298
299 // Parse all selected keywords for a note entry
300 // into one string to be displayed in the table.
301 parseKeywords(entry: Note) {
302 let parsedKeywords = '';
303
304 if (entry.goodDriving()) {
305 parsedKeywords += 'Good Driving ';
306 }
307 if (entry.badDriving()) {
308 parsedKeywords += 'Bad Driving ';
309 }
Emily Markovacf893f42024-03-13 19:03:10 -0700310 if (entry.noShow()) {
311 parsedKeywords += 'No Show ';
312 }
Filip Kujawa11dc4c92023-04-13 08:55:43 -0700313 if (entry.solidPlacing()) {
314 parsedKeywords += 'Solid Placing ';
Filip Kujawab5a16e32022-12-14 13:21:56 -0800315 }
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800316 if (entry.sketchyPlacing()) {
Filip Kujawa6f7f0b32023-03-30 13:26:08 -0700317 parsedKeywords += 'Sketchy Placing ';
Filip Kujawab5a16e32022-12-14 13:21:56 -0800318 }
319 if (entry.goodDefense()) {
320 parsedKeywords += 'Good Defense ';
321 }
322 if (entry.badDefense()) {
323 parsedKeywords += 'Bad Defense ';
324 }
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800325 if (entry.easilyDefended()) {
326 parsedKeywords += 'Easily Defended';
327 }
Filip Kujawab5a16e32022-12-14 13:21:56 -0800328
329 return parsedKeywords;
330 }
331}