Dark mode and minor interface tweaks (#2016)

Co-authored-by: Sam Freund <samf.236@proton.me>
This commit is contained in:
Devon Doyle
2025-08-04 01:15:33 -04:00
committed by GitHub
parent 3e19cd45cc
commit fce54d12c1
36 changed files with 956 additions and 765 deletions

View File

@@ -8,6 +8,9 @@ import PvIcon from "@/components/common/pv-icon.vue";
import PvInput from "@/components/common/pv-input.vue";
import { PipelineType } from "@/types/PipelineTypes";
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
import { useTheme } from "vuetify";
const theme = useTheme();
const changeCurrentCameraUniqueName = (cameraUniqueName: string) => {
useCameraSettingsStore().setCurrentCameraUniqueName(cameraUniqueName, true);
@@ -53,10 +56,7 @@ const saveCameraNameEdit = (newName: string) => {
useCameraSettingsStore()
.changeCameraNickname(newName, false)
.then((response) => {
useStateStore().showSnackbarMessage({
color: "success",
message: response.data.text || response.data
});
useStateStore().showSnackbarMessage({ color: "success", message: response.data.text || response.data });
useCameraSettingsStore().currentCameraSettings.nickname = newName;
})
.catch((error) => {
@@ -241,7 +241,7 @@ const wrappedCameras = computed<SelectItem[]>(() =>
</script>
<template>
<v-card color="primary">
<v-card color="surface" class="rounded-12">
<v-row no-gutters class="pl-4 pt-2 pb-0">
<v-col cols="10" class="pa-0">
<pv-select
@@ -326,19 +326,24 @@ const wrappedCameras = computed<SelectItem[]>(() =>
<pv-icon color="#c5c5c5" :right="true" icon-name="mdi-pencil" tooltip="Edit pipeline name" />
</v-list-item-title>
</v-list-item>
<v-list-item @click="duplicateCurrentPipeline">
<v-list-item-title>
<pv-icon color="#c5c5c5" :right="true" icon-name="mdi-content-copy" tooltip="Duplicate pipeline" />
</v-list-item-title>
</v-list-item>
<v-list-item @click="showCreatePipelineDialog">
<v-list-item-title>
<pv-icon color="#c5c5c5" :right="true" icon-name="mdi-plus" tooltip="Add new pipeline" />
<pv-icon color="green" :right="true" icon-name="mdi-plus" tooltip="Add new pipeline" />
</v-list-item-title>
</v-list-item>
<v-list-item @click="showPipelineDeletionConfirmationDialog = true">
<v-list-item-title>
<pv-icon color="red-darken-2" :right="true" icon-name="mdi-delete" tooltip="Delete pipeline" />
</v-list-item-title>
</v-list-item>
<v-list-item @click="duplicateCurrentPipeline">
<v-list-item-title>
<pv-icon color="#c5c5c5" :right="true" icon-name="mdi-content-copy" tooltip="Duplicate pipeline" />
<pv-icon
color="red-darken-2"
:right="true"
icon-name="mdi-trash-can-outline"
tooltip="Delete pipeline"
/>
</v-list-item-title>
</v-list-item>
</v-list>
@@ -370,7 +375,7 @@ const wrappedCameras = computed<SelectItem[]>(() =>
</v-col>
</v-row>
<v-dialog v-model="showPipelineCreationDialog" persistent width="500">
<v-card color="primary">
<v-card color="surface">
<v-card-title class="pb-0"> Create New Pipeline </v-card-title>
<v-card-text class="pt-0 pb-0">
<pv-input
@@ -391,42 +396,52 @@ const wrappedCameras = computed<SelectItem[]>(() =>
</v-card-text>
<v-card-actions class="pr-5 pt-10px pb-5">
<v-btn
color="#ffd843"
color="buttonPassive"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
@click="cancelPipelineCreation"
>
Cancel
</v-btn>
<v-btn
color="buttonActive"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:disabled="checkPipelineName(newPipelineName) !== true"
variant="flat"
@click="createNewPipeline"
>
Save
Create
</v-btn>
<v-btn color="error" variant="elevated" @click="cancelPipelineCreation"> Cancel </v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="showPipelineDeletionConfirmationDialog" width="500">
<v-card color="primary">
<v-card-title class="pb-0">Pipeline Deletion Confirmation</v-card-title>
<v-card color="surface">
<v-card-title class="pb-0">Delete Pipeline</v-card-title>
<v-card-text>
Are you sure you want to delete the pipeline
<b style="color: white; font-weight: bold">{{
useCameraSettingsStore().currentPipelineSettings.pipelineNickname
}}</b
>? This cannot be undone.
Are you sure you want to delete
<span style="color: white">"{{ useCameraSettingsStore().currentPipelineSettings.pipelineNickname }}"</span>?
This cannot be undone.
</v-card-text>
<v-card-actions class="pa-5 pt-0">
<v-btn variant="flat" color="error" @click="confirmDeleteCurrentPipeline"> Yes, I'm sure </v-btn>
<v-btn
variant="flat"
color="#ffd843"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
color="primary"
class="text-black"
@click="showPipelineDeletionConfirmationDialog = false"
>
No, take me back
Cancel
</v-btn>
<v-btn
color="error"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
@click="confirmDeleteCurrentPipeline"
>
Delete
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="showPipelineTypeChangeDialog" persistent width="600">
<v-card color="primary" dark>
<v-card color="surface" dark>
<v-card-title class="pb-0">Change Pipeline Type</v-card-title>
<v-card-text>
Are you sure you want to change the current pipeline type? This will cause all the pipeline settings to be
@@ -434,9 +449,20 @@ const wrappedCameras = computed<SelectItem[]>(() =>
settings.
</v-card-text>
<v-card-actions class="pa-5 pt-0">
<v-btn color="error" variant="elevated" @click="confirmChangePipelineType"> Yes, I'm sure </v-btn>
<v-btn color="#ffd843" variant="elevated" class="text-black" @click="cancelChangePipelineType">
No, take me back
<v-btn
color="buttonPassive"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
class="text-black"
@click="cancelChangePipelineType"
>
Cancel
</v-btn>
<v-btn
color="buttonActive"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
@click="confirmChangePipelineType"
>
Confirm
</v-btn>
</v-card-actions>
</v-card>

View File

@@ -39,19 +39,17 @@ const performanceRecommendation = computed<string>(() => {
</script>
<template>
<v-card color="primary" height="100%" class="d-flex flex-column" dark>
<v-card color="surface" height="100%" class="d-flex flex-column rounded-12" dark>
<v-card-title class="justify-space-between align-center pt-1 pb-1 d-flex">
<span>Cameras</span>
<v-chip
v-if="useCameraSettingsStore().currentCameraSettings.isConnected"
label
:color="fpsTooLow ? 'error' : ''"
style="font-size: 1rem; padding: 0; margin: 0"
:variant="fpsTooLow ? 'tonal' : 'text'"
:style="{ color: fpsTooLow ? '#C7EA46' : '#ff4d00' }"
:color="fpsTooLow ? 'error' : 'primary'"
style="font-size: 1.1rem; padding: 0; margin: 0"
variant="text"
>
<span class="pr-1"
>Processing @ {{ Math.round(useStateStore().currentPipelineResults?.fps || 0) }}&nbsp;FPS &ndash;</span
<span class="pr-1">{{ Math.round(useStateStore().currentPipelineResults?.fps || 0) }}&nbsp;FPS &ndash;</span
><span>{{ performanceRecommendation }}</span>
</v-chip>
<v-chip v-else label variant="text" color="red" style="font-size: 1rem; padding: 0; margin: 0">
@@ -61,7 +59,7 @@ const performanceRecommendation = computed<string>(() => {
v-model="driverMode"
:disabled="useCameraSettingsStore().isCalibrationMode || useCameraSettingsStore().pipelineNames.length === 0"
label="Driver Mode"
color="accent"
color="primary"
hide-details="auto"
/>
</v-card-title>

View File

@@ -15,6 +15,9 @@ import PnPTab from "@/components/dashboard/tabs/PnPTab.vue";
import Map3DTab from "@/components/dashboard/tabs/Map3DTab.vue";
import { WebsocketPipelineType } from "@/types/WebsocketDataTypes";
import { useDisplay } from "vuetify/lib/composables/display";
import { useTheme } from "vuetify";
const theme = useTheme();
interface ConfigOption {
tabName: string;
@@ -22,46 +25,16 @@ interface ConfigOption {
}
const allTabs = Object.freeze({
inputTab: {
tabName: "Input",
component: InputTab
},
thresholdTab: {
tabName: "Threshold",
component: ThresholdTab
},
contoursTab: {
tabName: "Contours",
component: ContoursTab
},
apriltagTab: {
tabName: "AprilTag",
component: AprilTagTab
},
arucoTab: {
tabName: "Aruco",
component: ArucoTab
},
objectDetectionTab: {
tabName: "Object Detection",
component: ObjectDetectionTab
},
outputTab: {
tabName: "Output",
component: OutputTab
},
targetsTab: {
tabName: "Targets",
component: TargetsTab
},
pnpTab: {
tabName: "PnP",
component: PnPTab
},
map3dTab: {
tabName: "3D",
component: Map3DTab
}
inputTab: { tabName: "Input", component: InputTab },
thresholdTab: { tabName: "Threshold", component: ThresholdTab },
contoursTab: { tabName: "Contours", component: ContoursTab },
apriltagTab: { tabName: "AprilTag", component: AprilTagTab },
arucoTab: { tabName: "Aruco", component: ArucoTab },
objectDetectionTab: { tabName: "Object Detection", component: ObjectDetectionTab },
outputTab: { tabName: "Output", component: OutputTab },
targetsTab: { tabName: "Targets", component: TargetsTab },
pnpTab: { tabName: "PnP", component: PnPTab },
map3dTab: { tabName: "3D", component: Map3DTab }
});
const selectedTabs = ref([0, 0, 0, 0]);
@@ -144,13 +117,13 @@ const onBeforeTabUpdate = () => {
<template>
<v-row no-gutters class="tabGroups">
<template v-if="!useCameraSettingsStore().hasConnected">
<v-col cols="12">
<v-card color="error">
<v-card-title class="text-white">
Camera has not connected. Please check your connection and try again.
</v-card-title>
</v-card>
</v-col>
<v-alert
color="error"
density="compact"
text="Camera is not connected. Please check your connection and try again."
icon="mdi-alert-circle-outline"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'tonal'"
/>
</template>
<template v-else>
<v-col
@@ -160,8 +133,8 @@ const onBeforeTabUpdate = () => {
:class="tabGroupIndex !== tabGroups.length - 1 && 'pr-3'"
@vue:before-update="onBeforeTabUpdate"
>
<v-card color="primary" height="100%" class="pr-5 pl-5">
<v-tabs v-model="selectedTabs[tabGroupIndex]" grow bg-color="primary" height="48" slider-color="accent">
<v-card color="surface" height="100%" class="pr-5 pl-5 rounded-12">
<v-tabs v-model="selectedTabs[tabGroupIndex]" grow bg-color="surface" height="48" slider-color="buttonActive">
<v-tab v-for="(tabConfig, index) in tabGroupData" :key="index">
{{ tabConfig.tabName }}
</v-tab>

View File

@@ -2,6 +2,9 @@
import { computed } from "vue";
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
import { useStateStore } from "@/stores/StateStore";
import { useTheme } from "vuetify";
const theme = useTheme();
const value = defineModel<number[]>();
@@ -18,8 +21,8 @@ const processingMode = computed<number>({
<template>
<v-card
:disabled="useCameraSettingsStore().isDriverMode || useStateStore().colorPickingMode"
class="mt-3"
color="primary"
class="mt-3 rounded-12"
color="surface"
style="flex-grow: 1; display: flex; flex-direction: column"
>
<v-row class="pa-3 pb-0 align-center">
@@ -27,21 +30,27 @@ const processingMode = computed<number>({
<p style="color: white">Processing Mode</p>
<v-btn-toggle v-model="processingMode" mandatory base-color="surface-variant" class="fill w-100">
<v-btn
color="secondary"
color="buttonPassive"
:disabled="!useCameraSettingsStore().hasConnected"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
class="w-50"
prepend-icon="mdi-square-outline"
>
<template #prepend>
<v-icon size="large">mdi-square-outline</v-icon>
</template>
<span>2D</span>
</v-btn>
<v-btn
color="secondary"
color="buttonPassive"
:disabled="
!useCameraSettingsStore().hasConnected || !useCameraSettingsStore().isCurrentVideoFormatCalibrated
"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
class="w-50"
prepend-icon="mdi-cube-outline"
>
<template #prepend>
<v-icon size="large">mdi-cube-outline</v-icon>
</template>
<span>3D</span>
</v-btn>
</v-btn-toggle>
@@ -51,12 +60,20 @@ const processingMode = computed<number>({
<v-col class="pa-4 pt-0">
<p style="color: white">Stream Display</p>
<v-btn-toggle v-model="value" :multiple="true" mandatory base-color="surface-variant" class="fill w-100">
<v-btn color="secondary" class="fill w-50">
<v-icon start class="mode-btn-icon">mdi-import</v-icon>
<v-btn
color="buttonPassive"
class="fill w-50"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
>
<v-icon start class="mode-btn-icon" size="large">mdi-import</v-icon>
<span class="mode-btn-label">Raw</span>
</v-btn>
<v-btn color="secondary" class="fill w-50">
<v-icon start class="mode-btn-icon">mdi-export</v-icon>
<v-btn
color="buttonPassive"
class="fill w-50"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
>
<v-icon start class="mode-btn-icon" size="large">mdi-export</v-icon>
<span class="mode-btn-label">Processed</span>
</v-btn>
</v-btn-toggle>

View File

@@ -7,6 +7,9 @@ import { computed } from "vue";
import { RobotOffsetType } from "@/types/SettingTypes";
import { useStateStore } from "@/stores/StateStore";
import { useDisplay } from "vuetify";
import { useTheme } from "vuetify";
const theme = useTheme();
const isTagPipeline = computed(
() =>
@@ -159,8 +162,9 @@ const interactiveCols = computed(() =>
<v-btn
size="small"
block
color="accent"
color="primary"
class="text-black"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
@click="useCameraSettingsStore().takeRobotOffsetPoint(RobotOffsetType.Single)"
>
Take Point
@@ -170,7 +174,8 @@ const interactiveCols = computed(() =>
<v-btn
size="small"
block
color="yellow-darken-3"
color="error"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
@click="useCameraSettingsStore().takeRobotOffsetPoint(RobotOffsetType.Clear)"
>
Clear All Points
@@ -185,8 +190,9 @@ const interactiveCols = computed(() =>
<v-btn
size="small"
block
color="accent"
color="primary"
class="text-black"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
@click="useCameraSettingsStore().takeRobotOffsetPoint(RobotOffsetType.DualFirst)"
>
Take First Point
@@ -196,8 +202,9 @@ const interactiveCols = computed(() =>
<v-btn
size="small"
block
color="accent"
color="primary"
class="text-black"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
@click="useCameraSettingsStore().takeRobotOffsetPoint(RobotOffsetType.DualSecond)"
>
Take Second Point
@@ -207,7 +214,8 @@ const interactiveCols = computed(() =>
<v-btn
size="small"
block
color="yellow-darken-3"
color="error"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
@click="useCameraSettingsStore().takeRobotOffsetPoint(RobotOffsetType.Clear)"
>
Clear All Points
@@ -238,6 +246,6 @@ const interactiveCols = computed(() =>
.metric-item-title {
font-size: 18px;
text-decoration: underline;
text-decoration-color: #ffd843;
text-decoration-color: rgb(var(--v-theme-primary));
}
</style>

View File

@@ -4,6 +4,9 @@ import { type ActivePipelineSettings, PipelineType } from "@/types/PipelineTypes
import { useStateStore } from "@/stores/StateStore";
import { angleModulus, toDeg } from "@/lib/MathUtils";
import { computed } from "vue";
import { useTheme } from "vuetify";
const theme = useTheme();
// TODO fix pipeline typing in order to fix this, the store settings call should be able to infer that only valid pipeline type settings are exposed based on pre-checks for the entire config section
// Defer reference to store access method
@@ -200,7 +203,12 @@ const resetCurrentBuffer = () => {
>Multi-tag pose standard deviation over the last
{{ useStateStore().currentMultitagBuffer?.length || "NaN" }}/100 samples
</v-card-subtitle>
<v-btn color="secondary" class="mb-4 mt-1" style="width: min-content" variant="flat" @click="resetCurrentBuffer"
<v-btn
color="buttonActive"
class="mb-4 mt-1"
style="width: min-content"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
@click="resetCurrentBuffer"
>Reset Samples</v-btn
>
<v-table density="compact">
@@ -274,7 +282,6 @@ th {
padding-right: 8px !important;
}
.v-table {
background-color: #006492 !important;
width: 100%;
font-size: 1rem !important;
@@ -287,11 +294,6 @@ th {
}
}
tbody {
:hover {
td {
background-color: #005281 !important;
}
}
tr {
td {
padding: 0 !important;
@@ -313,7 +315,7 @@ th {
}
::-webkit-scrollbar-thumb {
background-color: #ffd843;
background-color: rgb(var(--v-theme-accent));
border-radius: 10px;
}
}

View File

@@ -6,6 +6,9 @@ import PvSwitch from "@/components/common/pv-switch.vue";
import { useStateStore } from "@/stores/StateStore";
import { ColorPicker, type HSV } from "@/lib/ColorPicker";
import { useDisplay } from "vuetify";
import { useTheme } from "vuetify";
const theme = useTheme();
const averageHue = computed<number>(() => {
const isHueInverted = useCameraSettingsStore().currentPipelineSettings.hueInverted;
@@ -186,17 +189,25 @@ const interactiveCols = computed(() =>
<v-btn
size="small"
block
color="accent"
color="primary"
class="text-black"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
@click="enableColorPicking(useCameraSettingsStore().currentPipelineSettings.hueInverted ? 2 : 3)"
>
<v-icon start> mdi-minus </v-icon>
<v-icon start size="large"> mdi-minus </v-icon>
Shrink Range
</v-btn>
</v-col>
<v-col cols="4" class="pl-0 pr-0">
<v-btn color="accent" class="text-black" size="small" block @click="enableColorPicking(1)">
<v-icon start> mdi-plus-minus </v-icon>
<v-btn
color="primary"
class="text-black"
size="small"
block
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
@click="enableColorPicking(1)"
>
<v-icon start size="large"> mdi-plus-minus </v-icon>
{{ useCameraSettingsStore().currentPipelineSettings.hueInverted ? "Exclude" : "Set to" }} Average
</v-btn>
</v-col>
@@ -204,18 +215,28 @@ const interactiveCols = computed(() =>
<v-btn
size="small"
block
color="accent"
color="primary"
class="text-black"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
@click="enableColorPicking(useCameraSettingsStore().currentPipelineSettings.hueInverted ? 3 : 2)"
>
<v-icon start> mdi-plus </v-icon>
<v-icon start size="large"> mdi-plus </v-icon>
Expand Range
</v-btn>
</v-col>
</template>
<template v-else>
<v-card-text class="pa-0 pt-3 pb-3">
<v-btn block color="accent" class="text-black" size="small" @click="disableColorPicking"> Cancel </v-btn>
<v-btn
block
color="primary"
class="text-black"
size="small"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
@click="disableColorPicking"
>
Cancel
</v-btn>
</v-card-text>
</template>
</div>