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 a select all checkbox on the moonraker backup and restore dialogs #1448

Merged
merged 13 commits into from
Aug 9, 2023
Merged
66 changes: 66 additions & 0 deletions src/components/inputs/CheckboxList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<template>
<v-col class="pl-6">
<template v-if="selectAll">
<v-checkbox
v-model="selectAllModel"
:label="$t('Settings.GeneralTab.Everything')"
hide-details
class="mt-0"
:indeterminate="selectAllIndeterminate"
@change="$emit('update:selectedCheckboxes', selectedCheckboxes)"></v-checkbox>
<v-divider class="my-2" />
</template>
<template v-for="option in options">
<v-checkbox
:key="option.value"
v-model="selectedCheckboxes"
:label="option.label"
hide-details
class="mt-0"
:value="option.value"
@change="$emit('update:selectedCheckboxes', selectedCheckboxes)"></v-checkbox>
</template>
</v-col>
</template>

<script lang="ts">
import { Component, Mixins, Prop } from 'vue-property-decorator'
import BaseMixin from '../mixins/base'
import { computed } from 'vue'
import { TranslateResult } from 'vue-i18n'

@Component
export default class CheckboxList extends Mixins(BaseMixin) {
@Prop({ required: true })
declare readonly options: { label: string | TranslateResult; value: string }[]

@Prop({ type: Boolean, required: false, default: false })
declare readonly selectAll: boolean

selectedCheckboxes: (string | TranslateResult)[] = []
selectAllIndeterminate: boolean = false
selectAllModel = computed<boolean>({
get: this.getSelectAll,
set: this.setSelectAll,
})

getSelectAll(): boolean {
this.selectAllIndeterminate = false
if (0 < this.selectedCheckboxes.length && this.selectedCheckboxes.length < this.options.length) {
this.selectAllIndeterminate = true
return false
}

return this.selectedCheckboxes.length == this.options.length
}

setSelectAll(state: boolean) {
if (state) {
this.selectedCheckboxes = this.options.map((o) => o.value)
return
}

this.selectedCheckboxes = []
}
}
</script>
152 changes: 152 additions & 0 deletions src/components/mixins/settingsGeneralDatabase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import Component from 'vue-class-component'
import BaseMixin from './base'
import { TranslateResult } from 'vue-i18n'

