Merge "Delete //scouting/www:devserver"
diff --git a/aos/network/www/aos_plotter.ts b/aos/network/www/aos_plotter.ts
index b7d8771..cd2e131 100644
--- a/aos/network/www/aos_plotter.ts
+++ b/aos/network/www/aos_plotter.ts
@@ -28,7 +28,7 @@
 import {Connection} from 'org_frc971/aos/network/www/proxy';
 import {SubscriberRequest, ChannelRequest, TransferMethod} from 'org_frc971/aos/network/web_proxy_generated';
 import {Parser, Table} from 'org_frc971/aos/network/www/reflection'
-import {Schema} from 'org_frc971/external/com_github_google_flatbuffers/reflection/reflection_generated';
+import {Schema} from 'flatbuffers_reflection/reflection_generated';
 import {ByteBuffer} from 'flatbuffers';
 
 export class TimestampedMessage {
diff --git a/aos/network/www/proxy.ts b/aos/network/www/proxy.ts
index cf8e972..0bda8b9 100644
--- a/aos/network/www/proxy.ts
+++ b/aos/network/www/proxy.ts
@@ -1,7 +1,7 @@
 import {Builder, ByteBuffer, Offset} from 'flatbuffers';
 import {Channel as ChannelFb, Configuration} from 'org_frc971/aos/configuration_generated';
 import {ChannelRequest as ChannelRequestFb, ChannelState, MessageHeader, Payload, SdpType, SubscriberRequest, TransferMethod, WebSocketIce, WebSocketMessage, WebSocketSdp} from 'org_frc971/aos/network/web_proxy_generated';
-import {Schema} from 'org_frc971/external/com_github_google_flatbuffers/reflection/reflection_generated';
+import {Schema} from 'flatbuffers_reflection/reflection_generated';
 
 // There is one handler for each DataChannel, it maintains the state of
 // multi-part messages and delegates to a callback when the message is fully
diff --git a/frc971/analysis/plot_data_utils.ts b/frc971/analysis/plot_data_utils.ts
index 230e370..80c4cca 100644
--- a/frc971/analysis/plot_data_utils.ts
+++ b/frc971/analysis/plot_data_utils.ts
@@ -5,7 +5,7 @@
 import {ByteBuffer} from 'flatbuffers';
 import {Plot, Point} from 'org_frc971/aos/network/www/plotter';
 import {Connection} from 'org_frc971/aos/network/www/proxy';
-import {Schema} from 'org_frc971/external/com_github_google_flatbuffers/reflection/reflection_generated';
+import {Schema} from 'flatbuffers_reflection/reflection_generated';
 
 export function plotData(conn: Connection, parentDiv: Element) {
   // Set up a selection box to allow the user to choose between plots to show.
diff --git a/frc971/wpilib/imu_plot_utils.ts b/frc971/wpilib/imu_plot_utils.ts
index 08b3c13..c4b516b 100644
--- a/frc971/wpilib/imu_plot_utils.ts
+++ b/frc971/wpilib/imu_plot_utils.ts
@@ -5,7 +5,7 @@
 import {Point} from 'org_frc971/aos/network/www/plotter';
 import {Table} from 'org_frc971/aos/network/www/reflection';
 import {ByteBuffer} from 'flatbuffers';
-import {Schema} from 'org_frc971/external/com_github_google_flatbuffers/reflection/reflection_generated';
+import {Schema} from 'flatbuffers_reflection/reflection_generated';
 
 const FILTER_WINDOW_SIZE = 100;
 
diff --git a/scouting/webserver/static/static.go b/scouting/webserver/static/static.go
index 57824d9..92d8086 100644
--- a/scouting/webserver/static/static.go
+++ b/scouting/webserver/static/static.go
@@ -32,8 +32,11 @@
 	return http.HandlerFunc(fn)
 }
 
-// Serve pages given a port, directory to serve from, and an channel to pass the errors back to the caller.
+// Serve pages in the specified directory.
 func ServePages(scoutingServer server.ScoutingServer, directory string) {
 	// Serve the / endpoint given a folder of pages.
 	scoutingServer.Handle("/", NoCache(http.FileServer(http.Dir(directory))))
+	// Make an exception for pictures. We don't want the pictures to be
+	// pulled every time the page is refreshed.
+	scoutingServer.Handle("/pictures/", http.FileServer(http.Dir(directory)))
 }
diff --git a/scouting/www/BUILD b/scouting/www/BUILD
index 80b8c7f..e33416b 100644
--- a/scouting/www/BUILD
+++ b/scouting/www/BUILD
@@ -20,6 +20,7 @@
         "//scouting/www/import_match_list",
         "//scouting/www/match_list",
         "//scouting/www/notes",
+        "//scouting/www/shift_schedule",
         "@npm//@angular/animations",
         "@npm//@angular/common",
         "@npm//@angular/core",
diff --git a/scouting/www/app.ng.html b/scouting/www/app.ng.html
index ab1e433..1357228 100644
--- a/scouting/www/app.ng.html
+++ b/scouting/www/app.ng.html
@@ -46,6 +46,15 @@
       Import Match List
     </a>
   </li>
+  <li class="nav-item">
+    <a
+      class="nav-link"
+      [class.active]="tabIs('ShiftSchedule')"
+      (click)="switchTabToGuarded('ShiftSchedule')"
+    >
+      Shift Schedule
+    </a>
+  </li>
 </ul>
 
 <ng-container [ngSwitch]="tab">
@@ -63,4 +72,5 @@
   <app-import-match-list
     *ngSwitchCase="'ImportMatchList'"
   ></app-import-match-list>
+  <shift-schedule *ngSwitchCase="'ShiftSchedule'"></shift-schedule>
 </ng-container>
diff --git a/scouting/www/app.ts b/scouting/www/app.ts
index 02af125..85620f1 100644
--- a/scouting/www/app.ts
+++ b/scouting/www/app.ts
@@ -1,6 +1,11 @@
 import {Component, ElementRef, ViewChild} from '@angular/core';
 
-type Tab = 'MatchList' | 'Notes' | 'Entry' | 'ImportMatchList';
+type Tab =
+  | 'MatchList'
+  | 'Notes'
+  | 'Entry'
+  | 'ImportMatchList'
+  | 'ShiftSchedule';
 type TeamInMatch = {
   teamNumber: number;
   matchNumber: number;
diff --git a/scouting/www/app_module.ts b/scouting/www/app_module.ts
index 9c762f6..b3c788f 100644
--- a/scouting/www/app_module.ts
+++ b/scouting/www/app_module.ts
@@ -7,6 +7,7 @@
 import {ImportMatchListModule} from './import_match_list/import_match_list.module';
 import {MatchListModule} from './match_list/match_list.module';
 import {NotesModule} from './notes/notes.module';
+import {ShiftScheduleModule} from './shift_schedule/shift_schedule.module';
 
 @NgModule({
   declarations: [App],
@@ -17,6 +18,7 @@
     NotesModule,
     ImportMatchListModule,
     MatchListModule,
+    ShiftScheduleModule,
   ],
   exports: [App],
   bootstrap: [App],
diff --git a/scouting/www/shift_schedule/BUILD b/scouting/www/shift_schedule/BUILD
new file mode 100644
index 0000000..8fe99e4
--- /dev/null
+++ b/scouting/www/shift_schedule/BUILD
@@ -0,0 +1,27 @@
+load("@npm//@bazel/typescript:index.bzl", "ts_library")
+
+ts_library(
+    name = "shift_schedule",
+    srcs = [
+        "shift_schedule.component.ts",
+        "shift_schedule.module.ts",
+    ],
+    angular_assets = [
+        "shift_schedule.component.css",
+        "shift_schedule.ng.html",
+        "//scouting/www:common_css",
+    ],
+    compiler = "//tools:tsc_wrapped_with_angular",
+    target_compatible_with = ["@platforms//cpu:x86_64"],
+    use_angular_plugin = True,
+    visibility = ["//visibility:public"],
+    deps = [
+        "//scouting/webserver/requests/messages:error_response_ts_fbs",
+        "//scouting/webserver/requests/messages:request_all_matches_response_ts_fbs",
+        "//scouting/webserver/requests/messages:request_all_matches_ts_fbs",
+        "@com_github_google_flatbuffers//ts:flatbuffers_ts",
+        "@npm//@angular/common",
+        "@npm//@angular/core",
+        "@npm//@angular/forms",
+    ],
+)
diff --git a/scouting/www/shift_schedule/shift_schedule.component.css b/scouting/www/shift_schedule/shift_schedule.component.css
new file mode 100644
index 0000000..bafeb4c
--- /dev/null
+++ b/scouting/www/shift_schedule/shift_schedule.component.css
@@ -0,0 +1,17 @@
+* {
+  padding: 5px;
+}
+
+.badge {
+  height: 20px;
+}
+
+.red {
+  background-color: #dc3545;
+  border-radius: 5px;
+}
+
+.blue {
+  background-color: #0d6efd;
+  border-radius: 5px;
+}
diff --git a/scouting/www/shift_schedule/shift_schedule.component.ts b/scouting/www/shift_schedule/shift_schedule.component.ts
new file mode 100644
index 0000000..2f22653
--- /dev/null
+++ b/scouting/www/shift_schedule/shift_schedule.component.ts
@@ -0,0 +1,15 @@
+import {Component, OnInit} from '@angular/core';
+import {Builder, ByteBuffer} from 'flatbuffers';
+import {ErrorResponse} from 'org_frc971/scouting/webserver/requests/messages/error_response_generated';
+
+@Component({
+  selector: 'shift-schedule',
+  templateUrl: './shift_schedule.ng.html',
+  styleUrls: ['../common.css', './shift_schedule.component.css'],
+})
+export class ShiftsComponent {
+  progressMessage: string = '';
+  errorMessage: string = '';
+  // used to calculate shift blocks from starting match to ending match
+  numMatches: number[] = [20, 40, 60, 80, 100, 120, 140, 160, 180, 200];
+}
diff --git a/scouting/www/shift_schedule/shift_schedule.module.ts b/scouting/www/shift_schedule/shift_schedule.module.ts
new file mode 100644
index 0000000..0e5aa48
--- /dev/null
+++ b/scouting/www/shift_schedule/shift_schedule.module.ts
@@ -0,0 +1,12 @@
+import {CommonModule} from '@angular/common';
+import {NgModule} from '@angular/core';
+import {FormsModule} from '@angular/forms';
+
+import {ShiftsComponent} from './shift_schedule.component';
+
+@NgModule({
+  declarations: [ShiftsComponent],
+  exports: [ShiftsComponent],
+  imports: [CommonModule, FormsModule],
+})
+export class ShiftScheduleModule {}
diff --git a/scouting/www/shift_schedule/shift_schedule.ng.html b/scouting/www/shift_schedule/shift_schedule.ng.html
new file mode 100644
index 0000000..3a9cdf0
--- /dev/null
+++ b/scouting/www/shift_schedule/shift_schedule.ng.html
@@ -0,0 +1,33 @@
+<div class="header">
+  <h2>Shift Schedule</h2>
+</div>
+
+<div class="container-fluid">
+  <div class="row">
+    <div *ngFor="let num of numMatches">
+      <span class="badge bg-secondary rounded-left">
+        Scouting matches from {{num-19}} to {{num}}
+      </span>
+      <div class="list-group list-group-horizontal-sm">
+        <div class="redColumn">
+          <div *ngFor="let allianceNum of [1, 2, 3]">
+            <input
+              class="red text-center text-white fw-bold list-group-item list-group-item-action"
+              type="text"
+            />
+          </div>
+        </div>
+        <div class="blueColumn">
+          <div *ngFor="let allianceNum of [1, 2, 3]">
+            <input
+              class="blue text-center text-white fw-bold list-group-item list-group-item-action"
+              type="text"
+            />
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+  <button class="btn btn-primary">Save</button>
+  <span class="error_message" role="alert">{{ errorMessage }}</span>
+</div>
diff --git a/third_party/flatbuffers/build_defs.bzl b/third_party/flatbuffers/build_defs.bzl
index cb50726..7eaa4d3 100644
--- a/third_party/flatbuffers/build_defs.bzl
+++ b/third_party/flatbuffers/build_defs.bzl
@@ -379,7 +379,8 @@
         flatc_args = DEFAULT_FLATC_TS_ARGS,
         visibility = None,
         restricted_to = None,
-        include_reflection = True):
+        include_reflection = True,
+        package_name = None):
     """Generates a ts_library rule for a given flatbuffer definition.
 
     Args:
@@ -401,6 +402,7 @@
       include_reflection: Optional, Whether to depend on the flatbuffer
         reflection library automatically. Only really relevant for the
         target that builds the reflection library itself.
+      package_name: Optional, Package name to use for the generated code.
     """
     srcs_lib = "%s_srcs" % (name)
 
@@ -428,7 +430,7 @@
         "SRCS=($(SRCS));",
         "OUTS=($(OUTS));",
         "for i in $${!SRCS[@]}; do",
-        "sed 's/third_party\\/flatbuffers/external\\/com_github_google_flatbuffers/' $${SRCS[i]} > $${OUTS[i]};",
+        "sed \"s/'.*reflection\\/reflection_pregenerated/'flatbuffers_reflection\\/reflection_generated/\" $${SRCS[i]} > $${OUTS[i]};",
         "sed -i 's/_pregenerated/_generated/' $${OUTS[i]};",
         "done",
     ])
