mirror of
https://github.com/PhotonVision/photonvision
synced 2026-07-05 03:21:40 +00:00
Modal template for deletion confirmation (#2190)
## Description <!-- What changed? Why? (the code + comments should speak for itself on the "how") --> <!-- Fun screenshots or a cool video or something are super helpful as well. If this touches platform-specific behavior, this is where test evidence should be collected. --> <!-- Any issues this pull request closes or pull requests this supersedes should be linked with `Closes #issuenumber`. --> This adds a template modal that can be used for confirming that the user wants to delete something. The main goal is to reduce complication and duplicated code, and standardize the way we handle deletion. closes #2175 ## 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_ - [x] This PR has been [linted](https://docs.photonvision.org/en/latest/docs/contributing/linting.html). - [ ] 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 v2025.3.2 - [ ] 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 --------- Co-authored-by: Devolian <devondoyle@outlook.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import { useStateStore } from "@/stores/StateStore";
|
|||||||
import { computed, inject, ref } from "vue";
|
import { computed, inject, ref } from "vue";
|
||||||
import { axiosPost, getResolutionString, parseJsonFile } from "@/lib/PhotonUtils";
|
import { axiosPost, getResolutionString, parseJsonFile } from "@/lib/PhotonUtils";
|
||||||
import { useTheme } from "vuetify";
|
import { useTheme } from "vuetify";
|
||||||
|
import PvDeleteModal from "@/components/common/pv-delete-modal.vue";
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
@@ -12,7 +13,7 @@ const props = defineProps<{
|
|||||||
videoFormat: VideoFormat;
|
videoFormat: VideoFormat;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const confirmRemoveDialog = ref({ show: false, vf: {} as VideoFormat });
|
const confirmRemoveDialog = ref({ show: false, vf: props.videoFormat as VideoFormat });
|
||||||
|
|
||||||
const removeCalibration = (vf: VideoFormat) => {
|
const removeCalibration = (vf: VideoFormat) => {
|
||||||
axiosPost("/calibration/remove", "delete a camera calibration", {
|
axiosPost("/calibration/remove", "delete a camera calibration", {
|
||||||
@@ -20,8 +21,6 @@ const removeCalibration = (vf: VideoFormat) => {
|
|||||||
width: vf.resolution.width,
|
width: vf.resolution.width,
|
||||||
height: vf.resolution.height
|
height: vf.resolution.height
|
||||||
});
|
});
|
||||||
|
|
||||||
confirmRemoveDialog.value.show = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const exportCalibration = ref();
|
const exportCalibration = ref();
|
||||||
@@ -110,17 +109,6 @@ const calibrationImageURL = (index: number) =>
|
|||||||
<v-card-title class="pa-0"> Calibration Details </v-card-title>
|
<v-card-title class="pa-0"> Calibration Details </v-card-title>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" md="6" class="d-flex align-center pt-0 pt-md-3">
|
<v-col cols="12" md="6" class="d-flex align-center pt-0 pt-md-3">
|
||||||
<v-btn
|
|
||||||
color="error"
|
|
||||||
:disabled="!currentCalibrationCoeffs"
|
|
||||||
class="mr-2"
|
|
||||||
style="flex: 1"
|
|
||||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
|
||||||
@click="() => (confirmRemoveDialog = { show: true, vf: props.videoFormat })"
|
|
||||||
>
|
|
||||||
<v-icon start size="large">mdi-delete</v-icon>
|
|
||||||
<span>Delete</span>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn
|
<v-btn
|
||||||
color="buttonPassive"
|
color="buttonPassive"
|
||||||
class="mr-2"
|
class="mr-2"
|
||||||
@@ -140,6 +128,7 @@ const calibrationImageURL = (index: number) =>
|
|||||||
/>
|
/>
|
||||||
<v-btn
|
<v-btn
|
||||||
color="buttonPassive"
|
color="buttonPassive"
|
||||||
|
class="mr-2"
|
||||||
:disabled="!currentCalibrationCoeffs"
|
:disabled="!currentCalibrationCoeffs"
|
||||||
style="flex: 1"
|
style="flex: 1"
|
||||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||||
@@ -154,6 +143,16 @@ const calibrationImageURL = (index: number) =>
|
|||||||
:href="exportCalibrationURL"
|
:href="exportCalibrationURL"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
/>
|
/>
|
||||||
|
<v-btn
|
||||||
|
color="error"
|
||||||
|
:disabled="!currentCalibrationCoeffs"
|
||||||
|
style="flex: 1"
|
||||||
|
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||||
|
@click="() => (confirmRemoveDialog = { show: true, vf: props.videoFormat })"
|
||||||
|
>
|
||||||
|
<v-icon start size="large">mdi-delete</v-icon>
|
||||||
|
<span>Delete</span>
|
||||||
|
</v-btn>
|
||||||
</v-col>
|
</v-col>
|
||||||
</div>
|
</div>
|
||||||
<v-card-title class="pt-0 pb-0"
|
<v-card-title class="pt-0 pb-0"
|
||||||
@@ -312,32 +311,13 @@ const calibrationImageURL = (index: number) =>
|
|||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
|
||||||
<v-dialog v-model="confirmRemoveDialog.show" width="600">
|
<pv-delete-modal
|
||||||
<v-card color="surface" dark>
|
v-model="confirmRemoveDialog.show"
|
||||||
<v-card-title>Delete Calibration</v-card-title>
|
:width="500"
|
||||||
<v-card-text class="pt-0">
|
:title="'Delete Calibration'"
|
||||||
Are you sure you want to delete the calibration for {{ confirmRemoveDialog.vf.resolution.width }}x{{
|
:description="`Are you sure you want to delete the calibration for '${confirmRemoveDialog.vf.resolution.width}x${confirmRemoveDialog.vf.resolution.height}'? This action cannot be undone.`"
|
||||||
confirmRemoveDialog.vf.resolution.height
|
:on-confirm="() => removeCalibration(confirmRemoveDialog.vf)"
|
||||||
}}? This cannot be undone.
|
/>
|
||||||
<v-card-actions class="pt-5 pb-0 pr-0" style="justify-content: flex-end">
|
|
||||||
<v-btn
|
|
||||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
|
||||||
color="primary"
|
|
||||||
@click="() => (confirmRemoveDialog.show = false)"
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</v-btn>
|
|
||||||
<v-btn
|
|
||||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
|
||||||
color="error"
|
|
||||||
@click="removeCalibration(confirmRemoveDialog.vf)"
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</v-btn>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import PvSelect, { type SelectItem } from "@/components/common/pv-select.vue";
|
import PvSelect, { type SelectItem } from "@/components/common/pv-select.vue";
|
||||||
import PvInput from "@/components/common/pv-input.vue";
|
import PvDeleteModal from "@/components/common/pv-delete-modal.vue";
|
||||||
import PvNumberInput from "@/components/common/pv-number-input.vue";
|
import PvNumberInput from "@/components/common/pv-number-input.vue";
|
||||||
import PvSwitch from "@/components/common/pv-switch.vue";
|
import PvSwitch from "@/components/common/pv-switch.vue";
|
||||||
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
||||||
@@ -120,17 +120,9 @@ watchEffect(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const showDeleteCamera = ref(false);
|
const showDeleteCamera = ref(false);
|
||||||
const yesDeleteMySettingsText = ref("");
|
|
||||||
const deletingCamera = ref(false);
|
|
||||||
const deleteThisCamera = () => {
|
const deleteThisCamera = () => {
|
||||||
if (deletingCamera.value) return;
|
axiosPost("/utils/nukeOneCamera", "delete this camera", {
|
||||||
deletingCamera.value = true;
|
cameraUniqueName: useStateStore().currentCameraUniqueName
|
||||||
|
|
||||||
const payload = { cameraUniqueName: useStateStore().currentCameraUniqueName };
|
|
||||||
|
|
||||||
axiosPost("/utils/nukeOneCamera", "delete this camera", payload).finally(() => {
|
|
||||||
deletingCamera.value = false;
|
|
||||||
showDeleteCamera.value = false;
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const wrappedCameras = computed<SelectItem[]>(() =>
|
const wrappedCameras = computed<SelectItem[]>(() =>
|
||||||
@@ -208,45 +200,13 @@ const wrappedCameras = computed<SelectItem[]>(() =>
|
|||||||
</v-col>
|
</v-col>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
|
||||||
<v-dialog v-model="showDeleteCamera" width="800">
|
<pv-delete-modal
|
||||||
<v-card color="surface" flat>
|
v-model="showDeleteCamera"
|
||||||
<v-card-title> Delete {{ useCameraSettingsStore().currentCameraSettings.nickname }}? </v-card-title>
|
title="Delete Camera"
|
||||||
<v-card-text class="pt-0 pb-10px">
|
:description="`Are you sure you want to delete the camera '${useCameraSettingsStore().currentCameraSettings.nickname}'? This action cannot be undone.`"
|
||||||
Are you sure you want to delete "{{ useCameraSettingsStore().currentCameraSettings.nickname }}"? This cannot
|
:expected-confirmation-text="useCameraSettingsStore().currentCameraSettings.nickname"
|
||||||
be undone.
|
:on-confirm="deleteThisCamera"
|
||||||
</v-card-text>
|
/>
|
||||||
<v-card-text class="pt-0 pb-10px">
|
|
||||||
<pv-input
|
|
||||||
v-model="yesDeleteMySettingsText"
|
|
||||||
:label="'Type "' + useCameraSettingsStore().currentCameraName + '":'"
|
|
||||||
:label-cols="6"
|
|
||||||
:input-cols="6"
|
|
||||||
/>
|
|
||||||
</v-card-text>
|
|
||||||
<v-card-actions class="pa-5 pt-0">
|
|
||||||
<v-btn
|
|
||||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
|
||||||
color="primary"
|
|
||||||
class="text-black"
|
|
||||||
@click="showDeleteCamera = false"
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</v-btn>
|
|
||||||
<v-btn
|
|
||||||
color="error"
|
|
||||||
:disabled="
|
|
||||||
yesDeleteMySettingsText.toLowerCase() !== useCameraSettingsStore().currentCameraName.toLowerCase()
|
|
||||||
"
|
|
||||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
|
||||||
:loading="deletingCamera"
|
|
||||||
@click="deleteThisCamera"
|
|
||||||
>
|
|
||||||
<v-icon start class="open-icon" size="large"> mdi-trash-can-outline </v-icon>
|
|
||||||
<span class="open-label">Delete</span>
|
|
||||||
</v-btn>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
85
photon-client/src/components/common/pv-delete-modal.vue
Normal file
85
photon-client/src/components/common/pv-delete-modal.vue
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { useTheme } from "vuetify";
|
||||||
|
import pvInput from "./pv-input.vue";
|
||||||
|
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const value = defineModel<boolean | undefined>({ required: true });
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
expectedConfirmationText?: string;
|
||||||
|
onBackup?: () => void;
|
||||||
|
onConfirm: () => void;
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
deleteText?: string;
|
||||||
|
width?: number;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
width: 700
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const confirmationText = ref("");
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-dialog v-model="value" :width="props.width" dark>
|
||||||
|
<v-card color="surface" flat>
|
||||||
|
<v-card-title style="display: flex; justify-content: center">
|
||||||
|
{{ title }}
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text class="pt-0 pb-10px">
|
||||||
|
<span> {{ description }} </span>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-text v-if="expectedConfirmationText" class="pt-0 pb-0">
|
||||||
|
<pv-input
|
||||||
|
v-model="confirmationText"
|
||||||
|
:label="'Type "' + expectedConfirmationText + '":'"
|
||||||
|
:label-cols="6"
|
||||||
|
:input-cols="6"
|
||||||
|
/>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-text class="pt-10px">
|
||||||
|
<v-row class="align-center text-white">
|
||||||
|
<v-col v-if="onBackup" cols="6">
|
||||||
|
<v-btn
|
||||||
|
color="buttonActive"
|
||||||
|
style="float: right"
|
||||||
|
width="100%"
|
||||||
|
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||||
|
@click="onBackup"
|
||||||
|
>
|
||||||
|
<v-icon start class="open-icon" size="large"> mdi-export </v-icon>
|
||||||
|
<span class="open-label">Backup Data</span>
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
<v-col v-if="description" :cols="onBackup ? '6' : '12'">
|
||||||
|
<v-btn
|
||||||
|
color="error"
|
||||||
|
width="100%"
|
||||||
|
:disabled="
|
||||||
|
expectedConfirmationText
|
||||||
|
? confirmationText.toLowerCase() !== expectedConfirmationText.toLowerCase()
|
||||||
|
: false
|
||||||
|
"
|
||||||
|
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||||
|
@click="
|
||||||
|
onConfirm();
|
||||||
|
confirmationText = '';
|
||||||
|
value = false;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<v-icon start class="open-icon" size="large"> mdi-trash-can-outline </v-icon>
|
||||||
|
<span class="open-label">
|
||||||
|
{{ deleteText ?? title }}
|
||||||
|
</span>
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
@@ -9,6 +9,7 @@ import PvInput from "@/components/common/pv-input.vue";
|
|||||||
import { PipelineType } from "@/types/PipelineTypes";
|
import { PipelineType } from "@/types/PipelineTypes";
|
||||||
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
|
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
|
||||||
import { useTheme } from "vuetify";
|
import { useTheme } from "vuetify";
|
||||||
|
import PvDeleteModal from "@/components/common/pv-delete-modal.vue";
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
@@ -422,33 +423,13 @@ const wrappedCameras = computed<SelectItem[]>(() =>
|
|||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
<v-dialog v-model="showPipelineDeletionConfirmationDialog" width="500">
|
<pv-delete-modal
|
||||||
<v-card color="surface">
|
v-model="showPipelineDeletionConfirmationDialog"
|
||||||
<v-card-title class="pb-0">Delete Pipeline</v-card-title>
|
:width="500"
|
||||||
<v-card-text>
|
title="Delete Pipeline"
|
||||||
Are you sure you want to delete
|
description="Are you sure you want to delete the current pipeline? This action cannot be undone."
|
||||||
<span style="color: white">"{{ useCameraSettingsStore().currentPipelineSettings.pipelineNickname }}"</span>?
|
:on-confirm="confirmDeleteCurrentPipeline"
|
||||||
This cannot be undone.
|
/>
|
||||||
</v-card-text>
|
|
||||||
<v-card-actions class="pa-5 pt-0">
|
|
||||||
<v-btn
|
|
||||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
|
||||||
color="primary"
|
|
||||||
class="text-black"
|
|
||||||
@click="showPipelineDeletionConfirmationDialog = false"
|
|
||||||
>
|
|
||||||
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-dialog v-model="showPipelineTypeChangeDialog" persistent width="600">
|
||||||
<v-card color="surface" dark>
|
<v-card color="surface" dark>
|
||||||
<v-card-title class="pb-0">Change Pipeline Type</v-card-title>
|
<v-card-title class="pb-0">Change Pipeline Type</v-card-title>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { inject, ref } from "vue";
|
import { inject, ref } from "vue";
|
||||||
import { useStateStore } from "@/stores/StateStore";
|
import { useStateStore } from "@/stores/StateStore";
|
||||||
import PvSelect from "@/components/common/pv-select.vue";
|
import PvSelect from "@/components/common/pv-select.vue";
|
||||||
import PvInput from "@/components/common/pv-input.vue";
|
import PvDeleteModal from "@/components/common/pv-delete-modal.vue";
|
||||||
import { useTheme } from "vuetify";
|
import { useTheme } from "vuetify";
|
||||||
import { axiosPost } from "@/lib/PhotonUtils";
|
import { axiosPost } from "@/lib/PhotonUtils";
|
||||||
|
|
||||||
@@ -113,12 +113,8 @@ const handleSettingsImport = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const showFactoryReset = ref(false);
|
const showFactoryReset = ref(false);
|
||||||
const expected = "Delete Everything";
|
|
||||||
const yesDeleteMySettingsText = ref("");
|
|
||||||
const nukePhotonConfigDirectory = () => {
|
const nukePhotonConfigDirectory = () => {
|
||||||
axiosPost("/utils/nukeConfigDirectory", "delete the config directory");
|
axiosPost("/utils/nukeConfigDirectory", "delete the config directory");
|
||||||
|
|
||||||
showFactoryReset.value = false;
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -280,63 +276,15 @@ const nukePhotonConfigDirectory = () => {
|
|||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</div>
|
</div>
|
||||||
<v-dialog v-model="showFactoryReset" width="800" dark>
|
<pv-delete-modal
|
||||||
<v-card color="surface" flat>
|
v-model="showFactoryReset"
|
||||||
<v-card-title style="display: flex; justify-content: center">
|
title="Factory Reset PhotonVision"
|
||||||
<span class="open-label">
|
description="This will delete all settings and configurations stored on this device, including network settings. This action cannot be undone."
|
||||||
<v-icon end color="red" class="open-icon ma-1" size="large">mdi-alert-outline</v-icon>
|
expected-confirmation-text="Delete Everything"
|
||||||
Factory Reset PhotonVision
|
:on-confirm="nukePhotonConfigDirectory"
|
||||||
<v-icon end color="red" class="open-icon ma-1" size="large">mdi-alert-outline</v-icon>
|
:on-backup="openExportSettingsPrompt"
|
||||||
</span>
|
delete-text="Factory reset"
|
||||||
</v-card-title>
|
/>
|
||||||
<v-card-text class="pt-0 pb-10px">
|
|
||||||
<v-row class="align-center text-white">
|
|
||||||
<v-col cols="12" md="6">
|
|
||||||
<span> This will delete ALL OF YOUR SETTINGS and restart PhotonVision. </span>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" md="6">
|
|
||||||
<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"
|
|
||||||
style="color: black; text-decoration: none; display: none"
|
|
||||||
:href="`http://${address}/api/settings/photonvision_config.zip`"
|
|
||||||
download="photonvision-settings.zip"
|
|
||||||
target="_blank"
|
|
||||||
/>
|
|
||||||
</v-btn>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-card-text>
|
|
||||||
<v-card-text class="pt-0 pb-0">
|
|
||||||
<pv-input
|
|
||||||
v-model="yesDeleteMySettingsText"
|
|
||||||
:label="'Type "' + expected + '":'"
|
|
||||||
:label-cols="6"
|
|
||||||
:input-cols="6"
|
|
||||||
/>
|
|
||||||
</v-card-text>
|
|
||||||
<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" 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>
|
|
||||||
</v-btn>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
import { ref, computed, inject } from "vue";
|
import { ref, computed, inject } from "vue";
|
||||||
import { useStateStore } from "@/stores/StateStore";
|
import { useStateStore } from "@/stores/StateStore";
|
||||||
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
|
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
|
||||||
import type { ObjectDetectionModelProperties } from "@/types/SettingTypes";
|
import { type ObjectDetectionModelProperties } from "@/types/SettingTypes";
|
||||||
import pvInput from "@/components/common/pv-input.vue";
|
import PvDeleteModal from "@/components/common/pv-delete-modal.vue";
|
||||||
import { useTheme } from "vuetify";
|
import { useTheme } from "vuetify";
|
||||||
import { axiosPost } from "@/lib/PhotonUtils";
|
import { axiosPost } from "@/lib/PhotonUtils";
|
||||||
|
|
||||||
@@ -73,17 +73,9 @@ const handleImport = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const deleteModel = async (model: ObjectDetectionModelProperties) => {
|
const deleteModel = async (model: ObjectDetectionModelProperties) => {
|
||||||
useStateStore().showSnackbarMessage({
|
|
||||||
message: "Deleting Object Detection Model...",
|
|
||||||
color: "secondary",
|
|
||||||
timeout: -1
|
|
||||||
});
|
|
||||||
|
|
||||||
axiosPost("/objectdetection/delete", "delete an object detection model", {
|
axiosPost("/objectdetection/delete", "delete an object detection model", {
|
||||||
modelPath: model.modelPath
|
modelPath: model.modelPath
|
||||||
});
|
});
|
||||||
|
|
||||||
confirmDeleteDialog.value.show = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const renameModel = async (model: ObjectDetectionModelProperties, newName: string) => {
|
const renameModel = async (model: ObjectDetectionModelProperties, newName: string) => {
|
||||||
@@ -123,11 +115,8 @@ const openExportIndividualModelPrompt = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const showNukeDialog = ref(false);
|
const showNukeDialog = ref(false);
|
||||||
const expected = "Delete Models";
|
|
||||||
const yesDeleteMyModelsText = ref("");
|
|
||||||
const nukeModels = () => {
|
const nukeModels = () => {
|
||||||
axiosPost("/objectdetection/nuke", "clear and reset object detection models");
|
axiosPost("/objectdetection/nuke", "clear and reset object detection models");
|
||||||
showNukeDialog.value = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const showBulkImportDialog = ref(false);
|
const showBulkImportDialog = ref(false);
|
||||||
@@ -378,35 +367,20 @@ const handleBulkImport = () => {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</v-table>
|
</v-table>
|
||||||
|
|
||||||
<v-dialog v-model="confirmDeleteDialog.show" width="600">
|
<pv-delete-modal
|
||||||
<v-card color="surface" dark>
|
v-model="confirmDeleteDialog.show"
|
||||||
<v-card-title>Delete Object Detection Model</v-card-title>
|
:width="500"
|
||||||
<v-card-text class="pt-0">
|
:on-confirm="() => deleteModel(confirmDeleteDialog.model)"
|
||||||
Are you sure you want to delete the model {{ confirmDeleteDialog.model.nickname }}?
|
title="Delete Object Detection Model"
|
||||||
<v-card-actions class="pt-5 pb-0 pr-0" style="justify-content: flex-end">
|
:description="`Are you sure you want to delete the model ${confirmDeleteDialog.model.nickname}?`"
|
||||||
<v-btn
|
delete-text="Delete model"
|
||||||
: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-dialog v-model="showRenameDialog.show" width="600">
|
||||||
<v-card color="surface" dark>
|
<v-card color="surface" dark>
|
||||||
<v-card-title>Rename Object Detection Model</v-card-title>
|
<v-card-title>Rename Object Detection Model</v-card-title>
|
||||||
<v-card-text class="pt-0">
|
<v-card-text class="pt-0">
|
||||||
Enter a new name for the model {{ showRenameDialog.model.nickname }}:
|
Enter a new name for the model "{{ showRenameDialog.model.nickname }}":
|
||||||
<div class="pa-5 pb-0">
|
<div class="pa-5 pb-0">
|
||||||
<v-text-field v-model="showRenameDialog.newName" hide-details label="New Name" variant="underlined" />
|
<v-text-field v-model="showRenameDialog.newName" hide-details label="New Name" variant="underlined" />
|
||||||
</div>
|
</div>
|
||||||
@@ -462,64 +436,15 @@ const handleBulkImport = () => {
|
|||||||
</v-row>
|
</v-row>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<v-dialog v-model="showNukeDialog" width="800" dark>
|
<pv-delete-modal
|
||||||
<v-card color="surface" flat>
|
v-model="showNukeDialog"
|
||||||
<v-card-title style="display: flex; justify-content: center">
|
:on-backup="openExportPrompt"
|
||||||
<span class="open-label">
|
:on-confirm="nukeModels"
|
||||||
<v-icon end color="error" class="open-icon ma-1" size="large">mdi-alert-outline</v-icon>
|
title="Delete and Reset All Object Detection Models"
|
||||||
Clear and Reset Object Detection Models
|
:description="'This will delete ALL object detection models and re-extract the default object detection models. This action cannot be undone.'"
|
||||||
<v-icon end color="error" class="open-icon ma-1" size="large">mdi-alert-outline</v-icon>
|
:expected-confirmation-text="'Delete Models'"
|
||||||
</span>
|
delete-text="Delete all models"
|
||||||
</v-card-title>
|
/>
|
||||||
<v-card-text class="pt-0 pb-10px">
|
|
||||||
<v-row class="align-center text-white">
|
|
||||||
<v-col cols="12" md="6">
|
|
||||||
<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="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"
|
|
||||||
style="color: black; text-decoration: none; display: none"
|
|
||||||
:href="`http://${address}/api/objectdetection/export`"
|
|
||||||
download="photonvision-object-detection-models-export.zip"
|
|
||||||
target="_blank"
|
|
||||||
/>
|
|
||||||
</v-btn>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-card-text>
|
|
||||||
<v-card-text class="pt-0 pb-0">
|
|
||||||
<pv-input
|
|
||||||
v-model="yesDeleteMyModelsText"
|
|
||||||
:label="'Type "' + expected + '":'"
|
|
||||||
:label-cols="6"
|
|
||||||
:input-cols="6"
|
|
||||||
/>
|
|
||||||
</v-card-text>
|
|
||||||
<v-card-text class="pt-10px">
|
|
||||||
<v-btn
|
|
||||||
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" 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>
|
|
||||||
</v-btn>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -7,15 +7,13 @@ import {
|
|||||||
PVCameraInfo,
|
PVCameraInfo,
|
||||||
type PVCSICameraInfo,
|
type PVCSICameraInfo,
|
||||||
type PVFileCameraInfo,
|
type PVFileCameraInfo,
|
||||||
type PVUsbCameraInfo,
|
type PVUsbCameraInfo
|
||||||
type UiCameraConfiguration
|
|
||||||
} from "@/types/SettingTypes";
|
} from "@/types/SettingTypes";
|
||||||
import { axiosPost, getResolutionString } from "@/lib/PhotonUtils";
|
import { axiosPost, getResolutionString } from "@/lib/PhotonUtils";
|
||||||
import PhotonCameraStream from "@/components/app/photon-camera-stream.vue";
|
import PhotonCameraStream from "@/components/app/photon-camera-stream.vue";
|
||||||
import PvInput from "@/components/common/pv-input.vue";
|
import PvDeleteModal from "@/components/common/pv-delete-modal.vue";
|
||||||
import PvCameraInfoCard from "@/components/common/pv-camera-info-card.vue";
|
import PvCameraInfoCard from "@/components/common/pv-camera-info-card.vue";
|
||||||
import PvCameraMatchCard from "@/components/common/pv-camera-match-card.vue";
|
import PvCameraMatchCard from "@/components/common/pv-camera-match-card.vue";
|
||||||
import type { WebsocketCameraSettingsUpdate } from "@/types/WebsocketDataTypes";
|
|
||||||
import { useTheme } from "vuetify";
|
import { useTheme } from "vuetify";
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@@ -55,17 +53,14 @@ const deactivateModule = (cameraUniqueName: string) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const deletingCamera = ref(false);
|
const confirmDeleteDialog = ref({ show: false, nickname: "", cameraUniqueName: "" });
|
||||||
const deleteThisCamera = (cameraName: string) => {
|
const deletingCamera = ref<string | null>(null);
|
||||||
if (deletingCamera.value) return;
|
|
||||||
deletingCamera.value = true;
|
|
||||||
const payload = {
|
|
||||||
cameraUniqueName: cameraName
|
|
||||||
};
|
|
||||||
|
|
||||||
axiosPost("/utils/nukeOneCamera", "delete a camera", payload).finally(() => {
|
const deleteThisCamera = (cameraUniqueName: string) => {
|
||||||
setCameraDeleting(null);
|
if (deletingCamera.value) return;
|
||||||
deletingCamera.value = false;
|
deletingCamera.value = cameraUniqueName;
|
||||||
|
axiosPost("/utils/nukeOneCamera", "delete a camera", { cameraUniqueName: cameraUniqueName }).finally(() => {
|
||||||
|
deletingCamera.value = null;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -110,15 +105,6 @@ const setCameraView = (camera: PVCameraInfo | null, isConnected: boolean | null)
|
|||||||
viewingCamera.value = [camera, isConnected];
|
viewingCamera.value = [camera, isConnected];
|
||||||
};
|
};
|
||||||
|
|
||||||
const viewingDeleteCamera = ref(false);
|
|
||||||
const cameraToDelete = ref<UiCameraConfiguration | WebsocketCameraSettingsUpdate | null>(null);
|
|
||||||
const setCameraDeleting = (camera: UiCameraConfiguration | WebsocketCameraSettingsUpdate | null) => {
|
|
||||||
yesDeleteMySettingsText.value = "";
|
|
||||||
viewingDeleteCamera.value = camera !== null;
|
|
||||||
cameraToDelete.value = camera;
|
|
||||||
};
|
|
||||||
const yesDeleteMySettingsText = ref("");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the connection-type-specific camera info from the given PVCameraInfo object.
|
* Get the connection-type-specific camera info from the given PVCameraInfo object.
|
||||||
*/
|
*/
|
||||||
@@ -274,8 +260,16 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
|||||||
class="pa-0"
|
class="pa-0"
|
||||||
color="error"
|
color="error"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
|
:loading="module.uniqueName === deletingCamera"
|
||||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||||
@click="setCameraDeleting(module)"
|
@click="
|
||||||
|
() =>
|
||||||
|
(confirmDeleteDialog = {
|
||||||
|
show: true,
|
||||||
|
nickname: module.nickname,
|
||||||
|
cameraUniqueName: module.uniqueName
|
||||||
|
})
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<v-icon size="x-large">mdi-trash-can-outline</v-icon>
|
<v-icon size="x-large">mdi-trash-can-outline</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
@@ -360,8 +354,16 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
|||||||
class="pa-0"
|
class="pa-0"
|
||||||
color="error"
|
color="error"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
|
:loading="module.uniqueName === deletingCamera"
|
||||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||||
@click="setCameraDeleting(module)"
|
@click="
|
||||||
|
() =>
|
||||||
|
(confirmDeleteDialog = {
|
||||||
|
show: true,
|
||||||
|
nickname: module.nickname,
|
||||||
|
cameraUniqueName: module.uniqueName
|
||||||
|
})
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<v-icon size="x-large">mdi-trash-can-outline</v-icon>
|
<v-icon size="x-large">mdi-trash-can-outline</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
@@ -465,43 +467,13 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
|||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
|
|
||||||
<!-- Camera delete modal -->
|
<pv-delete-modal
|
||||||
<v-dialog v-model="viewingDeleteCamera" width="800">
|
v-model="confirmDeleteDialog.show"
|
||||||
<v-card v-if="cameraToDelete !== null" class="dialog-container" color="surface" flat>
|
title="Delete Camera"
|
||||||
<v-card-title> Delete {{ cameraToDelete.nickname }}? </v-card-title>
|
:description="`Are you sure you want to delete the camera '${useCameraSettingsStore().currentCameraSettings.nickname}'? This action cannot be undone.`"
|
||||||
<v-card-text class="pb-10px">
|
:expected-confirmation-text="confirmDeleteDialog.nickname"
|
||||||
Are you sure you want to delete "{{ cameraToDelete.nickname }}"? This cannot be undone.
|
:on-confirm="() => deleteThisCamera(confirmDeleteDialog.cameraUniqueName)"
|
||||||
</v-card-text>
|
/>
|
||||||
<v-card-text class="pt-0 pb-10px">
|
|
||||||
<pv-input
|
|
||||||
v-model="yesDeleteMySettingsText"
|
|
||||||
:label="'Type "' + cameraToDelete.nickname + '":'"
|
|
||||||
:label-cols="6"
|
|
||||||
:input-cols="6"
|
|
||||||
/>
|
|
||||||
</v-card-text>
|
|
||||||
<v-card-actions class="pa-5 pt-0">
|
|
||||||
<v-btn
|
|
||||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
|
||||||
color="primary"
|
|
||||||
class="text-black"
|
|
||||||
@click="cameraToDelete = null"
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</v-btn>
|
|
||||||
<v-btn
|
|
||||||
color="error"
|
|
||||||
:disabled="yesDeleteMySettingsText.toLowerCase() !== cameraToDelete.nickname.toLowerCase()"
|
|
||||||
:loading="deletingCamera"
|
|
||||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
|
||||||
@click="deleteThisCamera(cameraToDelete.uniqueName)"
|
|
||||||
>
|
|
||||||
<v-icon start class="open-icon" size="large"> mdi-trash-can-outline </v-icon>
|
|
||||||
<span class="open-label">Delete</span>
|
|
||||||
</v-btn>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user