Clean up spacing and other things in various parts of the UI (#1972)

## Description

After the Vue 3 upgrade, the spacing for various UI elements was left
inconsistent in many places. Dialogs were hit especially hard and had
some very inconsistent spacing. Additionally, the 24 pixels of padding
around all cards was noted as a waste of space and unnecessary, so it
has been shrunk down to 20 pixels to make the UI a tiny bit more compact
and to make it visually closer to some parts of the UI that have 16
pixels of padding (the camera views are the most notable example).
Padding between input elements has also been reduced to 20 pixels (this
required some hackery to get consistent sizes on input elements, since
switches and sliders have different heights.)

Some other minor UI tweaks were made, such as removing the divider
between dialog contents and dialog buttons because it visually looks
better, shrinking the banner padding so it doesn't displace as much
content, making the banner background one uniform color instead of a
highlight around the icon, fixing the targets tab so that the columns
stop shifting around when the values change, preserving newlines in the
log view, cleaning up the object detection UI, and making the import
dialogs have consistently inset input elements.

Old dashboard:

![image](https://github.com/user-attachments/assets/409c7ddd-4b7d-4535-9f3f-3970d9dd85f8)

New dashboard:

![image](https://github.com/user-attachments/assets/587ac540-1d6d-40e5-9c6b-00697bab6cbc)

Old Camera tab:

![image](https://github.com/user-attachments/assets/2f1d50a1-131f-4fb7-8617-e1cb4dc5504c)

New Camera tab:

![image](https://github.com/user-attachments/assets/6d5581b7-faff-400a-8e34-e3abf00e0af6)

Old Calibration Info:

![image](https://github.com/user-attachments/assets/81133cc1-c861-4746-9b1e-8320312037de)

New Calibration Info:

![image](https://github.com/user-attachments/assets/0de5935c-84a7-4606-bbc1-8e6d227b7b60)

Old Log Viewer:

![image](https://github.com/user-attachments/assets/f2c32a10-3353-4781-93d7-8e0ffa8ca7fe)

New Log Viewer:

![image](https://github.com/user-attachments/assets/0aeee866-c182-4e80-9025-56bf383d714f)

Old Pipeline Creation Dialog:

![image](https://github.com/user-attachments/assets/a0eb368d-d9af-4cb3-8d9c-fcd12a5caf36)

New Pipeline Creation Dialog:

![image](https://github.com/user-attachments/assets/f05f34a3-f42e-4e8f-9ccd-171a48980b8f)

Old Factory Reset:

![image](https://github.com/user-attachments/assets/9c16a7f7-a454-4ee4-8574-98abf9b94e2d)

New Factory Reset:

![image](https://github.com/user-attachments/assets/fb67888c-c4f1-4e8e-9d02-6943e7a918eb)

Old Pipeline Change:

![image](https://github.com/user-attachments/assets/3acb215a-6639-4d50-a4e6-18b50c3ec1bd)

New Pipeline Change:

![image](https://github.com/user-attachments/assets/a2b18582-cdbd-407c-9690-f11aecf78c76)

Old Import Dialog:

![image](https://github.com/user-attachments/assets/ff43b0bd-3f99-44e5-97fa-c250cd331790)

New Import Dialog:

![image](https://github.com/user-attachments/assets/7ec46023-d47a-45d7-80b8-6881b812300e)

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2024.3.1
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
This commit is contained in:
Gold856
2025-07-12 00:02:23 -04:00
committed by GitHub
parent dbbb00f955
commit d7e536dda9
29 changed files with 569 additions and 483 deletions

View File

@@ -73,8 +73,9 @@ if (!is_demo) {
<style lang="scss">
@use "@/assets/styles/settings";
@use "@/assets/styles/variables";
@use "sass:map";
@media #{map-get(settings.$display-breakpoints, 'md-and-down')} {
@media #{map.get(settings.$display-breakpoints, 'md-and-down')} {
html {
font-size: 14px !important;
}

View File

@@ -1,4 +1,4 @@
@import "@fontsource/prompt";
@use "@fontsource/prompt";
$default-font: "Prompt", sans-serif !default;
$body-font-family: $default-font;
@@ -24,10 +24,51 @@ html {
background: #005281 !important;
}
.v-card__title {
.v-banner {
padding: 4px !important;
}
.v-card-title,
.v-dialog > .v-overlay__content > .v-card > .v-card-title,
.v-dialog > .v-overlay__content > form > .v-card > .v-card-title {
padding: 20px;
word-break: break-word !important;
}
.v-card-text,
.v-dialog > .v-overlay__content > .v-card > .v-card-text,
.v-dialog > .v-overlay__content > form > .v-card > .v-card-text {
font-size: 1rem;
padding: 20px;
}
.v-card-subtitle,
.v-dialog > .v-overlay__content > .v-card > .v-card-subtitle,
.v-dialog > .v-overlay__content > form > .v-card > .v-card-subtitle {
font-size: 1rem;
padding: 20px;
}
.v-field__input {
padding: 0px !important;
}
.pb-10px {
padding-bottom: 10px !important;
}
.pt-10px {
padding-top: 10px !important;
}
.pl-10px {
padding-left: 10px !important;
}
.pr-10px {
padding-right: 10px !important;
}
.pa-10px {
padding: 10px !important;
}

View File

@@ -22,3 +22,8 @@ const logColorClass = computed<string>(() => {
<template>
<div :class="logColorClass">[{{ source.timestamp.toTimeString().split(" ")[0] }}] {{ source.message }}</div>
</template>
<style scoped>
div {
white-space: pre-wrap;
}
</style>

View File

@@ -74,7 +74,7 @@ document.addEventListener("keydown", (e) => {
<template>
<v-dialog v-model="useStateStore().showLogModal" width="1500" dark>
<v-card class="dialog-container pa-6" color="primary" flat>
<v-card class="dialog-container pa-5" color="primary" flat>
<!-- Logs header -->
<v-row class="pb-3">
<v-col cols="4">
@@ -168,7 +168,7 @@ document.addEventListener("keydown", (e) => {
.log-display {
/* Dialog data size - options */
height: calc(100% - 66px);
height: calc(100% - 56px);
padding: 10px;
background-color: #232c37 !important;
border-radius: 5px;

View File

@@ -215,9 +215,9 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
<template>
<div>
<v-card class="mb-3" color="primary" dark>
<v-card-title class="pa-6 pb-3">Camera Calibration</v-card-title>
<v-card-title>Camera Calibration</v-card-title>
<v-card-text v-show="!isCalibrating">
<v-card-subtitle class="pt-3 pl-2 pb-4 text-white">Current Calibration</v-card-subtitle>
<v-card-subtitle class="pt-0 pl-0 pr-0 text-white">Current Calibration</v-card-subtitle>
<v-table fixed-header height="100%" density="compact">
<thead>
<tr>
@@ -241,7 +241,7 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
<v-tooltip location="bottom">
<template #activator="{ props }">
<td v-bind="props" @click="setSelectedVideoFormat(value)">
<v-icon size="small" class="mr-2">mdi-information</v-icon>
<v-icon size="small">mdi-information</v-icon>
</td>
</template>
<span>Click for more info on this calibration.</span>
@@ -249,288 +249,293 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
</tr>
</tbody>
</v-table>
</v-card-text>
<v-card-text v-if="useCameraSettingsStore().isConnected" class="d-flex flex-column pa-6 pt-0">
<v-card-subtitle v-show="!isCalibrating" class="pl-0 pb-3 pt-3 text-white"
>Configure New Calibration</v-card-subtitle
>
<v-form ref="form" v-model="settingsValid">
<!-- TODO: the default videoFormatIndex is 0, but the list of unique video mode indexes might not include 0. getUniqueVideoResolutionStrings indexing is also different from the normal video mode indexing -->
<pv-select
v-model="useStateStore().calibrationData.videoFormatIndex"
label="Resolution"
:select-cols="8"
:disabled="isCalibrating"
tooltip="Resolution to calibrate at (you will have to calibrate every resolution you use 3D mode on)"
:items="getUniqueVideoResolutionStrings()"
/>
<pv-select
v-show="isCalibrating && boardType != CalibrationBoardTypes.Charuco"
v-model="useCameraSettingsStore().currentPipelineSettings.streamingFrameDivisor"
label="Decimation"
tooltip="Resolution to which camera frames are downscaled for detection. Calibration still uses full-res"
:items="calibrationDivisors"
:select-cols="8"
<div v-if="useCameraSettingsStore().isConnected" class="d-flex flex-column">
<v-card-subtitle v-show="!isCalibrating" class="pl-0 pb-3 pt-3 text-white"
>Configure New Calibration</v-card-subtitle
>
<v-form ref="form" v-model="settingsValid">
<!-- TODO: the default videoFormatIndex is 0, but the list of unique video mode indexes might not include 0. getUniqueVideoResolutionStrings indexing is also different from the normal video mode indexing -->
<pv-select
v-model="useStateStore().calibrationData.videoFormatIndex"
label="Resolution"
:select-cols="8"
:disabled="isCalibrating"
tooltip="Resolution to calibrate at (you will have to calibrate every resolution you use 3D mode on)"
:items="getUniqueVideoResolutionStrings()"
/>
<pv-select
v-show="isCalibrating && boardType != CalibrationBoardTypes.Charuco"
v-model="useCameraSettingsStore().currentPipelineSettings.streamingFrameDivisor"
label="Decimation"
tooltip="Resolution to which camera frames are downscaled for detection. Calibration still uses full-res"
:items="calibrationDivisors"
:select-cols="8"
@update:modelValue="
(v) => useCameraSettingsStore().changeCurrentPipelineSetting({ streamingFrameDivisor: +v }, false)
"
/>
<pv-select
v-model="boardType"
label="Board Type"
tooltip="Calibration board pattern to use"
:select-cols="8"
:items="['Chessboard', 'Charuco']"
:disabled="isCalibrating"
/>
<pv-select
v-show="boardType == CalibrationBoardTypes.Charuco"
v-model="tagFamily"
label="Tag Family"
tooltip="Dictionary of aruco markers on the charuco board"
:select-cols="8"
:items="['Dict_4X4_1000', 'Dict_5X5_1000', 'Dict_6X6_1000', 'Dict_7X7_1000']"
:disabled="isCalibrating"
/>
<pv-number-input
v-model="squareSizeIn"
label="Pattern Spacing (in)"
tooltip="Spacing between pattern features in inches"
:disabled="isCalibrating"
:rules="[(v) => v > 0 || 'Size must be positive']"
:label-cols="4"
/>
<pv-number-input
v-show="boardType == CalibrationBoardTypes.Charuco"
v-model="markerSizeIn"
label="Marker Size (in)"
tooltip="Size of the tag markers in inches must be smaller than pattern spacing"
:disabled="isCalibrating"
:rules="[(v) => v > 0 || 'Size must be positive']"
:label-cols="4"
/>
<pv-number-input
v-model="patternWidth"
label="Board Width (squares)"
tooltip="Width of the board in dots or chessboard squares"
:disabled="isCalibrating"
:rules="[(v) => v >= 4 || 'Width must be at least 4']"
:label-cols="4"
/>
<pv-number-input
v-model="patternHeight"
label="Board Height (squares)"
tooltip="Height of the board in dots or chessboard squares"
:disabled="isCalibrating"
:rules="[(v) => v >= 4 || 'Height must be at least 4']"
:label-cols="4"
/>
<pv-switch
v-show="boardType == CalibrationBoardTypes.Charuco"
v-model="useOldPattern"
label="Old OpenCV Pattern"
:disabled="isCalibrating"
tooltip="If enabled, Photon will use the old OpenCV pattern for calibration."
:label-cols="4"
/>
<div class="pb-5 pt-10px">
<v-banner
v-if="useSettingsStore().general.mrCalWorking"
rounded
bg-color="secondary"
color="secondary"
text-color="white"
icon="mdi-alert-circle-outline"
>
Mrcal was successfully loaded and will be used!
</v-banner>
<v-banner
v-else
rounded
bg-color="error"
color="error"
text-color="white"
icon="mdi-alert-circle-outline"
>
MrCal JNI could not be loaded! Consult journalctl logs for additional details.
</v-banner>
</div>
</v-form>
</div>
<div v-if="isCalibrating">
<pv-switch
v-model="drawAllSnapshots"
label="Draw Collected Corners"
:switch-cols="8"
tooltip="Draw all snapshots"
@update:modelValue="
(v) => useCameraSettingsStore().changeCurrentPipelineSetting({ streamingFrameDivisor: +v }, false)
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ drawAllSnapshots: args }, false)
"
/>
<pv-select
v-model="boardType"
label="Board Type"
tooltip="Calibration board pattern to use"
:select-cols="8"
:items="['Chessboard', 'Charuco']"
:disabled="isCalibrating"
/>
<pv-select
v-show="boardType == CalibrationBoardTypes.Charuco"
v-model="tagFamily"
label="Tag Family"
tooltip="Dictionary of aruco markers on the charuco board"
:select-cols="8"
:items="['Dict_4X4_1000', 'Dict_5X5_1000', 'Dict_6X6_1000', 'Dict_7X7_1000']"
:disabled="isCalibrating"
/>
<pv-number-input
v-model="squareSizeIn"
label="Pattern Spacing (in)"
tooltip="Spacing between pattern features in inches"
:disabled="isCalibrating"
:rules="[(v) => v > 0 || 'Size must be positive']"
:label-cols="4"
/>
<pv-number-input
v-show="boardType == CalibrationBoardTypes.Charuco"
v-model="markerSizeIn"
label="Marker Size (in)"
tooltip="Size of the tag markers in inches must be smaller than pattern spacing"
:disabled="isCalibrating"
:rules="[(v) => v > 0 || 'Size must be positive']"
:label-cols="4"
/>
<pv-number-input
v-model="patternWidth"
label="Board Width (squares)"
tooltip="Width of the board in dots or chessboard squares"
:disabled="isCalibrating"
:rules="[(v) => v >= 4 || 'Width must be at least 4']"
:label-cols="4"
/>
<pv-number-input
v-model="patternHeight"
label="Board Height (squares)"
tooltip="Height of the board in dots or chessboard squares"
:disabled="isCalibrating"
:rules="[(v) => v >= 4 || 'Height must be at least 4']"
:label-cols="4"
/>
<pv-switch
v-show="boardType == CalibrationBoardTypes.Charuco"
v-model="useOldPattern"
label="Old OpenCV Pattern"
:disabled="isCalibrating"
tooltip="If enabled, Photon will use the old OpenCV pattern for calibration."
v-model="useCameraSettingsStore().currentPipelineSettings.cameraAutoExposure"
label="Auto Exposure"
:label-cols="4"
tooltip="Enables or Disables camera automatic adjustment for current lighting conditions"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraAutoExposure: args }, false)
"
/>
<pv-slider
v-model="useCameraSettingsStore().currentPipelineSettings.cameraExposureRaw"
:disabled="useCameraSettingsStore().currentCameraSettings.pipelineSettings.cameraAutoExposure"
label="Exposure"
tooltip="Directly controls how long the camera shutter remains open. Units are dependant on the underlying driver."
:min="useCameraSettingsStore().minExposureRaw"
:max="useCameraSettingsStore().maxExposureRaw"
:slider-cols="7"
:step="1"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraExposureRaw: args }, false)
"
/>
<pv-slider
v-model="useCameraSettingsStore().currentPipelineSettings.cameraBrightness"
label="Brightness"
:min="0"
:max="100"
:slider-cols="7"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraBrightness: args }, false)
"
/>
<pv-slider
v-if="useCameraSettingsStore().currentPipelineSettings.cameraGain >= 0"
v-model="useCameraSettingsStore().currentPipelineSettings.cameraGain"
label="Camera Gain"
tooltip="Controls camera gain, similar to brightness"
:min="0"
:max="100"
:slider-cols="7"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraGain: args }, false)
"
/>
<pv-slider
v-if="useCameraSettingsStore().currentPipelineSettings.cameraRedGain !== -1"
v-model="useCameraSettingsStore().currentPipelineSettings.cameraRedGain"
label="Red AWB Gain"
:min="0"
:max="100"
:slider-cols="7"
tooltip="Controls red automatic white balance gain, which affects how the camera captures colors in different conditions"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraRedGain: args }, false)
"
/>
<pv-slider
v-if="useCameraSettingsStore().currentPipelineSettings.cameraBlueGain !== -1"
v-model="useCameraSettingsStore().currentPipelineSettings.cameraBlueGain"
label="Blue AWB Gain"
:min="0"
:max="100"
:slider-cols="7"
tooltip="Controls blue automatic white balance gain, which affects how the camera captures colors in different conditions"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraBlueGain: args }, false)
"
/>
<v-banner
v-if="useSettingsStore().general.mrCalWorking"
v-if="tooManyPoints"
rounded
color="secondary"
text-color="white"
class="mt-3"
color="error"
text-color="white"
icon="mdi-alert-circle-outline"
>
Mrcal was successfully loaded and will be used!
Too many corners. Finish calibration now!
</v-banner>
<v-banner v-else rounded color="error" text-color="white" class="mt-3" icon="mdi-alert-circle-outline">
MrCal JNI could not be loaded! Consult journalctl logs for additional details.
</v-banner>
</v-form>
</v-card-text>
<v-card-text v-if="isCalibrating" class="pa-6 pt-0">
<pv-switch
v-model="drawAllSnapshots"
label="Draw Collected Corners"
:switch-cols="8"
tooltip="Draw all snapshots"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ drawAllSnapshots: args }, false)
"
/>
<pv-switch
v-model="useCameraSettingsStore().currentPipelineSettings.cameraAutoExposure"
label="Auto Exposure"
:label-cols="4"
tooltip="Enables or Disables camera automatic adjustment for current lighting conditions"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraAutoExposure: args }, false)
"
/>
<pv-slider
v-model="useCameraSettingsStore().currentPipelineSettings.cameraExposureRaw"
:disabled="useCameraSettingsStore().currentCameraSettings.pipelineSettings.cameraAutoExposure"
label="Exposure"
tooltip="Directly controls how long the camera shutter remains open. Units are dependant on the underlying driver."
:min="useCameraSettingsStore().minExposureRaw"
:max="useCameraSettingsStore().maxExposureRaw"
:slider-cols="7"
:step="1"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraExposureRaw: args }, false)
"
/>
<pv-slider
v-model="useCameraSettingsStore().currentPipelineSettings.cameraBrightness"
label="Brightness"
:min="0"
:max="100"
:slider-cols="7"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraBrightness: args }, false)
"
/>
<pv-slider
v-if="useCameraSettingsStore().currentPipelineSettings.cameraGain >= 0"
v-model="useCameraSettingsStore().currentPipelineSettings.cameraGain"
label="Camera Gain"
tooltip="Controls camera gain, similar to brightness"
:min="0"
:max="100"
:slider-cols="7"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraGain: args }, false)
"
/>
<pv-slider
v-if="useCameraSettingsStore().currentPipelineSettings.cameraRedGain !== -1"
v-model="useCameraSettingsStore().currentPipelineSettings.cameraRedGain"
label="Red AWB Gain"
:min="0"
:max="100"
:slider-cols="7"
tooltip="Controls red automatic white balance gain, which affects how the camera captures colors in different conditions"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraRedGain: args }, false)
"
/>
<pv-slider
v-if="useCameraSettingsStore().currentPipelineSettings.cameraBlueGain !== -1"
v-model="useCameraSettingsStore().currentPipelineSettings.cameraBlueGain"
label="Blue AWB Gain"
:min="0"
:max="100"
:slider-cols="7"
tooltip="Controls blue automatic white balance gain, which affects how the camera captures colors in different conditions"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraBlueGain: args }, false)
"
/>
<v-banner
v-if="tooManyPoints"
rounded
class="mt-3"
color="error"
text-color="white"
icon="mdi-alert-circle-outline"
>
Too many corners. Finish calibration now!
</v-banner>
</v-card-text>
<v-card-text v-if="isCalibrating" class="d-flex justify-center align-center pa-6 pt-0">
<v-chip
variant="flat"
label
:color="useStateStore().calibrationData.hasEnoughImages ? 'secondary' : 'grey-darken-2'"
>
Snapshots: {{ useStateStore().calibrationData.imageCount }} of at least
{{ useStateStore().calibrationData.minimumImageCount }}
</v-chip>
</v-card-text>
<v-card-text class="d-flex pa-6 pt-0">
<v-col cols="6" class="pa-0 pr-2">
</div>
<div v-if="isCalibrating" class="d-flex justify-center align-center pt-10px pb-5">
<v-chip
variant="flat"
label
:color="useStateStore().calibrationData.hasEnoughImages ? 'secondary' : 'grey-darken-2'"
>
Snapshots: {{ useStateStore().calibrationData.imageCount }} of at least
{{ useStateStore().calibrationData.minimumImageCount }}
</v-chip>
</div>
<div class="d-flex">
<v-col cols="6" class="pa-0 pr-2">
<v-btn
size="small"
block
color="secondary"
:disabled="!settingsValid || tooManyPoints"
@click="isCalibrating ? useCameraSettingsStore().takeCalibrationSnapshot() : startCalibration()"
>
<v-icon start class="calib-btn-icon"> {{ isCalibrating ? "mdi-camera" : "mdi-flag-outline" }} </v-icon>
<span class="calib-btn-label">{{ isCalibrating ? "Take Snapshot" : "Start Calibration" }}</span>
</v-btn>
</v-col>
<v-col cols="6" class="pa-0 pl-2">
<v-btn
size="small"
block
:color="useStateStore().calibrationData.hasEnoughImages ? 'accent' : 'error'"
:disabled="!isCalibrating || !settingsValid"
@click="endCalibration"
>
<v-icon start class="calib-btn-icon">
{{ useStateStore().calibrationData.hasEnoughImages ? "mdi-flag-checkered" : "mdi-flag-off-outline" }}
</v-icon>
<span class="calib-btn-label">{{
useStateStore().calibrationData.hasEnoughImages ? "Finish Calibration" : "Cancel Calibration"
}}</span>
</v-btn>
</v-col>
</div>
<div class="pt-5">
<v-btn
color="accent"
size="small"
block
color="secondary"
:disabled="!settingsValid || tooManyPoints"
@click="isCalibrating ? useCameraSettingsStore().takeCalibrationSnapshot() : startCalibration()"
variant="outlined"
:disabled="!settingsValid"
@click="downloadCalibBoard"
>
<v-icon start class="calib-btn-icon"> {{ isCalibrating ? "mdi-camera" : "mdi-flag-outline" }} </v-icon>
<span class="calib-btn-label">{{ isCalibrating ? "Take Snapshot" : "Start Calibration" }}</span>
<v-icon start class="calib-btn-icon"> mdi-download </v-icon>
<span class="calib-btn-label">Generate Board</span>
</v-btn>
</v-col>
<v-col cols="6" class="pa-0 pl-2">
<v-btn
size="small"
block
:color="useStateStore().calibrationData.hasEnoughImages ? 'accent' : 'error'"
:disabled="!isCalibrating || !settingsValid"
@click="endCalibration"
>
<v-icon start class="calib-btn-icon">
{{ useStateStore().calibrationData.hasEnoughImages ? "mdi-flag-checkered" : "mdi-flag-off-outline" }}
</v-icon>
<span class="calib-btn-label">{{
useStateStore().calibrationData.hasEnoughImages ? "Finish Calibration" : "Cancel Calibration"
}}</span>
</v-btn>
</v-col>
</v-card-text>
<v-card-text class="pa-6 pt-0">
<v-btn
color="accent"
size="small"
block
variant="outlined"
:disabled="!settingsValid"
@click="downloadCalibBoard"
>
<v-icon start class="calib-btn-icon"> mdi-download </v-icon>
<span class="calib-btn-label">Generate Board</span>
</v-btn>
</div>
</v-card-text>
</v-card>
<v-dialog v-model="showCalibEndDialog" width="500px" :persistent="true">
<v-card color="primary" dark>
<v-card-title class="pb-8"> Camera Calibration </v-card-title>
<div class="ml-3">
<v-col style="text-align: center">
<template v-if="calibCanceled">
<v-icon color="blue" size="70"> mdi-cancel </v-icon>
<v-card-text
>Camera Calibration has been Canceled, the backend is attempting to cleanly cancel the calibration
process.</v-card-text
>
</template>
<!-- No result reported yet -->
<template v-else-if="calibSuccess === undefined">
<v-progress-circular indeterminate :size="70" :width="8" color="accent" />
<v-card-text>Camera is being calibrated. This process may take several minutes...</v-card-text>
</template>
<!-- Got positive result -->
<template v-else-if="calibSuccess">
<v-icon color="green" size="70"> mdi-check-bold </v-icon>
<v-card-text>
Camera has been successfully calibrated for
{{
getUniqueVideoResolutionStrings().find(
(v) => v.value === useStateStore().calibrationData.videoFormatIndex
)?.name
}}!
</v-card-text>
</template>
<template v-else>
<v-icon color="red" size="70"> mdi-close </v-icon>
<v-card-text
>Camera calibration failed! Make sure that the photos are taken such that the rainbow grid circles align
with the corners of the chessboard, and try again. More information is available in the program
logs.</v-card-text
>
</template>
</v-col>
<v-card-title> Camera Calibration </v-card-title>
<div style="text-align: center">
<template v-if="calibCanceled">
<v-icon color="blue" size="70"> mdi-cancel </v-icon>
<v-card-text>
Camera Calibration has been Canceled, the backend is attempting to cleanly cancel the calibration process.
</v-card-text>
</template>
<!-- No result reported yet -->
<template v-else-if="calibSuccess === undefined">
<v-progress-circular indeterminate :size="70" :width="8" color="accent" />
<v-card-text>Camera is being calibrated. This process may take several minutes...</v-card-text>
</template>
<!-- Got positive result -->
<template v-else-if="calibSuccess">
<v-icon color="green" size="70"> mdi-check-bold </v-icon>
<v-card-text>
Camera has been successfully calibrated for
{{
getUniqueVideoResolutionStrings().find(
(v) => v.value === useStateStore().calibrationData.videoFormatIndex
)?.name
}}!
</v-card-text>
</template>
<template v-else>
<v-icon color="red" size="70"> mdi-close </v-icon>
<v-card-text>
Camera calibration failed! Make sure that the photos are taken such that the rainbow grid circles align
with the corners of the chessboard, and try again. More information is available in the program logs.
</v-card-text>
</template>
</div>
<v-card-actions>
<v-card-actions class="pa-5 pt-0">
<v-spacer />
<v-btn v-if="!isCalibrating" color="white" variant="text" @click="showCalibEndDialog = false"> OK </v-btn>
</v-card-actions>
@@ -543,6 +548,11 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
</template>
<style scoped lang="scss">
th {
text-align: center !important;
padding: 0 8px !important;
}
.v-table {
text-align: center;
width: 100%;

View File

@@ -91,9 +91,9 @@ const calibrationImageURL = (index: number) =>
<template>
<v-card color="primary" dark>
<div class="d-flex flex-wrap pr-md-3">
<div class="d-flex flex-wrap pt-2 pl-2 pr-2">
<v-col cols="12" md="6">
<v-card-title class="pl-3 pb-0 pb-md-4"> Calibration Details </v-card-title>
<v-card-title class="pa-0"> Calibration Details </v-card-title>
</v-col>
<v-col cols="6" md="3" class="d-flex align-center pt-0 pt-md-3 pl-6 pl-md-3">
<v-btn color="secondary" style="width: 100%" @click="openUploadPhotonCalibJsonPrompt">
@@ -126,7 +126,7 @@ const calibrationImageURL = (index: number) =>
/>
</v-col>
</div>
<v-card-title class="pl-6 pt-0 pb-0"
<v-card-title class="pt-0 pb-0"
>{{ useCameraSettingsStore().currentCameraName }}@{{ getResolutionString(videoFormat.resolution) }}</v-card-title
>
<v-card-text v-if="!currentCalibrationCoeffs">
@@ -135,7 +135,7 @@ const calibrationImageURL = (index: number) =>
bg-color="secondary"
color="secondary"
text-color="white"
class="pt-3 pb-3 mt-3"
class="pt-3 pb-3"
density="compact"
icon="mdi-alert-circle-outline"
>
@@ -248,8 +248,8 @@ const calibrationImageURL = (index: number) =>
</template>
</v-table>
</v-card-text>
<v-card-title v-if="currentCalibrationCoeffs" class="pt-0">Individual Observations</v-card-title>
<v-card-text v-if="currentCalibrationCoeffs">
<v-card-title v-if="currentCalibrationCoeffs" class="pt-0 pb-0">Individual Observations</v-card-title>
<v-card-text v-if="currentCalibrationCoeffs" class="pt-0">
<v-data-table
density="compact"
style="width: 100%"

View File

@@ -93,20 +93,20 @@ const expanded = ref([]);
<template>
<v-card style="background-color: #006492">
<v-card-title>Camera Control</v-card-title>
<v-card-text>
<v-card-text class="pt-0">
<v-btn color="secondary" @click="fetchSnapshots">
<v-icon start class="open-icon"> mdi-folder </v-icon>
<span class="open-label">Show Saved Snapshots</span>
</v-btn>
</v-card-text>
<v-dialog v-model="showSnapshotViewerDialog">
<v-card class="pt-3 pl-5 pr-5" color="primary" flat>
<v-card color="primary" flat>
<v-card-title> View Saved Frame Snapshots </v-card-title>
<v-divider />
<v-card-text v-if="imgData.length === 0" style="font-size: 18px; font-weight: 600" class="pt-4">
There are no snapshots saved
</v-card-text>
<div v-else class="pb-2">
<v-card-text v-else>
<v-data-table
v-model:expanded="expanded"
:headers="[
@@ -155,7 +155,7 @@ const expanded = ref([]);
>Snapshot Timestamps may be incorrect as they depend on when the coprocessor was last connected to the
internet</span
>
</div>
</v-card-text>
</v-card>
</v-dialog>
</v-card>

View File

@@ -170,8 +170,8 @@ const wrappedCameras = computed<SelectItem[]>(() =>
<template>
<v-card class="mb-3" color="primary" dark>
<v-card-title class="pa-6 pb-0">Camera Settings</v-card-title>
<v-card-text class="pa-6 pt-3">
<v-card-title class="pb-0">Camera Settings</v-card-title>
<v-card-text class="pt-3">
<pv-select
v-model="useStateStore().currentCameraUniqueName"
label="Camera"
@@ -202,7 +202,7 @@ const wrappedCameras = computed<SelectItem[]>(() =>
:select-cols="8"
/>
</v-card-text>
<v-card-text class="d-flex pa-6 pt-0">
<v-card-text class="d-flex pt-0">
<v-col cols="6" class="pa-0 pr-2">
<v-btn block size="small" color="secondary" :disabled="!settingsHaveChanged()" @click="saveCameraSettings">
<v-icon start> mdi-content-save </v-icon>
@@ -218,10 +218,10 @@ const wrappedCameras = computed<SelectItem[]>(() =>
</v-card-text>
<v-dialog v-model="showDeleteCamera" width="800">
<v-card class="dialog-container pa-3 pb-2" color="primary" flat>
<v-card color="primary" flat>
<v-card-title> Delete {{ useCameraSettingsStore().currentCameraSettings.nickname }}? </v-card-title>
<v-card-text>
<v-row class="align-center pt-6">
<v-card-text class="pt-0 pb-10px">
<v-row class="align-center">
<v-col cols="12" md="6">
<span class="text-white"> This will delete ALL OF YOUR SETTINGS and restart PhotonVision. </span>
</v-col>
@@ -240,7 +240,7 @@ const wrappedCameras = computed<SelectItem[]>(() =>
</v-col>
</v-row>
</v-card-text>
<v-card-text>
<v-card-text class="pt-0 pb-0">
<pv-input
v-model="yesDeleteMySettingsText"
:label="'Type &quot;' + useCameraSettingsStore().currentCameraName + '&quot;:'"
@@ -248,7 +248,7 @@ const wrappedCameras = computed<SelectItem[]>(() =>
:input-cols="6"
/>
</v-card-text>
<v-card-text>
<v-card-text class="pt-10px">
<v-btn
block
color="error"

View File

@@ -30,36 +30,30 @@ const fpsTooLow = computed<boolean>(() => {
<template>
<v-card id="camera-settings-camera-view-card" class="camera-settings-camera-view-card" color="primary" dark>
<v-card-title class="justify-space-between align-content-center pa-0 pl-6 pr-6">
<div class="d-flex flex-wrap pt-4 pb-4">
<div>
<span class="mr-4" style="white-space: nowrap"> Cameras </span>
</div>
<div>
<v-chip
v-if="useCameraSettingsStore().currentCameraSettings.isConnected"
label
:color="fpsTooLow ? 'error' : 'transparent'"
style="font-size: 1rem; padding: 0; margin: 0"
>
<span class="pr-1" :style="{ color: fpsTooLow ? '#C7EA46' : '#ff4d00' }">
{{ Math.round(useStateStore().currentPipelineResults?.fps || 0) }}&nbsp;FPS &ndash;
{{ Math.min(Math.round(useStateStore().currentPipelineResults?.latency || 0), 9999) }} ms latency
</span>
</v-chip>
<v-chip v-else label color="red" variant="text" style="font-size: 1rem; padding: 0; margin: 0">
<span class="pr-1">Camera not connected</span>
</v-chip>
</div>
</div>
<div class="d-flex align-center">
<v-card-title class="justify-space-between align-content-center pt-0 pb-0">
<div class="d-flex flex-wrap align-center pt-4 pb-4">
<span class="mr-4" style="white-space: nowrap"> Cameras </span>
<v-chip
v-if="useCameraSettingsStore().currentCameraSettings.isConnected"
label
:color="fpsTooLow ? 'error' : 'transparent'"
style="font-size: 1rem; padding: 0; margin: 0"
>
<span class="pr-1" :style="{ color: fpsTooLow ? '#C7EA46' : '#ff4d00' }">
{{ Math.round(useStateStore().currentPipelineResults?.fps || 0) }}&nbsp;FPS &ndash;
{{ Math.min(Math.round(useStateStore().currentPipelineResults?.latency || 0), 9999) }} ms latency
</span>
</v-chip>
<v-chip v-else label color="red" variant="text" style="font-size: 1rem; padding: 0; margin: 0">
<span class="pr-1">Camera not connected</span>
</v-chip>
<v-switch
v-model="driverMode"
:disabled="useCameraSettingsStore().isCalibrationMode || useCameraSettingsStore().pipelineNames.length === 0"
label="Driver Mode"
style="margin-left: auto"
color="accent"
class="pt-2 pb-2"
density="compact"
hide-details="auto"
/>
</div>
@@ -127,10 +121,6 @@ th {
text-align: center;
}
.v-input--switch {
margin-top: 0;
}
.stream-container {
display: flex;
justify-content: center;

View File

@@ -42,11 +42,11 @@ const handleKeydown = ({ key }) => {
<template>
<div class="d-flex">
<v-col :cols="labelCols || 12 - inputCols" class="d-flex align-center pl-0">
<v-col :cols="labelCols || 12 - inputCols" class="d-flex align-center pl-0 pt-10px pb-10px">
<tooltipped-label :tooltip="tooltip" :label="label" />
</v-col>
<v-col :cols="inputCols" class="d-flex align-center pr-0">
<v-col :cols="inputCols" class="d-flex align-center pr-0 pt-10px pb-10px">
<v-text-field
v-model="value"
density="compact"

View File

@@ -28,10 +28,10 @@ const localValue = computed({
<template>
<div class="d-flex">
<v-col :cols="labelCols" class="d-flex pl-0 align-center">
<v-col :cols="labelCols" class="d-flex pl-0 pt-10px pb-10px align-center">
<tooltipped-label :tooltip="tooltip" :label="label" />
</v-col>
<v-col class="pr-0">
<v-col class="pr-0 pt-10px pb-10px">
<v-text-field
v-model="localValue"
class="mt-0 pt-0"

View File

@@ -21,11 +21,11 @@ withDefaults(
<template>
<div class="d-flex">
<v-col :cols="12 - inputCols" class="d-flex align-center pl-0">
<v-col :cols="12 - inputCols" class="d-flex align-center pl-0 pt-10px pb-10px">
<tooltipped-label :tooltip="tooltip" :label="label" />
</v-col>
<v-col :cols="inputCols" class="d-flex align-center pr-0">
<v-radio-group v-model="value" row:mandatory="true" hide-details="auto">
<v-col :cols="inputCols" class="pr-0 pt-10px pb-10px">
<v-radio-group v-model="value" row:mandatory="true" inline hide-details="auto">
<v-radio
v-for="(radioName, index) in list"
:key="index"
@@ -39,9 +39,3 @@ withDefaults(
</v-col>
</div>
</template>
<style scoped>
.v-input--radio-group {
padding-top: 0;
margin-top: 0;
}
</style>

View File

@@ -40,10 +40,10 @@ const items = computed<SelectItem[]>(() => {
<template>
<div class="d-flex">
<v-col :cols="12 - selectCols" class="d-flex align-center pl-0">
<v-col :cols="12 - selectCols" class="d-flex align-center pl-0 pt-10px pb-10px">
<tooltipped-label :tooltip="tooltip" :label="label" />
</v-col>
<v-col :cols="selectCols" class="d-flex align-center pr-0">
<v-col :cols="selectCols" class="d-flex align-center pr-0 pt-10px pb-10px">
<v-select
v-model="value"
:items="items"

View File

@@ -44,10 +44,10 @@ const localValue = computed({
<template>
<div class="d-flex">
<v-col :cols="12 - sliderCols" class="pl-0 d-flex align-center">
<v-col :cols="12 - sliderCols" class="pl-0 pt-10px pb-10px d-flex align-center">
<tooltipped-label :tooltip="tooltip" :label="label" />
</v-col>
<v-col :cols="sliderCols - 1" class="pl-0">
<v-col :cols="sliderCols - 1" class="pl-0 pt-10px pb-10px">
<v-slider
v-model="localValue"
class="align-center"
@@ -63,7 +63,7 @@ const localValue = computed({
@click:prepend="localValue -= step"
/>
</v-col>
<v-col :cols="1" class="pr-0">
<v-col :cols="1" class="pr-0 pt-10px pb-10px">
<v-text-field
:model-value="localValue"
color="accent"

View File

@@ -20,16 +20,17 @@ withDefaults(
<template>
<div class="d-flex">
<v-col :cols="12 - switchCols || labelCols" class="d-flex align-center pl-0 pt-2 pb-2">
<v-col :cols="12 - switchCols || labelCols" class="d-flex align-center pl-0">
<tooltipped-label :tooltip="tooltip" :label="label" />
</v-col>
<v-col :cols="switchCols || 12 - labelCols" class="d-flex align-center pr-0 pt-2 pb-2">
<v-col :cols="switchCols || 12 - labelCols" class="d-flex align-center pr-0">
<v-switch v-model="value" :disabled="disabled" color="#ffd843" hide-details density="compact" />
</v-col>
</div>
</template>
<style scoped>
.v-input--selection-controls {
margin-top: 0px;
.v-col {
padding-top: 6px !important;
padding-bottom: 6px !important;
}
</style>

View File

@@ -242,7 +242,7 @@ const wrappedCameras = computed<SelectItem[]>(() =>
<template>
<v-card color="primary">
<v-row style="padding: 20px 12px 0 30px">
<v-row no-gutters class="pl-4 pt-2 pb-0">
<v-col cols="10" class="pa-0">
<pv-select
v-if="!isCameraNameEdit"
@@ -281,7 +281,7 @@ const wrappedCameras = computed<SelectItem[]>(() =>
/>
</v-col>
</v-row>
<v-row style="padding: 0 12px 0 30px">
<v-row no-gutters class="pl-4 pb-0 pt-0">
<v-col cols="10" class="pa-0">
<pv-select
v-if="!isPipelineNameEdit"
@@ -353,7 +353,7 @@ const wrappedCameras = computed<SelectItem[]>(() =>
/>
</v-col>
</v-row>
<v-row style="padding: 0 12px 24px 30px">
<v-row no-gutters class="pl-4 pt-0 pb-4">
<v-col cols="10" class="pa-0">
<pv-select
v-model="currentPipelineType"
@@ -371,27 +371,25 @@ const wrappedCameras = computed<SelectItem[]>(() =>
</v-row>
<v-dialog v-model="showPipelineCreationDialog" persistent width="500">
<v-card color="primary">
<v-card-title> Create New Pipeline </v-card-title>
<v-card-text>
<v-card-title class="pb-0"> Create New Pipeline </v-card-title>
<v-card-text class="pt-0 pb-0">
<pv-input
v-model="newPipelineName"
placeholder="Pipeline Name"
:label-cols="3"
:input-cols="12 - 3"
:label-cols="4"
:input-cols="12 - 4"
label="Pipeline Name"
:rules="[(v) => checkPipelineName(v)]"
/>
<pv-select
v-model="newPipelineType"
:select-cols="12 - 3"
:select-cols="12 - 4"
label="Tracking Type"
tooltip="Pipeline type, which changes the type of processing that will happen on input frames"
:items="validNewPipelineTypes"
/>
</v-card-text>
<v-divider />
<v-card-actions>
<v-spacer />
<v-card-actions class="pr-5 pt-10px pb-5">
<v-btn
color="#ffd843"
:disabled="checkPipelineName(newPipelineName) !== true"
@@ -406,7 +404,7 @@ const wrappedCameras = computed<SelectItem[]>(() =>
</v-dialog>
<v-dialog v-model="showPipelineDeletionConfirmationDialog" width="500">
<v-card color="primary">
<v-card-title> Pipeline Deletion Confirmation </v-card-title>
<v-card-title class="pb-0">Pipeline Deletion Confirmation</v-card-title>
<v-card-text>
Are you sure you want to delete the pipeline
<b style="color: white; font-weight: bold">{{
@@ -414,9 +412,7 @@ const wrappedCameras = computed<SelectItem[]>(() =>
}}</b
>? This cannot be undone.
</v-card-text>
<v-divider />
<v-card-actions>
<v-spacer />
<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"
@@ -431,15 +427,13 @@ const wrappedCameras = computed<SelectItem[]>(() =>
</v-dialog>
<v-dialog v-model="showPipelineTypeChangeDialog" persistent width="600">
<v-card color="primary" dark>
<v-card-title>Change Pipeline Type</v-card-title>
<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
overwritten and they will be lost. If this isn't what you want, duplicate this pipeline first or export
settings.
</v-card-text>
<v-divider />
<v-card-actions>
<v-spacer />
<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

View File

@@ -88,9 +88,6 @@ const performanceRecommendation = computed<string>(() => {
</template>
<style scoped>
.v-input--switch {
margin-top: 0;
}
.stream-viewer-container {
display: flex;
justify-content: center;

View File

@@ -160,13 +160,13 @@ const onBeforeTabUpdate = () => {
:class="tabGroupIndex !== tabGroups.length - 1 && 'pr-3'"
@vue:before-update="onBeforeTabUpdate"
>
<v-card color="primary" height="100%" class="pr-4 pl-4">
<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-tab v-for="(tabConfig, index) in tabGroupData" :key="index">
{{ tabConfig.tabName }}
</v-tab>
</v-tabs>
<div class="pl-2 pr-2 pt-3 pb-3">
<div class="pt-10px pb-10px">
<KeepAlive>
<Component :is="tabGroupData[selectedTabs[tabGroupIndex]].component" />
</KeepAlive>

View File

@@ -132,16 +132,20 @@ const interactiveCols = computed(() =>
v-if="useCameraSettingsStore().currentPipelineSettings.offsetRobotOffsetMode !== RobotOffsetPointMode.None"
class="metrics-table mt-3 mb-3"
>
<tr>
<th v-for="(item, itemIndex) in offsetPoints" :key="itemIndex" class="metric-item metric-item-title">
{{ item.header }}
</th>
</tr>
<tr>
<td v-for="(item, itemIndex) in offsetPoints" :key="itemIndex" class="metric-item">
{{ item.value }}
</td>
</tr>
<thead>
<tr>
<th v-for="(item, itemIndex) in offsetPoints" :key="itemIndex" class="metric-item metric-item-title">
{{ item.header }}
</th>
</tr>
</thead>
<tbody>
<tr>
<td v-for="(item, itemIndex) in offsetPoints" :key="itemIndex" class="metric-item">
{{ item.value }}
</td>
</tr>
</tbody>
</table>
<div
v-if="useCameraSettingsStore().currentPipelineSettings.offsetRobotOffsetMode !== RobotOffsetPointMode.None"

View File

@@ -105,7 +105,7 @@ const resetCurrentBuffer = () => {
<td class="text-center">{{ target.pitch.toFixed(2) }}&deg;</td>
<td class="text-center">{{ target.yaw.toFixed(2) }}&deg;</td>
<td class="text-center">{{ target.skew.toFixed(2) }}&deg;</td>
<td class="text-center">{{ target.area.toFixed(2) }}&deg;</td>
<td class="text-center">{{ target.area.toFixed(2) }}%</td>
</template>
<template v-else>
<td class="text-center">{{ target.pose?.x.toFixed(3) }}&nbsp;m</td>
@@ -269,6 +269,10 @@ const resetCurrentBuffer = () => {
</template>
<style scoped lang="scss">
th {
padding-left: 8px !important;
padding-right: 8px !important;
}
.v-table {
background-color: #006492 !important;
width: 100%;
@@ -290,6 +294,7 @@ const resetCurrentBuffer = () => {
}
tr {
td {
padding: 0 !important;
font-size: 1rem !important;
color: white !important;
}

View File

@@ -18,8 +18,8 @@ const quaternionToEuler = (rot_quat: Quaternion): { x: number; y: number; z: num
<template>
<v-card style="background-color: #006492">
<v-card-title class="pa-6">AprilTag Field Layout</v-card-title>
<v-card-text class="pa-6 pt-0">
<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>

View File

@@ -239,8 +239,8 @@ const nukePhotonConfigDirectory = () => {
<template>
<v-card class="mb-3" style="background-color: #006492">
<v-card-title class="pa-6">Device Control</v-card-title>
<div class="pa-6 pt-0">
<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">
@@ -280,10 +280,10 @@ const nukePhotonConfigDirectory = () => {
"
>
<v-card color="primary" dark>
<v-card-title>Import Settings</v-card-title>
<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
<v-row class="mt-6 ml-4">
<div class="pa-5 pb-0">
<pv-select
v-model="importType"
label="Type"
@@ -298,21 +298,19 @@ const nukePhotonConfigDirectory = () => {
:select-cols="10"
style="width: 100%"
/>
</v-row>
<v-row class="mt-6 ml-4 mr-8">
<v-file-input
class="pb-5"
v-model="importFile"
variant="underlined"
:disabled="importType === undefined"
:error-messages="importType === undefined ? 'Settings type not selected' : ''"
:accept="importType === ImportType.AllSettings ? '.zip' : '.json'"
/>
</v-row>
<v-row class="mt-12 ml-8 mr-8 mb-1" style="display: flex; align-items: center; justify-content: center">
<v-btn color="secondary" :disabled="importFile === null" @click="handleSettingsImport">
<v-icon start class="open-icon"> mdi-import </v-icon>
<span class="open-label">Import Settings</span>
</v-btn>
</v-row>
</div>
</v-card-text>
</v-card>
</v-dialog>
@@ -370,18 +368,18 @@ const nukePhotonConfigDirectory = () => {
</div>
<v-dialog v-model="showFactoryReset" width="800" dark>
<v-card color="primary" class="pa-3" flat>
<v-card-title style="justify-content: center" class="pb-6">
<v-card color="primary" 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>
Factory Reset PhotonVision
<v-icon end color="error" class="open-icon ma-1">mdi-nuke</v-icon>
</span>
</v-card-title>
<v-card-text class="pt-3">
<v-card-text class="pt-0 pb-10px">
<v-row class="align-center text-white">
<v-col cols="12" md="6">
<span class="mt-3"> This will delete ALL OF YOUR SETTINGS and restart PhotonVision. </span>
<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">
@@ -398,7 +396,7 @@ const nukePhotonConfigDirectory = () => {
</v-col>
</v-row>
</v-card-text>
<v-card-text>
<v-card-text class="pt-0 pb-0">
<pv-input
v-model="yesDeleteMySettingsText"
:label="'Type &quot;' + expected + '&quot;:'"
@@ -406,7 +404,7 @@ const nukePhotonConfigDirectory = () => {
:input-cols="6"
/>
</v-card-text>
<v-card-text>
<v-card-text class="pt-10px">
<v-btn
color="error"
:disabled="yesDeleteMySettingsText.toLowerCase() !== expected.toLowerCase()"

View File

@@ -4,18 +4,16 @@ import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
</script>
<template>
<v-card class="mb-3 pr-6 pb-3" style="background-color: #006492">
<v-card-title>LED Control</v-card-title>
<div class="ml-5">
<v-card class="mb-3" style="background-color: #006492">
<v-card-title class="pb-10px">LED Control</v-card-title>
<v-card-text>
<pv-slider
v-model="useSettingsStore().lighting.brightness"
label="Brightness"
class="pt-2"
:slider-cols="12"
:min="0"
:max="100"
@update:modelValue="(args) => useSettingsStore().changeLEDBrightness(args)"
/>
</div>
</v-card-text>
</v-card>
</template>

View File

@@ -121,14 +121,14 @@ onBeforeMount(() => {
<template>
<v-card class="mb-3" style="background-color: #006492">
<v-card-title class="pl-6" style="display: flex; justify-content: space-between">
<span class="pt-2 pb-2">Stats</span>
<v-card-title style="display: flex; justify-content: space-between">
<span>Stats</span>
<v-btn variant="text" @click="fetchMetrics">
<v-icon start class="open-icon">mdi-reload</v-icon>
Last Fetched: {{ metricsLastFetched }}
</v-btn>
</v-card-title>
<v-card-text class="pa-6 pt-0 pb-3">
<v-card-text class="pt-0 pb-3">
<v-card-subtitle class="pa-0" style="font-size: 16px">General Metrics</v-card-subtitle>
<v-table class="metrics-table mt-3">
<thead>
@@ -165,7 +165,7 @@ onBeforeMount(() => {
</tbody>
</v-table>
</v-card-text>
<v-card-text class="pa-6 pt-4">
<v-card-text class="pt-4">
<v-card-subtitle class="pa-0 pb-1" style="font-size: 16px">Hardware Metrics</v-card-subtitle>
<v-table class="metrics-table mt-3">
<thead>

View File

@@ -142,10 +142,10 @@ watchEffect(() => {
<template>
<v-card class="mb-3" style="background-color: #006492">
<v-card-title class="pa-6">Global Settings</v-card-title>
<div class="pa-6 pt-0">
<v-divider class="pb-3" />
<v-card-title class="pl-0 pt-3 pb-3">Networking</v-card-title>
<v-card-title>Global Settings</v-card-title>
<div class="pa-5 pt-0">
<v-divider class="pb-2" />
<v-card-title class="pl-0 pt-3 pb-10px">Networking</v-card-title>
<v-form ref="form" v-model="settingsValid">
<pv-input
v-model="tempSettingsStruct.ntServerAddress"
@@ -207,8 +207,8 @@ watchEffect(() => {
useSettingsStore().network.networkingDisabled
"
/>
<v-divider class="mt-3 pb-3" />
<v-card-title class="pl-0 pt-3 pb-3">Advanced Networking</v-card-title>
<v-divider class="mt-10px pb-2" />
<v-card-title class="pl-0 pt-3 pb-10px">Advanced Networking</v-card-title>
<pv-switch
v-show="!useSettingsStore().network.networkingDisabled"
v-model="tempSettingsStruct.shouldManage"
@@ -259,8 +259,8 @@ watchEffect(() => {
>
This mode is intended for debugging; it should be off for proper usage. PhotonLib will NOT work!
</v-banner>
<v-divider class="mt-3 pb-3" />
<v-card-title class="pl-0 pt-3 pb-3">Miscellaneous</v-card-title>
<v-divider class="mt-10px pb-2" />
<v-card-title class="pl-0 pt-3 pb-10px">Miscellaneous</v-card-title>
<pv-switch
v-model="tempSettingsStruct.shouldPublishProto"
label="Also Publish Protobuf"
@@ -277,7 +277,7 @@ watchEffect(() => {
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>
<v-divider class="mt-3 mb-6" />
<v-divider class="mt-10px pb-5" />
</v-form>
<v-btn
color="accent"
@@ -293,6 +293,9 @@ watchEffect(() => {
</template>
<style>
.mt-10px {
margin-top: 10px !important;
}
.v-banner__wrapper {
padding: 6px !important;
}

View File

@@ -271,7 +271,7 @@ const handleBulkImport = () => {
<template>
<v-card class="mb-3" style="background-color: #006492">
<v-card-title class="pa-5">Object Detection</v-card-title>
<v-card-title>Object Detection</v-card-title>
<div class="pa-5 pt-0">
<v-row>
<v-col cols="12" sm="6">
@@ -293,12 +293,12 @@ const handleBulkImport = () => {
"
>
<v-card color="primary" dark>
<v-card-title class="pa-5 pb-0">Import New Object Detection Model</v-card-title>
<v-card-text class="pa-5">
<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
640x640 YOLOv5, YOLOv8, and YOLOv11 models trained and converted to `.rknn` format for RK3588 CPUs are
currently supported!
<div class="pa-5">
<div class="pa-5 pb-0">
<v-file-input v-model="importModelFile" variant="underlined" label="Model File" accept=".rknn" />
<v-text-field
v-model="importLabels"
@@ -317,6 +317,7 @@ const handleBulkImport = () => {
/>
<v-btn
color="secondary"
width="100%"
:disabled="
importModelFile === null ||
importLabels === null ||
@@ -341,13 +342,13 @@ const handleBulkImport = () => {
</v-btn>
<v-dialog v-model="showBulkImportDialog" width="600">
<v-card color="primary" dark>
<v-card-title class="pa-5 pb-0">Import Multiple Object Detection Models</v-card-title>
<v-card-text class="pa-5">
<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">
<div class="pa-5 pb-0">
<v-file-input v-model="importFile" variant="underlined" label="Zip File" accept=".zip" />
<v-btn color="secondary" :disabled="importFile === null" @click="handleBulkImport()">
<v-btn color="secondary" width="100%" :disabled="importFile === null" @click="handleBulkImport()">
<v-icon start class="open-icon"> mdi-import </v-icon>
<span class="open-label">Bulk Import</span>
</v-btn>
@@ -377,16 +378,21 @@ const handleBulkImport = () => {
</v-col>
</v-row>
<v-row>
<v-col cols="12">
<v-simple-table fixed-header height="100%" density="compact" dark>
<v-col cols="">
<v-table fixed-header height="100%" density="compact" dark>
<thead style="font-size: 1.25rem">
<tr>
<th class="text-left">Available Models</th>
<th>Model Nicknames</th>
<th>Labels</th>
<th>Delete</th>
<th>Edit</th>
<th>Info</th>
</tr>
</thead>
<tbody>
<tr v-for="model in supportedModels" :key="model.modelPath">
<td>{{ model.nickname }}</td>
<td>{{ model.labels.join(", ") }}</td>
<td class="text-right">
<v-btn
icon
@@ -416,58 +422,62 @@ const handleBulkImport = () => {
</td>
</tr>
</tbody>
</v-simple-table>
</v-table>
<v-dialog v-model="confirmDeleteDialog.show" width="600">
<v-card color="primary" dark>
<v-card-title>Delete Object Detection Model</v-card-title>
<v-card-text>
Are you sure you want to delete the model
{{ confirmDeleteDialog.model.nickname }}?
<v-row class="mt-12 ml-8 mr-8 mb-1" style="display: flex; align-items: center; justify-content: center">
<v-btn text @click="confirmDeleteDialog.show = false" color="secondary">Cancel</v-btn>
<v-btn color="error" @click="deleteModel(confirmDeleteDialog.model)">Delete</v-btn>
</v-row>
<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-card-actions>
</v-card-text>
</v-card>
</v-dialog>
<v-dialog v-model="showRenameDialog.show" width="600">
<v-card color="primary" dark>
<v-card-title>Rename Object Detection Model</v-card-title>
<v-card-text>
<v-card-text class="pt-0">
Enter a new name for the model {{ showRenameDialog.model.nickname }}:
<v-row class="mt-6 ml-4 mr-8">
<v-text-field v-model="showRenameDialog.newName" label="New Name" />
</v-row>
<v-row>
<v-btn text @click="showRenameDialog.show = false" color="error">Cancel</v-btn>
<v-btn text color="secondary" @click="renameModel(showRenameDialog.model, showRenameDialog.newName)"
<div class="pa-5 pb-0">
<v-text-field v-model="showRenameDialog.newName" hide-details label="New Name" variant="underlined" />
</div>
<v-card-actions class="pt-5 pb-0 pr-0" style="justify-content: flex-end">
<v-btn
variant="elevated"
color="secondary"
@click="renameModel(showRenameDialog.model, showRenameDialog.newName)"
>Rename</v-btn
>
</v-row>
<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-title>Object Detection Model Info</v-card-title>
<v-btn color="secondary" @click="openExportIndividualModelPrompt">
<v-icon left class="open-icon"> mdi-export </v-icon>
<span class="open-label">Export Model</span>
</v-btn>
<a
ref="exportIndividualModel"
style="color: black; text-decoration: none; display: none"
:href="`http://${address}/api/objectdetection/exportIndividual?modelPath=${showInfo.model.modelPath.replace('file:', '')}`"
:download="`${showInfo.model.nickname}_${showInfo.model.family}_${showInfo.model.version}_${showInfo.model.resolutionWidth}x${showInfo.model.resolutionHeight}_${showInfo.model.labels.join('_')}.${showInfo.model.family.toLowerCase()}`"
target="_blank"
/>
<v-card-text>
<p>Model Path: {{ showInfo.model.modelPath }}</p>
<p>Model Nickname: {{ showInfo.model.nickname }}</p>
<p>Model Family: {{ showInfo.model.family }}</p>
<p>Model Version: {{ showInfo.model.version }}</p>
<p>Model Label(s): {{ showInfo.model.labels.join(", ") }}</p>
<p>Model Resolution: {{ showInfo.model.resolutionWidth }} x {{ showInfo.model.resolutionHeight }}</p>
<v-card-text class="pt-0">
<v-btn color="secondary" width="100%" @click="openExportIndividualModelPrompt">
<v-icon left class="open-icon"> mdi-export </v-icon>
<span class="open-label">Export Model</span>
</v-btn>
<a
ref="exportIndividualModel"
style="color: black; text-decoration: none; display: none"
:href="`http://${address}/api/objectdetection/exportIndividual?modelPath=${showInfo.model.modelPath.replace('file:', '')}`"
:download="`${showInfo.model.nickname}_${showInfo.model.family}_${showInfo.model.version}_${showInfo.model.resolutionWidth}x${showInfo.model.resolutionHeight}_${showInfo.model.labels.join('_')}.${showInfo.model.family.toLowerCase()}`"
target="_blank"
/>
<div class="pt-5">
<p>Model Path: {{ showInfo.model.modelPath }}</p>
<p>Model Nickname: {{ showInfo.model.nickname }}</p>
<p>Model Family: {{ showInfo.model.family }}</p>
<p>Model Version: {{ showInfo.model.version }}</p>
<p>Model Label(s): {{ showInfo.model.labels.join(", ") }}</p>
<p>Model Resolution: {{ showInfo.model.resolutionWidth }} x {{ showInfo.model.resolutionHeight }}</p>
</div>
</v-card-text>
</v-card>
</v-dialog>
@@ -476,18 +486,18 @@ const handleBulkImport = () => {
</div>
<v-dialog v-model="showNukeDialog" width="800" dark>
<v-card color="primary" class="pa-3" flat>
<v-card-title style="justify-content: center" class="pb-6">
<v-card color="primary" 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>
Clear and Reset Object Detection Models
<v-icon end color="error" class="open-icon ma-1">mdi-nuke</v-icon>
</span>
</v-card-title>
<v-card-text class="pt-3">
<v-card-text class="pt-0 pb-10px">
<v-row class="align-center text-white">
<v-col cols="12" md="6">
<span class="mt-3"> This will delete ALL OF YOUR MODELS and re-extract the default models. </span>
<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">
@@ -504,7 +514,7 @@ const handleBulkImport = () => {
</v-col>
</v-row>
</v-card-text>
<v-card-text>
<v-card-text class="pt-0 pb-0">
<pv-input
v-model="yesDeleteMyModelsText"
:label="'Type &quot;' + expected + '&quot;:'"
@@ -512,9 +522,10 @@ const handleBulkImport = () => {
:input-cols="6"
/>
</v-card-text>
<v-card-text>
<v-card-text class="pt-10px">
<v-btn
color="error"
width="100%"
:disabled="yesDeleteMyModelsText.toLowerCase() !== expected.toLowerCase()"
@click="nukeModels"
>
@@ -530,9 +541,14 @@ const handleBulkImport = () => {
</template>
<style scoped lang="scss">
.v-btn {
.v-col-12 > .v-btn {
width: 100%;
}
.pt-10px {
padding-top: 10px !important;
}
@media only screen and (max-width: 351px) {
.open-icon {
margin: 0 !important;
@@ -552,6 +568,7 @@ const handleBulkImport = () => {
background-color: #006492 !important;
font-size: 1rem !important;
color: white !important;
text-align: center !important;
}
td {

View File

@@ -290,17 +290,20 @@ export const PlaceholderCameraSettings: UiCameraConfiguration = {
{
resolution: { width: 1920, height: 1080 },
fps: 60,
pixelFormat: "RGB"
pixelFormat: "RGB",
index: 0
},
{
resolution: { width: 1280, height: 720 },
fps: 60,
pixelFormat: "RGB"
pixelFormat: "RGB",
index: 1
},
{
resolution: { width: 640, height: 480 },
fps: 30,
pixelFormat: "RGB"
pixelFormat: "RGB",
index: 2
}
],
completeCalibrations: [

View File

@@ -289,10 +289,11 @@ const openExportSettingsPrompt = () => {
cols="12"
sm="6"
lg="4"
class="pr-0"
>
<v-card color="primary">
<v-card-title>{{ cameraInfoFor(module.matchedCameraInfo).name }}</v-card-title>
<v-card-subtitle v-if="!cameraCononected(cameraInfoFor(module.matchedCameraInfo).uniquePath)" class="pb-2"
<v-card-subtitle v-if="!cameraCononected(cameraInfoFor(module.matchedCameraInfo).uniquePath)"
>Status: <span class="inactive-status">Disconnected</span></v-card-subtitle
>
<v-card-subtitle
@@ -300,11 +301,10 @@ const openExportSettingsPrompt = () => {
cameraCononected(cameraInfoFor(module.matchedCameraInfo).uniquePath) &&
camerasMatch(getMatchedDevice(module.matchedCameraInfo), module.matchedCameraInfo)
"
class="pb-2"
>Status: <span class="active-status">Active</span></v-card-subtitle
>
<v-card-subtitle v-else class="pb-2">Status: <span class="mismatch-status">Mismatch</span></v-card-subtitle>
<v-card-text>
<v-card-subtitle v-else>Status: <span class="mismatch-status">Mismatch</span></v-card-subtitle>
<v-card-text class="pt-3">
<v-table density="compact">
<tbody>
<tr>
@@ -395,11 +395,18 @@ const openExportSettingsPrompt = () => {
</v-col>
<!-- Disabled modules -->
<v-col v-for="module in disabledVisionModules" :key="`disabled-${module.uniqueName}`" cols="12" sm="6" lg="4">
<v-card color="primary">
<v-col
v-for="module in disabledVisionModules"
:key="`disabled-${module.uniqueName}`"
cols="12"
sm="6"
lg="4"
class="pr-0"
>
<v-card class="pr-0" color="primary">
<v-card-title>{{ module.nickname }}</v-card-title>
<v-card-subtitle class="pb-2">Status: <span class="inactive-status">Deactivated</span></v-card-subtitle>
<v-card-text>
<v-card-subtitle>Status: <span class="inactive-status">Deactivated</span></v-card-subtitle>
<v-card-text class="pt-3">
<v-table density="compact">
<tbody>
<tr>
@@ -466,9 +473,9 @@ const openExportSettingsPrompt = () => {
</v-col>
<!-- Unassigned cameras -->
<v-col v-for="(camera, index) in unmatchedCameras" :key="index" cols="12" sm="6" lg="4">
<v-card color="primary">
<v-card-title class="pb-2">
<v-col v-for="(camera, index) in unmatchedCameras" :key="index" cols="12" sm="6" lg="4" class="pr-0">
<v-card class="pr-0" color="primary">
<v-card-title>
<span v-if="camera.PVUsbCameraInfo">USB Camera:</span>
<span v-else-if="camera.PVCSICameraInfo">CSI Camera:</span>
<span v-else-if="camera.PVFileCameraInfo">File Camera:</span>
@@ -476,7 +483,7 @@ const openExportSettingsPrompt = () => {
&nbsp;<span>{{ cameraInfoFor(camera)?.name ?? cameraInfoFor(camera)?.baseName }}</span>
</v-card-title>
<v-card-subtitle>Status: Unassigned</v-card-subtitle>
<v-card-text>
<v-card-text class="pt-3">
<span style="word-break: break-all">{{ cameraInfoFor(camera)?.path }}</span>
</v-card-text>
<v-card-text class="pt-0">
@@ -503,7 +510,7 @@ const openExportSettingsPrompt = () => {
</v-col>
<!-- Info card -->
<v-col cols="12" sm="6" lg="4">
<v-col cols="12" sm="6" lg="4" class="pr-0">
<v-card
dark
flat
@@ -531,7 +538,7 @@ const openExportSettingsPrompt = () => {
<PvCameraInfoCard :camera="viewingCamera[0]" />
</v-card-text>
<v-card-text v-else-if="!camerasMatch(getMatchedDevice(viewingCamera[0]), viewingCamera[0])">
<v-banner rounded color="error" text-color="white" icon="mdi-information-outline" class="mb-3">
<v-banner rounded bg-color="error" text-color="white" icon="mdi-information-outline" class="mb-3">
It looks like a different camera may have been connected to this device! Compare the following information
carefully.
</v-banner>
@@ -545,10 +552,10 @@ const openExportSettingsPrompt = () => {
<!-- Camera delete modal -->
<v-dialog v-model="viewingDeleteCamera" width="800">
<v-card v-if="cameraToDelete !== null" class="dialog-container pa-3 pb-2" color="primary" flat>
<v-card v-if="cameraToDelete !== null" class="dialog-container" color="primary" flat>
<v-card-title> Delete {{ cameraToDelete.nickname }}? </v-card-title>
<v-card-text>
<v-row class="align-center pt-6">
<v-card-text class="pb-10px">
<v-row class="align-center">
<v-col cols="12" md="6">
<span class="text-white"> This will delete ALL OF YOUR SETTINGS and restart PhotonVision. </span>
</v-col>
@@ -567,7 +574,7 @@ const openExportSettingsPrompt = () => {
</v-col>
</v-row>
</v-card-text>
<v-card-text>
<v-card-text class="pt-0 pb-0">
<pv-input
v-model="yesDeleteMySettingsText"
:label="'Type &quot;' + cameraToDelete.nickname + '&quot;:'"
@@ -575,7 +582,7 @@ const openExportSettingsPrompt = () => {
:input-cols="6"
/>
</v-card-text>
<v-card-text>
<v-card-text class="pt-10px">
<v-btn
block
color="error"
@@ -593,7 +600,17 @@ const openExportSettingsPrompt = () => {
</template>
<style scoped>
td {
padding: 0 !important;
}
.v-card-subtitle {
padding-top: 0px !important;
padding-bottom: 8px !important;
}
.v-card-title {
padding-bottom: 0 !important;
text-wrap-mode: wrap !important;
}

View File

@@ -64,7 +64,15 @@ const showCameraSetupDialog = ref(useCameraSettingsStore().needsCameraConfigurat
<template>
<v-container class="pa-3" fluid>
<v-banner v-if="arducamWarningShown" rounded color="error" dark class="mb-3" icon="mdi-alert-circle-outline">
<v-banner
v-if="arducamWarningShown"
rounded
bg-color="error"
color="error"
dark
class="mb-3"
icon="mdi-alert-circle-outline"
>
<span
>Arducam Camera Detected! Please configure the camera model in the <a href="#/cameras">Cameras tab</a>!
</span>
@@ -116,7 +124,7 @@ const showCameraSetupDialog = ref(useCameraSettingsStore().needsCameraConfigurat
>
<v-card flat color="primary">
<v-card-title>Setup some cameras to get started!</v-card-title>
<v-card-text>
<v-card-text class="pt-0">
No cameras activated - head to the <router-link to="/cameraConfigs">Camera matching tab</router-link> to set
some up!
</v-card-text>