blob: b3e1e92840b763cbd2037c199877820553aca9d5 [file] [log] [blame]
Philipp Schrader817cce32022-03-26 15:00:00 -07001import {
2 Component,
3 ElementRef,
4 EventEmitter,
5 Input,
6 OnInit,
7 Output,
8 ViewChild,
9} from '@angular/core';
Ravago Jones2813c032022-03-16 23:44:11 -070010import {FormsModule} from '@angular/forms';
James Kuszmauldac091f2022-03-22 09:35:06 -070011import {Builder, ByteBuffer} from 'flatbuffers';
Philipp Schraderd7efa2b2023-02-17 21:15:13 -080012import {ErrorResponse} from '../../webserver/requests/messages/error_response_generated';
Philipp Schrader817cce32022-03-26 15:00:00 -070013import {
Filip Kujawa0ef334c2023-02-20 19:42:45 -080014 StartMatchAction,
Emily Markovadcadcb62024-02-03 13:07:17 -080015 ScoreType,
16 StageType,
17 Submit2024Actions,
Filip Kujawa0b4b1e52023-04-15 14:05:40 -070018 MobilityAction,
Emily Markovadcadcb62024-02-03 13:07:17 -080019 PenaltyAction,
20 PickupNoteAction,
21 PlaceNoteAction,
Filip Kujawa0ef334c2023-02-20 19:42:45 -080022 RobotDeathAction,
23 EndMatchAction,
24 ActionType,
25 Action,
Emily Markovadcadcb62024-02-03 13:07:17 -080026} from '../../webserver/requests/messages/submit_2024_actions_generated';
Philipp Schrader8702b782023-04-15 17:33:37 -070027import {Match} from '../../webserver/requests/messages/request_all_matches_response_generated';
28import {MatchListRequestor} from '@org_frc971/scouting/www/rpc';
Philipp Schrader8b8ed672022-03-05 18:08:50 -080029
Philipp Schrader817cce32022-03-26 15:00:00 -070030type Section =
31 | 'Team Selection'
Filip Kujawa0ef334c2023-02-20 19:42:45 -080032 | 'Init'
33 | 'Pickup'
34 | 'Place'
35 | 'Endgame'
36 | 'Dead'
Philipp Schrader817cce32022-03-26 15:00:00 -070037 | 'Review and Submit'
38 | 'Success';
Philipp Schrader80587432022-03-05 15:41:22 -080039
Philipp Schrader8aeb14f2022-04-08 21:23:18 -070040// TODO(phil): Deduplicate with match_list.component.ts.
41const COMP_LEVELS = ['qm', 'ef', 'qf', 'sf', 'f'] as const;
42type CompLevel = typeof COMP_LEVELS[number];
43
44// TODO(phil): Deduplicate with match_list.component.ts.
45const COMP_LEVEL_LABELS: Record<CompLevel, string> = {
46 qm: 'Qualifications',
47 ef: 'Eighth Finals',
48 qf: 'Quarter Finals',
49 sf: 'Semi Finals',
50 f: 'Finals',
51};
52
Filip Kujawa0ef334c2023-02-20 19:42:45 -080053type ActionT =
54 | {
55 type: 'startMatchAction';
56 timestamp?: number;
57 position: number;
58 }
59 | {
Filip Kujawa0b4b1e52023-04-15 14:05:40 -070060 type: 'mobilityAction';
61 timestamp?: number;
62 mobility: boolean;
63 }
64 | {
Emily Markovadcadcb62024-02-03 13:07:17 -080065 type: 'pickupNoteAction';
Filip Kujawa4413a592023-03-01 10:54:34 -080066 timestamp?: number;
Filip Kujawa0ef334c2023-02-20 19:42:45 -080067 auto?: boolean;
68 }
69 | {
Emily Markovadcadcb62024-02-03 13:07:17 -080070 type: 'placeNoteAction';
Filip Kujawa0ef334c2023-02-20 19:42:45 -080071 timestamp?: number;
Emily Markovadcadcb62024-02-03 13:07:17 -080072 scoreType: ScoreType;
Filip Kujawa0ef334c2023-02-20 19:42:45 -080073 auto?: boolean;
74 }
75 | {
76 type: 'robotDeathAction';
77 timestamp?: number;
78 robotOn: boolean;
79 }
80 | {
Emily Markovadcadcb62024-02-03 13:07:17 -080081 type: 'penaltyAction';
82 timestamp?: number;
83 penalties: number;
84 }
85 | {
Filip Kujawa0ef334c2023-02-20 19:42:45 -080086 type: 'endMatchAction';
Emily Markovadcadcb62024-02-03 13:07:17 -080087 stageType: StageType;
88 trapNote: boolean;
Filip Kujawa0ef334c2023-02-20 19:42:45 -080089 timestamp?: number;
90 }
91 | {
92 // This is not a action that is submitted,
93 // It is used for undoing purposes.
94 type: 'endAutoPhase';
95 timestamp?: number;
Emily Markovadcadcb62024-02-03 13:07:17 -080096 }
97 | {
98 // This is not a action that is submitted,
99 // It is used for undoing purposes.
100 type: 'endTeleopPhase';
101 timestamp?: number;
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800102 };
emilym38d08ba2022-10-22 15:25:01 -0700103
Philipp Schrader23993e82022-03-18 18:54:00 -0700104@Component({
105 selector: 'app-entry',
106 templateUrl: './entry.ng.html',
Philipp Schrader175a93c2023-02-19 13:13:40 -0800107 styleUrls: ['../app/common.css', './entry.component.css'],
Philipp Schrader23993e82022-03-18 18:54:00 -0700108})
Philipp Schrader75021f52023-04-09 21:14:13 -0700109export class EntryComponent implements OnInit {
Philipp Schrader36df73a2022-03-17 23:27:24 -0700110 // Re-export the type here so that we can use it in the `[value]` attribute
111 // of radio buttons.
Philipp Schrader8aeb14f2022-04-08 21:23:18 -0700112 readonly COMP_LEVELS = COMP_LEVELS;
113 readonly COMP_LEVEL_LABELS = COMP_LEVEL_LABELS;
Emily Markovadcadcb62024-02-03 13:07:17 -0800114 readonly ScoreType = ScoreType;
Philipp Schrader36df73a2022-03-17 23:27:24 -0700115
Ravago Jones2813c032022-03-16 23:44:11 -0700116 section: Section = 'Team Selection';
Ravago Jones2813c032022-03-16 23:44:11 -0700117 @Input() matchNumber: number = 1;
Emily Markovae68b7632023-12-30 14:17:55 -0800118 @Input() teamNumber: string = '1';
Philipp Schrader30b4a682022-04-16 14:36:17 -0700119 @Input() setNumber: number = 1;
Philipp Schrader8aeb14f2022-04-08 21:23:18 -0700120 @Input() compLevel: CompLevel = 'qm';
Philipp Schrader75021f52023-04-09 21:14:13 -0700121 @Input() skipTeamSelection = false;
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800122
Philipp Schrader8702b782023-04-15 17:33:37 -0700123 matchList: Match[] = [];
124
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800125 actionList: ActionT[] = [];
Philipp Schrader8702b782023-04-15 17:33:37 -0700126 progressMessage: string = '';
Ravago Jones2813c032022-03-16 23:44:11 -0700127 errorMessage: string = '';
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800128 autoPhase: boolean = true;
Filip Kujawab73e94c2023-04-19 09:33:14 -0700129 mobilityCompleted: boolean = false;
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800130
Philipp Schradere1498852023-04-15 18:06:45 -0700131 preScouting: boolean = false;
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800132 matchStartTimestamp: number = 0;
Emily Markovadcadcb62024-02-03 13:07:17 -0800133 penalties: number = 0;
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800134
Philipp Schrader8702b782023-04-15 17:33:37 -0700135 teamSelectionIsValid = false;
136
137 constructor(private readonly matchListRequestor: MatchListRequestor) {}
138
Philipp Schrader75021f52023-04-09 21:14:13 -0700139 ngOnInit() {
140 // When the user navigated from the match list, we can skip the team
141 // selection. I.e. we trust that the user clicked the correct button.
142 this.section = this.skipTeamSelection ? 'Init' : 'Team Selection';
Philipp Schrader8702b782023-04-15 17:33:37 -0700143
144 if (this.section == 'Team Selection') {
145 this.fetchMatchList();
146 }
147 }
148
149 async fetchMatchList() {
150 this.progressMessage = 'Fetching match list. Please be patient.';
151 this.errorMessage = '';
152
153 try {
154 this.matchList = await this.matchListRequestor.fetchMatchList();
155 this.progressMessage = 'Successfully fetched match list.';
156 } catch (e) {
157 this.errorMessage = e;
158 this.progressMessage = '';
159 }
160 }
161
162 // This gets called when the user changes something on the Init screen.
163 // It makes sure that the user can't click "Next" until the information is
Philipp Schradere1498852023-04-15 18:06:45 -0700164 // valid, or this is for pre-scouting.
Philipp Schrader8702b782023-04-15 17:33:37 -0700165 updateTeamSelectionValidity(): void {
Philipp Schradere1498852023-04-15 18:06:45 -0700166 this.teamSelectionIsValid = this.preScouting || this.matchIsInMatchList();
Philipp Schrader8702b782023-04-15 17:33:37 -0700167 }
168
169 matchIsInMatchList(): boolean {
170 // If the user deletes the content of the teamNumber field, the value here
171 // is undefined. Guard against that.
172 if (this.teamNumber == null) {
173 return false;
174 }
Emily Markovae68b7632023-12-30 14:17:55 -0800175 const teamNumber = this.teamNumber;
Philipp Schrader8702b782023-04-15 17:33:37 -0700176
177 for (const match of this.matchList) {
178 if (
179 this.matchNumber == match.matchNumber() &&
180 this.setNumber == match.setNumber() &&
181 this.compLevel == match.compLevel() &&
182 (teamNumber === match.r1() ||
183 teamNumber === match.r2() ||
184 teamNumber === match.r3() ||
185 teamNumber === match.b1() ||
186 teamNumber === match.b2() ||
187 teamNumber === match.b3())
188 ) {
189 return true;
190 }
191 }
192 return false;
Philipp Schrader75021f52023-04-09 21:14:13 -0700193 }
194
Emily Markovadcadcb62024-02-03 13:07:17 -0800195 addPenalty(): void {
196 this.penalties += 1;
197 }
198
199 removePenalty(): void {
200 if (this.penalties > 0) {
201 this.penalties -= 1;
202 }
203 }
204
205 addPenalties(): void {
206 this.addAction({type: 'penaltyAction', penalties: this.penalties});
207 }
208
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800209 addAction(action: ActionT): void {
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800210 if (action.type == 'startMatchAction') {
211 // Unix nanosecond timestamp.
212 this.matchStartTimestamp = Date.now() * 1e6;
213 action.timestamp = 0;
214 } else {
215 // Unix nanosecond timestamp relative to match start.
216 action.timestamp = Date.now() * 1e6 - this.matchStartTimestamp;
217 }
218
Emily Markovadcadcb62024-02-03 13:07:17 -0800219 if (action.type == 'endMatchAction') {
220 // endMatchAction occurs at the same time as penaltyAction so add to its timestamp to make it unique.
221 action.timestamp += 1;
222 }
223
Filip Kujawab73e94c2023-04-19 09:33:14 -0700224 if (action.type == 'mobilityAction') {
225 this.mobilityCompleted = true;
226 }
227
Emily Markovadcadcb62024-02-03 13:07:17 -0800228 if (action.type == 'pickupNoteAction' || action.type == 'placeNoteAction') {
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800229 action.auto = this.autoPhase;
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800230 }
231 this.actionList.push(action);
232 }
233
234 undoLastAction() {
235 if (this.actionList.length > 0) {
236 let lastAction = this.actionList.pop();
237 switch (lastAction?.type) {
238 case 'endAutoPhase':
239 this.autoPhase = true;
Emily Markovadcadcb62024-02-03 13:07:17 -0800240 this.section = 'Pickup';
241 case 'pickupNoteAction':
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800242 this.section = 'Pickup';
243 break;
Emily Markovadcadcb62024-02-03 13:07:17 -0800244 case 'endTeleopPhase':
245 this.section = 'Pickup';
246 break;
247 case 'placeNoteAction':
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800248 this.section = 'Place';
249 break;
250 case 'endMatchAction':
Emily Markovadcadcb62024-02-03 13:07:17 -0800251 this.section = 'Endgame';
252 case 'mobilityAction':
253 this.mobilityCompleted = false;
254 break;
255 case 'startMatchAction':
256 this.section = 'Init';
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800257 break;
Filip Kujawa9f56d0e2023-03-03 19:44:43 -0800258 case 'robotDeathAction':
259 // TODO(FILIP): Return user to the screen they
260 // clicked dead robot on. Pickup is fine for now but
261 // might cause confusion.
262 this.section = 'Pickup';
263 break;
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800264 default:
265 break;
266 }
267 }
268 }
269
Emily Markovadcadcb62024-02-03 13:07:17 -0800270 stringifyScoreType(scoreType: ScoreType): String {
271 return ScoreType[scoreType];
Emily Markovaf4b06a22023-05-10 17:44:09 -0700272 }
273
Emily Markovadcadcb62024-02-03 13:07:17 -0800274 stringifyStageType(stageType: StageType): String {
275 return StageType[stageType];
Emily Markovaf4b06a22023-05-10 17:44:09 -0700276 }
277
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800278 changeSectionTo(target: Section) {
Philipp Schrader8702b782023-04-15 17:33:37 -0700279 // Clear the messages since they won't be relevant in the next section.
280 this.errorMessage = '';
281 this.progressMessage = '';
282
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800283 this.section = target;
284 }
Philipp Schrader80587432022-03-05 15:41:22 -0800285
Ravago Jones2813c032022-03-16 23:44:11 -0700286 @ViewChild('header') header: ElementRef;
Philipp Schrader6b2e9502022-03-15 23:42:56 -0700287
Ravago Jones2813c032022-03-16 23:44:11 -0700288 private scrollToTop() {
289 this.header.nativeElement.scrollIntoView();
290 }
291
Emily Markovadcadcb62024-02-03 13:07:17 -0800292 async submit2024Actions() {
James Kuszmauldac091f2022-03-22 09:35:06 -0700293 const builder = new Builder();
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800294 const actionOffsets: number[] = [];
295
296 for (const action of this.actionList) {
297 let actionOffset: number | undefined;
298 console.log(action.type);
299
300 switch (action.type) {
301 case 'startMatchAction':
302 const startMatchActionOffset =
303 StartMatchAction.createStartMatchAction(builder, action.position);
304 actionOffset = Action.createAction(
305 builder,
Philipp Schrader8c878a22023-03-20 22:36:38 -0700306 BigInt(action.timestamp || 0),
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800307 ActionType.StartMatchAction,
308 startMatchActionOffset
309 );
310 break;
Filip Kujawa0b4b1e52023-04-15 14:05:40 -0700311 case 'mobilityAction':
312 const mobilityActionOffset = MobilityAction.createMobilityAction(
313 builder,
314 action.mobility
315 );
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800316 actionOffset = Action.createAction(
317 builder,
Philipp Schrader8c878a22023-03-20 22:36:38 -0700318 BigInt(action.timestamp || 0),
Filip Kujawa0b4b1e52023-04-15 14:05:40 -0700319 ActionType.MobilityAction,
320 mobilityActionOffset
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800321 );
322 break;
Emily Markovadcadcb62024-02-03 13:07:17 -0800323 case 'penaltyAction':
324 const penaltyActionOffset = PenaltyAction.createPenaltyAction(
325 builder,
326 action.penalties
327 );
Filip Kujawa4c286442023-03-03 10:41:22 -0800328 actionOffset = Action.createAction(
329 builder,
Philipp Schrader8c878a22023-03-20 22:36:38 -0700330 BigInt(action.timestamp || 0),
Emily Markovadcadcb62024-02-03 13:07:17 -0800331 ActionType.PenaltyAction,
332 penaltyActionOffset
Filip Kujawa4c286442023-03-03 10:41:22 -0800333 );
334 break;
Emily Markovadcadcb62024-02-03 13:07:17 -0800335 case 'pickupNoteAction':
336 const pickupNoteActionOffset =
337 PickupNoteAction.createPickupNoteAction(
Filip Kujawa0b4b1e52023-04-15 14:05:40 -0700338 builder,
Filip Kujawa0b4b1e52023-04-15 14:05:40 -0700339 action.auto || false
340 );
341 actionOffset = Action.createAction(
342 builder,
343 BigInt(action.timestamp || 0),
Emily Markovadcadcb62024-02-03 13:07:17 -0800344 ActionType.PickupNoteAction,
345 pickupNoteActionOffset
Filip Kujawa0b4b1e52023-04-15 14:05:40 -0700346 );
347 break;
Emily Markovadcadcb62024-02-03 13:07:17 -0800348 case 'placeNoteAction':
349 const placeNoteActionOffset = PlaceNoteAction.createPlaceNoteAction(
350 builder,
351 action.scoreType,
352 action.auto || false
353 );
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800354 actionOffset = Action.createAction(
355 builder,
Philipp Schrader8c878a22023-03-20 22:36:38 -0700356 BigInt(action.timestamp || 0),
Emily Markovadcadcb62024-02-03 13:07:17 -0800357 ActionType.PlaceNoteAction,
358 placeNoteActionOffset
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800359 );
360 break;
361
362 case 'robotDeathAction':
363 const robotDeathActionOffset =
364 RobotDeathAction.createRobotDeathAction(builder, action.robotOn);
365 actionOffset = Action.createAction(
366 builder,
Philipp Schrader8c878a22023-03-20 22:36:38 -0700367 BigInt(action.timestamp || 0),
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800368 ActionType.RobotDeathAction,
369 robotDeathActionOffset
370 );
371 break;
372
373 case 'endMatchAction':
374 const endMatchActionOffset = EndMatchAction.createEndMatchAction(
375 builder,
Emily Markovadcadcb62024-02-03 13:07:17 -0800376 action.stageType,
377 action.trapNote
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800378 );
379 actionOffset = Action.createAction(
380 builder,
Philipp Schrader8c878a22023-03-20 22:36:38 -0700381 BigInt(action.timestamp || 0),
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800382 ActionType.EndMatchAction,
383 endMatchActionOffset
384 );
385 break;
386
387 case 'endAutoPhase':
388 // Not important action.
389 break;
390
Emily Markovadcadcb62024-02-03 13:07:17 -0800391 case 'endTeleopPhase':
392 // Not important action.
393 break;
394
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800395 default:
396 throw new Error(`Unknown action type`);
397 }
398
399 if (actionOffset !== undefined) {
400 actionOffsets.push(actionOffset);
401 }
402 }
Emily Markovae68b7632023-12-30 14:17:55 -0800403 const teamNumberFb = builder.createString(this.teamNumber);
Philipp Schradere859e6e2023-03-22 19:59:51 -0700404 const compLevelFb = builder.createString(this.compLevel);
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800405
Emily Markovadcadcb62024-02-03 13:07:17 -0800406 const actionsVector = Submit2024Actions.createActionsListVector(
Philipp Schrader817cce32022-03-26 15:00:00 -0700407 builder,
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800408 actionOffsets
Philipp Schrader817cce32022-03-26 15:00:00 -0700409 );
Emily Markovadcadcb62024-02-03 13:07:17 -0800410 Submit2024Actions.startSubmit2024Actions(builder);
411 Submit2024Actions.addTeamNumber(builder, teamNumberFb);
412 Submit2024Actions.addMatchNumber(builder, this.matchNumber);
413 Submit2024Actions.addSetNumber(builder, this.setNumber);
414 Submit2024Actions.addCompLevel(builder, compLevelFb);
415 Submit2024Actions.addActionsList(builder, actionsVector);
416 Submit2024Actions.addPreScouting(builder, this.preScouting);
417 builder.finish(Submit2024Actions.endSubmit2024Actions(builder));
Ravago Jones2813c032022-03-16 23:44:11 -0700418
419 const buffer = builder.asUint8Array();
Emily Markovadcadcb62024-02-03 13:07:17 -0800420 const res = await fetch('/requests/submit/submit_2024_actions', {
Philipp Schrader817cce32022-03-26 15:00:00 -0700421 method: 'POST',
422 body: buffer,
423 });
Ravago Jones2813c032022-03-16 23:44:11 -0700424
425 if (res.ok) {
426 // We successfully submitted the data. Report success.
427 this.section = 'Success';
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800428 this.actionList = [];
Ravago Jones2813c032022-03-16 23:44:11 -0700429 } else {
430 const resBuffer = await res.arrayBuffer();
431 const fbBuffer = new ByteBuffer(new Uint8Array(resBuffer));
James Kuszmauldac091f2022-03-22 09:35:06 -0700432 const parsedResponse = ErrorResponse.getRootAsErrorResponse(fbBuffer);
Ravago Jones2813c032022-03-16 23:44:11 -0700433
434 const errorMessage = parsedResponse.errorMessage();
Philipp Schrader817cce32022-03-26 15:00:00 -0700435 this.errorMessage = `Received ${res.status} ${res.statusText}: "${errorMessage}"`;
Alex Perrybb3d2062022-03-05 18:14:33 -0800436 }
Ravago Jones2813c032022-03-16 23:44:11 -0700437 }
Philipp Schrader80587432022-03-05 15:41:22 -0800438}