Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add option to hide other Klipper & Moonraker instances #2029

Merged
merged 2 commits into from
Dec 1, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/assets/styles/utils.css
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
min-width: auto !important;
}

.minHeight30 {
min-height: 30px !important;
}

.minHeight36 {
min-height: 36px;
}
Expand Down
216 changes: 70 additions & 146 deletions src/components/TheTopCornerMenu.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
<style scoped>
.minheight30 {
min-height: 30px;
}
</style>

<template>
<div>
<v-menu v-model="showMenu" bottom left :offset-y="true" :close-on-content-click="false">
Expand All @@ -18,7 +12,7 @@
{{ $t('App.TopCornerMenu.KlipperControl') }}
</v-subheader>
<v-list-item
class="minheight30 pr-2"
class="minHeight30 pr-2"
link
@click="checkDialog(klipperRestart, 'klipper', 'restart')">
<v-list-item-title>{{ $t('App.TopCornerMenu.KlipperRestart') }}</v-list-item-title>
Expand All @@ -27,7 +21,7 @@
</v-list-item-action>
</v-list-item>
<v-list-item
class="minheight30 pr-2"
class="minHeight30 pr-2"
link
@click="checkDialog(klipperFirmwareRestart, 'klipper', 'firmwareRestart')">
<v-list-item-title>{{ $t('App.TopCornerMenu.KlipperFirmwareRestart') }}</v-list-item-title>
Expand All @@ -37,42 +31,15 @@
</v-list-item>
</template>
<template v-if="services.length">
<v-divider v-if="klipperState !== 'disconnected'" class="mt-0"></v-divider>
<v-divider v-if="klipperState !== 'disconnected'" class="mt-0" />
<v-subheader class="pt-2" style="height: auto">
{{ $t('App.TopCornerMenu.ServiceControl') }}
</v-subheader>
<v-list-item v-for="service in services" :key="service" class="minheight30 pr-2">
<v-list-item-title>
<v-tooltip left>
<template #activator="{ on, attrs }">
<span v-bind="attrs" v-on="on">
{{ service.charAt(0).toUpperCase() + service.slice(1) }}
</span>
</template>
<span>{{ getServiceState(service) }} ({{ getServiceSubState(service) }})</span>
</v-tooltip>
</v-list-item-title>
<v-list-item-action class="my-0 d-flex flex-row" style="min-width: auto">
<v-btn
v-if="getServiceState(service) === 'inactive'"
icon
small
@click="checkDialog(serviceStart, service, 'start')">
<v-icon small>{{ mdiPlay }}</v-icon>
</v-btn>
<v-btn v-else icon small @click="checkDialog(serviceRestart, service, 'restart')">
<v-icon small>{{ mdiRestart }}</v-icon>
</v-btn>
<v-btn
icon
small
:disabled="getServiceState(service) === 'inactive' || service === 'moonraker'"
:style="service === 'moonraker' ? 'visibility: hidden;' : ''"
@click="checkDialog(serviceStop, service, 'stop')">
<v-icon small>{{ mdiStop }}</v-icon>
</v-btn>
</v-list-item-action>
</v-list-item>
<top-corner-menu-service
v-for="service in services"
:key="service"
:service="service"
@close-menu="showMenu = false" />
</template>
<template v-if="powerDevices.length">
<v-divider class="mt-0"></v-divider>
Expand All @@ -82,7 +49,7 @@
<v-list-item
v-for="(device, index) in powerDevices"
:key="index"
class="minheight30 pr-2"
class="minHeight30 pr-2"
:disabled="
device.status === 'error' ||
(device.locked_while_printing && ['printing', 'paused'].includes(printer_state))
Expand All @@ -98,13 +65,13 @@
</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 pr-2" link @click="checkDialog(hostReboot, 'host', 'reboot')">
<v-list-item class="minHeight30 pr-2" link @click="checkDialog(hostReboot, 'host', 'reboot')">
<v-list-item-title>{{ $t('App.TopCornerMenu.Reboot') }}</v-list-item-title>
<v-list-item-action class="my-0 d-flex flex-row" style="min-width: auto">
<v-icon class="mr-2" small>{{ mdiPower }}</v-icon>
</v-list-item-action>
</v-list-item>
<v-list-item class="minheight30 pr-2" link @click="checkDialog(hostShutdown, 'host', 'shutdown')">
<v-list-item class="minHeight30 pr-2" link @click="checkDialog(hostShutdown, 'host', 'shutdown')">
<v-list-item-title>{{ $t('App.TopCornerMenu.Shutdown') }}</v-list-item-title>
<v-list-item-action class="my-0 d-flex flex-row" style="min-width: auto">
<v-icon class="mr-2" small>{{ mdiPower }}</v-icon>
Expand Down Expand Up @@ -133,35 +100,14 @@
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="dialogConfirmation.show" width="400" :fullscreen="isMobile">
<panel
card-class="confirm-top-corner-menu-dialog"
:icon="mdiAlert"
:title="dialogConfirmation.title"
:margin-bottom="false">
<template #buttons>
<v-btn icon tile @click="dialogConfirmation.show = false">
<v-icon>{{ mdiCloseThick }}</v-icon>
</v-btn>
</template>
<v-card-text class="pt-3">
<v-row>
<v-col>
<p class="body-2">{{ dialogConfirmation.description }}</p>
</v-col>
</v-row>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text @click="dialogConfirmation.show = false">
{{ $t('App.TopCornerMenu.Cancel') }}
</v-btn>
<v-btn text color="error" @click="executeDialog">
{{ dialogConfirmation.actionButtonText }}
</v-btn>
</v-card-actions>
</panel>
</v-dialog>
<confirmation-dialog
:show="dialogConfirmation.show"
:title="dialogConfirmation.title"
:text="dialogConfirmation.description"
:action-button-text="dialogConfirmation.actionButtonText"
:cancel-button-text="$t('App.TopCornerMenu.Cancel')"
@action="executeDialog"
@close="dialogConfirmation.show = false" />
</div>
</template>