@Component
export default class SettingsGeneralDatabase extends BaseMixin {
get availableKeys() {
return [
// gui namespace
{
value: 'general',
label: this.$t('Settings.GeneralTab.General'),
},
{
value: 'control',
label: this.$t('Settings.ControlTab.Control'),
},
{
value: 'dashboard',
label: this.$t('Settings.DashboardTab.Dashboard'),
},
{
value: 'editor',
label: this.$t('Settings.EditorTab.Editor'),
},
{
value: 'gcodeViewer',
label: this.$t('Settings.GCodeViewerTab.GCodeViewer'),
},
{
value: 'navigation',
label: this.$t('Settings.GeneralTab.DBNavigation'),
},
{
value: 'uiSettings',
label: this.$t('Settings.UiSettingsTab.UiSettings'),
},
{
value: 'view',
label: this.$t('Settings.GeneralTab.DbView'),
},

// gui modules
{
value: 'console',
label: this.$t('Settings.ConsoleTab.Console'),
},
{
value: 'gcodehistory',
label: this.$t('Settings.GeneralTab.DbConsoleHistory'),
},
{
value: 'macros',
label: this.$t('Settings.MacrosTab.Macros'),
},
{
value: 'notifications',
label: this.$t('App.Notifications.Notifications'),
},
{
value: 'presets',
label: this.$t('Settings.PresetsTab.PreheatPresets'),
},
{
value: 'remoteprinters',
label: this.$t('Settings.RemotePrintersTab.RemotePrinters'),
},
{
value: 'timelapse',
label: this.$t('Settings.TimelapseTab.Timelapse'),
},
]
}

async loadBackupableNamespaces() {
// init namespaces
let backupableNamespaces: { value: string; label: string | TranslateResult }[] = []

// load DB namespaces from moonraker
const urlRequestDbList = this.$store.getters['socket/getUrl'] + '/server/database/list'
const availableNamespaces = await fetch(urlRequestDbList)
// read json
.then((response) => response?.json())
// extract result namespaces
.then((response) => response?.result?.namespaces ?? [])
.catch(() => {
window.console.error('Cannot load Moonraker DB namespaces')
return []
})

// load mainsail keys, if mainsail namespace exists
if (availableNamespaces.includes('mainsail')) {
const urlRequestMainsailNamespace =
this.$store.getters['socket/getUrl'] + '/server/database/item?namespace=mainsail'
backupableNamespaces = await fetch(urlRequestMainsailNamespace)
// read json
.then((response) => response?.json())
// extract result object
.then((response) => response?.result?.value ?? {})
// extract keys from mainsail gui object
.then((objects) => Object.keys(objects))
// filter initVersion
.then((keys) => keys.filter((key) => key !== 'initVersion'))
// convert to locale
.then((keys) =>
keys.map((key) => {
const namespace = this.availableKeys.find((namespace) => namespace.value === key)
if (namespace) return namespace

// fallback return key name
return { value: key, label: key }
})
)

backupableNamespaces = backupableNamespaces.sort(this.sortNamespaces)
}

// add timelapse if exists
if (availableNamespaces.includes('timelapse')) {
backupableNamespaces.push({
value: 'timelapse',
label: this.$t('Settings.GeneralTab.DbTimelapseSettings'),
})
}

// add webcams if exists
if (availableNamespaces.includes('webcams')) {
backupableNamespaces.push({
value: 'webcams',
label: this.$t('Settings.WebcamsTab.Webcams'),
})
}

return backupableNamespaces
}

sortNamespaces(
a: { value: string; label: string | TranslateResult },
b: { value: string; label: string | TranslateResult }
) {
if (a.value === 'general') return -1
if (b.value === 'general') return 1

const stringA = a.label.toString().toLowerCase()
const stringB = b.label.toString().toLowerCase()

if (stringA < stringB) return -1
if (stringA > stringB) return 1

return 0
}
}
88 changes: 88 additions & 0 deletions src/components/settings/General/GeneralBackup.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<template>
<div>
<v-btn :loading="loadings.includes('backupDbButton')" small @click="openDialog">
{{ $t('Settings.GeneralTab.Backup') }}
</v-btn>
<v-dialog v-model="showDialog" persistent :width="360">
<panel
:title="$t('Settings.GeneralTab.Backup')"
card-class="mainsail-backup-dialog"
:margin-bottom="false"
:icon="mdiHelpCircle">
<template #buttons>
<v-btn icon tile @click="closeDialog">
<v-icon>{{ mdiCloseThick }}</v-icon>
</v-btn>
</template>
<v-card-text>
<v-row>
<v-col>
<p class="mb-0">{{ $t('Settings.GeneralTab.BackupDialog') }}</p>
</v-col>
</v-row>
<v-row>
<checkbox-list
:options="backupableNamespaces"
select-all
@update:selectedCheckboxes="onSelectBackupCheckboxes" />
</v-row>
<v-row>
<v-col class="text-center">
<v-btn color="red" :loading="loadings.includes('backupMainsail')" @click="backupMainsail">
{{ $t('Settings.GeneralTab.Backup') }}
</v-btn>
</v-col>
</v-row>
</v-card-text>
</panel>
</v-dialog>
</div>
</template>

<script lang="ts">
import Component from 'vue-class-component'
import { Mixins } from 'vue-property-decorator'
import BaseMixin from '@/components/mixins/base'
import SettingsRow from '@/components/settings/SettingsRow.vue'
import Panel from '@/components/ui/Panel.vue'
import { mdiCloseThick, mdiHelpCircle } from '@mdi/js'
import CheckboxList from '@/components/inputs/CheckboxList.vue'
import { TranslateResult } from 'vue-i18n'
import SettingsGeneralDatabase from '@/components/mixins/settingsGeneralDatabase'

