mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-30 02:31:40 +00:00
Dark mode and minor interface tweaks (#2016)
Co-authored-by: Sam Freund <samf.236@proton.me>
This commit is contained in:
@@ -8,23 +8,19 @@ const quaternionToEuler = (rot_quat: Quaternion): { x: number; y: number; z: num
|
||||
const quat = new ThreeQuat(rot_quat.X, rot_quat.Y, rot_quat.Z, rot_quat.W);
|
||||
const euler = new Euler().setFromQuaternion(quat, "ZYX");
|
||||
|
||||
return {
|
||||
x: toDeg(euler.x),
|
||||
y: toDeg(euler.y),
|
||||
z: toDeg(euler.z)
|
||||
};
|
||||
return { x: toDeg(euler.x), y: toDeg(euler.y), z: toDeg(euler.z) };
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card style="background-color: #006492">
|
||||
<v-card color="surface" class="rounded-12">
|
||||
<v-card-title>AprilTag Field Layout</v-card-title>
|
||||
<v-card-text class="pt-0">
|
||||
<p>Field width: {{ useSettingsStore().currentFieldLayout.field.width.toFixed(2) }} meters</p>
|
||||
<p>Field length: {{ useSettingsStore().currentFieldLayout.field.length.toFixed(2) }} meters</p>
|
||||
|
||||
<!-- Simple table height must be set here and in the CSS for the fixed-header to work -->
|
||||
<v-table fixed-header height="100%" density="compact" dark>
|
||||
<v-table fixed-header height="100%" density="compact">
|
||||
<template #default>
|
||||
<thead style="font-size: 1.25rem">
|
||||
<tr>
|
||||
@@ -57,11 +53,9 @@ const quaternionToEuler = (rot_quat: Quaternion): { x: number; y: number; z: num
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
background-color: #006492 !important;
|
||||
|
||||
th,
|
||||
td {
|
||||
background-color: #006492 !important;
|
||||
font-size: 1rem !important;
|
||||
color: white !important;
|
||||
}
|
||||
@@ -70,10 +64,6 @@ const quaternionToEuler = (rot_quat: Quaternion): { x: number; y: number; z: num
|
||||
font-family: monospace !important;
|
||||
}
|
||||
|
||||
tbody :hover td {
|
||||
background-color: #005281 !important;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0.55em;
|
||||
@@ -86,7 +76,7 @@ const quaternionToEuler = (rot_quat: Quaternion): { x: number; y: number; z: num
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: #ffd843;
|
||||
background-color: rgb(var(--v-theme-accent));
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,15 @@ import { useStateStore } from "@/stores/StateStore";
|
||||
import PvSelect from "@/components/common/pv-select.vue";
|
||||
import PvInput from "@/components/common/pv-input.vue";
|
||||
import axios from "axios";
|
||||
import { useTheme } from "vuetify";
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const restartProgram = () => {
|
||||
axios
|
||||
.post("/utils/restartProgram")
|
||||
.then(() => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Successfully sent program restart request",
|
||||
color: "success"
|
||||
});
|
||||
useStateStore().showSnackbarMessage({ message: "Successfully sent program restart request", color: "success" });
|
||||
})
|
||||
.catch((error) => {
|
||||
// This endpoint always return 204 regardless of outcome
|
||||
@@ -98,10 +98,7 @@ const handleOfflineUpdate = () => {
|
||||
}
|
||||
})
|
||||
.then((response) => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: response.data.text || response.data,
|
||||
color: "success"
|
||||
});
|
||||
useStateStore().showSnackbarMessage({ message: response.data.text || response.data, color: "success" });
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response) {
|
||||
@@ -170,14 +167,9 @@ const handleSettingsImport = () => {
|
||||
}
|
||||
|
||||
axios
|
||||
.post(`/settings${settingsEndpoint}`, formData, {
|
||||
headers: { "Content-Type": "multipart/form-data" }
|
||||
})
|
||||
.post(`/settings${settingsEndpoint}`, formData, { headers: { "Content-Type": "multipart/form-data" } })
|
||||
.then((response) => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: response.data.text || response.data,
|
||||
color: "success"
|
||||
});
|
||||
useStateStore().showSnackbarMessage({ message: response.data.text || response.data, color: "success" });
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response) {
|
||||
@@ -238,35 +230,50 @@ const nukePhotonConfigDirectory = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card class="mb-3" style="background-color: #006492">
|
||||
<v-card class="mb-3 rounded-12" color="surface">
|
||||
<v-card-title>Device Control</v-card-title>
|
||||
<div class="pa-5 pt-0">
|
||||
<v-row>
|
||||
<v-col cols="12" lg="4" md="6">
|
||||
<v-btn color="error" @click="restartProgram">
|
||||
<v-icon start class="open-icon"> mdi-restart </v-icon>
|
||||
<v-btn
|
||||
color="buttonActive"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="restartProgram"
|
||||
>
|
||||
<v-icon start class="open-icon" size="large"> mdi-restart </v-icon>
|
||||
<span class="open-label">Restart PhotonVision</span>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<v-col cols="12" lg="4" md="6">
|
||||
<v-btn color="error" @click="restartDevice">
|
||||
<v-icon start class="open-icon"> mdi-restart-alert </v-icon>
|
||||
<v-btn
|
||||
color="buttonActive"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="restartDevice"
|
||||
>
|
||||
<v-icon start class="open-icon" size="large"> mdi-restart-alert </v-icon>
|
||||
<span class="open-label">Restart Device</span>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<v-col cols="12" lg="4">
|
||||
<v-btn color="secondary" @click="openOfflineUpdatePrompt">
|
||||
<v-icon start class="open-icon"> mdi-upload </v-icon>
|
||||
<v-btn
|
||||
color="buttonPassive"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="openOfflineUpdatePrompt"
|
||||
>
|
||||
<v-icon start class="open-icon" size="large"> mdi-upload </v-icon>
|
||||
<span class="open-label">Offline Update</span>
|
||||
</v-btn>
|
||||
<input ref="offlineUpdate" type="file" accept=".jar" style="display: none" @change="handleOfflineUpdate" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-divider class="mt-3 pb-3" />
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-btn color="secondary" @click="() => (showImportDialog = true)">
|
||||
<v-icon start class="open-icon"> mdi-import </v-icon>
|
||||
<v-btn
|
||||
color="buttonPassive"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="() => (showImportDialog = true)"
|
||||
>
|
||||
<v-icon start class="open-icon" size="large"> mdi-import </v-icon>
|
||||
<span class="open-label">Import Settings</span>
|
||||
</v-btn>
|
||||
<v-dialog
|
||||
@@ -279,7 +286,7 @@ const nukePhotonConfigDirectory = () => {
|
||||
}
|
||||
"
|
||||
>
|
||||
<v-card color="primary" dark>
|
||||
<v-card color="surface" dark>
|
||||
<v-card-title class="pb-0">Import Settings</v-card-title>
|
||||
<v-card-text>
|
||||
Upload and apply previously saved or exported PhotonVision settings to this device
|
||||
@@ -299,14 +306,19 @@ const nukePhotonConfigDirectory = () => {
|
||||
style="width: 100%"
|
||||
/>
|
||||
<v-file-input
|
||||
class="pb-5"
|
||||
v-model="importFile"
|
||||
class="pb-5"
|
||||
variant="underlined"
|
||||
:disabled="importType === undefined"
|
||||
:error-messages="importType === undefined ? 'Settings type not selected' : ''"
|
||||
:accept="importType === ImportType.AllSettings ? '.zip' : '.json'"
|
||||
/>
|
||||
<v-btn color="secondary" :disabled="importFile === null" @click="handleSettingsImport">
|
||||
<v-btn
|
||||
color="primary"
|
||||
:disabled="importFile === null"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="handleSettingsImport"
|
||||
>
|
||||
<v-icon start class="open-icon"> mdi-import </v-icon>
|
||||
<span class="open-label">Import Settings</span>
|
||||
</v-btn>
|
||||
@@ -316,8 +328,12 @@ const nukePhotonConfigDirectory = () => {
|
||||
</v-dialog>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-btn color="secondary" @click="openExportSettingsPrompt">
|
||||
<v-icon start class="open-icon"> mdi-export </v-icon>
|
||||
<v-btn
|
||||
color="buttonPassive"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="openExportSettingsPrompt"
|
||||
>
|
||||
<v-icon start class="open-icon" size="large"> mdi-export </v-icon>
|
||||
<span class="open-label">Export Settings</span>
|
||||
</v-btn>
|
||||
<a
|
||||
@@ -329,8 +345,12 @@ const nukePhotonConfigDirectory = () => {
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-btn color="secondary" @click="openExportLogsPrompt">
|
||||
<v-icon start class="open-icon"> mdi-download </v-icon>
|
||||
<v-btn
|
||||
color="buttonPassive"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="openExportLogsPrompt"
|
||||
>
|
||||
<v-icon start class="open-icon" size="large"> mdi-download </v-icon>
|
||||
<span class="open-label">Download logs</span>
|
||||
|
||||
<!-- Special hidden link that gets 'clicked' when the user exports journalctl logs -->
|
||||
@@ -344,36 +364,36 @@ const nukePhotonConfigDirectory = () => {
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-btn color="secondary" @click="useStateStore().showLogModal = true">
|
||||
<v-icon start class="open-icon"> mdi-eye </v-icon>
|
||||
<span class="open-label">View program logs</span>
|
||||
<v-btn
|
||||
color="buttonPassive"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="useStateStore().showLogModal = true"
|
||||
>
|
||||
<v-icon start class="open-icon" size="large"> mdi-eye </v-icon>
|
||||
<span class="open-label">View logs</span>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-divider class="mt-3 pb-3" />
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-btn color="error" @click="() => (showFactoryReset = true)">
|
||||
<v-icon start class="open-icon"> mdi-skull-crossbones </v-icon>
|
||||
<span class="open-icon">
|
||||
{{
|
||||
$vuetify.display.mdAndUp
|
||||
? "Factory Reset PhotonVision and delete EVERYTHING"
|
||||
: "Factory Reset PhotonVision"
|
||||
}}
|
||||
</span>
|
||||
<v-btn
|
||||
color="error"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="() => (showFactoryReset = true)"
|
||||
>
|
||||
<v-icon start class="open-icon" size="large"> mdi-trash-can-outline </v-icon>
|
||||
<span class="open-icon"> Factory Reset PhotonVision </span>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
|
||||
<v-dialog v-model="showFactoryReset" width="800" dark>
|
||||
<v-card color="primary" flat>
|
||||
<v-card color="surface" flat>
|
||||
<v-card-title style="display: flex; justify-content: center">
|
||||
<span class="open-label">
|
||||
<v-icon end color="error" class="open-icon ma-1">mdi-nuke</v-icon>
|
||||
<v-icon end color="red" class="open-icon ma-1" size="large">mdi-alert-outline</v-icon>
|
||||
Factory Reset PhotonVision
|
||||
<v-icon end color="error" class="open-icon ma-1">mdi-nuke</v-icon>
|
||||
<v-icon end color="red" class="open-icon ma-1" size="large">mdi-alert-outline</v-icon>
|
||||
</span>
|
||||
</v-card-title>
|
||||
<v-card-text class="pt-0 pb-10px">
|
||||
@@ -382,8 +402,13 @@ const nukePhotonConfigDirectory = () => {
|
||||
<span> This will delete ALL OF YOUR SETTINGS and restart PhotonVision. </span>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-btn color="secondary" style="float: right" @click="openExportSettingsPrompt">
|
||||
<v-icon start class="open-icon"> mdi-export </v-icon>
|
||||
<v-btn
|
||||
color="primary"
|
||||
style="float: right"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="openExportSettingsPrompt"
|
||||
>
|
||||
<v-icon start class="open-icon" size="large"> mdi-export </v-icon>
|
||||
<span class="open-label">Backup Settings</span>
|
||||
<a
|
||||
ref="exportSettings"
|
||||
@@ -407,10 +432,11 @@ const nukePhotonConfigDirectory = () => {
|
||||
<v-card-text class="pt-10px">
|
||||
<v-btn
|
||||
color="error"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
:disabled="yesDeleteMySettingsText.toLowerCase() !== expected.toLowerCase()"
|
||||
@click="nukePhotonConfigDirectory"
|
||||
>
|
||||
<v-icon start class="open-icon"> mdi-trash-can-outline </v-icon>
|
||||
<v-icon start class="open-icon" size="large"> mdi-trash-can-outline </v-icon>
|
||||
<span class="open-label">
|
||||
{{ $vuetify.display.mdAndUp ? "Delete everything, I have backed up what I need" : "Delete Everything" }}
|
||||
</span>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card class="mb-3" style="background-color: #006492">
|
||||
<v-card class="mb-3 rounded-12" color="surface">
|
||||
<v-card-title class="pb-10px">LED Control</v-card-title>
|
||||
<v-card-text>
|
||||
<pv-slider
|
||||
|
||||
@@ -10,30 +10,14 @@ interface MetricItem {
|
||||
|
||||
const generalMetrics = computed<MetricItem[]>(() => {
|
||||
const stats = [
|
||||
{
|
||||
header: "Version",
|
||||
value: useSettingsStore().general.version || "Unknown"
|
||||
},
|
||||
{
|
||||
header: "Hardware Model",
|
||||
value: useSettingsStore().general.hardwareModel || "Unknown"
|
||||
},
|
||||
{
|
||||
header: "Platform",
|
||||
value: useSettingsStore().general.hardwarePlatform || "Unknown"
|
||||
},
|
||||
|
||||
{
|
||||
header: "GPU Acceleration",
|
||||
value: useSettingsStore().general.gpuAcceleration || "Unknown"
|
||||
}
|
||||
{ header: "Version", value: useSettingsStore().general.version || "Unknown" },
|
||||
{ header: "Hardware Model", value: useSettingsStore().general.hardwareModel || "Unknown" },
|
||||
{ header: "Platform", value: useSettingsStore().general.hardwarePlatform || "Unknown" },
|
||||
{ header: "GPU Acceleration", value: useSettingsStore().general.gpuAcceleration || "Unknown" }
|
||||
];
|
||||
|
||||
if (!useSettingsStore().network.networkingDisabled) {
|
||||
stats.push({
|
||||
header: "IP Address",
|
||||
value: useSettingsStore().metrics.ipAddress || "Unknown"
|
||||
});
|
||||
stats.push({ header: "IP Address", value: useSettingsStore().metrics.ipAddress || "Unknown" });
|
||||
}
|
||||
|
||||
return stats;
|
||||
@@ -141,16 +125,16 @@ onBeforeMount(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card class="mb-3" style="background-color: #006492">
|
||||
<v-card class="mb-3 rounded-12" color="surface">
|
||||
<v-card-title style="display: flex; justify-content: space-between">
|
||||
<span>Stats</span>
|
||||
<span>Metrics</span>
|
||||
<v-btn variant="text" @click="fetchMetrics">
|
||||
<v-icon start class="open-icon">mdi-reload</v-icon>
|
||||
<v-icon start class="open-icon" size="large">mdi-reload</v-icon>
|
||||
Last Fetched: {{ metricsLastFetched }}
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
<v-card-text class="pt-0 pb-3">
|
||||
<v-card-subtitle class="pa-0" style="font-size: 16px">General Metrics</v-card-subtitle>
|
||||
<v-card-subtitle class="pa-0" style="font-size: 16px">General</v-card-subtitle>
|
||||
<v-table class="metrics-table mt-3">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -187,7 +171,7 @@ onBeforeMount(() => {
|
||||
</v-table>
|
||||
</v-card-text>
|
||||
<v-card-text class="pt-4">
|
||||
<v-card-subtitle class="pa-0 pb-1" style="font-size: 16px">Hardware Metrics</v-card-subtitle>
|
||||
<v-card-subtitle class="pa-0 pb-1" style="font-size: 16px">Hardware</v-card-subtitle>
|
||||
<v-table class="metrics-table mt-3">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -233,46 +217,52 @@ onBeforeMount(() => {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
$stats-table-border: rgba(255, 255, 255, 0.5);
|
||||
$stats-table-inner: rgba(255, 255, 255, 0.1);
|
||||
|
||||
.t {
|
||||
border-top: 1px solid white;
|
||||
border-right: 1px solid white;
|
||||
border-top: 1px solid $stats-table-border;
|
||||
border-right: 1px solid $stats-table-border;
|
||||
border-bottom: 1px solid $stats-table-inner !important;
|
||||
}
|
||||
|
||||
.b {
|
||||
border-bottom: 1px solid white;
|
||||
border-right: 1px solid white;
|
||||
border-bottom: 1px solid $stats-table-border;
|
||||
border-right: 1px solid $stats-table-border;
|
||||
}
|
||||
|
||||
.tl {
|
||||
border-top: 1px solid white;
|
||||
border-left: 1px solid white;
|
||||
border-right: 1px solid white;
|
||||
border-top: 1px solid $stats-table-border;
|
||||
border-left: 1px solid $stats-table-border;
|
||||
border-right: 1px solid $stats-table-border;
|
||||
border-bottom: 1px solid $stats-table-inner !important;
|
||||
border-top-left-radius: 5px;
|
||||
}
|
||||
|
||||
.tr {
|
||||
border-top: 1px solid white;
|
||||
border-right: 1px solid white;
|
||||
border-top: 1px solid $stats-table-border;
|
||||
border-right: 1px solid $stats-table-border;
|
||||
border-bottom: 1px solid $stats-table-inner !important;
|
||||
border-top-right-radius: 5px;
|
||||
}
|
||||
|
||||
.bl {
|
||||
border-bottom: 1px solid white;
|
||||
border-left: 1px solid white;
|
||||
border-right: 1px solid white;
|
||||
border-bottom: 1px solid $stats-table-border;
|
||||
border-left: 1px solid $stats-table-border;
|
||||
border-right: 1px solid $stats-table-border;
|
||||
border-bottom-left-radius: 5px;
|
||||
}
|
||||
|
||||
.br {
|
||||
border-bottom: 1px solid white;
|
||||
border-right: 1px solid white;
|
||||
border-bottom: 1px solid $stats-table-border;
|
||||
border-right: 1px solid $stats-table-border;
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
|
||||
.metric-item {
|
||||
font-size: 16px !important;
|
||||
padding: 1px 15px 1px 10px;
|
||||
border-right: 1px solid;
|
||||
border-right: 1px solid $stats-table-border;
|
||||
font-weight: normal;
|
||||
color: white !important;
|
||||
text-align: center !important;
|
||||
@@ -280,22 +270,9 @@ onBeforeMount(() => {
|
||||
|
||||
.metric-item-title {
|
||||
font-size: 18px !important;
|
||||
text-decoration: underline;
|
||||
text-decoration-color: #ffd843;
|
||||
}
|
||||
|
||||
.v-table {
|
||||
thead,
|
||||
tbody {
|
||||
background-color: #006492;
|
||||
}
|
||||
|
||||
:hover {
|
||||
tbody > tr {
|
||||
background-color: #005281 !important;
|
||||
}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0.55em;
|
||||
@@ -308,7 +285,7 @@ onBeforeMount(() => {
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: #ffd843;
|
||||
background-color: rgb(var(--v-theme-accent));
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ import PvSwitch from "@/components/common/pv-switch.vue";
|
||||
import PvSelect from "@/components/common/pv-select.vue";
|
||||
import { type ConfigurableNetworkSettings, NetworkConnectionType } from "@/types/SettingTypes";
|
||||
import { useStateStore } from "@/stores/StateStore";
|
||||
import { useTheme } from "vuetify";
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
// Copy object to remove reference to store
|
||||
const tempSettingsStruct = ref<ConfigurableNetworkSettings>(Object.assign({}, useSettingsStore().network));
|
||||
@@ -83,16 +86,10 @@ const saveGeneralSettings = () => {
|
||||
useSettingsStore()
|
||||
.updateGeneralSettings(payload)
|
||||
.then((response) => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: response.data.text || response.data,
|
||||
color: "success"
|
||||
});
|
||||
useStateStore().showSnackbarMessage({ message: response.data.text || response.data, color: "success" });
|
||||
|
||||
// Update the local settings cause the backend checked their validity. Assign is to deref value
|
||||
useSettingsStore().network = {
|
||||
...useSettingsStore().network,
|
||||
...Object.assign({}, tempSettingsStruct.value)
|
||||
};
|
||||
useSettingsStore().network = { ...useSettingsStore().network, ...Object.assign({}, tempSettingsStruct.value) };
|
||||
})
|
||||
.catch((error) => {
|
||||
resetTempSettingsStruct();
|
||||
@@ -141,7 +138,7 @@ watchEffect(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card class="mb-3" style="background-color: #006492">
|
||||
<v-card class="mb-3 rounded-12" color="surface">
|
||||
<v-card-title>Global Settings</v-card-title>
|
||||
<div class="pa-5 pt-0">
|
||||
<v-divider class="pb-2" />
|
||||
@@ -159,16 +156,15 @@ watchEffect(() => {
|
||||
'The NetworkTables Server Address must be a valid Team Number, IP address, or Hostname'
|
||||
]"
|
||||
/>
|
||||
<v-banner
|
||||
<v-alert
|
||||
v-if="!isValidNetworkTablesIP(tempSettingsStruct.ntServerAddress) && !tempSettingsStruct.runNTServer"
|
||||
rounded
|
||||
bg-color="error"
|
||||
text-color="white"
|
||||
style="margin: 10px 0"
|
||||
class="pt-3 pb-3"
|
||||
color="error"
|
||||
density="compact"
|
||||
text="The NetworkTables Server Address is not set or is invalid. NetworkTables is unable to connect."
|
||||
icon="mdi-alert-circle-outline"
|
||||
>
|
||||
The NetworkTables Server Address is not set or is invalid. NetworkTables is unable to connect.
|
||||
</v-banner>
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'tonal'"
|
||||
/>
|
||||
<pv-radio
|
||||
v-show="!useSettingsStore().network.networkingDisabled"
|
||||
v-model="tempSettingsStruct.connectionType"
|
||||
@@ -230,35 +226,34 @@ watchEffect(() => {
|
||||
tooltip="Name of the interface PhotonVision should manage the IP address of"
|
||||
:items="useSettingsStore().networkInterfaceNames"
|
||||
/>
|
||||
<v-banner
|
||||
<v-alert
|
||||
v-if="
|
||||
!useSettingsStore().networkInterfaceNames.length &&
|
||||
tempSettingsStruct.shouldManage &&
|
||||
useSettingsStore().network.canManage &&
|
||||
!useSettingsStore().network.networkingDisabled
|
||||
"
|
||||
rounded
|
||||
bg-color="error"
|
||||
text-color="white"
|
||||
icon="mdi-information-outline"
|
||||
>
|
||||
Photon cannot detect any wired connections! Please send program logs to the developers for help.
|
||||
</v-banner>
|
||||
class="pt-3 pb-3"
|
||||
color="error"
|
||||
density="compact"
|
||||
text="Cannot detect any wired connections! Send program logs to the developers for help."
|
||||
icon="mdi-alert-circle-outline"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'tonal'"
|
||||
/>
|
||||
<pv-switch
|
||||
v-model="tempSettingsStruct.runNTServer"
|
||||
label="Run NetworkTables Server (Debugging Only)"
|
||||
tooltip="If enabled, this device will create a NT server. This is useful for home debugging, but should be disabled on-robot."
|
||||
:label-cols="4"
|
||||
/>
|
||||
<v-banner
|
||||
<v-alert
|
||||
v-if="tempSettingsStruct.runNTServer"
|
||||
rounded
|
||||
bg-color="error"
|
||||
text-color="white"
|
||||
color="buttonActive"
|
||||
density="compact"
|
||||
text="This mode is intended for debugging and should be off for proper usage. PhotonLib will NOT work!"
|
||||
icon="mdi-information-outline"
|
||||
>
|
||||
This mode is intended for debugging; it should be off for proper usage. PhotonLib will NOT work!
|
||||
</v-banner>
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'tonal'"
|
||||
/>
|
||||
<v-divider class="mt-10px pb-2" />
|
||||
<v-card-title class="pl-0 pt-3 pb-10px">Miscellaneous</v-card-title>
|
||||
<pv-switch
|
||||
@@ -267,21 +262,19 @@ watchEffect(() => {
|
||||
tooltip="If enabled, Photon will publish all pipeline results in both the Packet and Protobuf formats. This is useful for visualizing pipeline results from NT viewers such as glass and logging software such as AdvantageScope. Note: photon-lib will ignore this value and is not recommended on the field for performance."
|
||||
:label-cols="4"
|
||||
/>
|
||||
<v-banner
|
||||
<v-alert
|
||||
v-if="tempSettingsStruct.shouldPublishProto"
|
||||
rounded
|
||||
bg-color="error"
|
||||
text-color="white"
|
||||
color="buttonActive"
|
||||
density="compact"
|
||||
text="This mode is intended for debugging and may reduce performance; it should be off for field use."
|
||||
icon="mdi-information-outline"
|
||||
>
|
||||
This mode is intended for debugging; it should be off for field use. You may notice a performance hit by using
|
||||
this mode.
|
||||
</v-banner>
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'tonal'"
|
||||
/>
|
||||
<v-divider class="mt-10px pb-5" />
|
||||
</v-form>
|
||||
<v-btn
|
||||
color="accent"
|
||||
:variant="!settingsValid || !settingsHaveChanged() ? 'tonal' : 'elevated'"
|
||||
color="primary"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
style="color: black; width: 100%"
|
||||
:disabled="!settingsValid || !settingsHaveChanged()"
|
||||
@click="saveGeneralSettings"
|
||||
@@ -296,7 +289,4 @@ watchEffect(() => {
|
||||
.mt-10px {
|
||||
margin-top: 10px !important;
|
||||
}
|
||||
.v-banner__wrapper {
|
||||
padding: 6px !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,6 +5,9 @@ import { useStateStore } from "@/stores/StateStore";
|
||||
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
|
||||
import type { ObjectDetectionModelProperties } from "@/types/SettingTypes";
|
||||
import pvInput from "@/components/common/pv-input.vue";
|
||||
import { useTheme } from "vuetify";
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const showImportDialog = ref(false);
|
||||
const showInfo = ref({ show: false, model: {} as ObjectDetectionModelProperties });
|
||||
@@ -18,7 +21,7 @@ const showRenameDialog = ref({
|
||||
const address = inject<string>("backendHost");
|
||||
|
||||
const importModelFile = ref<File | null>(null);
|
||||
const importLabels = ref<String | null>(null);
|
||||
const importLabels = ref<string | null>(null);
|
||||
const importHeight = ref<number | null>(null);
|
||||
const importWidth = ref<number | null>(null);
|
||||
const importVersion = ref<string | null>(null);
|
||||
@@ -220,7 +223,7 @@ const handleBulkImport = () => {
|
||||
formData.append("data", importFile.value);
|
||||
|
||||
axios
|
||||
.post(`/objectdetection/bulkimport`, formData, {
|
||||
.post("/objectdetection/bulkimport", formData, {
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
onUploadProgress: ({ progress }) => {
|
||||
const uploadPercentage = (progress || 0) * 100.0;
|
||||
@@ -270,12 +273,17 @@ const handleBulkImport = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card class="mb-3" style="background-color: #006492">
|
||||
<v-card class="mb-3" color="surface">
|
||||
<v-card-title>Object Detection</v-card-title>
|
||||
<div class="pa-5 pt-0">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-btn color="secondary" class="justify-center" @click="() => (showImportDialog = true)">
|
||||
<v-btn
|
||||
color="buttonActive"
|
||||
class="justify-center"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="() => (showImportDialog = true)"
|
||||
>
|
||||
<v-icon start class="open-icon"> mdi-import </v-icon>
|
||||
<span class="open-label">Import Model</span>
|
||||
</v-btn>
|
||||
@@ -292,7 +300,7 @@ const handleBulkImport = () => {
|
||||
}
|
||||
"
|
||||
>
|
||||
<v-card color="primary" dark>
|
||||
<v-card color="surface" dark>
|
||||
<v-card-title class="pb-0">Import New Object Detection Model</v-card-title>
|
||||
<v-card-text>
|
||||
Upload a new object detection model to this device that can be used in a pipeline. Note that ONLY
|
||||
@@ -316,7 +324,7 @@ const handleBulkImport = () => {
|
||||
:items="['YOLOv5', 'YOLOv8', 'YOLO11']"
|
||||
/>
|
||||
<v-btn
|
||||
color="secondary"
|
||||
color="buttonActive"
|
||||
width="100%"
|
||||
:disabled="
|
||||
importModelFile === null ||
|
||||
@@ -325,9 +333,10 @@ const handleBulkImport = () => {
|
||||
importHeight === null ||
|
||||
importVersion === null
|
||||
"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="handleImport()"
|
||||
>
|
||||
<v-icon start class="open-icon"> mdi-import </v-icon>
|
||||
<v-icon start class="open-icon" size="large"> mdi-import </v-icon>
|
||||
<span class="open-label">Import Object Detection Model</span>
|
||||
</v-btn>
|
||||
</div>
|
||||
@@ -336,20 +345,31 @@ const handleBulkImport = () => {
|
||||
</v-dialog>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-btn color="secondary" class="justify-center" @click="() => (showBulkImportDialog = true)">
|
||||
<v-btn
|
||||
color="buttonActive"
|
||||
class="justify-center"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="() => (showBulkImportDialog = true)"
|
||||
>
|
||||
<v-icon start class="open-icon"> mdi-import </v-icon>
|
||||
<span class="open-label">Bulk Import</span>
|
||||
</v-btn>
|
||||
<v-dialog v-model="showBulkImportDialog" width="600">
|
||||
<v-card color="primary" dark>
|
||||
<v-card color="surface" dark>
|
||||
<v-card-title class="pb-0">Import Multiple Object Detection Models</v-card-title>
|
||||
<v-card-text>
|
||||
Upload a zip file containing multiple object detection models to this device. Note this zip file should
|
||||
only come from a previous export of object detection models.
|
||||
<div class="pa-5 pb-0">
|
||||
<v-file-input v-model="importFile" variant="underlined" label="Zip File" accept=".zip" />
|
||||
<v-btn color="secondary" width="100%" :disabled="importFile === null" @click="handleBulkImport()">
|
||||
<v-icon start class="open-icon"> mdi-import </v-icon>
|
||||
<v-btn
|
||||
color="buttonActive"
|
||||
width="100%"
|
||||
:disabled="importFile === null"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="handleBulkImport()"
|
||||
>
|
||||
<v-icon start class="open-icon" size="large"> mdi-import </v-icon>
|
||||
<span class="open-label">Bulk Import</span>
|
||||
</v-btn>
|
||||
</div>
|
||||
@@ -358,7 +378,11 @@ const handleBulkImport = () => {
|
||||
</v-dialog>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-btn color="secondary" @click="openExportPrompt">
|
||||
<v-btn
|
||||
color="buttonPassive"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="openExportPrompt"
|
||||
>
|
||||
<v-icon start class="open-icon"> mdi-export </v-icon>
|
||||
<span class="open-label">Export Models</span>
|
||||
</v-btn>
|
||||
@@ -371,7 +395,11 @@ const handleBulkImport = () => {
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-btn color="error" @click="() => (showNukeDialog = true)">
|
||||
<v-btn
|
||||
color="error"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="() => (showNukeDialog = true)"
|
||||
>
|
||||
<v-icon left class="open-icon"> mdi-trash </v-icon>
|
||||
<span class="open-label">Clear and reset models</span>
|
||||
</v-btn>
|
||||
@@ -398,45 +426,66 @@ const handleBulkImport = () => {
|
||||
icon
|
||||
small
|
||||
color="error"
|
||||
@click="() => (confirmDeleteDialog = { show: true, model })"
|
||||
title="Delete Model"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="() => (confirmDeleteDialog = { show: true, model })"
|
||||
>
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
<v-icon size="large">mdi-trash-can-outline</v-icon>
|
||||
</v-btn>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<v-btn
|
||||
icon
|
||||
small
|
||||
color="primary"
|
||||
@click="() => (showRenameDialog = { show: true, model, newName: '' })"
|
||||
color="buttonActive"
|
||||
title="Rename Model"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="() => (showRenameDialog = { show: true, model, newName: '' })"
|
||||
>
|
||||
<v-icon>mdi-pencil</v-icon>
|
||||
<v-icon size="large">mdi-pencil</v-icon>
|
||||
</v-btn>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<v-btn icon small color="info" @click="() => (showInfo = { show: true, model })">
|
||||
<v-icon>mdi-information</v-icon>
|
||||
<v-btn
|
||||
icon
|
||||
small
|
||||
color="buttonPassive"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="() => (showInfo = { show: true, model })"
|
||||
>
|
||||
<v-icon size="large">mdi-information</v-icon>
|
||||
</v-btn>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
|
||||
<v-dialog v-model="confirmDeleteDialog.show" width="600">
|
||||
<v-card color="primary" dark>
|
||||
<v-card color="surface" dark>
|
||||
<v-card-title>Delete Object Detection Model</v-card-title>
|
||||
<v-card-text class="pt-0">
|
||||
Are you sure you want to delete the model {{ confirmDeleteDialog.model.nickname }}?
|
||||
<v-card-actions class="pt-5 pb-0 pr-0" style="justify-content: flex-end">
|
||||
<v-btn variant="elevated" color="error" @click="deleteModel(confirmDeleteDialog.model)">Delete</v-btn>
|
||||
<v-btn variant="elevated" @click="confirmDeleteDialog.show = false" color="secondary">Cancel</v-btn>
|
||||
<v-btn
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
color="buttonPassive"
|
||||
@click="confirmDeleteDialog.show = false"
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
color="error"
|
||||
@click="deleteModel(confirmDeleteDialog.model)"
|
||||
>
|
||||
Delete
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<v-dialog v-model="showRenameDialog.show" width="600">
|
||||
<v-card color="primary" dark>
|
||||
<v-card color="surface" dark>
|
||||
<v-card-title>Rename Object Detection Model</v-card-title>
|
||||
<v-card-text class="pt-0">
|
||||
Enter a new name for the model {{ showRenameDialog.model.nickname }}:
|
||||
@@ -445,22 +494,32 @@ const handleBulkImport = () => {
|
||||
</div>
|
||||
<v-card-actions class="pt-5 pb-0 pr-0" style="justify-content: flex-end">
|
||||
<v-btn
|
||||
variant="elevated"
|
||||
color="secondary"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
color="error"
|
||||
@click="showRenameDialog.show = false"
|
||||
>Cancel</v-btn
|
||||
>
|
||||
<v-btn
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
color="buttonActive"
|
||||
@click="renameModel(showRenameDialog.model, showRenameDialog.newName)"
|
||||
>Rename</v-btn
|
||||
>
|
||||
<v-btn variant="elevated" @click="showRenameDialog.show = false" color="error">Cancel</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<v-dialog v-model="showInfo.show" width="600">
|
||||
<v-card color="primary" dark>
|
||||
<v-card color="surface" dark>
|
||||
<v-card-title>Object Detection Model Info</v-card-title>
|
||||
<v-card-text class="pt-0">
|
||||
<v-btn color="secondary" width="100%" @click="openExportIndividualModelPrompt">
|
||||
<v-icon left class="open-icon"> mdi-export </v-icon>
|
||||
<v-btn
|
||||
color="buttonPassive"
|
||||
width="100%"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="openExportIndividualModelPrompt"
|
||||
>
|
||||
<v-icon left class="open-icon" size="large"> mdi-export </v-icon>
|
||||
<span class="open-label">Export Model</span>
|
||||
</v-btn>
|
||||
<a
|
||||
@@ -486,12 +545,12 @@ const handleBulkImport = () => {
|
||||
</div>
|
||||
|
||||
<v-dialog v-model="showNukeDialog" width="800" dark>
|
||||
<v-card color="primary" flat>
|
||||
<v-card color="surface" flat>
|
||||
<v-card-title style="display: flex; justify-content: center">
|
||||
<span class="open-label">
|
||||
<v-icon end color="error" class="open-icon ma-1">mdi-nuke</v-icon>
|
||||
<v-icon end color="error" class="open-icon ma-1" size="large">mdi-alert-outline</v-icon>
|
||||
Clear and Reset Object Detection Models
|
||||
<v-icon end color="error" class="open-icon ma-1">mdi-nuke</v-icon>
|
||||
<v-icon end color="error" class="open-icon ma-1" size="large">mdi-alert-outline</v-icon>
|
||||
</span>
|
||||
</v-card-title>
|
||||
<v-card-text class="pt-0 pb-10px">
|
||||
@@ -500,8 +559,13 @@ const handleBulkImport = () => {
|
||||
<span> This will delete ALL OF YOUR MODELS and re-extract the default models. </span>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-btn color="secondary" style="float: right" @click="openExportPrompt">
|
||||
<v-icon start class="open-icon"> mdi-export </v-icon>
|
||||
<v-btn
|
||||
color="buttonActive"
|
||||
style="float: right"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="openExportPrompt"
|
||||
>
|
||||
<v-icon start class="open-icon" size="large"> mdi-export </v-icon>
|
||||
<span class="open-label">Backup Models</span>
|
||||
<a
|
||||
ref="exportModels"
|
||||
@@ -527,9 +591,10 @@ const handleBulkImport = () => {
|
||||
color="error"
|
||||
width="100%"
|
||||
:disabled="yesDeleteMyModelsText.toLowerCase() !== expected.toLowerCase()"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="nukeModels"
|
||||
>
|
||||
<v-icon start class="open-icon"> mdi-trash-can-outline </v-icon>
|
||||
<v-icon start class="open-icon" size="large"> mdi-trash-can-outline </v-icon>
|
||||
<span class="open-label">
|
||||
{{ $vuetify.display.mdAndUp ? "Delete models, I have backed up what I need" : "Delete Models" }}
|
||||
</span>
|
||||
@@ -561,11 +626,9 @@ const handleBulkImport = () => {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
background-color: #006492 !important;
|
||||
|
||||
th,
|
||||
td {
|
||||
background-color: #006492 !important;
|
||||
font-size: 1rem !important;
|
||||
color: white !important;
|
||||
text-align: center !important;
|
||||
@@ -575,10 +638,6 @@ const handleBulkImport = () => {
|
||||
font-family: monospace !important;
|
||||
}
|
||||
|
||||
tbody :hover td {
|
||||
background-color: #005281 !important;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0.55em;
|
||||
@@ -591,7 +650,7 @@ const handleBulkImport = () => {
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: #ffd843;
|
||||
background-color: rgb(var(--v-theme-accent));
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user