mirror of
https://github.com/PhotonVision/photonvision
synced 2026-07-04 03:11:40 +00:00
* Implement new UI backend stuff * Kinda partially add resolution accuracy list * camera calibration go brrrrrrrr * ayyyy calibration works * Maybe fix grouping * Reorganize camera view * Fix settings not getting sent * Make pretty (#4) * Reorganize camera view * Apply some cosmetic layout changes to the cameras page * Fix pipeline rollback bug when starting on non-dashboard pages Co-authored-by: Matt <matthew.morley.ca@gmail.com> * Fix naming mismatch * Mostly make stuff work * rename robot-relative pose to camera-relative pose * SolvePNP memes, fix isFovConfigurable * Change config path to photonvision_config * netmask go poof, fix zip download? * Update index.js * Fix multi cam stuff? * Use LinearFilter instead * Fix multicam * aaa * start adding restart device and restart program, fix square size bug * Add some debug stuff * oop * Start fixing tests * Fix tests * Make target box proportinal * run spotless * Make crosshair h o t p i n k * Address review comments * Address review 2 electric booaloo * Possibly implement vendor FOV? * Make centroid crosshair gren * actually use FOV * Fix tests * actually fix tests Co-authored-by: Declan Freeman-Gleason <declanfreemangleason@gmail.com>
369 lines
11 KiB
Vue
369 lines
11 KiB
Vue
<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' : ''">
|
|
<v-list-item-icon class="mr-0">
|
|
<img
|
|
v-if="!compact"
|
|
class="logo"
|
|
src="./assets/logoLarge.png"
|
|
>
|
|
<img
|
|
v-else
|
|
class="logo"
|
|
src="./assets/logoSmall.png"
|
|
>
|
|
</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
|
|
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"
|
|
>
|
|
<v-list-item-icon>
|
|
<v-icon>mdi-settings</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>Advanced Mode</v-list-item-title>
|
|
</v-list-item-content>
|
|
</v-list-item>
|
|
|
|
<v-list-item style="position: absolute; bottom: 0; left: 0;">
|
|
<v-list-item-icon>
|
|
<v-icon v-if="$store.state.backendConnected">
|
|
mdi-wifi
|
|
</v-icon>
|
|
<v-icon
|
|
v-else
|
|
class="pulse"
|
|
style="border-radius: 100%;"
|
|
>
|
|
mdi-wifi-off
|
|
</v-icon>
|
|
</v-list-item-icon>
|
|
<v-list-item-content>
|
|
<v-list-item-title>
|
|
{{ $store.state.backendConnected ? "Connected" : "Trying to connect..." }}
|
|
</v-list-item-title>
|
|
</v-list-item-content>
|
|
</v-list-item>
|
|
</v-list>
|
|
</v-navigation-drawer>
|
|
<v-content>
|
|
<v-container
|
|
fluid
|
|
fill-height
|
|
>
|
|
<v-layout>
|
|
<v-flex>
|
|
<router-view @save="startTimer" />
|
|
<v-snackbar
|
|
v-model="saveSnackbar"
|
|
:timeout="1000"
|
|
top
|
|
color="accent"
|
|
>
|
|
<div style="text-align: center;width: 100%;">
|
|
<h4>Saved All changes</h4>
|
|
</div>
|
|
</v-snackbar>
|
|
<div v-if="isLogger">
|
|
<keep-alive>
|
|
<log-view
|
|
class="loggerClass"
|
|
:log="log"
|
|
/>
|
|
</keep-alive>
|
|
</div>
|
|
</v-flex>
|
|
</v-layout>
|
|
</v-container>
|
|
</v-content>
|
|
</v-app>
|
|
</template>
|
|
|
|
<script>
|
|
import logView from '@femessage/log-viewer';
|
|
|
|
export default {
|
|
name: 'App',
|
|
components: {
|
|
logView
|
|
},
|
|
data: () => ({
|
|
// Used so that we can switch back to the previously selected pipeline after camera calibration
|
|
previouslySelectedIndex: undefined,
|
|
timer: undefined,
|
|
isLogger: false,
|
|
log: "",
|
|
}),
|
|
computed: {
|
|
saveSnackbar: {
|
|
get() {
|
|
return this.$store.state.saveBar;
|
|
},
|
|
set(value) {
|
|
this.$store.commit("saveBar", value);
|
|
}
|
|
},
|
|
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.isLogger = !this.isLogger;
|
|
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;
|
|
|
|
}
|
|
});
|
|
|
|
this.$options.sockets.onmessage = (data) => {
|
|
try {
|
|
let message = this.$msgPack.decode(data.data);
|
|
for (let prop in message) {
|
|
if (message.hasOwnProperty(prop)) {
|
|
this.handleMessage(prop, message[prop]);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('error: ' + JSON.stringify(data.data) + " , " + error);
|
|
}
|
|
};
|
|
this.$options.sockets.onopen = () => {
|
|
this.$store.state.backendConnected = true;
|
|
};
|
|
|
|
let closed = () => {
|
|
this.$store.state.backendConnected = false;
|
|
};
|
|
this.$options.sockets.onclose = closed;
|
|
this.$options.sockets.onerror = closed;
|
|
|
|
this.$connect();
|
|
},
|
|
methods: {
|
|
handleMessage(key, value) {
|
|
if (key === "logMessage") {
|
|
this.logMessage(value["logMessage"], value["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 {
|
|
switch (key) {
|
|
default: {
|
|
console.error("Unknown message from backend: " + value);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
toggleCompactMode() {
|
|
this.compact = !this.compact;
|
|
},
|
|
saveSettings() {
|
|
clearInterval(this.timer);
|
|
this.saveSnackbar = true;
|
|
this.handleInput("command", "save");
|
|
},
|
|
startTimer() {
|
|
if (this.timer !== undefined) {
|
|
clearInterval(this.timer);
|
|
}
|
|
this.timer = setInterval(this.saveSettings, 4000);
|
|
},
|
|
logMessage(message, level) {
|
|
const colors = ["\u001B[30m", "\u001B[31m", "\u001B[33m", "\u001B[32m", "\u001B[37m", "\u001B[36m"]
|
|
const reset = "\u001b[0m"
|
|
this.log += `${colors[level]}${message}${reset}\n`
|
|
console.log(message)
|
|
},
|
|
switchToDriverMode() {
|
|
this.previouslySelectedIndex = this.$store.getters.currentPipelineIndex;
|
|
this.handleInputWithIndex('currentPipeline', -1)
|
|
},
|
|
rollbackPipelineIndex() {
|
|
if (this.previouslySelectedIndex !== null) {
|
|
this.handleInputWithIndex('currentPipeline', this.previouslySelectedIndex || 0);
|
|
}
|
|
this.previouslySelectedIndex = null;
|
|
}
|
|
}
|
|
};
|
|
</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 0px 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);
|
|
}
|
|
}
|
|
|
|
.logo {
|
|
width: 100%;
|
|
height: 70px;
|
|
object-fit: contain;
|
|
}
|
|
|
|
.loggerClass {
|
|
position: absolute;
|
|
bottom: 0;
|
|
height: 25% !important;
|
|
left: 0;
|
|
right: 0;
|
|
box-shadow: #282828 0 0 5px 1px;
|
|
background-color: #2b2b2b;
|
|
}
|
|
|
|
::-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 {
|
|
background-color: #232c37;
|
|
padding: 0 !important;
|
|
}
|
|
|
|
#title {
|
|
color: #ffd843;
|
|
}
|
|
|
|
span {
|
|
color: white;
|
|
}
|
|
</style>
|
|
|
|
<style>
|
|
/* Hack */
|
|
.v-divider {
|
|
border-color: white !important;
|
|
}
|
|
|
|
.v-input {
|
|
font-size: 1rem !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> |