Skip to content

Commit d27b3d0

Browse files
samfreundDevonRD
andauthored
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>
1 parent 77e5545 commit d27b3d0

File tree

7 files changed

+188
-337
lines changed

7 files changed

+188
-337
lines changed

photon-client/src/components/cameras/CameraCalibrationInfoCard.vue

Lines changed: 20 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,22 @@ import { useStateStore } from "@/stores/StateStore";
55
import { computed, inject, ref } from "vue";
66
import { axiosPost, getResolutionString, parseJsonFile } from "@/lib/PhotonUtils";
77
import { useTheme } from "vuetify";
8+
import PvDeleteModal from "@/components/common/pv-delete-modal.vue";
89
910
const theme = useTheme();
1011
1112
const props = defineProps<{
1213
videoFormat: VideoFormat;
1314
}>();
1415
15-
const confirmRemoveDialog = ref({ show: false, vf: {} as VideoFormat });
16+
const confirmRemoveDialog = ref({ show: false, vf: props.videoFormat as VideoFormat });
1617
1718
const removeCalibration = (vf: VideoFormat) => {
1819
axiosPost("/calibration/remove", "delete a camera calibration", {
1920
cameraUniqueName: useCameraSettingsStore().currentCameraSettings.uniqueName,
2021
width: vf.resolution.width,
2122
height: vf.resolution.height
2223
});
23-
24-
confirmRemoveDialog.value.show = false;
2524
};
2625
2726
const exportCalibration = ref();
@@ -110,17 +109,6 @@ const calibrationImageURL = (index: number) =>
110109
<v-card-title class="pa-0"> Calibration Details </v-card-title>
111110
</v-col>
112111
<v-col cols="12" md="6" class="d-flex align-center pt-0 pt-md-3">
113-
<v-btn
114-
color="error"
115-
:disabled="!currentCalibrationCoeffs"
116-
class="mr-2"
117-
style="flex: 1"
118-
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
119-
@click="() => (confirmRemoveDialog = { show: true, vf: props.videoFormat })"
120-
>
121-
<v-icon start size="large">mdi-delete</v-icon>
122-
<span>Delete</span>
123-
</v-btn>
124112
<v-btn
125113
color="buttonPassive"
126114
class="mr-2"
@@ -140,6 +128,7 @@ const calibrationImageURL = (index: number) =>
140128
/>
141129
<v-btn
142130
color="buttonPassive"
131+
class="mr-2"
143132
:disabled="!currentCalibrationCoeffs"
144133
style="flex: 1"
145134
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
@@ -154,6 +143,16 @@ const calibrationImageURL = (index: number) =>
154143
:href="exportCalibrationURL"
155144
target="_blank"
156145
/>
146+
<v-btn
147+
color="error"
148+
:disabled="!currentCalibrationCoeffs"
149+
style="flex: 1"
150+
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
151+
@click="() => (confirmRemoveDialog = { show: true, vf: props.videoFormat })"
152+
>
153+
<v-icon start size="large">mdi-delete</v-icon>
154+
<span>Delete</span>
155+
</v-btn>
157156
</v-col>
158157
</div>
159158
<v-card-title class="pt-0 pb-0"
@@ -312,32 +311,13 @@ const calibrationImageURL = (index: number) =>
312311
</v-card-text>
313312
</v-card>
314313

