[PhotonClient] Vite and Typescript complete refactor (#884)

This commit is contained in:
Sriman Achanta
2023-08-21 01:51:35 -04:00
committed by GitHub
parent 8397b43bef
commit f623e4a1cc
119 changed files with 11821 additions and 19318 deletions

View File

@@ -1,399 +1,84 @@
<script setup lang="ts">
import { useStateStore } from "@/stores/StateStore";
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
import { AutoReconnectingWebsocket } from "@/lib/AutoReconnectingWebsocket";
import { inject } from "vue";
import PhotonSidebar from "@/components/app/photon-sidebar.vue";
import PhotonLogView from "@/components/app/photon-log-view.vue";
import PhotonErrorSnackbar from "@/components/app/photon-error-snackbar.vue";
const websocket = new AutoReconnectingWebsocket(
`ws://${inject("backendHost")}/websocket_data`,
() => {
useStateStore().$patch({ backendConnected: true });
},
(data) => {
if(data.log !== undefined) {
useStateStore().addLogFromWebsocket(data.log);
}
if(data.settings !== undefined) {
useSettingsStore().updateGeneralSettingsFromWebsocket(data.settings);
}
if(data.cameraSettings !== undefined) {
useCameraSettingsStore().updateCameraSettingsFromWebsocket(data.cameraSettings);
}
if(data.ntConnectionInfo !== undefined) {
useStateStore().updateNTConnectionStatusFromWebsocket(data.ntConnectionInfo);
}
if(data.metrics !== undefined) {
useSettingsStore().updateMetricsFromWebsocket(data.metrics);
}
if(data.updatePipelineResult !== undefined) {
useStateStore().updatePipelineResultsFromWebsocket(data.updatePipelineResult);
}
if(data.mutatePipelineSettings !== undefined && data.cameraIndex !== undefined) {
useCameraSettingsStore().changePipelineSettingsInStore(data.mutatePipelineSettings, data.cameraIndex);
}
if(data.calibrationData !== undefined) {
useStateStore().updateCalibrationStateValuesFromWebsocket(data.calibrationData);
}
},
() => {
useStateStore().$patch({ backendConnected: false });
}
);
useStateStore().$patch({ websocket: websocket });
</script>
<template>
<v-app>
<!-- Although most of the app runs with the "light" theme, the navigation drawer needs to have white text and icons so it uses the dark theme-->
<v-navigation-drawer
dark
app
permanent
:mini-variant="compact"
color="primary"
>
<v-list>
<!-- List item for the heading; note that there are some tricks in setting padding and image width make things look right -->
<v-list-item
:class="compact ? 'pr-0 pl-0' : ''"
style="display: flex; justify-content: center"
>
<v-list-item-icon class="mr-0">
<img
v-if="!compact"
class="logo"
src="@/assets/logos/logoLarge.svg"
alt="large logo"
>
<img
v-else
class="logo"
src="@/assets/logos/logoSmall.svg"
alt="small logo"
>
</v-list-item-icon>
</v-list-item>
<v-list-item
link
to="/dashboard"
@click="rollbackPipelineIndex()"
>
<v-list-item-icon>
<v-icon>mdi-view-dashboard</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>Dashboard</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item
ref="camerasTabOpener"
link
to="/cameras"
@click="switchToDriverMode()"
>
<v-list-item-icon>
<v-icon>mdi-camera</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>Cameras</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item
link
to="/settings"
@click="switchToSettingsTab()"
>
<v-list-item-icon>
<v-icon>mdi-cog</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>Settings</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item
link
to="/docs"
>
<v-list-item-icon>
<v-icon>mdi-bookshelf</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>Documentation</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item
v-if="this.$vuetify.breakpoint.mdAndUp"
link
@click.stop="toggleCompactMode"
>
<v-list-item-icon>
<v-icon v-if="compact">
mdi-chevron-right
</v-icon>
<v-icon v-else>
mdi-chevron-left
</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>Compact Mode</v-list-item-title>
</v-list-item-content>
</v-list-item>
<div style="position: absolute; bottom: 0; left: 0;">
<v-list-item>
<v-list-item-icon>
<v-icon v-if="$store.state.settings.networkSettings.runNTServer">
mdi-server
</v-icon>
<v-icon v-else-if="$store.state.ntConnectionInfo.connected">
mdi-robot
</v-icon>
<v-icon
v-else
style="border-radius: 100%"
>
mdi-robot-off
</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title
v-if="$store.state.settings.networkSettings.runNTServer"
class="text-wrap"
>
NetworkTables server running for <span class="accent--text">{{ $store.state.ntConnectionInfo.clients }}</span> clients
</v-list-item-title>
<v-list-item-title
v-else-if="$store.state.ntConnectionInfo.connected && $store.state.backendConnected"
class="text-wrap"
style="flex-direction: column; display: flex"
>
NetworkTables Server Connected!
<span
class="accent--text"
>
{{ $store.state.ntConnectionInfo.address }}
</span>
</v-list-item-title>
<v-list-item-title
v-else
class="text-wrap"
style="flex-direction: column; display: flex"
>
Not connected to NetworkTables Server!
</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item>
<v-list-item-icon>
<v-icon v-if="$store.state.backendConnected">
mdi-server-network
</v-icon>
<v-icon
v-else
style="border-radius: 100%;"
>
mdi-server-network-off
</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title class="text-wrap">
{{ $store.state.backendConnected ? "Backend Connected" : "Trying to connect to Backend" }}
</v-list-item-title>
</v-list-item-content>
</v-list-item>
</div>
</v-list>
</v-navigation-drawer>
<photon-sidebar />
<v-main>
<v-container
class="main-container"
fluid
fill-height
>
<v-layout>
<v-flex>
<router-view @switch-to-cameras="switchToDriverMode" />
<router-view />
</v-flex>
</v-layout>
</v-container>
</v-main>
<v-dialog
v-model="$store.state.logsOverlay"
width="1500"
dark
>
<logs />
</v-dialog>
<v-dialog
v-model="needsTeamNumberSet"
width="500"
dark
persistent
>
<v-card
dark
color="primary"
flat
>
<v-card-title>NetworkTables Server Address Not Set</v-card-title>
<v-card-text>
PhotonVision cannot connect to the NetworkTables Server. Please visit the
<router-link
to="/settings"
class="accent--text"
@click="switchToSettingsTab"
>
networking settings tab
</router-link>
and set the NetworkTables Server Address.
</v-card-text>
</v-card>
</v-dialog>
<photon-log-view />
<photon-error-snackbar />
</v-app>
</template>
<script>
import Logs from "./views/LogsView"
import { ReconnectingWebsocket } from "./plugins/ReconnectingWebsocket.js"
<style lang="scss">
@import 'vuetify/src/styles/settings/_variables';
export default {
name: 'App',
components: {
Logs
},
data: () => ({
// Used so that we can switch back to the previously selected pipeline after camera calibration
previouslySelectedIndices: [],
timer: undefined,
teamNumberDialog: true,
websocket: null,
}),
computed: {
needsTeamNumberSet: {
get() {
return this.$store.state.settings.networkSettings.ntServerAddress == ""
&& this.teamNumberDialog && this.$store.state.backendConnected
&& !this.$route.name.toLowerCase().includes("settings");
}
},
compact: {
get() {
if (this.$store.state.compactMode === undefined) {
return this.$vuetify.breakpoint.smAndDown;
} else {
return this.$store.state.compactMode || this.$vuetify.breakpoint.smAndDown;
}
},
set(value) {
// compactMode is the user's preference for compact mode; it overrides screen size
this.$store.commit("compactMode", value);
localStorage.setItem("compactMode", value);
},
},
},
created() {
document.addEventListener("keydown", e => {
switch (e.key) {
case "`":
this.$store.state.logsOverlay = !this.$store.state.logsOverlay;
break;
case "z":
if (e.ctrlKey && this.$store.getters.canUndo) {
this.$store.dispatch('undo', { vm: this });
}
break;
case "y":
if (e.ctrlKey && this.$store.getters.canRedo) {
this.$store.dispatch('redo', { vm: this });
}
break;
}
});
const wsDataURL = 'ws://' + this.$address + '/websocket_data';
this.websocket = new ReconnectingWebsocket(
wsDataURL,
// On data in
(event) => {
try {
let message = this.$msgPack.decode(event.data);
for (let prop in message) {
if (message.hasOwnProperty(prop)) {
this.handleMessage(prop, message[prop]);
}
}
} catch (error) {
console.log(event)
console.error('error: ' + JSON.stringify(event.data) + " , " + error);
}
},
// on connect
(event) => {
event; this.$store.commit("backendConnected", true);
this.$store.state.connectedCallbacks.forEach(it => it());
},
// on disconnect
(event) => { event; this.$store.commit("backendConnected", false) }
);
this.$store.commit("websocket", this.websocket);
},
methods: {
handleMessage(key, value) {
if (key === "logMessage") {
this.logMessage(value["logMessage"], value["logLevel"]);
} else if (key === "log") {
this.logMessage(value["logMessage"]["logMessage"], value["logMessage"]["logLevel"]);
} else if (key === "updatePipelineResult") {
this.$store.commit('mutatePipelineResults', value)
} else if (this.$store.state.hasOwnProperty(key)) {
this.$store.commit(key, value);
} else if (this.$store.getters.currentPipelineSettings.hasOwnProperty(key)) {
this.$store.commit('mutatePipeline', { [key]: value });
} else if (this.$store.state.settings.hasOwnProperty(key)) {
this.$store.commit('mutateSettings', { [key]: value });
} else {
console.error("Unknown message from backend: " + value);
}
},
toggleCompactMode() {
this.compact = !this.compact;
},
// eslint-disable-next-line no-unused-vars
logMessage(message, levelInt) {
this.$store.commit('logString', {
['level']: levelInt,
['message']: message
})
},
switchToDriverMode() {
if (!this.previouslySelectedIndices) this.previouslySelectedIndices = [];
for (const [i, cameraSettings] of this.$store.state.cameraSettings.entries()) {
this.previouslySelectedIndices[i] = cameraSettings.currentPipelineIndex;
this.handleInputWithIndex('currentPipeline', -1, i);
}
},
rollbackPipelineIndex() {
if (this.previouslySelectedIndices !== null) {
for (const [i] of this.$store.state.cameraSettings.entries()) {
this.handleInputWithIndex('currentPipeline', this.previouslySelectedIndices[i] || 0, i);
}
}
this.previouslySelectedIndices = null;
},
switchToSettingsTab() {
this.axios.post('http://' + this.$address + '/api/utils/publishMetrics')
}
}
};
</script>
<style lang="sass">
@import "./scss/variables.scss"
</style>
<style>
.pulse {
animation: pulse-animation 2s infinite;
}
@keyframes pulse-animation {
0% {
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.2);
background-color: rgba(0, 0, 0, 0.2);
}
100% {
box-shadow: 0 0 0 20px rgba(0, 0, 0, 0);
background-color: rgba(0, 0, 0, 0);
@media #{map-get($display-breakpoints, 'md-and-down')} {
html {
font-size: 14px !important;
}
}
.logo {
width: 100%;
height: 70px;
object-fit: contain;
}
::-webkit-scrollbar {
width: 0.5em;
border-radius: 5px;
}
::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background-color: #ffd843;
border-radius: 10px;
}
.container {
.main-container {
background-color: #232c37;
padding: 0 !important;
}
@@ -402,30 +87,3 @@ export default {
color: #ffd843;
}
</style>
<style>
/* Hacks */
.v-divider {
border-color: white !important;
}
.v-input {
font-size: 1rem !important;
}
/* This is unfortunately the only way to override table background color */
.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr:hover:not(.v-data-table__expanded__content):not(.v-data-table__empty-wrapper) {
background: #005281 !important;
}
</style>
<style lang="scss">
@import '~vuetify/src/styles/settings/_variables';
@media #{map-get($display-breakpoints, 'md-and-down')} {
html {
font-size: 14px !important;
}
}
</style>