blob: d88c25aa838432847797a62d517f2f053376e651 [file] [log] [blame]
import {Component, OnInit} from '@angular/core';
import {Builder, ByteBuffer} from 'flatbuffers';
import {ErrorResponse} from '@org_frc971/scouting/webserver/requests/messages/error_response_generated';
import {
Ranking,
RequestAllDriverRankingsResponse,
} from '@org_frc971/scouting/webserver/requests/messages/request_all_driver_rankings_response_generated';
import {
Stats2024,
Request2024DataScoutingResponse,
} from '@org_frc971/scouting/webserver/requests/messages/request_2024_data_scouting_response_generated';
import {
PitImage,
RequestAllPitImagesResponse,
} from '@org_frc971/scouting/webserver/requests/messages/request_all_pit_images_response_generated';
import {
Note,
RequestAllNotesResponse,
} from '@org_frc971/scouting/webserver/requests/messages/request_all_notes_response_generated';
import {Delete2024DataScouting} from '@org_frc971/scouting/webserver/requests/messages/delete_2024_data_scouting_generated';
import {Delete2024DataScoutingResponse} from '@org_frc971/scouting/webserver/requests/messages/delete_2024_data_scouting_response_generated';
import {
MatchListRequestor,
ViewDataRequestor,
} from '@org_frc971/scouting/www/rpc';
type Source = 'Notes' | 'Stats2024' | 'PitImages' | 'DriverRanking';
//TODO(Filip): Deduplicate
const COMP_LEVEL_LABELS = {
qm: 'Qualifications',
ef: 'Eighth Finals',
qf: 'Quarter Finals',
sf: 'Semi Finals',
f: 'Finals',
};
@Component({
selector: 'app-view',
templateUrl: './view.ng.html',
styleUrls: ['../app/common.css', './view.component.css'],
})
export class ViewComponent {
constructor(private readonly viewDataRequestor: ViewDataRequestor) {}
// Make COMP_LEVEL_LABELS available in view.ng.html.
readonly COMP_LEVEL_LABELS = COMP_LEVEL_LABELS;
// Progress and error messages to display to
// the user when fetching data.
progressMessage: string = '';
errorMessage: string = '';
// The current data source being displayed.
currentSource: Source = 'Notes';
// Current sort (ascending/descending match numbers).
// noteList is sorted based on team number until match
// number is added for note scouting.
ascendingSort = true;
// Stores the corresponding data.
noteList: Note[] = [];
driverRankingList: Ranking[] = [];
pitImageList: PitImage[][] = [];
statList: Stats2024[] = [];
// Fetch notes on initialization.
ngOnInit() {
this.fetchCurrentSource();
}
// Called when a user changes the sort direction.
// Changes the data order between ascending/descending.
sortData() {
this.ascendingSort = !this.ascendingSort;
if (!this.ascendingSort) {
this.driverRankingList.sort((a, b) => b.matchNumber() - a.matchNumber());
this.noteList.sort(function (a, b) {
return b.team().localeCompare(a.team(), undefined, {numeric: true});
});
this.pitImageList.sort(function (a, b) {
return b[0]
.teamNumber()
.localeCompare(a[0].teamNumber(), undefined, {numeric: true});
});
this.statList.sort((a, b) => b.matchNumber() - a.matchNumber());
} else {
this.driverRankingList.sort((a, b) => a.matchNumber() - b.matchNumber());
this.noteList.sort(function (a, b) {
return a.team().localeCompare(b.team(), undefined, {numeric: true});
});
this.pitImageList.sort(function (a, b) {
return a[0]
.teamNumber()
.localeCompare(b[0].teamNumber(), undefined, {numeric: true});
});
this.statList.sort((a, b) => a.matchNumber() - b.matchNumber());
}
}
// Called when a user selects a new data source
// from the dropdown.
switchDataSource(target: Source) {
this.currentSource = target;
this.progressMessage = '';
this.errorMessage = '';
this.noteList = [];
this.driverRankingList = [];
this.statList = [];
this.pitImageList = [];
this.fetchCurrentSource();
}
// Call the method to fetch data for the current source.
fetchCurrentSource() {
switch (this.currentSource) {
case 'Notes': {
this.fetchNotes();
}
case 'Stats2024': {
this.fetchStats2024();
}
case 'PitImages': {
this.fetchPitImages();
}
case 'DriverRanking': {
this.fetchDriverRanking();
}
}
}
// TODO(Filip): Add delete functionality.
// Gets called when a user clicks the delete icon (note scouting).
async deleteNoteData() {
const block_alerts = document.getElementById(
'block_alerts'
) as HTMLInputElement;
if (block_alerts.checked || window.confirm('Actually delete data?')) {
this.errorMessage = 'Deleting data has not been implemented yet.';
return;
}
}
// TODO(Filip): Add delete functionality.
// Gets called when a user clicks the delete icon (driver ranking).
async deleteDriverRankingData() {
const block_alerts = document.getElementById(
'block_alerts'
) as HTMLInputElement;
if (block_alerts.checked || window.confirm('Actually delete data?')) {
this.errorMessage = 'Deleting data has not been implemented yet.';
return;
}
}
// Gets called when a user clicks the delete icon.
async delete2024DataScouting(
compLevel: string,
matchNumber: number,
setNumber: number,
teamNumber: string
) {
const block_alerts = document.getElementById(
'block_alerts'
) as HTMLInputElement;
if (block_alerts.checked || window.confirm('Actually delete data?')) {
await this.requestDelete2024DataScouting(
compLevel,
matchNumber,
setNumber,
teamNumber
);
await this.fetchStats2024();
}
}
async requestDelete2024DataScouting(
compLevel: string,
matchNumber: number,
setNumber: number,
teamNumber: string
) {
this.progressMessage = 'Deleting data. Please be patient.';
const builder = new Builder();
const compLevelData = builder.createString(compLevel);
const teamNumberData = builder.createString(teamNumber);
builder.finish(
Delete2024DataScouting.createDelete2024DataScouting(
builder,
compLevelData,
matchNumber,
setNumber,
teamNumberData
)
);
const buffer = builder.asUint8Array();
const res = await fetch('/requests/delete/delete_2024_data_scouting', {
method: 'POST',
body: buffer,
});
if (!res.ok) {
const resBuffer = await res.arrayBuffer();
const fbBuffer = new ByteBuffer(new Uint8Array(resBuffer));
const parsedResponse = ErrorResponse.getRootAsErrorResponse(fbBuffer);
const errorMessage = parsedResponse.errorMessage();
this.errorMessage = `Received ${res.status} ${res.statusText}: "${errorMessage}"`;
}
}
// Fetch all driver ranking data and store in driverRankingList.
async fetchDriverRanking() {
this.progressMessage = 'Fetching driver ranking data. Please be patient.';
this.errorMessage = '';
try {
this.driverRankingList =
await this.viewDataRequestor.fetchDriverRankingList();
this.progressMessage = 'Successfully fetched driver ranking data.';
} catch (e) {
this.errorMessage = e;
this.progressMessage = '';
}
}
// Fetch all pit image data and store in pitImageList.
async fetchPitImages() {
this.progressMessage = 'Fetching pit image list. Please be patient.';
this.errorMessage = '';
try {
const initialPitImageList =
await this.viewDataRequestor.fetchPitImagesList();
let officialPitImageList = [];
// Use iteration to make an array of arrays containing pit image data for individual teams.
// Ex. [ [ {971PitImageData} , {971PitImage2Data} ], [ {432PitImageData} ] , [ {213PitImageData} ] ]
for (let pitImage of initialPitImageList) {
let found = false;
for (let arr of officialPitImageList) {
if (arr[0].teamNumber() == pitImage.teamNumber()) {
arr.push(pitImage);
found = true;
}
}
if (!found) {
officialPitImageList.push([pitImage]);
}
}
// Sort the arrays based on image file names so their order is predictable.
for (let arr of officialPitImageList) {
arr.sort((a, b) => (a.imagePath() > b.imagePath() ? 1 : -1));
}
this.pitImageList = officialPitImageList;
this.sortData();
this.progressMessage = 'Successfully fetched pit image list.';
} catch (e) {
this.errorMessage = e;
this.progressMessage = '';
}
}
// Fetch all data scouting (stats) data and store in statList.
async fetchStats2024() {
this.progressMessage = 'Fetching stats list. Please be patient.';
this.errorMessage = '';
try {
this.statList = await this.viewDataRequestor.fetchStats2024List();
this.progressMessage = 'Successfully fetched stats list.';
} catch (e) {
this.errorMessage = e;
this.progressMessage = '';
}
}
// Fetch all notes data and store in noteList.
async fetchNotes() {
this.progressMessage = 'Fetching notes list. Please be patient.';
this.errorMessage = '';
try {
this.noteList = await this.viewDataRequestor.fetchNoteList();
this.progressMessage = 'Successfully fetched note list.';
} catch (e) {
this.errorMessage = e;
this.progressMessage = '';
}
}
// Parse all selected keywords for a note entry
// into one string to be displayed in the table.
parseKeywords(entry: Note) {
let parsedKeywords = '';
if (entry.goodDriving()) {
parsedKeywords += 'Good Driving ';
}
if (entry.badDriving()) {
parsedKeywords += 'Bad Driving ';
}
if (entry.noShow()) {
parsedKeywords += 'No Show ';
}
if (entry.solidPlacing()) {
parsedKeywords += 'Solid Placing ';
}
if (entry.sketchyPlacing()) {
parsedKeywords += 'Sketchy Placing ';
}
if (entry.goodDefense()) {
parsedKeywords += 'Good Defense ';
}
if (entry.badDefense()) {
parsedKeywords += 'Bad Defense ';
}
if (entry.easilyDefended()) {
parsedKeywords += 'Easily Defended';
}
return parsedKeywords;
}
}