315-
<v-dialog v-model="confirmRemoveDialog.show" width="600">
316-
<v-card color="surface" dark>
317-
<v-card-title>Delete Calibration</v-card-title>
318-
<v-card-text class="pt-0">
319-
Are you sure you want to delete the calibration for {{ confirmRemoveDialog.vf.resolution.width }}x{{
320-
confirmRemoveDialog.vf.resolution.height
321-
}}? This cannot be undone.
322-
<v-card-actions class="pt-5 pb-0 pr-0" style="justify-content: flex-end">
323-
<v-btn
324-
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
325-
color="primary"
326-
@click="() => (confirmRemoveDialog.show = false)"
327-
>
328-
Cancel
329-
</v-btn>
330-
<v-btn
331-
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
332-
color="error"
333-
@click="removeCalibration(confirmRemoveDialog.vf)"
334-
>
335-
Delete
336-
</v-btn>
337-
</v-card-actions>
338-
</v-card-text>
339-
</v-card>
340-
</v-dialog>
314+
<pv-delete-modal
315+
v-model="confirmRemoveDialog.show"
316+
:width="500"
317+
:title="'Delete Calibration'"
318+
: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.`"
319+
:on-confirm="() => removeCalibration(confirmRemoveDialog.vf)"
320+
/>
341321
</template>
342322

343323
<style scoped>

photon-client/src/components/cameras/CameraSettingsCard.vue

Lines changed: 10 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script setup lang="ts">
22
import PvSelect, { type SelectItem } from "@/components/common/pv-select.vue";
3-
import PvInput from "@/components/common/pv-input.vue";
3+
import PvDeleteModal from "@/components/common/pv-delete-modal.vue";
44
import PvNumberInput from "@/components/common/pv-number-input.vue";
55
import PvSwitch from "@/components/common/pv-switch.vue";
66
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
@@ -120,17 +120,9 @@ watchEffect(() => {
120120
});
121121
122122
const showDeleteCamera = ref(false);
123-
const yesDeleteMySettingsText = ref("");
124-
const deletingCamera = ref(false);
125123
const deleteThisCamera = () => {
126-
if (deletingCamera.value) return;
127-
deletingCamera.value = true;
128-
129-
const payload = { cameraUniqueName: useStateStore().currentCameraUniqueName };
130-
131-
axiosPost("/utils/nukeOneCamera", "delete this camera", payload).finally(() => {
132-
deletingCamera.value = false;
133-
showDeleteCamera.value = false;
124+
axiosPost("/utils/nukeOneCamera", "delete this camera", {
125+
cameraUniqueName: useStateStore().currentCameraUniqueName
134126
});
135127
};
136128
const wrappedCameras = computed<SelectItem[]>(() =>
@@ -208,45 +200,13 @@ const wrappedCameras = computed<SelectItem[]>(() =>
208200
</v-col>
209201
</v-card-text>
210202

211-
<v-dialog v-model="showDeleteCamera" width="800">
212-
<v-card color="surface" flat>
213-
<v-card-title> Delete {{ useCameraSettingsStore().currentCameraSettings.nickname }}? </v-card-title>
214-
<v-card-text class="pt-0 pb-10px">
215-
Are you sure you want to delete "{{ useCameraSettingsStore().currentCameraSettings.nickname }}"? This cannot
216-
be undone.
217-
</v-card-text>
218-
<v-card-text class="pt-0 pb-10px">
219-
<pv-input
220-
v-model="yesDeleteMySettingsText"
221-
:label="'Type &quot;' + useCameraSettingsStore().currentCameraName + '&quot;:'"
222-
:label-cols="6"
223-
:input-cols="6"
224-
/>
225-
</v-card-text>
226-
<v-card-actions class="pa-5 pt-0">
227-
<v-btn
228-
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
229-
color="primary"
230-
class="text-black"
231-
@click="showDeleteCamera = false"
232-
>
233-
Cancel
234-
</v-btn>
235-
<v-btn
236-
color="error"
237-
:disabled="
238-
yesDeleteMySettingsText.toLowerCase() !== useCameraSettingsStore().currentCameraName.toLowerCase()
239-
"
240-
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
241-
:loading="deletingCamera"
242-
@click="deleteThisCamera"
243-
>
244-
<v-icon start class="open-icon" size="large"> mdi-trash-can-outline </v-icon>
245-
<span class="open-label">Delete</span>
246-
</v-btn>
247-
</v-card-actions>
248-
</v-card>
249-
</v-dialog>
203+
<pv-delete-modal
204+
v-model="showDeleteCamera"
205+
title="Delete Camera"
206+
:description="`Are you sure you want to delete the camera '${useCameraSettingsStore().currentCameraSettings.nickname}'? This action cannot be undone.`"
207+
:expected-confirmation-text="useCameraSettingsStore().currentCameraSettings.nickname"
208+
:on-confirm="deleteThisCamera"
209+
/>
250210
</v-card>
251211
</template>
252212

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<script setup lang="ts">
2+
import { ref } from "vue";
3+
import { useTheme } from "vuetify";
4+
import pvInput from "./pv-input.vue";
5+
6+
const theme = useTheme();
7+
8+
const value = defineModel<boolean | undefined>({ required: true });
9+
10+
const props = withDefaults(
11+
defineProps<{
12+
expectedConfirmationText?: string;
13+
onBackup?: () => void;
14+
onConfirm: () => void;
15+
title: string;
16+
description?: string;
17+
deleteText?: string;
18+
width?: number;
19+
}>(),
20+
{
21+
width: 700
22+
}
23+
);
24+
25+
const confirmationText = ref("");
26+
</script>
27+
28+
<template>
29+
<v-dialog v-model="value" :width="props.width" dark>
30+
<v-card color="surface" flat>
31+
<v-card-title style="display: flex; justify-content: center">
32+
{{ title }}
33+
</v-card-title>
34+
<v-card-text class="pt-0 pb-10px">
35+
<span> {{ description }} </span>
36+
</v-card-text>
37+
<v-card-text v-if="expectedConfirmationText" class="pt-0 pb-0">
38+
<pv-input
39+
v-model="confirmationText"
40+
:label="'Type &quot;' + expectedConfirmationText + '&quot;:'"
41+
:label-cols="6"
42+
:input-cols="6"
43+
/>
44+
</v-card-text>
45+
<v-card-text class="pt-10px">
46+
<v-row class="align-center text-white">
47+
<v-col v-if="onBackup" cols="6">
48+
<v-btn
49+
color="buttonActive"
50+
style="float: right"
51+
width="100%"
52+
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
53+
@click="onBackup"
54+
>
55+
<v-icon start class="open-icon" size="large"> mdi-export </v-icon>
56+
<span class="open-label">Backup Data</span>
57+
</v-btn>
58+
</v-col>
59+
<v-col v-if="description" :cols="onBackup ? '6' : '12'">
60+
<v-btn
61+
color="error"
62+
width="100%"
63+
:disabled="
64+
expectedConfirmationText
65+
? confirmationText.toLowerCase() !== expectedConfirmationText.toLowerCase()
66+
: false
67+
"
68+
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
69+
@click="
70+
onConfirm();
71+
confirmationText = '';
72+
value = false;
73+
"
74+
>
75+
<v-icon start class="open-icon" size="large"> mdi-trash-can-outline </v-icon>
76+
<span class="open-label">
77+
{{ deleteText ?? title }}
78+
</span>
79+
</v-btn>
80+
</v-col>
81+
</v-row>
82+
</v-card-text>
83+
</v-card>
84+
</v-dialog>
85+
</template>

photon-client/src/components/dashboard/CameraAndPipelineSelectCard.vue

Lines changed: 8 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import PvInput from "@/components/common/pv-input.vue";
99
import { PipelineType } from "@/types/PipelineTypes";
1010
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
1111
import { useTheme } from "vuetify";
12+
import PvDeleteModal from "@/components/common/pv-delete-modal.vue";
1213
1314
const theme = useTheme();
1415
@@ -422,33 +423,13 @@ const wrappedCameras = computed<SelectItem[]>(() =>
422423
</v-card-actions>
423424
</v-card>
424425
</v-dialog>
425-
<v-dialog v-model="showPipelineDeletionConfirmationDialog" width="500">
426-
<v-card color="surface">
427-
<v-card-title class="pb-0">Delete Pipeline</v-card-title>
428-
<v-card-text>
429-
Are you sure you want to delete
430-
<span style="color: white">"{{ useCameraSettingsStore().currentPipelineSettings.pipelineNickname }}"</span>?
431-
This cannot be undone.
432-
</v-card-text>
433-
<v-card-actions class="pa-5 pt-0">
434-
<v-btn
435-
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
436-
color="primary"
437-
class="text-black"
438-
@click="showPipelineDeletionConfirmationDialog = false"
439-
>
440-
Cancel
441-
</v-btn>
442-
<v-btn
443-
color="error"
444-
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
445-
@click="confirmDeleteCurrentPipeline"
446-
>
447-
Delete
448-
</v-btn>
449-
</v-card-actions>
450-
</v-card>
451-
</v-dialog>
426+
<pv-delete-modal
427+
v-model="showPipelineDeletionConfirmationDialog"
428+
:width="500"
429+
title="Delete Pipeline"
430+
description="Are you sure you want to delete the current pipeline? This action cannot be undone."
431+
:on-confirm="confirmDeleteCurrentPipeline"
432+
/>
452433
<v-dialog v-model="showPipelineTypeChangeDialog" persistent width="600">
453434
<v-card color="surface" dark>
454435
<v-card-title class="pb-0">Change Pipeline Type</v-card-title>

0 commit comments

Comments
 (0)