feat: customize sidebar navi (#1336)

This commit is contained in:
Stefan Dej 2023-05-06 23:01:06 +02:00 committed by GitHub
parent e6505fe55a
commit a3316eb23b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 590 additions and 170 deletions

View File

@ -80,6 +80,7 @@ import SettingsDashboardTab from '@/components/settings/SettingsDashboardTab.vue
import SettingsGCodeViewerTab from '@/components/settings/SettingsGCodeViewerTab.vue'
import SettingsEditorTab from '@/components/settings/SettingsEditorTab.vue'
import SettingsTimelapseTab from '@/components/settings/SettingsTimelapseTab.vue'
import SettingsNavigationTab from '@/components/settings/SettingsNavigationTab.vue'
import Panel from '@/components/ui/Panel.vue'
import {
@ -98,6 +99,7 @@ import {
mdiVideo3d,
mdiWebcam,
mdiDipSwitch,
mdiMenu,
} from '@mdi/js'
import SettingsMiscellaneousTab from '@/components/settings/SettingsMiscellaneousTab.vue'
@Component({
@ -116,6 +118,7 @@ import SettingsMiscellaneousTab from '@/components/settings/SettingsMiscellaneou
SettingsEditorTab,
SettingsTimelapseTab,
SettingsMiscellaneousTab,
SettingsNavigationTab,
},
})
export default class TheSettingsMenu extends Mixins(BaseMixin) {
@ -194,6 +197,11 @@ export default class TheSettingsMenu extends Mixins(BaseMixin) {
name: 'miscellaneous',
title: this.$t('Settings.MiscellaneousTab.Miscellaneous'),
},
{
icon: mdiMenu,
name: 'navigation',
title: this.$t('Settings.NavigationTab.Navigation'),
},
]
if (this.moonrakerComponents.includes('timelapse')) {

View File

@ -1,46 +1,3 @@
<style>
.nav-logo {
height: 32px;
}
.small-list-item {
height: var(--sidebar-menu-item-height);
}
.no-text-decoration {
text-decoration: none;
background-color: transparent;
}
.no-background:before {
background-color: rgba(255, 255, 255, 0) !important;
}
.no-border {
border: 0 !important;
}
</style>
<style scoped>
.active-nav-item {
border-right: 4px solid var(--v-primary-base);
}
.menu-item-icon {
opacity: 0.85;
}
.menu-item-title {
line-height: 30px;
font-size: 14px;
font-weight: 600;
text-transform: uppercase;
opacity: 0.85;
}
.nav-scrollbar {
height: 100%;
}
</style>
<template>
<v-navigation-drawer
:key="navigationStyle"
@ -59,78 +16,27 @@
v-if="isMobile"
router
to="/"
:class="
'sidebar-logo no-text-decoration no-background no-border ' +
(navigationStyle === 'iconsOnly' ? 'pa-0 justify-center' : '')
"
:class="mobileLogoClass"
:style="'height: ' + topbarHeight + 'px'"
:ripple="false">
<template v-if="sidebarLogo">
<img :src="sidebarLogo" :style="logoCssVars" class="nav-logo" alt="Logo" />
</template>
<template v-else>
<mainsail-logo
:color="logoColor"
:style="logoCssVars"
class="nav-logo"
:ripple="false"></mainsail-logo>
<mainsail-logo :color="logoColor" :style="logoCssVars" class="nav-logo" :ripple="false" />
</template>
<template v-if="navigationStyle !== 'iconsOnly'">
<span class="text-h6 font-weight-regular text-truncate">{{ printerName }}</span>
</template>
</v-list-item>
<template v-if="countPrinters">
<v-tooltip right :open-delay="500" :disabled="navigationStyle !== 'iconsOnly'">
<template #activator="{ on, attrs }">
<v-list-item
router
to="/allPrinters"
class="small-list-item mt-1"
v-bind="attrs"
v-on="on">
<v-list-item-icon class="my-3 mr-3 menu-item-icon">
<v-icon>{{ mdiViewDashboardOutline }}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title tile class="menu-item-title">
{{ $t('App.Printers') }}
</v-list-item-title>
</v-list-item-content>
</v-list-item>
</template>
<span>{{ $t('App.Printers') }}</span>
</v-tooltip>
<v-divider class="my-1"></v-divider>
</template>
<div v-for="(category, index) in naviPoints" :key="index">
<v-tooltip right :open-delay="500" :disabled="navigationStyle !== 'iconsOnly'">
<template #activator="{ on, attrs }">
<v-list-item
router
:to="category.path"
class="small-list-item"
v-bind="attrs"
v-on="on">
<v-list-item-icon class="my-3 mr-3 menu-item-icon">
<v-icon>{{ category.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title tile class="menu-item-title">
{{ $t(`Router.${category.title}`) }}
</v-list-item-title>
</v-list-item-content>
</v-list-item>
</template>
<span>{{ $t(`Router.${category.title}`) }}</span>
</v-tooltip>
</div>
<sidebar-item v-for="(category, index) in visibleNaviPoints" :key="index" :item="category" />
</v-list-item-group>
</v-list>
</overlay-scrollbars>
<template #append>
<v-list-item class="small-list-item mb-2">
<v-list-item-icon class="menu-item-icon">
<about-dialog></about-dialog>
<about-dialog />
</v-list-item-icon>
</v-list-item>
</template>
@ -139,32 +45,27 @@
<script lang="ts">
import Component from 'vue-class-component'
import routes, { AppRoute } from '@/routes'
import { Mixins } from 'vue-property-decorator'
import BaseMixin from '@/components/mixins/base'
import { PrinterStateKlipperConfig } from '@/store/printer/types'
import TheSelectPrinterDialog from '@/components/TheSelectPrinterDialog.vue'
import AboutDialog from '@/components/dialogs/AboutDialog.vue'
import { navigationWidth, topbarHeight } from '@/store/variables'
import MainsailLogo from '@/components/ui/MainsailLogo.vue'
import { mdiViewDashboardOutline } from '@mdi/js'
import SidebarItem from '@/components/ui/SidebarItem.vue'
import NavigationMixin from '@/components/mixins/navigation'
@Component({
components: {
SidebarItem,
TheSelectPrinterDialog,
AboutDialog,
MainsailLogo,
},
})
export default class TheSidebar extends Mixins(BaseMixin) {
export default class TheSidebar extends Mixins(NavigationMixin, BaseMixin) {
navigationWidth = navigationWidth
topbarHeight = topbarHeight
/**
* Icons
*/
mdiViewDashboardOutline = mdiViewDashboardOutline
get naviDrawer(): boolean {
return this.$store.state.naviDrawer
}
@ -181,45 +82,12 @@ export default class TheSidebar extends Mixins(BaseMixin) {
return this.$store.getters['files/getSidebarBackground']
}
get naviPoints(): AppRoute[] {
return routes.filter((element) => {
return element.showInNavi && this.showInNavi(element)
})
}
get klippy_state(): string {
return this.$store.state.server.klippy_state
}
get boolNaviWebcam(): boolean {
return this.$store.state.gui.uiSettings.boolWebcamNavi
}
get moonrakerComponents(): string[] {
return this.$store.state.server.components
}
get registeredDirectories(): string[] {
return this.$store.state.server.registered_directories
}
get klipperConfigfileSettings(): PrinterStateKlipperConfig[] {
return this.$store.state.printer.configfile?.settings ?? {}
}
get currentPage(): string {
return this.$route.fullPath
}
get countPrinters() {
return this.$store.getters['farm/countPrinters']
}
get boolNaviTemp(): boolean {
if (!this.isMobile && this.$vuetify.breakpoint.mdAndDown) {
return true
}
return false
return !this.isMobile && this.$vuetify.breakpoint.mdAndDown
}
get sidebarCssVars(): any {
@ -255,16 +123,15 @@ export default class TheSidebar extends Mixins(BaseMixin) {
return {}
}
showInNavi(route: AppRoute): boolean {
if (['shutdown', 'error', 'disconnected'].includes(this.klippy_state) && !route.alwaysShow) return false
else if (route.title === 'Webcam' && !this.boolNaviWebcam) return false
else if (route.moonrakerComponent && !this.moonrakerComponents.includes(route.moonrakerComponent)) return false
else if (route.registeredDirectory && !this.registeredDirectories.includes(route.registeredDirectory))
return false
else if (route.klipperComponent && !(route.klipperComponent in this.klipperConfigfileSettings)) return false
else if (route.klipperIsConnected && !this.klippyIsConnected) return false
get mobileLogoClass() {
const output = ['sidebar-logo', 'no-text-decoration', 'no-background', 'no-border']
return true
if (this.navigationStyle === 'iconsOnly') {
output.push('pa-0')
output.push('justify-center')
}
return output
}
mounted() {
@ -272,3 +139,30 @@ export default class TheSidebar extends Mixins(BaseMixin) {
}
}
</script>
<style scoped>
.no-text-decoration {
text-decoration: none;
background-color: transparent;
}
.no-background:before {
background-color: rgba(255, 255, 255, 0) !important;
}
.no-border {
border: 0 !important;
}
.nav-logo {
height: 32px;
}
.menu-item-icon {
opacity: 0.85;
}
.nav-scrollbar {
height: 100%;
}
</style>

View File

@ -0,0 +1,172 @@
import Component from 'vue-class-component'
import routes, { AppRoute } from '@/routes'
import { Mixins, Watch } from 'vue-property-decorator'
import { mdiLinkVariant, mdiViewDashboardOutline } from '@mdi/js'
import BaseMixin from '@/components/mixins/base'
import { PrinterStateKlipperConfig } from '@/store/printer/types'
import { GuiNavigationStateEntry } from '@/store/gui/navigation/types'
export interface NaviPoint {
type: 'link' | 'route'
title: string
to?: string
href?: string
target?: string
icon: string
position: number
visible: boolean
}
@Component
export default class NavigationMixin extends Mixins(BaseMixin) {
private customNaviLinks: NaviPoint[] = []
get countPrinters() {
return this.$store.getters['farm/countPrinters']
}
get routesNaviPoints(): NaviPoint[] {
const points: NaviPoint[] = []
if (this.countPrinters) {
points.push({
title: this.$t('App.Printers'),
icon: mdiViewDashboardOutline,
to: '/allPrinters',
position: 0,
} as NaviPoint)
}
routes
.filter((element) => {
return element.showInNavi && this.showInNavi(element)
})
.forEach((element) => {
const [position, visible] = this.getUiSettings({
type: 'route',
title: element.title ?? 'unknown',
visible: true,
position: element.position ?? 999,
})
points.push({
type: 'route',
title: this.$t(`Router.${element.title}`),
icon: element.icon,
to: element.path,
position,
visible,
} as NaviPoint)
})
if (this.customNaviLinks.length) {
this.customNaviLinks.forEach((element) => {
const [position, visible] = this.getUiSettings({
type: 'link',
title: element.title ?? 'unknown',
visible: element.visible ?? true,
position: element.position ?? 999,
})
points.push({
type: 'link',
title: element.title,
icon: element.icon,
href: element.href,
target: element.target,
position,
visible,
})
})
}
return points
}
get naviPoints(): NaviPoint[] {
return this.routesNaviPoints.sort((a, b) => a.position - b.position)
}
get visibleNaviPoints(): NaviPoint[] {
return this.naviPoints.filter((entry) => entry.visible)
}
get uiSettings(): GuiNavigationStateEntry[] {
return this.$store.state.gui.navigation.entries
}
get klippy_state(): string {
return this.$store.state.server.klippy_state
}
get boolNaviWebcam(): boolean {
return this.$store.state.gui.uiSettings.boolWebcamNavi
}
get moonrakerComponents(): string[] {
return this.$store.state.server.components
}
get registeredDirectories(): string[] {
return this.$store.state.server.registered_directories
}
get klipperConfigfileSettings(): PrinterStateKlipperConfig[] {
return this.$store.state.printer.configfile?.settings ?? {}
}
get sidebarNaviFile(): string {
return this.$store.getters['files/getCustomNaviPoints']
}
get webcamCount(): number {
return this.$store.getters['gui/webcams/getWebcams'].length
}
@Watch('sidebarNaviFile', { immediate: true })
async sidebarNaviFileChanged(newVal: string) {
this.customNaviLinks = []
// stop if no file is set
if (!newVal) return
const content = await fetch(newVal)
.then((res) => res.json())
.catch((err) => {
window.console.error('Unable to parse .theme/navi.json.')
throw err
})
content.forEach((item: NaviPoint) => {
this.customNaviLinks.push({
title: item.title ?? 'Unknown',
icon: item.icon ?? mdiLinkVariant,
href: item.href ?? '#',
target: item.target ?? undefined,
position: item.position ?? 999,
} as NaviPoint)
})
}
showInNavi(route: AppRoute): boolean {
if (['shutdown', 'error', 'disconnected'].includes(this.klippy_state) && !route.alwaysShow) return false
else if (route.title === 'Webcam' && this.webcamCount === 0) return false
else if (route.moonrakerComponent && !this.moonrakerComponents.includes(route.moonrakerComponent)) return false
else if (route.registeredDirectory && !this.registeredDirectories.includes(route.registeredDirectory))
return false
else if (route.klipperComponent && !(route.klipperComponent in this.klipperConfigfileSettings)) return false
else if (route.klipperIsConnected && !this.klippyIsConnected) return false
return true
}
getUiSettings(entry: GuiNavigationStateEntry): [number, boolean] {
const index = this.uiSettings.findIndex((point) => {
return point.title === entry.title && point.type === entry.type
})
if (index === -1) return [entry.position, entry.visible]
return [this.uiSettings[index].position, this.uiSettings[index].visible]
}
}

View File

@ -0,0 +1,53 @@
<template>
<div>
<v-card-text>
<h3 class="text-h5 mb-3">{{ $t('Settings.NavigationTab.Navigation') }}</h3>
<draggable v-model="sortableNaviPoints" handle=".handle" ghost-class="ghost" group="navigation-points">
<settings-navigation-tab-item
v-for="(naviPoint, index) in sortableNaviPoints"
:key="index"
class="dragable-item my-2 mx-0"
:navi-point="naviPoint" />
</draggable>
</v-card-text>
</div>
</template>
<script lang="ts">
import { Component, Mixins } from 'vue-property-decorator'
import BaseMixin from '@/components/mixins/base'
import NavigationMixin, { NaviPoint } from '@/components/mixins/navigation'
import SettingsRow from '@/components/settings/SettingsRow.vue'
import draggable from 'vuedraggable'
import SettingsNavigationTabItem from '@/components/settings/SettingsNavigationTabItem.vue'
@Component({
components: { SettingsNavigationTabItem, SettingsRow, draggable },
})
export default class SettingsNavigationTab extends Mixins(NavigationMixin, BaseMixin) {
get sortableNaviPoints() {
return this.naviPoints.filter((naviPoint) => naviPoint.position > 0)
}
set sortableNaviPoints(newVal: NaviPoint[]) {
// update store with new positions
newVal.forEach((naviPoint, index) => {
this.$store.dispatch('gui/navigation/updatePos', {
type: naviPoint.type,
title: naviPoint.title,
visible: naviPoint.visible,
position: index + 1,
})
})
// upload to moonraker db
this.$store.dispatch('gui/navigation/upload')
}
}
</script>
<style scoped>
.dragable-item {
background-color: #282828;
}
</style>

View File

@ -0,0 +1,62 @@
<template>
<v-row class="dragable-item my-2 mx-0">
<v-col class="col-auto pr-0 d-flex py-2">
<v-icon class="handle">{{ mdiDragVertical }}</v-icon>
</v-col>
<v-col class="py-2">
<settings-row :title="title" :sub-title="subtitle" :dynamic-slot-width="true">
<v-icon :color="checkboxColor" v-html="checkboxIcon" @click="changeVisibility" />
</settings-row>
</v-col>
</v-row>
</template>
<script lang="ts">
import { Component, Mixins, Prop } from 'vue-property-decorator'
import BaseMixin from '@/components/mixins/base'
import NavigationMixin, { NaviPoint } from '@/components/mixins/navigation'
import SettingsRow from '@/components/settings/SettingsRow.vue'
import draggable from 'vuedraggable'
import { mdiDragVertical, mdiCheckboxMarked, mdiCheckboxBlankOutline } from '@mdi/js'
@Component({
components: { SettingsRow, draggable },
})
export default class SettingsNavigationTab extends Mixins(NavigationMixin, BaseMixin) {
mdiDragVertical = mdiDragVertical
@Prop({ type: Object, required: true }) naviPoint!: NaviPoint
get title() {
return this.naviPoint.title
}
get subtitle() {
if (this.naviPoint.type === 'link') return `URL: ${this.naviPoint.href ?? 'Unknown'}`
return undefined
}
get checkboxColor() {
if (this.naviPoint.visible) return 'primary'
return 'grey lighten-1'
}
get checkboxIcon() {
if (this.naviPoint.visible) return mdiCheckboxMarked
return mdiCheckboxBlankOutline
}
changeVisibility() {
this.$store.dispatch('gui/navigation/changeVisibility', this.naviPoint)
}
}
</script>
<style scoped>
.dragable-item {
background-color: #282828;
}
</style>

View File

@ -65,10 +65,6 @@
<v-switch v-model="boolBigThumbnail" hide-details class="mt-0"></v-switch>
</settings-row>
<v-divider class="my-2"></v-divider>
<settings-row :title="$t('Settings.UiSettingsTab.ShowWebcamInNavigation').toString()">
<v-switch v-model="boolWebcamInNavigation" hide-details class="mt-0"></v-switch>
</settings-row>
<v-divider class="my-2"></v-divider>
<settings-row
:title="$t('Settings.UiSettingsTab.DisplayCANCEL_PRINT').toString()"
:sub-title="$t('Settings.UiSettingsTab.DisplayCANCEL_PRINTDescription').toString()"
@ -215,14 +211,6 @@ export default class SettingsUiSettingsTab extends Mixins(BaseMixin) {
this.$store.dispatch('gui/saveSetting', { name: 'uiSettings.boolBigThumbnail', value: newVal })
}
get boolWebcamInNavigation() {
return this.$store.state.gui.uiSettings.boolWebcamNavi ?? false
}
set boolWebcamInNavigation(newVal) {
this.$store.dispatch('gui/saveSetting', { name: 'uiSettings.boolWebcamNavi', value: newVal })
}
get displayCancelPrint() {
return this.$store.state.gui.uiSettings.displayCancelPrint
}

View File

@ -0,0 +1,89 @@
<template>
<div>
<v-tooltip right :open-delay="500" :disabled="navigationStyle !== 'iconsOnly'">
<template #activator="{ on, attrs }">
<v-list-item
router
:to="to"
:href="href"
:target="target"
class="small-list-item"
v-bind="attrs"
v-on="on">
<v-list-item-icon class="my-3 mr-3 menu-item-icon">
<v-icon>{{ icon }}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title tile class="menu-item-title">
{{ title }}
</v-list-item-title>
</v-list-item-content>
</v-list-item>
</template>
<span>{{ title }}</span>
</v-tooltip>
<v-divider v-if="borderBottom" class="my-1" />
</div>
</template>
<script lang="ts">
import Component from 'vue-class-component'
import { Mixins, Prop } from 'vue-property-decorator'
import BaseMixin from '@/components/mixins/base'
import { NaviPoint } from '@/components/mixins/navigation'
@Component
export default class SidebarItem extends Mixins(BaseMixin) {
@Prop({ type: Object, required: true }) item!: NaviPoint
get navigationStyle() {
return this.$store.state.gui.uiSettings.navigationStyle
}
get icon() {
return this.item.icon
}
get title() {
return this.item.title
}
get to() {
return this.item.to ?? undefined
}
get href() {
return this.item.href ?? undefined
}
get target() {
return this.item.target ?? undefined
}
get borderBottom() {
return this.item.to === '/allPrinters'
}
}
</script>
<style scoped>
.small-list-item {
height: var(--sidebar-menu-item-height);
}
.active-nav-item {
border-right: 4px solid var(--v-primary-base);
}
.menu-item-icon {
opacity: 0.85;
}
.menu-item-title {
line-height: 30px;
font-size: 14px;
font-weight: 600;
text-transform: uppercase;
opacity: 0.85;
}
</style>

View File

@ -911,6 +911,9 @@
"UnableToLoadLight": "Unable to load light",
"UnableToLoadPreset": "Unable to load preset"
},
"NavigationTab": {
"Navigation": "Navigation"
},
"PresetsTab": {
"AddPreset": "add preset",
"Cooldown": "Cooldown",
@ -1036,7 +1039,6 @@
"PowerDeviceName": "Printer power device",
"PowerDeviceNameDescription": "Select which Moonraker power device should be used to power on the printer.",
"Primary": "Primary",
"ShowWebcamInNavigation": "Show Webcam in navigation",
"UiSettings": "UI-Settings"
},
"Update": "update",

View File

@ -28,6 +28,7 @@ const routes: AppRoute[] = [
component: Dashboard,
alwaysShow: true,
showInNavi: true,
position: 10,
},
{
title: 'Printers',
@ -43,6 +44,7 @@ const routes: AppRoute[] = [
component: Webcam,
alwaysShow: true,
showInNavi: true,
position: 20,
},
{
title: 'Console',
@ -52,6 +54,7 @@ const routes: AppRoute[] = [
alwaysShow: true,
showInNavi: true,
klipperIsConnected: true,
position: 30,
},
{
title: 'Heightmap',
@ -61,6 +64,7 @@ const routes: AppRoute[] = [
alwaysShow: false,
showInNavi: true,
klipperComponent: 'bed_mesh',
position: 40,
},
{
title: 'G-Code Files',
@ -70,6 +74,7 @@ const routes: AppRoute[] = [
alwaysShow: true,
showInNavi: true,
registeredDirectory: 'gcodes',
position: 50,
},
{
title: 'G-Code Viewer',
@ -78,6 +83,7 @@ const routes: AppRoute[] = [
component: () => import('../pages/Viewer.vue'),
alwaysShow: true,
showInNavi: true,
position: 60,
},
{
title: 'History',
@ -87,6 +93,7 @@ const routes: AppRoute[] = [
alwaysShow: true,
showInNavi: true,
moonrakerComponent: 'history',
position: 70,
},
{
title: 'Timelapse',
@ -96,6 +103,7 @@ const routes: AppRoute[] = [
alwaysShow: true,
showInNavi: true,
moonrakerComponent: 'timelapse',
position: 80,
},
{
title: 'Machine',
@ -104,6 +112,7 @@ const routes: AppRoute[] = [
component: Machine,
alwaysShow: true,
showInNavi: true,
position: 90,
},
{
title: null,
@ -130,4 +139,5 @@ export interface AppRoute {
klipperComponent?: string
klipperIsConnected?: boolean
children?: AppRoute[]
position?: number
}

View File

@ -199,11 +199,14 @@ export const getters: GetterTree<FileState, any> = {
const file = directory?.childrens?.find(
(element: FileStateFile) =>
element.filename !== undefined &&
element.filename.substr(0, element.filename.lastIndexOf('.')) === acceptName &&
acceptExtensions.includes(element.filename.substr(element.filename.lastIndexOf('.') + 1))
element.filename?.slice(0, element.filename?.lastIndexOf('.')) === acceptName &&
acceptExtensions.includes(element.filename?.slice(element.filename?.lastIndexOf('.') + 1))
)
return file ? rootGetters['socket/getUrl'] + '/server/files/config/' + themeDir + '/' + file.filename : null
if (!file) return null
return `${rootGetters['socket/getUrl']}/server/files/config/${themeDir}/${
file.filename
}?timestamp=${file.modified.getTime()}`
},
getSidebarLogo: (state, getters) => {
@ -234,6 +237,13 @@ export const getters: GetterTree<FileState, any> = {
return getters['getThemeFileUrl'](acceptName, acceptExtensions) ?? null
},
getCustomNaviPoints: (state, getters) => {
const acceptName = 'navi'
const acceptExtensions = ['json']
return getters['getThemeFileUrl'](acceptName, acceptExtensions) ?? null
},
getCustomFavicons: (state, getters) => {
const acceptName16 = 'favicon-32x32'
const acceptName32 = 'favicon-32x32'

View File

@ -10,10 +10,11 @@ import { console } from '@/store/gui/console'
import { gcodehistory } from '@/store/gui/gcodehistory'
import { macros } from '@/store/gui/macros'
import { miscellaneous } from '@/store/gui/miscellaneous'
import { navigation } from '@/store/gui/navigation'
import { notifications } from '@/store/gui/notifications'
import { presets } from '@/store/gui/presets'
import { remoteprinters } from '@/store/gui/remoteprinters'
import { webcams } from '@/store/gui/webcams'
import { notifications } from '@/store/gui/notifications'
export const getDefaultState = (): GuiState => {
return {
@ -141,6 +142,9 @@ export const getDefaultState = (): GuiState => {
showGCodePanel: false,
cncMode: false,
},
navigation: {
entries: [],
},
uiSettings: {
logo: defaultLogoColor,
primary: defaultPrimaryColor,
@ -152,7 +156,6 @@ export const getDefaultState = (): GuiState => {
boolBigThumbnail: true,
boolWideNavDrawer: false,
boolHideUploadAndPrintButton: false,
boolWebcamNavi: false,
navigationStyle: 'iconsAndText',
powerDeviceName: null,
hideSaveConfigForBedMash: false,
@ -263,6 +266,7 @@ export const gui: Module<GuiState, any> = {
gcodehistory,
macros,
miscellaneous,
navigation,
notifications,
presets,
remoteprinters,

View File

@ -0,0 +1,27 @@
import { ActionTree } from 'vuex'
import { RootState } from '@/store/types'
import Vue from 'vue'
import { GuiNavigationState, GuiNavigationStateEntry } from '@/store/gui/navigation/types'
export const actions: ActionTree<GuiNavigationState, RootState> = {
reset({ commit }) {
commit('reset')
},
upload({ state }) {
Vue.$socket.emit('server.database.post_item', {
namespace: 'mainsail',
key: 'navigation.entries',
value: state.entries,
})
},
updatePos({ commit }, payload: GuiNavigationStateEntry) {
commit('updatePos', payload)
},
changeVisibility({ commit, dispatch }, payload: GuiNavigationStateEntry) {
commit('changeVisibility', payload)
dispatch('upload')
},
}

View File

@ -0,0 +1,5 @@
import { GetterTree } from 'vuex'
import { GuiNavigationState } from './types'
// eslint-disable-next-line
export const getters: GetterTree<GuiNavigationState, any> = {}

View File

@ -0,0 +1,23 @@
import { Module } from 'vuex'
import { actions } from '@/store/gui/navigation/actions'
import { mutations } from '@/store/gui/navigation/mutations'
import { getters } from '@/store/gui/navigation/getters'
import { GuiNavigationState } from '@/store/gui/navigation/types'
export const getDefaultState = (): GuiNavigationState => {
return {
entries: [],
}
}
// initial state
const state = getDefaultState()
// eslint-disable-next-line
export const navigation: Module<GuiNavigationState, any> = {
namespaced: true,
state,
getters,
actions,
mutations,
}

View File

@ -0,0 +1,62 @@
import { getDefaultState } from './index'
import { MutationTree } from 'vuex'
import { GuiNavigationState, GuiNavigationStateEntry } from './types'
import Vue from 'vue'
export const mutations: MutationTree<GuiNavigationState> = {
reset(state) {
Object.assign(state, getDefaultState())
},
updatePos(state, payload: GuiNavigationStateEntry) {
const index = state.entries.findIndex((entry) => {
return entry.type === payload.type && entry.title === payload.title
})
// update existing entry
if (index !== -1) {
state.entries[index].position = payload.position
return
}
// create new entry
const newEntry: GuiNavigationStateEntry = {
type: payload.type,
title: payload.title,
visible: payload.visible,
position: payload.position,
}
// copy old array
const entries = [...state.entries]
// add new entry
entries.push(newEntry)
// set new array
Vue.set(state, 'entries', entries)
},
changeVisibility(state, payload: GuiNavigationStateEntry) {
const index = state.entries.findIndex((entry) => {
return entry.type === payload.type && entry.title === payload.title
})
// update existing entry
if (index !== -1) {
state.entries[index].visible = !payload.visible
return
}
// create new entry
const newEntry: GuiNavigationStateEntry = {
type: payload.type,
title: payload.title,
visible: !payload.visible,
position: payload.position,
}
// copy old array
const entries = [...state.entries]
// add new entry
entries.push(newEntry)
// set new array
Vue.set(state, 'entries', entries)
},
}

View File

@ -0,0 +1,10 @@
export interface GuiNavigationState {
entries: GuiNavigationStateEntry[]
}
export interface GuiNavigationStateEntry {
type: 'route' | 'link'
title: string
visible: boolean
position: number
}

View File

@ -5,6 +5,7 @@ import { GuiRemoteprintersState } from '@/store/gui/remoteprinters/types'
import { ServerHistoryStateJob } from '@/store/server/history/types'
import { GuiNotificationState } from '@/store/gui/notifications/types'
import { FileStateFile, FileStateGcodefile } from '@/store/files/types'
import { GuiNavigationState } from '@/store/gui/navigation/types'
export interface GuiState {
general: {
@ -90,6 +91,7 @@ export interface GuiState {
cncMode: boolean
}
macros?: GuiMacrosState
navigation: GuiNavigationState
notifications?: GuiNotificationState
presets?: GuiPresetsState
remoteprinters?: GuiRemoteprintersState
@ -104,7 +106,6 @@ export interface GuiState {
boolBigThumbnail: boolean
boolWideNavDrawer: boolean
boolHideUploadAndPrintButton: boolean
boolWebcamNavi: boolean
navigationStyle: 'iconsAndText' | 'iconsOnly'
powerDeviceName: string | null
hideSaveConfigForBedMash: boolean