scouting: Deduplicate action definitions in entry.component.ts
I found it tedious to keep the definitions up-to-date between the .fbs
file and the entry.component.ts file. This patch fixes the issue by
using the generated flatbuffer types everywhere.
I added a helper library to deal with `actionTakenType` properly.
Otherwise the user would have to deal with it manually.
Since Angular templates don't seem to allow manual type casting, I
created a pipe to do it for me.
Signed-off-by: Philipp Schrader <philipp.schrader@gmail.com>
Change-Id: I5f60e2d7b89978f40b5758bb8d04e800d6de230d
diff --git a/scouting/www/entry/entry.component.ts b/scouting/www/entry/entry.component.ts
index d2c922e..cd59884 100644
--- a/scouting/www/entry/entry.component.ts
+++ b/scouting/www/entry/entry.component.ts
@@ -12,20 +12,29 @@
import {ErrorResponse} from '@org_frc971/scouting/webserver/requests/messages/error_response_generated';
import {
StartMatchAction,
+ StartMatchActionT,
ScoreType,
StageType,
Submit2024Actions,
MobilityAction,
+ MobilityActionT,
PenaltyAction,
+ PenaltyActionT,
PickupNoteAction,
+ PickupNoteActionT,
PlaceNoteAction,
+ PlaceNoteActionT,
RobotDeathAction,
+ RobotDeathActionT,
EndMatchAction,
+ EndMatchActionT,
ActionType,
Action,
+ ActionT,
} from '@org_frc971/scouting/webserver/requests/messages/submit_2024_actions_generated';
import {Match} from '@org_frc971/scouting/webserver/requests/messages/request_all_matches_response_generated';
import {MatchListRequestor} from '@org_frc971/scouting/www/rpc';
+import {ActionHelper, ConcreteAction} from './action_helper';
import * as pako from 'pako';
type Section =
@@ -59,57 +68,12 @@
// The default index into QR_CODE_PIECE_SIZES.
const DEFAULT_QR_CODE_PIECE_SIZE_INDEX = QR_CODE_PIECE_SIZES.indexOf(750);
-type ActionT =
- | {
- type: 'startMatchAction';
- timestamp?: number;
- position: number;
- }
- | {
- type: 'mobilityAction';
- timestamp?: number;
- mobility: boolean;
- }
- | {
- type: 'pickupNoteAction';
- timestamp?: number;
- auto?: boolean;
- }
- | {
- type: 'placeNoteAction';
- timestamp?: number;
- scoreType: ScoreType;
- auto?: boolean;
- }
- | {
- type: 'robotDeathAction';
- timestamp?: number;
- robotDead: boolean;
- }
- | {
- type: 'penaltyAction';
- timestamp?: number;
- penalties: number;
- }
- | {
- type: 'endMatchAction';
- stageType: StageType;
- trapNote: boolean;
- spotlight: boolean;
- timestamp?: number;
- }
- | {
- // This is not a action that is submitted,
- // It is used for undoing purposes.
- type: 'endAutoPhase';
- timestamp?: number;
- }
- | {
- // This is not a action that is submitted,
- // It is used for undoing purposes.
- type: 'endTeleopPhase';
- timestamp?: number;
- };
+// The actions that are purely used for tracking state. They don't actually
+// have any permanent meaning and will not be saved in the database.
+const STATE_ACTIONS: ActionType[] = [
+ ActionType.EndAutoPhaseAction,
+ ActionType.EndTeleopPhaseAction,
+];
@Component({
selector: 'app-entry',
@@ -124,6 +88,15 @@
readonly QR_CODE_PIECE_SIZES = QR_CODE_PIECE_SIZES;
readonly ScoreType = ScoreType;
readonly StageType = StageType;
+ readonly ActionT = ActionT;
+ readonly ActionType = ActionType;
+ readonly StartMatchActionT = StartMatchActionT;
+ readonly MobilityActionT = MobilityActionT;
+ readonly PickupNoteActionT = PickupNoteActionT;
+ readonly PlaceNoteActionT = PlaceNoteActionT;
+ readonly RobotDeathActionT = RobotDeathActionT;
+ readonly PenaltyActionT = PenaltyActionT;
+ readonly EndMatchActionT = EndMatchActionT;
section: Section = 'Team Selection';
@Input() matchNumber: number = 1;
@@ -136,6 +109,7 @@
matchList: Match[] = [];
+ actionHelper: ActionHelper;
actionList: ActionT[] = [];
progressMessage: string = '';
errorMessage: string = '';
@@ -168,6 +142,12 @@
constructor(private readonly matchListRequestor: MatchListRequestor) {}
ngOnInit() {
+ this.actionHelper = new ActionHelper(
+ (actionType: ActionType, action: ConcreteAction) => {
+ this.addAction(actionType, action);
+ }
+ );
+
// When the user navigated from the match list, we can skip the team
// selection. I.e. we trust that the user clicked the correct button.
this.section = this.skipTeamSelection ? 'Init' : 'Team Selection';
@@ -236,60 +216,58 @@
}
addPenalties(): void {
- this.addAction({type: 'penaltyAction', penalties: this.penalties});
+ this.actionHelper.addPenaltyAction({penalties: this.penalties});
}
- addAction(action: ActionT): void {
- if (action.type == 'startMatchAction') {
+ addAction(actionType: ActionType, action: ConcreteAction): void {
+ let timestamp: number = 0;
+
+ if (actionType == ActionType.StartMatchAction) {
// Unix nanosecond timestamp.
this.matchStartTimestamp = Date.now() * 1e6;
- action.timestamp = 0;
} else {
// Unix nanosecond timestamp relative to match start.
- action.timestamp = Date.now() * 1e6 - this.matchStartTimestamp;
+ timestamp = Date.now() * 1e6 - this.matchStartTimestamp;
}
- if (action.type == 'endMatchAction') {
+ if (actionType == ActionType.EndMatchAction) {
// endMatchAction occurs at the same time as penaltyAction so add to its
// timestamp to make it unique.
- action.timestamp += 1;
+ timestamp += 1;
}
- if (action.type == 'mobilityAction') {
+ if (actionType == ActionType.MobilityAction) {
this.mobilityCompleted = true;
}
- if (action.type == 'pickupNoteAction' || action.type == 'placeNoteAction') {
- action.auto = this.autoPhase;
- }
- this.actionList.push(action);
+ this.actionList.push(new ActionT(BigInt(timestamp), actionType, action));
}
undoLastAction() {
if (this.actionList.length > 0) {
let lastAction = this.actionList.pop();
- switch (lastAction?.type) {
- case 'endAutoPhase':
+ switch (lastAction?.actionTakenType) {
+ case ActionType.EndAutoPhaseAction:
this.autoPhase = true;
this.section = 'Pickup';
- case 'pickupNoteAction':
+ case ActionType.PickupNoteAction:
this.section = 'Pickup';
break;
- case 'endTeleopPhase':
+ case ActionType.EndTeleopPhaseAction:
this.section = 'Pickup';
break;
- case 'placeNoteAction':
+ case ActionType.PlaceNoteAction:
this.section = 'Place';
break;
- case 'endMatchAction':
+ case ActionType.EndMatchAction:
this.section = 'Endgame';
- case 'mobilityAction':
+ case ActionType.MobilityAction:
this.mobilityCompleted = false;
break;
- case 'startMatchAction':
+ case ActionType.StartMatchAction:
this.section = 'Init';
break;
- case 'robotDeathAction':
+ case ActionType.RobotDeathAction:
// TODO(FILIP): Return user to the screen they
// clicked dead robot on. Pickup is fine for now but
// might cause confusion.
@@ -331,111 +309,11 @@
const actionOffsets: number[] = [];
for (const action of this.actionList) {
- let actionOffset: number | undefined;
-
- switch (action.type) {
- case 'startMatchAction':
- const startMatchActionOffset =
- StartMatchAction.createStartMatchAction(builder, action.position);
- actionOffset = Action.createAction(
- builder,
- BigInt(action.timestamp || 0),
- ActionType.StartMatchAction,
- startMatchActionOffset
- );
- break;
- case 'mobilityAction':
- const mobilityActionOffset = MobilityAction.createMobilityAction(
- builder,
- action.mobility
- );
- actionOffset = Action.createAction(
- builder,
- BigInt(action.timestamp || 0),
- ActionType.MobilityAction,
- mobilityActionOffset
- );
- break;
- case 'penaltyAction':
- const penaltyActionOffset = PenaltyAction.createPenaltyAction(
- builder,
- action.penalties
- );
- actionOffset = Action.createAction(
- builder,
- BigInt(action.timestamp || 0),
- ActionType.PenaltyAction,
- penaltyActionOffset
- );
- break;
- case 'pickupNoteAction':
- const pickupNoteActionOffset =
- PickupNoteAction.createPickupNoteAction(
- builder,
- action.auto || false
- );
- actionOffset = Action.createAction(
- builder,
- BigInt(action.timestamp || 0),
- ActionType.PickupNoteAction,
- pickupNoteActionOffset
- );
- break;
- case 'placeNoteAction':
- const placeNoteActionOffset = PlaceNoteAction.createPlaceNoteAction(
- builder,
- action.scoreType,
- action.auto || false
- );
- actionOffset = Action.createAction(
- builder,
- BigInt(action.timestamp || 0),
- ActionType.PlaceNoteAction,
- placeNoteActionOffset
- );
- break;
-
- case 'robotDeathAction':
- const robotDeathActionOffset =
- RobotDeathAction.createRobotDeathAction(builder, action.robotDead);
- actionOffset = Action.createAction(
- builder,
- BigInt(action.timestamp || 0),
- ActionType.RobotDeathAction,
- robotDeathActionOffset
- );
- break;
-
- case 'endMatchAction':
- const endMatchActionOffset = EndMatchAction.createEndMatchAction(
- builder,
- action.stageType,
- action.trapNote,
- action.spotlight
- );
- actionOffset = Action.createAction(
- builder,
- BigInt(action.timestamp || 0),
- ActionType.EndMatchAction,
- endMatchActionOffset
- );
- break;
-
- case 'endAutoPhase':
- // Not important action.
- break;
-
- case 'endTeleopPhase':
- // Not important action.
- break;
-
- default:
- throw new Error(`Unknown action type`);
+ if (STATE_ACTIONS.includes(action.actionTakenType)) {
+ // Actions only used for undo purposes are not submitted.
+ continue;
}
-
- if (actionOffset !== undefined) {
- actionOffsets.push(actionOffset);
- }
+ actionOffsets.push(action.pack(builder));
}
const teamNumberFb = builder.createString(this.teamNumber);
const compLevelFb = builder.createString(this.compLevel);