Expand All @@ -171,17 +117,10 @@ import { Mixins } from 'vue-property-decorator'
import BaseMixin from '@/components/mixins/base'
import { ServerPowerStateDevice } from '@/store/server/power/types'
import Panel from '@/components/ui/Panel.vue'
import {
mdiAlert,
mdiCloseThick,
mdiPowerStandby,
mdiRestart,
mdiPlay,
mdiPower,
mdiStop,
mdiToggleSwitch,
mdiToggleSwitchOff,
} from '@mdi/js'
import { mdiCloseThick, mdiPowerStandby, mdiRestart, mdiPower, mdiToggleSwitch, mdiToggleSwitchOff } from '@mdi/js'
import TopCornerMenuService from '@/components/ui/TopCornerMenuService.vue'
import ConfirmationDialog from '@/components/dialogs/ConfirmationDialog.vue'
import ServiceMixins from '@/components/mixins/services'

interface dialogPowerDeviceChange {
show: boolean
Expand All @@ -199,16 +138,13 @@ interface dialogConfirmation {
}

@Component({
components: { Panel },
components: { ConfirmationDialog, TopCornerMenuService, Panel },
})
export default class TheTopCornerMenu extends Mixins(BaseMixin) {
mdiAlert = mdiAlert
export default class TheTopCornerMenu extends Mixins(BaseMixin, ServiceMixins) {
mdiCloseThick = mdiCloseThick
mdiPowerStandby = mdiPowerStandby
mdiRestart = mdiRestart
mdiPlay = mdiPlay
mdiPower = mdiPower
mdiStop = mdiStop
mdiToggleSwitch = mdiToggleSwitch
mdiToggleSwitchOff = mdiToggleSwitchOff

Expand All @@ -229,64 +165,67 @@ export default class TheTopCornerMenu extends Mixins(BaseMixin) {
}

get services() {
const services =
let services =
this.$store.state.server.system_info?.available_services?.filter(
(name: string) => name !== 'klipper_mcu'
) ?? []
services.sort()
return services
}

get powerDevices() {
const devices = this.$store.getters['server/power/getDevices'] ?? []

return devices.filter((device: ServerPowerStateDevice) => !device.device.startsWith('_'))
}

get service_states() {
return this.$store.state.server.system_info?.service_state ?? {}
}
if (this.hideOtherInstances && this.klipperInstance !== '') {
services = services.filter(
(name: string) =>
(!name.toLowerCase().startsWith('klipper-') && name.toLowerCase() !== 'klipper') ||
name === this.klipperInstance
)
}

getServiceState(name: string) {
if (name in this.service_states) return this.service_states[name].active_state
if (this.hideOtherInstances && this.moonrakerInstance !== '') {
services = services.filter(
(name: string) =>
(!name.toLowerCase().startsWith('moonraker-') && name.toLowerCase() !== 'moonraker') ||
name === this.moonrakerInstance
)
}

return null
return services.sort()
}

getServiceSubState(name: string) {
if (name in this.service_states) return this.service_states[name].sub_state
get powerDevices() {
const devices = this.$store.getters['server/power/getDevices'] ?? []

return null
return devices.filter((device: ServerPowerStateDevice) => !device.device.startsWith('_'))
}

checkDialog(executableFunction: any, serviceName: string, action: string) {
if (this.printerIsPrinting) {
this.dialogConfirmation.executableFunction = executableFunction
this.dialogConfirmation.serviceName = serviceName
if (!this.printerIsPrinting) {
executableFunction(serviceName)
return
}

const actionUppercase = action.trim().charAt(0).toUpperCase() + action.trim().slice(1)
let titleKey = 'App.TopCornerMenu.ConfirmationDialog.Title.Service' + actionUppercase
let descriptionKey = 'App.TopCornerMenu.ConfirmationDialog.Description.Service' + actionUppercase
let buttonKey = 'App.TopCornerMenu.' + actionUppercase
this.dialogConfirmation.executableFunction = executableFunction
this.dialogConfirmation.serviceName = serviceName

if (serviceName === 'klipper' && ['stop', 'restart', 'firmwareRestart'].includes(action)) {
titleKey =
'App.TopCornerMenu.ConfirmationDialog.Title.' +
(action !== 'stop' ? 'Klipper' : 'Service') +
actionUppercase
descriptionKey = 'App.TopCornerMenu.ConfirmationDialog.Description.Klipper' + actionUppercase
const actionUppercase = action.trim().charAt(0).toUpperCase() + action.trim().slice(1)
let titleKey = 'App.TopCornerMenu.ConfirmationDialog.Title.Service' + actionUppercase
let descriptionKey = 'App.TopCornerMenu.ConfirmationDialog.Description.Service' + actionUppercase
let buttonKey = 'App.TopCornerMenu.' + actionUppercase

if (action === 'firmwareRestart') buttonKey = 'App.TopCornerMenu.KlipperFirmwareRestart'
} else if (serviceName === 'host') {
titleKey = 'App.TopCornerMenu.ConfirmationDialog.Title.Host' + actionUppercase
descriptionKey = 'App.TopCornerMenu.ConfirmationDialog.Description.Host' + actionUppercase
}
if (serviceName === 'klipper' && ['stop', 'restart', 'firmwareRestart'].includes(action)) {
titleKey =
'App.TopCornerMenu.ConfirmationDialog.Title.' +
(action !== 'stop' ? 'Klipper' : 'Service') +
actionUppercase
descriptionKey = 'App.TopCornerMenu.ConfirmationDialog.Description.Klipper' + actionUppercase

if (action === 'firmwareRestart') buttonKey = 'App.TopCornerMenu.KlipperFirmwareRestart'
} else if (serviceName === 'host') {
titleKey = 'App.TopCornerMenu.ConfirmationDialog.Title.Host' + actionUppercase
descriptionKey = 'App.TopCornerMenu.ConfirmationDialog.Description.Host' + actionUppercase
}

this.dialogConfirmation.title = this.$t(titleKey).toString()
this.dialogConfirmation.description = this.$t(descriptionKey).toString()
this.dialogConfirmation.actionButtonText = this.$t(buttonKey).toString()
this.dialogConfirmation.show = true
} else executableFunction(serviceName)
this.dialogConfirmation.title = this.$t(titleKey).toString()
this.dialogConfirmation.description = this.$t(descriptionKey).toString()
this.dialogConfirmation.actionButtonText = this.$t(buttonKey).toString()
this.dialogConfirmation.show = true
}

executeDialog() {
Expand All @@ -306,21 +245,6 @@ export default class TheTopCornerMenu extends Mixins(BaseMixin) {
this.$socket.emit('printer.gcode.script', { script: 'FIRMWARE_RESTART' })
}

serviceStart(service: string) {
this.showMenu = false
this.$socket.emit('machine.services.start', { service: service })
}

serviceRestart(service: string) {
this.showMenu = false
this.$socket.emit('machine.services.restart', { service: service })
}

serviceStop(service: string) {
this.showMenu = false
this.$socket.emit('machine.services.stop', { service: service })
}

changeSwitch(device: ServerPowerStateDevice, value: string) {
this.dialogPowerDeviceChange.device = device.device
this.dialogPowerDeviceChange.value = value
Expand Down
52 changes: 52 additions & 0 deletions src/components/dialogs/ConfirmationDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<template>
<v-dialog :value="show" width="400" :fullscreen="isMobile">
<panel card-class="confirm-top-corner-menu-dialog" :icon="mdiAlert" :title="title" :margin-bottom="false">
<template #buttons>
<v-btn icon tile @click="close">
<v-icon>{{ mdiCloseThick }}</v-icon>
</v-btn>
</template>
<v-card-text class="pt-3">
<v-row>
<v-col>
<p class="body-2">{{ text }}</p>
</v-col>
</v-row>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn text @click="close">{{ cancelButtonText }}</v-btn>
<v-btn text color="error" @click="action">{{ actionButtonText }}</v-btn>
</v-card-actions>
</panel>
</v-dialog>
</template>
<script lang="ts">
import Component from 'vue-class-component'
import Panel from '@/components/ui/Panel.vue'
import { Mixins, Prop } from 'vue-property-decorator'
import BaseMixin from '@/components/mixins/base'
import { mdiAlert, mdiCloseThick } from '@mdi/js'

@Component({
components: { Panel },
})
export default class ConfirmationDialog extends Mixins(BaseMixin) {
mdiAlert = mdiAlert
mdiCloseThick = mdiCloseThick

@Prop({ type: Boolean, required: true }) show!: boolean
@Prop({ type: String, required: true }) title!: string
@Prop({ type: String, required: true }) text!: string
@Prop({ type: String, required: true }) actionButtonText!: string
@Prop({ type: String, required: true }) cancelButtonText!: string

action() {
this.$emit('action')
}

close() {
this.$emit('close')
}
}
</script>
Loading
Loading