blob: 5afc8e211855d09b4d59117a9347feb9c0cc9994 [file] [log] [blame]
Filip Kujawa7dd49952022-12-02 12:09:13 -08001import {Component, HostListener} from '@angular/core';
Alex Perrybb901052022-03-23 19:46:15 -07002import {Builder, ByteBuffer} from 'flatbuffers';
Philipp Schradere5d13942024-03-17 15:44:35 -07003import {ErrorResponse} from '@org_frc971/scouting/webserver/requests/messages/error_response_generated';
4import {RequestNotesForTeam} from '@org_frc971/scouting/webserver/requests/messages/request_notes_for_team_generated';
Philipp Schrader817cce32022-03-26 15:00:00 -07005import {
6 Note as NoteFb,
7 RequestNotesForTeamResponse,
Philipp Schradere5d13942024-03-17 15:44:35 -07008} from '@org_frc971/scouting/webserver/requests/messages/request_notes_for_team_response_generated';
9import {SubmitNotes} from '@org_frc971/scouting/webserver/requests/messages/submit_notes_generated';
10import {SubmitNotesResponse} from '@org_frc971/scouting/webserver/requests/messages/submit_notes_response_generated';
Alex Perrybb901052022-03-23 19:46:15 -070011
Filip Kujawaf947cb42022-11-21 10:00:30 -080012/*
13For new games, the keywords being used will likely need to be updated.
14To update the keywords complete the following:
15 1) Update the Keywords Interface and KEYWORD_CHECKBOX_LABELS in notes.component.ts
16 The keys of Keywords and KEYWORD_CHECKBOX_LABELS should match.
17 2) In notes.component.ts, update the setTeamNumber() method with the new keywords.
18 3) Add/Edit the new keywords in /scouting/webserver/requests/messages/submit_notes.fbs.
19 4) In notes.component.ts, update the submitData() method with the newKeywords
20 so that it matches the updated flatbuffer
21 5) In db.go, update the NotesData struct and the
22 AddNotes method with the new keywords
23 6) In db_test.go update the TestNotes method so the test uses the keywords
24 7) Update the submitNoteScoutingHandler in requests.go with the new keywords
25 8) Finally, update the corresponding test in requests_test.go (TestSubmitNotes)
26
27 Note: If you change the number of keywords you might need to
28 update how they are displayed in notes.ng.html
29*/
30
31// TeamSelection: Display form to add a team to the teams being scouted.
32// Data: Display the note textbox and keyword selection form
33// for all the teams being scouted.
Philipp Schrader817cce32022-03-26 15:00:00 -070034type Section = 'TeamSelection' | 'Data';
Alex Perrybb901052022-03-23 19:46:15 -070035
Emily Markovacf893f42024-03-13 19:03:10 -070036const COMP_LEVELS = ['qm', 'ef', 'qf', 'sf', 'f'] as const;
37type CompLevel = typeof COMP_LEVELS[number];
38
39// TODO(phil): Deduplicate with match_list.component.ts.
40const COMP_LEVEL_LABELS: Record<CompLevel, string> = {
41 qm: 'Qualifications',
42 ef: 'Eighth Finals',
43 qf: 'Quarter Finals',
44 sf: 'Semi Finals',
45 f: 'Finals',
46};
47
Filip Kujawaf947cb42022-11-21 10:00:30 -080048// Every keyword checkbox corresponds to a boolean.
49// If the boolean is True, the checkbox is selected
50// and the note scout saw that the robot being scouted
51// displayed said property (ex. Driving really well -> goodDriving)
52interface Keywords {
53 goodDriving: boolean;
54 badDriving: boolean;
Filip Kujawa11dc4c92023-04-13 08:55:43 -070055 solidPlacing: boolean;
Filip Kujawa7ddd5652023-03-07 19:56:15 -080056 sketchyPlacing: boolean;
Filip Kujawaf947cb42022-11-21 10:00:30 -080057 goodDefense: boolean;
58 badDefense: boolean;
Filip Kujawa7ddd5652023-03-07 19:56:15 -080059 easilyDefended: boolean;
Emily Markovacf893f42024-03-13 19:03:10 -070060 noShow: boolean;
Alex Perrybb901052022-03-23 19:46:15 -070061}
62
Filip Kujawaf947cb42022-11-21 10:00:30 -080063interface Input {
Emily Markovae68b7632023-12-30 14:17:55 -080064 teamNumber: string;
Filip Kujawaf947cb42022-11-21 10:00:30 -080065 notesData: string;
66 keywordsData: Keywords;
Emily Markovacf893f42024-03-13 19:03:10 -070067 matchNumber: number;
68 setNumber: number;
69 compLevel: string;
Filip Kujawaf947cb42022-11-21 10:00:30 -080070}
71
72const KEYWORD_CHECKBOX_LABELS = {
73 goodDriving: 'Good Driving',
74 badDriving: 'Bad Driving',
Emily Markovaa287ac52024-03-07 19:28:58 -080075 solidPlacing: 'Solid Shooting',
76 sketchyPlacing: 'Sketchy Shooting',
Filip Kujawaf947cb42022-11-21 10:00:30 -080077 goodDefense: 'Good Defense',
78 badDefense: 'Bad Defense',
Filip Kujawa7ddd5652023-03-07 19:56:15 -080079 easilyDefended: 'Easily Defended',
Emily Markovacf893f42024-03-13 19:03:10 -070080 noShow: 'No Show',
Filip Kujawaf947cb42022-11-21 10:00:30 -080081} as const;
82
Alex Perrybb901052022-03-23 19:46:15 -070083@Component({
84 selector: 'frc971-notes',
85 templateUrl: './notes.ng.html',
Philipp Schrader175a93c2023-02-19 13:13:40 -080086 styleUrls: ['../app/common.css', './notes.component.css'],
Alex Perrybb901052022-03-23 19:46:15 -070087})
88export class Notes {
Filip Kujawaf947cb42022-11-21 10:00:30 -080089 // Re-export KEYWORD_CHECKBOX_LABELS so that we can
90 // use it in the checkbox properties.
Emily Markovacf893f42024-03-13 19:03:10 -070091 readonly COMP_LEVELS = COMP_LEVELS;
92 readonly COMP_LEVEL_LABELS = COMP_LEVEL_LABELS;
Filip Kujawaf947cb42022-11-21 10:00:30 -080093 readonly KEYWORD_CHECKBOX_LABELS = KEYWORD_CHECKBOX_LABELS;
94
95 // Necessary in order to iterate the keys of KEYWORD_CHECKBOX_LABELS.
96 Object = Object;
97
Alex Perrybb901052022-03-23 19:46:15 -070098 section: Section = 'TeamSelection';
Alex Perrybb901052022-03-23 19:46:15 -070099
100 errorMessage = '';
Emily Markovacf893f42024-03-13 19:03:10 -0700101 teamNumber: string = '1';
102 matchNumber: number = 1;
103 setNumber: number = 1;
104 compLevel: CompLevel = 'qm';
Alex Perrybb901052022-03-23 19:46:15 -0700105
Filip Kujawaf947cb42022-11-21 10:00:30 -0800106 // Data inputted by user is stored in this array.
107 // Includes the team number, notes, and keyword selection.
108 newData: Input[] = [];
Alex Perrybb901052022-03-23 19:46:15 -0700109
Filip Kujawa7dd49952022-12-02 12:09:13 -0800110 // Keyboard shortcuts to switch between text areas.
111 // Listens for Ctrl + number and focuses on the
112 // corresponding textbox.
113 // More Info: https://angular.io/api/core/HostListener
114
115 @HostListener('window:keyup', ['$event'])
116 onEvent(event: KeyboardEvent) {
117 if (event.ctrlKey) {
118 if (event.code.includes('Digit')) {
119 this.handleFocus(event.key);
120 }
121 }
122 }
123
124 handleFocus(digit: string) {
125 let textArea = <HTMLInputElement>(
126 document.getElementById('text-input-' + digit)
127 );
128 if (textArea != null) {
129 textArea.focus();
130 }
131 }
132
Emily Markovacf893f42024-03-13 19:03:10 -0700133 setTeamData() {
Filip Kujawaf947cb42022-11-21 10:00:30 -0800134 let data: Input = {
Emily Markovacf893f42024-03-13 19:03:10 -0700135 teamNumber: this.teamNumber,
136 notesData:
137 'Match ' +
138 this.matchNumber +
139 ' Set ' +
140 this.setNumber +
141 ' ' +
142 COMP_LEVEL_LABELS[this.compLevel] +
143 ' \nAuto: \nTeleop: \nEndgame: ',
Filip Kujawaf947cb42022-11-21 10:00:30 -0800144 keywordsData: {
145 goodDriving: false,
146 badDriving: false,
Filip Kujawa11dc4c92023-04-13 08:55:43 -0700147 solidPlacing: false,
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800148 sketchyPlacing: false,
Filip Kujawaf947cb42022-11-21 10:00:30 -0800149 goodDefense: false,
150 badDefense: false,
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800151 easilyDefended: false,
Emily Markovacf893f42024-03-13 19:03:10 -0700152 noShow: false,
Filip Kujawaf947cb42022-11-21 10:00:30 -0800153 },
Emily Markovacf893f42024-03-13 19:03:10 -0700154 matchNumber: this.matchNumber,
155 setNumber: this.setNumber,
156 compLevel: this.compLevel,
Filip Kujawaf947cb42022-11-21 10:00:30 -0800157 };
Alex Perrybb901052022-03-23 19:46:15 -0700158
Filip Kujawaf947cb42022-11-21 10:00:30 -0800159 this.newData.push(data);
160 this.section = 'Data';
161 }
Alex Perrybb901052022-03-23 19:46:15 -0700162
Filip Kujawaf947cb42022-11-21 10:00:30 -0800163 removeTeam(index: number) {
164 this.newData.splice(index, 1);
165 if (this.newData.length == 0) {
166 this.section = 'TeamSelection';
Alex Perrybb901052022-03-23 19:46:15 -0700167 } else {
Filip Kujawaf947cb42022-11-21 10:00:30 -0800168 this.section = 'Data';
Alex Perrybb901052022-03-23 19:46:15 -0700169 }
170 }
171
Filip Kujawaf947cb42022-11-21 10:00:30 -0800172 addTeam() {
Philipp Schrader817cce32022-03-26 15:00:00 -0700173 this.section = 'TeamSelection';
Alex Perrybb901052022-03-23 19:46:15 -0700174 }
Philipp Schrader817cce32022-03-26 15:00:00 -0700175
Alex Perrybb901052022-03-23 19:46:15 -0700176 async submitData() {
Filip Kujawaf947cb42022-11-21 10:00:30 -0800177 for (let i = 0; i < this.newData.length; i++) {
178 const builder = new Builder();
179 const dataFb = builder.createString(this.newData[i].notesData);
Filip Kujawaba55bb32022-12-02 14:08:32 -0800180
Emily Markovae68b7632023-12-30 14:17:55 -0800181 const teamNumber = builder.createString(this.newData[i].teamNumber);
Emily Markovacf893f42024-03-13 19:03:10 -0700182 const compLevel = builder.createString(this.newData[i].compLevel);
Emily Markovae68b7632023-12-30 14:17:55 -0800183
Filip Kujawaf947cb42022-11-21 10:00:30 -0800184 builder.finish(
185 SubmitNotes.createSubmitNotes(
186 builder,
Emily Markovae68b7632023-12-30 14:17:55 -0800187 teamNumber,
Filip Kujawaf947cb42022-11-21 10:00:30 -0800188 dataFb,
189 this.newData[i].keywordsData.goodDriving,
190 this.newData[i].keywordsData.badDriving,
Filip Kujawa11dc4c92023-04-13 08:55:43 -0700191 this.newData[i].keywordsData.solidPlacing,
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800192 this.newData[i].keywordsData.sketchyPlacing,
Filip Kujawaf947cb42022-11-21 10:00:30 -0800193 this.newData[i].keywordsData.goodDefense,
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800194 this.newData[i].keywordsData.badDefense,
Emily Markovacf893f42024-03-13 19:03:10 -0700195 this.newData[i].keywordsData.easilyDefended,
196 this.newData[i].keywordsData.noShow,
197 this.newData[i].matchNumber,
198 this.newData[i].setNumber,
199 compLevel
Filip Kujawaf947cb42022-11-21 10:00:30 -0800200 )
201 );
Alex Perrybb901052022-03-23 19:46:15 -0700202
Filip Kujawaf947cb42022-11-21 10:00:30 -0800203 const buffer = builder.asUint8Array();
204 const res = await fetch('/requests/submit/submit_notes', {
205 method: 'POST',
206 body: buffer,
207 });
Alex Perrybb901052022-03-23 19:46:15 -0700208
Filip Kujawaf947cb42022-11-21 10:00:30 -0800209 if (!res.ok) {
210 const resBuffer = await res.arrayBuffer();
211 const fbBuffer = new ByteBuffer(new Uint8Array(resBuffer));
212 const parsedResponse = ErrorResponse.getRootAsErrorResponse(fbBuffer);
213 const errorMessage = parsedResponse.errorMessage();
214 this.errorMessage = `Received ${res.status} ${res.statusText}: "${errorMessage}"`;
215 }
Alex Perrybb901052022-03-23 19:46:15 -0700216 }
Filip Kujawaf947cb42022-11-21 10:00:30 -0800217
218 this.newData = [];
219 this.errorMessage = '';
220 this.section = 'TeamSelection';
Alex Perrybb901052022-03-23 19:46:15 -0700221 }
Philipp Schrader02db74b2023-02-17 20:36:58 -0800222
223 labelToId(label: String): String {
224 return label.replaceAll(' ', '_').toLowerCase();
225 }
Alex Perrybb901052022-03-23 19:46:15 -0700226}