Skip to content

Commit

Permalink
feat: adds optional confirmation dialogs for emergency stop and power…
Browse files Browse the repository at this point in the history
… device change (#384)

Signed-off-by: Pedro Lamas <pedrolamas@gmail.com>
  • Loading branch information
pedrolamas committed Oct 20, 2021
1 parent 182d7f1 commit a02963c
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 40 deletions.
116 changes: 77 additions & 39 deletions src/components/TheTopCornerMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,59 @@
</style>

<template>
<v-menu bottom left :offset-y="true" :close-on-content-click="false" v-model="showMenu">
<template v-slot:activator="{ on, attrs }">
<v-btn color="grey darken-3" v-bind="attrs" v-on="on" class="ml-5 minwidth-0 px-2">
<v-icon>mdi-power-standby</v-icon>
</v-btn>
</template>
<v-list dense>
<v-subheader class="" style="height: auto;">{{ $t("App.TopCornerMenu.KlipperControl") }}</v-subheader>
<v-list-item class="minheight30" link @click="klipperRestart()">
<v-list-item-title><v-icon class="mr-2" small>mdi-restart</v-icon>{{ $t("App.TopCornerMenu.KlipperRestart") }}</v-list-item-title>
</v-list-item>
<v-list-item class="minheight30" link @click="klipperFirmwareRestart()">
<v-list-item-title><v-icon class="mr-2" small>mdi-restart</v-icon>{{ $t("App.TopCornerMenu.KlipperFirmwareRestart") }}</v-list-item-title>
</v-list-item>
<template v-if="services.length">
<v-divider class="mt-0"></v-divider>
<v-subheader class="pt-2" style="height: auto;">{{ $t("App.TopCornerMenu.RestartServices") }}</v-subheader>
<v-list-item class="minheight30" link @click="serviceRestart(service)" v-for="service in services" v-bind:key="service">
<v-list-item-title><v-icon class="mr-2" small>mdi-restart</v-icon>{{ service.charAt(0).toUpperCase() + service.slice(1) }}</v-list-item-title>
</v-list-item>
<div>
<v-menu bottom left :offset-y="true" :close-on-content-click="false" v-model="showMenu">
<template v-slot:activator="{ on, attrs }">
<v-btn color="grey darken-3" v-bind="attrs" v-on="on" class="ml-5 minwidth-0 px-2">
<v-icon>mdi-power-standby</v-icon>
</v-btn>
</template>
<template v-if="powerDevices.length">
<v-list dense>
<v-subheader class="" style="height: auto;">{{ $t("App.TopCornerMenu.KlipperControl") }}</v-subheader>
<v-list-item class="minheight30" link @click="klipperRestart()">
<v-list-item-title><v-icon class="mr-2" small>mdi-restart</v-icon>{{ $t("App.TopCornerMenu.KlipperRestart") }}</v-list-item-title>
</v-list-item>
<v-list-item class="minheight30" link @click="klipperFirmwareRestart()">
<v-list-item-title><v-icon class="mr-2" small>mdi-restart</v-icon>{{ $t("App.TopCornerMenu.KlipperFirmwareRestart") }}</v-list-item-title>
</v-list-item>
<template v-if="services.length">
<v-divider class="mt-0"></v-divider>
<v-subheader class="pt-2" style="height: auto;">{{ $t("App.TopCornerMenu.RestartServices") }}</v-subheader>
<v-list-item class="minheight30" link @click="serviceRestart(service)" v-for="service in services" v-bind:key="service">
<v-list-item-title><v-icon class="mr-2" small>mdi-restart</v-icon>{{ service.charAt(0).toUpperCase() + service.slice(1) }}</v-list-item-title>
</v-list-item>
</template>
<template v-if="powerDevices.length">
<v-divider class="mt-0"></v-divider>
<v-subheader class="pt-2" style="height: auto;">{{ $t("App.TopCornerMenu.PowerDevices") }}</v-subheader>
<v-list-item v-for="(device, index) in powerDevices" v-bind:key="index" class="minheight30" @click="changeSwitch(device, device.status)" :disabled="(device.status === 'error' || device.locked_while_printing && ['printing', 'paused'].includes(printer_state))">
<v-list-item-title>
<v-icon class="mr-2" :color="device.status === 'on' ? '' : 'grey darken-2'">mdi-{{ device.status === 'on' ? 'toggle-switch' : 'toggle-switch-off' }}</v-icon>{{ device.device }}
</v-list-item-title>
</v-list-item>
</template>
<v-divider class="mt-0"></v-divider>
<v-subheader class="pt-2" style="height: auto;">{{ $t("App.TopCornerMenu.PowerDevices") }}</v-subheader>
<v-list-item v-for="(device, index) in powerDevices" v-bind:key="index" class="minheight30" @click="changeSwitch(device, device.status)" :disabled="(device.status === 'error' || device.locked_while_printing && ['printing', 'paused'].includes(printer_state))">
<v-list-item-title>
<v-icon class="mr-2" :color="device.status === 'on' ? '' : 'grey darken-2'">mdi-{{ device.status === 'on' ? 'toggle-switch' : 'toggle-switch-off' }}</v-icon>{{ device.device }}
</v-list-item-title>
<v-subheader class="pt-2" style="height: auto;">{{ $t("App.TopCornerMenu.HostControl") }}</v-subheader>
<v-list-item class="minheight30" link @click="hostReboot()">
<v-list-item-title><v-icon class="mr-2" small>mdi-power</v-icon>{{ $t("App.TopCornerMenu.Reboot") }}</v-list-item-title>
</v-list-item>
</template>
<v-divider class="mt-0"></v-divider>
<v-subheader class="pt-2" style="height: auto;">{{ $t("App.TopCornerMenu.HostControl") }}</v-subheader>
<v-list-item class="minheight30" link @click="hostReboot()">
<v-list-item-title><v-icon class="mr-2" small>mdi-power</v-icon>{{ $t("App.TopCornerMenu.Reboot") }}</v-list-item-title>
</v-list-item>
<v-list-item class="minheight30" link @click="hostShutdown()">
<v-list-item-title><v-icon class="mr-2" small>mdi-power</v-icon>{{ $t("App.TopCornerMenu.Shutdown") }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
<v-list-item class="minheight30" link @click="hostShutdown()">
<v-list-item-title><v-icon class="mr-2" small>mdi-power</v-icon>{{ $t("App.TopCornerMenu.Shutdown") }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
<v-dialog v-model="dialogPowerDeviceChange.show" width="400" :fullscreen="isMobile">
<v-card>
<v-card-title class="headline">{{ $t(this.dialogPowerDeviceChange.value === 'off' ? 'PowerDeviceChangeDialog.TurnDeviceOn' : 'PowerDeviceChangeDialog.TurnDeviceOff', {'device': dialogPowerDeviceChange.device}) }}</v-card-title>
<v-card-text>{{ $t('PowerDeviceChangeDialog.AreYouSure') }}</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="red darken-1" text @click="dialogPowerDeviceChange.show = false">{{ $t('PowerDeviceChangeDialog.No')}}</v-btn>
<v-btn color="green darken-1" text @click="powerDeviceToggle">{{$t('PowerDeviceChangeDialog.Yes')}}</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>

<script lang="ts">
Expand All @@ -54,9 +67,20 @@ import { Mixins } from 'vue-property-decorator'
import BaseMixin from '@/components/mixins/base'
import {ServerPowerStateDevice} from '@/store/server/power/types'
interface dialogPowerDeviceChange {
show: boolean
device: string
value: string
}
@Component
export default class TheTopCornerMenu extends Mixins(BaseMixin) {
showMenu = false
dialogPowerDeviceChange : dialogPowerDeviceChange = {
show: false,
device: '',
value: ''
}
get services() {
const services = this.$store.state.server.system_info?.available_services?.filter((name: string) => name !== 'klipper_mcu') ?? []
Expand Down Expand Up @@ -86,8 +110,22 @@ export default class TheTopCornerMenu extends Mixins(BaseMixin) {
}
changeSwitch(device: ServerPowerStateDevice, value: string) {
const rpc = (value === 'off' ? 'machine.device_power.on' : 'machine.device_power.off')
this.$socket.emit(rpc,{ [device.device]: null },{ action: 'server/power/responseToggle' })
this.dialogPowerDeviceChange.device = device.device
this.dialogPowerDeviceChange.value = value
const confirmOnPowerDeviceChange = this.$store.state.gui.general.confirmOnPowerDeviceChange
if (confirmOnPowerDeviceChange) {
this.dialogPowerDeviceChange.show = true
}
else {
this.powerDeviceToggle()
}
}
powerDeviceToggle() {
this.dialogPowerDeviceChange.show = false
const rpc = (this.dialogPowerDeviceChange.value === 'off' ? 'machine.device_power.on' : 'machine.device_power.off')
this.$socket.emit(rpc,{ [this.dialogPowerDeviceChange.device]: null },{ action: 'server/power/responseToggle' })
}
hostReboot() {
Expand Down
26 changes: 25 additions & 1 deletion src/components/TheTopbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
class="button-min-width-auto px-3"
v-if="klippyIsConnected"
:loading="loadings.includes('topbarEmergencyStop')"
@click="emergencyStop">
@click="btnEmergencyStop">
<v-icon class="mr-md-2">mdi-alert-circle-outline</v-icon><span class="d-none d-md-flex">{{ $t("App.TopBar.EmergencyStop") }}</span>
</v-btn>
<the-settings-menu></the-settings-menu>
Expand Down Expand Up @@ -63,6 +63,17 @@
</v-btn>
</template>
</v-snackbar>
<v-dialog v-model="showEmergencyStopDialog" width="400" :fullscreen="isMobile">
<v-card>
<v-card-title class="headline">{{ $t('EmergencyStopDialog.EmergencyStop') }}</v-card-title>
<v-card-text>{{ $t('EmergencyStopDialog.AreYouSure') }}</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="red darken-1" text @click="showEmergencyStopDialog = false">{{ $t('EmergencyStopDialog.No')}}</v-btn>
<v-btn color="green darken-1" text @click="emergencyStop">{{$t('EmergencyStopDialog.Yes')}}</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>

Expand Down Expand Up @@ -98,6 +109,8 @@ type uploadSnackbar = {
}
})
export default class TheTopbar extends Mixins(BaseMixin) {
showEmergencyStopDialog = false
uploadSnackbar: uploadSnackbar = {
status: false,
filename: '',
Expand Down Expand Up @@ -137,7 +150,18 @@ export default class TheTopbar extends Mixins(BaseMixin) {
return this.$store.state.printer.configfile?.save_config_pending ?? false
}
btnEmergencyStop() {
const confirmOnEmergencyStop = this.$store.state.gui.general.confirmOnEmergencyStop
if (confirmOnEmergencyStop) {
this.showEmergencyStopDialog = true
}
else {
this.emergencyStop()
}
}
emergencyStop() {
this.showEmergencyStopDialog = false
this.$socket.emit('printer.emergency_stop', {}, { loading: 'topbarEmergencyStop' })
}
Expand Down
24 changes: 24 additions & 0 deletions src/components/settings/SettingsGeneralTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@
<v-switch v-model="displayZOffsetStandby" hide-details class="mt-0"></v-switch>
</settings-row>
<v-divider class="my-2"></v-divider>
<settings-row :title="$t('Settings.GeneralTab.ConfirmOnEmergencyStop')" :sub-title="$t('Settings.GeneralTab.ConfirmOnEmergencyStopDescription')" :dynamicSlotWidth="true">
<v-switch v-model="confirmOnEmergencyStop" hide-details class="mt-0"></v-switch>
</settings-row>
<v-divider class="my-2"></v-divider>
<settings-row :title="$t('Settings.GeneralTab.ConfirmOnPowerDeviceChange')" :sub-title="$t('Settings.GeneralTab.ConfirmOnPowerDeviceChangeDescription')" :dynamicSlotWidth="true">
<v-switch v-model="confirmOnPowerDeviceChange" hide-details class="mt-0"></v-switch>
</settings-row>
<v-divider class="my-2"></v-divider>
<settings-row :title="$t('Settings.GeneralTab.FactoryReset')" :dynamicSlotWidth="true">
<v-btn @click="dialogResetMainsail=true" color="error" small>{{ $t('Settings.GeneralTab.FactoryReset') }}</v-btn>
</settings-row>
Expand Down Expand Up @@ -133,6 +141,22 @@ export default class SettingsGeneralTab extends Mixins(BaseMixin) {
this.$store.dispatch('gui/saveSetting', {name: 'general.displayZOffsetStandby', value: newVal })
}
get confirmOnEmergencyStop() {
return this.$store.state.gui.general.confirmOnEmergencyStop
}
set confirmOnEmergencyStop(newVal) {
this.$store.dispatch('gui/saveSetting', {name: 'general.confirmOnEmergencyStop', value: newVal })
}
get confirmOnPowerDeviceChange() {
return this.$store.state.gui.general.confirmOnPowerDeviceChange
}
set confirmOnPowerDeviceChange(newVal) {
this.$store.dispatch('gui/saveSetting', {name: 'general.confirmOnPowerDeviceChange', value: newVal })
}
resetMainsail() {
this.$store.dispatch('gui/resetMoonrakerDB')
this.dialogResetMainsail = false
Expand Down
17 changes: 17 additions & 0 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,19 @@
"DescriptionPreviouslyTemperatureLimited": "rPi uC (3A+/3B+ only) temperature was at least once above the soft limit (default 60C) since last power-on."
}
},
"EmergencyStopDialog": {
"EmergencyStop": "Emergency Stop",
"AreYouSure": "Are you sure?",
"No": "No",
"Yes": "Yes"
},
"PowerDeviceChangeDialog": {
"TurnDeviceOn": "Turn {device} on",
"TurnDeviceOff": "Turn {device} off",
"AreYouSure": "Are you sure?",
"No": "No",
"Yes": "Yes"
},
"ConnectionDialog": {
"Connecting": "Connecting to {host}",
"Failed": "Connection failed",
Expand Down Expand Up @@ -491,6 +504,10 @@
"DisplayCANCEL_PRINTDescription": "Shows the CANCEL_PRINT button permanently - no second layer confirmation needed.",
"DisplayZOffset": "Show Z-Offset-Panel",
"DisplayZOffsetDescription": "Otherwise, the panel will appear only after a print has started.",
"ConfirmOnEmergencyStop": "Require confirm on Emergency Stop",
"ConfirmOnEmergencyStopDescription": "Show a confirmation dialog on Emergency Stop",
"ConfirmOnPowerDeviceChange": "Require confirm on Device Power changes",
"ConfirmOnPowerDeviceChangeDescription": "Show a confirmation dialog on Device Power changes",
"FactoryReset": "Factory reset",
"FactoryInfo": "Do you really want to reset mainsail to factory settings?",
"ResetMainsail": "reset mainsail"
Expand Down
2 changes: 2 additions & 0 deletions src/store/gui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export const getDefaultState = (): GuiState => {
language: 'en',
displayCancelPrint: false,
displayZOffsetStandby: false,
confirmOnEmergencyStop: false,
confirmOnPowerDeviceChange: false,
},
theme: {
logo: defaultLogoColor,
Expand Down

0 comments on commit a02963c

Please sign in to comment.