Add Pit Scouting Tab

Signed-off-by: Emily Markova <emily.markova@gmail.com>
Change-Id: Iede446546e20f2915bb53e134050b5025976da36
diff --git a/scouting/www/pit_scouting/BUILD b/scouting/www/pit_scouting/BUILD
new file mode 100644
index 0000000..740dee1
--- /dev/null
+++ b/scouting/www/pit_scouting/BUILD
@@ -0,0 +1,18 @@
+load("@npm//:defs.bzl", "npm_link_all_packages")
+load("//tools/build_rules:js.bzl", "ng_pkg")
+
+npm_link_all_packages(name = "node_modules")
+
+ng_pkg(
+    name = "pit_scouting",
+    extra_srcs = [
+        "//scouting/www:app_common_css",
+    ],
+    deps = [
+        ":node_modules/@angular/forms",
+        "//scouting/webserver/requests/messages:error_response_ts_fbs",
+        "//scouting/webserver/requests/messages:submit_pit_image_response_ts_fbs",
+        "//scouting/webserver/requests/messages:submit_pit_image_ts_fbs",
+        "@com_github_google_flatbuffers//ts:flatbuffers_ts",
+    ],
+)
diff --git a/scouting/www/pit_scouting/package.json b/scouting/www/pit_scouting/package.json
new file mode 100644
index 0000000..e58fc51
--- /dev/null
+++ b/scouting/www/pit_scouting/package.json
@@ -0,0 +1,7 @@
+{
+	"name": "@org_frc971/scouting/www/pit_scouting",
+	"private": true,
+	"dependencies": {
+			"@angular/forms": "15.1.5"
+	}
+}
diff --git a/scouting/www/pit_scouting/pit_scouting.component.css b/scouting/www/pit_scouting/pit_scouting.component.css
new file mode 100644
index 0000000..b224d73
--- /dev/null
+++ b/scouting/www/pit_scouting/pit_scouting.component.css
@@ -0,0 +1,8 @@
+* {
+  padding: 10px;
+}
+
+button {
+  touch-action: manipulation;
+  margin-top: 1vh;
+}
diff --git a/scouting/www/pit_scouting/pit_scouting.component.ts b/scouting/www/pit_scouting/pit_scouting.component.ts
new file mode 100644
index 0000000..b74c119
--- /dev/null
+++ b/scouting/www/pit_scouting/pit_scouting.component.ts
@@ -0,0 +1,75 @@
+import {Component} from '@angular/core';
+import {Builder, ByteBuffer} from 'flatbuffers';
+import {ErrorResponse} from '../../webserver/requests/messages/error_response_generated';
+import {SubmitPitImage} from '../../webserver/requests/messages/submit_pit_image_generated';
+
+type Section = 'TeamSelection' | 'Data';
+
+interface Input {
+  teamNumber: number;
+  pitImage: HTMLImageElement;
+}
+
+@Component({
+  selector: 'app-pit-scouting',
+  templateUrl: './pit_scouting.ng.html',
+  styleUrls: ['../app/common.css', './pit_scouting.component.css'],
+})
+export class PitScoutingComponent {
+  section: Section = 'Data';
+
+  errorMessage = '';
+  teamNumber: number = 971;
+  pitImage: string = '';
+
+  async readFile(file): Promise<ArrayBuffer> {
+    return new Promise((resolve, reject) => {
+      let reader = new FileReader();
+      reader.addEventListener('loadend', (e) =>
+        resolve(e.target.result as ArrayBuffer)
+      );
+      reader.addEventListener('error', reject);
+      reader.readAsArrayBuffer(file);
+    });
+  }
+
+  async getImageData() {
+    const selectedFile = (<HTMLInputElement>document.getElementById('pitImage'))
+      .files[0];
+    return new Uint8Array(await this.readFile(selectedFile));
+  }
+
+  async submitData() {
+    const builder = new Builder();
+    const teamNumber = builder.createString(this.teamNumber.toString());
+    const pitImage = builder.createString(this.pitImage.toString());
+    const imageData = SubmitPitImage.createImageDataVector(
+      builder,
+      await this.getImageData()
+    );
+    builder.finish(
+      SubmitPitImage.createSubmitPitImage(
+        builder,
+        teamNumber,
+        pitImage,
+        imageData
+      )
+    );
+
+    const buffer = builder.asUint8Array();
+    const res = await fetch('/requests/submit/submit_pit_image', {
+      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}"`;
+    }
+    this.section = 'TeamSelection';
+    this.pitImage = '';
+  }
+}
diff --git a/scouting/www/pit_scouting/pit_scouting.module.ts b/scouting/www/pit_scouting/pit_scouting.module.ts
new file mode 100644
index 0000000..7d92f6c
--- /dev/null
+++ b/scouting/www/pit_scouting/pit_scouting.module.ts
@@ -0,0 +1,11 @@
+import {NgModule} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {FormsModule} from '@angular/forms';
+import {PitScoutingComponent} from './pit_scouting.component';
+
+@NgModule({
+  declarations: [PitScoutingComponent],
+  exports: [PitScoutingComponent],
+  imports: [CommonModule, FormsModule],
+})
+export class PitScoutingModule {}
diff --git a/scouting/www/pit_scouting/pit_scouting.ng.html b/scouting/www/pit_scouting/pit_scouting.ng.html
new file mode 100644
index 0000000..98aa313
--- /dev/null
+++ b/scouting/www/pit_scouting/pit_scouting.ng.html
@@ -0,0 +1,30 @@
+<div class="header">
+  <h2>Pit Scouting</h2>
+</div>
+<ng-container [ngSwitch]="section">
+  <div *ngSwitchCase="'Data'" id="pitImageSelection" class="container-fluid">
+    <label for="teamNumber">Team Number</label>
+    <input
+      [(ngModel)]="teamNumber"
+      type="string"
+      id="teamNumber"
+      min="1"
+      max="9999"
+    />
+    <form action="setPitImage()">
+      <label for="pitImage">Select pit image:</label>
+      <br />
+      <input
+        id="pitImage"
+        [(ngModel)]="pitImage"
+        type="file"
+        accept="image/*"
+      />
+    </form>
+    <button id="submit-button" class="btn btn-success" (click)="submitData()">
+      Submit
+    </button>
+  </div>
+
+  <span class="error_message" role="alert">{{ errorMessage }}</span>
+</ng-container>