@Component({
components: { Panel, SettingsRow, CheckboxList },
})
export default class SettingsGeneralTabBackupDatabase extends Mixins(BaseMixin, SettingsGeneralDatabase) {
mdiHelpCircle = mdiHelpCircle
mdiCloseThick = mdiCloseThick

showDialog = false
backupableNamespaces: { value: string; label: string | TranslateResult }[] = []
backupCheckboxes: string[] = []

async mounted() {
this.backupableNamespaces = await this.loadBackupableNamespaces()
}

onSelectBackupCheckboxes(backupCheckboxes: string[]) {
this.backupCheckboxes = backupCheckboxes
}

async backupMainsail() {
await this.$store.dispatch('socket/addLoading', 'backupMainsail')
await this.$store.dispatch('gui/backupMoonrakerDB', this.backupCheckboxes)
await this.$store.dispatch('socket/removeLoading', 'backupMainsail')
this.closeDialog()
}

async openDialog() {
this.backupableNamespaces = await this.loadBackupableNamespaces()
this.showDialog = true
}

closeDialog() {
this.showDialog = false
}
}
</script>
89 changes: 89 additions & 0 deletions src/components/settings/General/GeneralReset.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<template>
<div>
<v-btn color="error" small @click="openDialog">
{{ $t('Settings.GeneralTab.FactoryReset') }}
</v-btn>
<v-dialog v-model="showDialog" persistent :width="360">
<panel
:title="$t('Settings.GeneralTab.FactoryReset')"
card-class="mainsail-reset-dialog"
:margin-bottom="false"
:icon="mdiHelpCircle">
<template #buttons>
<v-btn icon tile @click="closeDialog">
<v-icon>{{ mdiCloseThick }}</v-icon>
</v-btn>
</template>
<v-card-text>
<v-row>
<v-col>
<p class="mb-0">{{ $t('Settings.GeneralTab.FactoryDialog') }}</p>
</v-col>
</v-row>
<v-row>
<checkbox-list
:options="resetableNamespaces"
select-all
@update:selectedCheckboxes="onSelectResetCheckboxes" />
</v-row>
<v-row>
<v-col class="text-center">
<v-btn
color="red"
:loading="loadings.includes('resetMainsail')"
@click="resetMainsailAction">
{{ $t('Settings.GeneralTab.Reset') }}
</v-btn>
</v-col>
</v-row>
</v-card-text>
</panel>
</v-dialog>
</div>
</template>

<script lang="ts">
import Component from 'vue-class-component'
import { Mixins } from 'vue-property-decorator'
import BaseMixin from '@/components/mixins/base'
import SettingsRow from '@/components/settings/SettingsRow.vue'
import Panel from '@/components/ui/Panel.vue'
import { mdiCloseThick, mdiHelpCircle } from '@mdi/js'
import CheckboxList from '@/components/inputs/CheckboxList.vue'
import { TranslateResult } from 'vue-i18n'
import SettingsGeneralDatabase from '@/components/mixins/settingsGeneralDatabase'

@Component({
components: { Panel, SettingsRow, CheckboxList },
})
export default class SettingsGeneralTabResetDatabase extends Mixins(BaseMixin, SettingsGeneralDatabase) {
mdiHelpCircle = mdiHelpCircle
mdiCloseThick = mdiCloseThick

showDialog = false
resetableNamespaces: { value: string; label: string | TranslateResult }[] = []
resetCheckboxes: string[] = []

async mounted() {
this.resetableNamespaces = await this.loadBackupableNamespaces()
}

onSelectResetCheckboxes(resetCheckboxes: string[]) {
this.resetCheckboxes = resetCheckboxes
}

resetMainsailAction() {
this.$store.dispatch('socket/addLoading', 'resetMainsail')
this.$store.dispatch('gui/resetMoonrakerDB', this.resetCheckboxes)
}

async openDialog() {
this.resetableNamespaces = await this.loadBackupableNamespaces()
this.showDialog = true
}

closeDialog() {
this.showDialog = false
}
}
</script>
Loading