@@ -469,6 +471,7 @@
         restricted_to = restricted_to,
         target_compatible_with = target_compatible_with,
         deps = [name + "_ts"],
+        package_name = package_name,
     )
     native.filegroup(
         name = "%s_includes" % (name),
diff --git a/third_party/flatbuffers/reflection/BUILD.bazel b/third_party/flatbuffers/reflection/BUILD.bazel
index 7948e12..aa421db 100644
--- a/third_party/flatbuffers/reflection/BUILD.bazel
+++ b/third_party/flatbuffers/reflection/BUILD.bazel
@@ -8,6 +8,7 @@
 
 flatbuffer_ts_library(
     name = "reflection_ts_fbs",
+    package_name = "flatbuffers_reflection",
     srcs = ["reflection.fbs"],
     include_reflection = False,
     visibility = ["//visibility:public"],
diff --git a/y2020/control_loops/drivetrain/localizer_plotter.ts b/y2020/control_loops/drivetrain/localizer_plotter.ts
index 9fe80de..b2120ef 100644
--- a/y2020/control_loops/drivetrain/localizer_plotter.ts
+++ b/y2020/control_loops/drivetrain/localizer_plotter.ts
@@ -6,7 +6,7 @@
 import {Point} from 'org_frc971/aos/network/www/plotter';
 import {Table} from 'org_frc971/aos/network/www/reflection';
 import {ByteBuffer} from 'flatbuffers';
-import {Schema} from 'org_frc971/external/com_github_google_flatbuffers/reflection/reflection_generated';
+import {Schema} from 'flatbuffers_reflection/reflection_generated';
 
 const TIME = AosPlotter.TIME;
 
diff --git a/y2020/control_loops/superstructure/turret_plotter.ts b/y2020/control_loops/superstructure/turret_plotter.ts
index 2c3fb01..5cb15c6 100644
--- a/y2020/control_loops/superstructure/turret_plotter.ts
+++ b/y2020/control_loops/superstructure/turret_plotter.ts
@@ -7,7 +7,7 @@
 import {Point} from 'org_frc971/aos/network/www/plotter';
 import {Table} from 'org_frc971/aos/network/www/reflection';
 import {ByteBuffer} from 'flatbuffers';
-import {Schema} from 'org_frc971/external/com_github_google_flatbuffers/reflection/reflection_generated';
+import {Schema} from 'flatbuffers_reflection/reflection_generated';
 
 import Connection = proxy.Connection;
 
diff --git a/y2022/vision/vision_plotter.ts b/y2022/vision/vision_plotter.ts
index 8b9fa7f..adbf900 100644
--- a/y2022/vision/vision_plotter.ts
+++ b/y2022/vision/vision_plotter.ts
@@ -4,7 +4,7 @@
 import {BLUE, BROWN, CYAN, GREEN, PINK, RED, WHITE} from 'org_frc971/aos/network/www/colors';
 import {Connection} from 'org_frc971/aos/network/www/proxy';
 import {Table} from 'org_frc971/aos/network/www/reflection';
-import {Schema} from 'org_frc971/external/com_github_google_flatbuffers/reflection/reflection_generated';
+import {Schema} from 'flatbuffers_reflection/reflection_generated';
 import {TargetEstimate} from 'org_frc971/y2022/vision/target_estimate_generated';