diff --git a/components.d.ts b/components.d.ts index b0ea7f817e..eceb239f32 100644 --- a/components.d.ts +++ b/components.d.ts @@ -42,7 +42,9 @@ declare module 'vue' { AppNamedSwitch: typeof import('./src/components/ui/AppNamedSwitch.vue')['default'] AppNamedTextField: typeof import('./src/components/ui/AppNamedTextField.vue')['default'] AppNavDrawer: typeof import('./src/components/layout/AppNavDrawer.vue')['default'] + AppNavExternalItem: typeof import('./src/components/ui/AppNavExternalItem.vue')['default'] AppNavItem: typeof import('./src/components/ui/AppNavItem.vue')['default'] + AppNavLinkIcon: typeof import('./src/components/ui/AppNavLinkIcon.vue')['default'] AppNotificationMenu: typeof import('./src/components/layout/AppNotificationMenu.vue')['default'] AppObservedColumn: typeof import('./src/components/layout/AppObservedColumn.vue')['default'] AppQrCode: typeof import('./src/components/ui/AppQrCode.vue')['default'] @@ -79,6 +81,7 @@ declare module 'vue' { VAlert: typeof import('vuetify/lib')['VAlert'] VApp: typeof import('vuetify/lib')['VApp'] VAppBar: typeof import('vuetify/lib')['VAppBar'] + VAutocomplete: typeof import('vuetify/lib')['VAutocomplete'] VBadge: typeof import('vuetify/lib')['VBadge'] VBtn: typeof import('vuetify/lib')['VBtn'] VBtnToggle: typeof import('vuetify/lib')['VBtnToggle'] diff --git a/public/config.json b/public/config.json index cf8db19e7d..b7207bf914 100644 --- a/public/config.json +++ b/public/config.json @@ -12,32 +12,40 @@ "color": "#2196F3", "isDark": true, "logo": { - "src": "logo_fluidd.svg" - } + "src": "logo_fluidd.svg", + "icon": "icon_fluidd.svg" + }, + "links": [{ "title": "Fluidd", "url": "https://docs.fluidd.xyz/" }] }, { "name": "Annex", "color": "#96CC4A", "isDark": true, "logo": { - "src": "logo_annex.svg" - } + "src": "logo_annex.svg", + "icon": "icon_annex.svg" + }, + "links": [{ "title": "Annex", "url": "https://annex-engineering.eu/" }] }, { "name": "BTT", "color": "#475A91", "isDark": true, "logo": { - "src": "logo_btt.svg" - } + "src": "logo_btt.svg", + "icon": "icon_btt.svg" + }, + "links": [{ "title": "BTT", "url": "https://bigtree-tech.com/" }] }, { "name": "Cocoa Press", "color": "#F27121", "isDark": true, "logo": { - "src": "logo_cocoapress.svg" - } + "src": "logo_cocoapress.svg", + "icon": "icon_cocoapress.svg" + }, + "links": [{ "title": "Cocoa Press", "url": "https://cocoapress.com/" }] }, { "name": "EVA", @@ -46,72 +54,90 @@ "logo": { "src": "logo_eva.svg", "dark": "#232323", - "light": "#ffffff" - } + "light": "#ffffff", + "icon": "icon_eva.svg" + }, + "links": [{ "title": "EVA", "url": "https://main.eva-3d.page/" }] }, { "name": "HevORT", "color": "#dfff3e", "isDark": true, "logo": { - "src": "logo_hevort.svg" - } + "src": "logo_hevort.svg", + "icon": "icon_hevort.svg" + }, + "links": [{ "title": "HevORT", "url": "https://hevort.com/" }] }, { "name": "Kingroon", "color": "#DA7A2C", "isDark": true, "logo": { - "src": "logo_kingroon.svg" - } + "src": "logo_kingroon.svg", + "icon": "icon_kingroon.svg" + }, + "links": [{ "title": "Kingroon", "url": "https://kingroon.com/" }] }, { "name": "Klipper", "color": "#B12F36", "isDark": true, "logo": { - "src": "logo_klipper.svg" - } + "src": "logo_klipper.svg", + "icon": "icon_klipper.svg" + }, + "links": [{ "title": "Klipper", "url": "https://www.klipper3d.org/" }] }, { "name": "LDO", "color": "#326799", "isDark": true, "logo": { - "src": "logo_ldo.svg" - } + "src": "logo_ldo.svg", + "icon": "icon_ldo.svg" + }, + "links": [{ "title": "LDO", "url": "https://ldomotion.com/" }] }, { "name": "Mellow", "color": "#003DFF", "isDark": true, "logo": { - "src": "logo_mellow.svg" - } + "src": "logo_mellow.svg", + "icon": "icon_mellow.svg" + }, + "links": [{ "title": "Mellow", "url": "https://3dmellow.com/" }] }, { "name": "Peopoly", "color": "#007CC2", "isDark": true, "logo": { - "src": "logo_peopoly.svg" - } + "src": "logo_peopoly.svg", + "icon": "icon_peopoly.svg" + }, + "links": [{ "title": "Peopoly", "url": "https://peopoly.net/" }] }, { "name": "Prusa", "color": "#E05D2D", "isDark": false, "logo": { - "src": "logo_prusa.svg" - } + "src": "logo_prusa.svg", + "icon": "icon_prusa.svg" + }, + "links": [{ "title": "Prusa", "url": "https://www.prusa3d.com/" }] }, { "name": "Qidi Tech", - "color": "#5B7AEA", + "color": "#3885EA", "isDark": true, "logo": { - "src": "logo_qidi.svg" - } + "src": "logo_qidi.svg", + "icon": "icon_qidi.svg" + }, + "links": [{ "title": "Qidi Tech", "url": "https://qidi3d.com/" }] }, { "name": "RatRig", @@ -120,82 +146,100 @@ "logo": { "src": "logo_ratrig.svg", "dark": "#232323", - "light": "#ffffff" - } + "light": "#ffffff", + "icon": "icon_ratrig.svg" + }, + "links": [{ "title": "RatRig", "url": "https://ratrig.com/" }] }, { "name": "Siboor", "color": "#32E0DF", "isDark": true, "logo": { - "src": "logo_siboor.svg" - } + "src": "logo_siboor.svg", + "icon": "icon_siboor.svg" + }, + "links": [{ "title": "Siboor", "url": "https://www.siboor.com/" }] }, { "name": "Voron", "color": "#FF2300", "isDark": true, "logo": { - "src": "logo_voron.svg" - } + "src": "logo_voron.svg", + "icon": "icon_voron.svg" + }, + "links": [{ "title": "Voron", "url": "https://vorondesign.com/" }] }, { "name": "VzBot", "color": "#FF2300", "isDark": true, "logo": { - "src": "logo_vzbot.svg" - } + "src": "logo_vzbot.svg", + "icon": "icon_vzbot.svg" + }, + "links": [{ "title": "VzBot", "url": "https://vez3d.com/" }] }, { "name": "Z-Bolt", - "color": "#2196F3", + "color": "#FFFFFF", "isDark": true, "logo": { "src": "logo_z-bolt.svg", - "dark": "#232323", - "light": "#ffffff" - } + "icon": "icon_z-bolt.svg" + }, + "links": [{ "title": "Z-Bolt", "url": "https://z-bolt.ru/" }] }, { "name": "ZeroG", "color": "#e34234", "isDark": true, "logo": { - "src": "logo_zerog.svg" - } + "src": "logo_zerog.svg", + "icon": "icon_zerog.svg" + }, + "links": [{ "title": "ZeroG", "url": "https://zerog.one/" }] }, { "name": "SnakeOil", "color": "#4bc3ca", "isDark": true, "logo": { - "src": "logo_snakeoil.svg" - } + "src": "logo_snakeoil.svg", + "icon": "icon_snakeoil.svg" + }, + "links": [{ "title": "SnakeOil", "url": "https://github.com/SnakeOilXY/SnakeOil-XY" }] }, { "name": "Printers for Ants", "color": "#42d62c", "isDark": true, "logo": { - "src": "logo_pfa.svg" - } + "src": "logo_pfa.svg", + "icon": "icon_pfa.svg" + }, + "links": [{ "title": "Printers for Ants", "url": "https://3dprintersforants.com/" }] }, { "name": "Micron", "color": "#42d62c", "isDark": true, "logo": { - "src": "logo_micron.svg" - } + "src": "logo_micron.svg", + "icon": "icon_micron.svg" + }, + "links": [{ "title": "Micron", "url": "https://3dprintersforants.com/" }] }, { "name": "Salad Fork", "color": "#0cff28", "isDark": true, "logo": { - "src": "logo_salad_fork.svg" - } + "src": "logo_salad_fork.svg", + "icon": "icon_salad_fork.svg" + }, + "links": [{ "title": "Salad Fork", "url": "https://3dprintersforants.com/" }] } ] } diff --git a/public/icon_annex.svg b/public/icon_annex.svg new file mode 100644 index 0000000000..ebbbd8e524 --- /dev/null +++ b/public/icon_annex.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icon_btt.svg b/public/icon_btt.svg new file mode 100644 index 0000000000..e2f008ab6c --- /dev/null +++ b/public/icon_btt.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icon_cocoapress.svg b/public/icon_cocoapress.svg new file mode 100644 index 0000000000..34b48d8cab --- /dev/null +++ b/public/icon_cocoapress.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icon_eva.svg b/public/icon_eva.svg new file mode 100644 index 0000000000..f908d63151 --- /dev/null +++ b/public/icon_eva.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icon_fluidd.svg b/public/icon_fluidd.svg new file mode 100644 index 0000000000..01cdbf7615 --- /dev/null +++ b/public/icon_fluidd.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icon_hevort.svg b/public/icon_hevort.svg new file mode 100644 index 0000000000..41f3bf80a4 --- /dev/null +++ b/public/icon_hevort.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icon_kingroon.svg b/public/icon_kingroon.svg new file mode 100644 index 0000000000..40b63a686c --- /dev/null +++ b/public/icon_kingroon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/public/icon_klipper.svg b/public/icon_klipper.svg new file mode 100644 index 0000000000..8e9a1152ab --- /dev/null +++ b/public/icon_klipper.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icon_ldo.svg b/public/icon_ldo.svg new file mode 100644 index 0000000000..c6c1461ebb --- /dev/null +++ b/public/icon_ldo.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icon_mellow.svg b/public/icon_mellow.svg new file mode 100644 index 0000000000..585ab120cd --- /dev/null +++ b/public/icon_mellow.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icon_micron.svg b/public/icon_micron.svg new file mode 100644 index 0000000000..8c94327beb --- /dev/null +++ b/public/icon_micron.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icon_peopoly.svg b/public/icon_peopoly.svg new file mode 100644 index 0000000000..a8f00e3df5 --- /dev/null +++ b/public/icon_peopoly.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icon_pfa.svg b/public/icon_pfa.svg new file mode 100644 index 0000000000..0776e76b27 --- /dev/null +++ b/public/icon_pfa.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icon_prusa.svg b/public/icon_prusa.svg new file mode 100644 index 0000000000..78210f6bd9 --- /dev/null +++ b/public/icon_prusa.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icon_qidi.svg b/public/icon_qidi.svg new file mode 100644 index 0000000000..4db270db86 --- /dev/null +++ b/public/icon_qidi.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icon_ratrig.svg b/public/icon_ratrig.svg new file mode 100644 index 0000000000..d3c6e8feeb --- /dev/null +++ b/public/icon_ratrig.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icon_salad_fork.svg b/public/icon_salad_fork.svg new file mode 100644 index 0000000000..9d8fe3b8bf --- /dev/null +++ b/public/icon_salad_fork.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icon_siboor.svg b/public/icon_siboor.svg new file mode 100644 index 0000000000..af22f2df77 --- /dev/null +++ b/public/icon_siboor.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icon_snakeoil.svg b/public/icon_snakeoil.svg new file mode 100644 index 0000000000..9794d01244 --- /dev/null +++ b/public/icon_snakeoil.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icon_voron.svg b/public/icon_voron.svg new file mode 100644 index 0000000000..e76b91e4ac --- /dev/null +++ b/public/icon_voron.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icon_vzbot.svg b/public/icon_vzbot.svg new file mode 100644 index 0000000000..02b5550a80 --- /dev/null +++ b/public/icon_vzbot.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/icon_z-bolt.svg b/public/icon_z-bolt.svg new file mode 100644 index 0000000000..7ee075454a --- /dev/null +++ b/public/icon_z-bolt.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icon_zerog.svg b/public/icon_zerog.svg new file mode 100644 index 0000000000..c40915f194 --- /dev/null +++ b/public/icon_zerog.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/logo_eva.svg b/public/logo_eva.svg index 7fb291b7f5..99420b1e3b 100644 --- a/public/logo_eva.svg +++ b/public/logo_eva.svg @@ -1,3 +1,3 @@ - + diff --git a/public/logo_ratrig.svg b/public/logo_ratrig.svg index cb40a3b423..9196e7fb6f 100644 --- a/public/logo_ratrig.svg +++ b/public/logo_ratrig.svg @@ -1,3 +1,3 @@ - + diff --git a/public/logo_z-bolt.svg b/public/logo_z-bolt.svg index a17dbad594..a6a29cce6a 100644 --- a/public/logo_z-bolt.svg +++ b/public/logo_z-bolt.svg @@ -1,3 +1,3 @@ - + diff --git a/server/config.json b/server/config.json index 66fca972f2..78ed789ed7 100644 --- a/server/config.json +++ b/server/config.json @@ -12,24 +12,40 @@ "color": "#2196F3", "isDark": true, "logo": { - "src": "logo_fluidd.svg" - } + "src": "logo_fluidd.svg", + "icon": "icon_fluidd.svg" + }, + "links": [{ "title": "Fluidd", "url": "https://docs.fluidd.xyz/" }] }, { "name": "Annex", "color": "#96CC4A", "isDark": true, "logo": { - "src": "logo_annex.svg" - } + "src": "logo_annex.svg", + "icon": "icon_annex.svg" + }, + "links": [{ "title": "Annex", "url": "https://annex-engineering.eu/" }] }, { "name": "BTT", "color": "#475A91", "isDark": true, "logo": { - "src": "logo_btt.svg" - } + "src": "logo_btt.svg", + "icon": "icon_btt.svg" + }, + "links": [{ "title": "BTT", "url": "https://bigtree-tech.com/" }] + }, + { + "name": "Cocoa Press", + "color": "#F27121", + "isDark": true, + "logo": { + "src": "logo_cocoapress.svg", + "icon": "icon_cocoapress.svg" + }, + "links": [{ "title": "Cocoa Press", "url": "https://cocoapress.com/" }] }, { "name": "EVA", @@ -38,72 +54,90 @@ "logo": { "src": "logo_eva.svg", "dark": "#232323", - "light": "#ffffff" - } + "light": "#ffffff", + "icon": "icon_eva.svg" + }, + "links": [{ "title": "EVA", "url": "https://main.eva-3d.page/" }] }, { "name": "HevORT", "color": "#dfff3e", "isDark": true, "logo": { - "src": "logo_hevort.svg" - } + "src": "logo_hevort.svg", + "icon": "icon_hevort.svg" + }, + "links": [{ "title": "HevORT", "url": "https://hevort.com/" }] }, { "name": "Kingroon", "color": "#DA7A2C", "isDark": true, "logo": { - "src": "logo_kingroon.svg" - } + "src": "logo_kingroon.svg", + "icon": "icon_kingroon.svg" + }, + "links": [{ "title": "Kingroon", "url": "https://kingroon.com/" }] }, { "name": "Klipper", "color": "#B12F36", "isDark": true, "logo": { - "src": "logo_klipper.svg" - } + "src": "logo_klipper.svg", + "icon": "icon_klipper.svg" + }, + "links": [{ "title": "Klipper", "url": "https://www.klipper3d.org/" }] }, { "name": "LDO", "color": "#326799", "isDark": true, "logo": { - "src": "logo_ldo.svg" - } + "src": "logo_ldo.svg", + "icon": "icon_ldo.svg" + }, + "links": [{ "title": "LDO", "url": "https://ldomotion.com/" }] }, { "name": "Mellow", "color": "#003DFF", "isDark": true, "logo": { - "src": "logo_mellow.svg" - } + "src": "logo_mellow.svg", + "icon": "icon_mellow.svg" + }, + "links": [{ "title": "Mellow", "url": "https://3dmellow.com/" }] }, { "name": "Peopoly", "color": "#007CC2", "isDark": true, "logo": { - "src": "logo_peopoly.svg" - } + "src": "logo_peopoly.svg", + "icon": "icon_peopoly.svg" + }, + "links": [{ "title": "Peopoly", "url": "https://peopoly.net/" }] }, { "name": "Prusa", "color": "#E05D2D", "isDark": false, "logo": { - "src": "logo_prusa.svg" - } + "src": "logo_prusa.svg", + "icon": "icon_prusa.svg" + }, + "links": [{ "title": "Prusa", "url": "https://www.prusa3d.com/" }] }, { "name": "Qidi Tech", - "color": "#5B7AEA", + "color": "#3885EA", "isDark": true, "logo": { - "src": "logo_qidi.svg" - } + "src": "logo_qidi.svg", + "icon": "icon_qidi.svg" + }, + "links": [{ "title": "Qidi Tech", "url": "https://qidi3d.com/" }] }, { "name": "RatRig", @@ -112,82 +146,100 @@ "logo": { "src": "logo_ratrig.svg", "dark": "#232323", - "light": "#ffffff" - } + "light": "#ffffff", + "icon": "icon_ratrig.svg" + }, + "links": [{ "title": "RatRig", "url": "https://ratrig.com/" }] }, { "name": "Siboor", "color": "#32E0DF", "isDark": true, "logo": { - "src": "logo_siboor.svg" - } + "src": "logo_siboor.svg", + "icon": "icon_siboor.svg" + }, + "links": [{ "title": "Siboor", "url": "https://www.siboor.com/" }] }, { "name": "Voron", "color": "#FF2300", "isDark": true, "logo": { - "src": "logo_voron.svg" - } + "src": "logo_voron.svg", + "icon": "icon_voron.svg" + }, + "links": [{ "title": "Voron", "url": "https://vorondesign.com/" }] }, { "name": "VzBot", "color": "#FF2300", "isDark": true, "logo": { - "src": "logo_vzbot.svg" - } + "src": "logo_vzbot.svg", + "icon": "icon_vzbot.svg" + }, + "links": [{ "title": "VzBot", "url": "https://vez3d.com/" }] }, { "name": "Z-Bolt", - "color": "#2196F3", + "color": "#FFFFFF", "isDark": true, "logo": { "src": "logo_z-bolt.svg", - "dark": "#232323", - "light": "#ffffff" - } + "icon": "icon_z-bolt.svg" + }, + "links": [{ "title": "Z-Bolt", "url": "https://z-bolt.ru/" }] }, { "name": "ZeroG", "color": "#e34234", "isDark": true, "logo": { - "src": "logo_zerog.svg" - } + "src": "logo_zerog.svg", + "icon": "icon_zerog.svg" + }, + "links": [{ "title": "ZeroG", "url": "https://zerog.one/" }] }, { "name": "SnakeOil", "color": "#4bc3ca", "isDark": true, "logo": { - "src": "logo_snakeoil.svg" - } + "src": "logo_snakeoil.svg", + "icon": "icon_snakeoil.svg" + }, + "links": [{ "title": "SnakeOil", "url": "https://github.com/SnakeOilXY/SnakeOil-XY" }] }, { "name": "Printers for Ants", "color": "#42d62c", "isDark": true, "logo": { - "src": "logo_pfa.svg" - } + "src": "logo_pfa.svg", + "icon": "icon_pfa.svg" + }, + "links": [{ "title": "Printers for Ants", "url": "https://3dprintersforants.com/" }] }, { "name": "Micron", "color": "#42d62c", "isDark": true, "logo": { - "src": "logo_micron.svg" - } + "src": "logo_micron.svg", + "icon": "icon_micron.svg" + }, + "links": [{ "title": "Micron", "url": "https://3dprintersforants.com/" }] }, { "name": "Salad Fork", "color": "#0cff28", "isDark": true, "logo": { - "src": "logo_salad_fork.svg" - } + "src": "logo_salad_fork.svg", + "icon": "icon_salad_fork.svg" + }, + "links": [{ "title": "Salad Fork", "url": "https://3dprintersforants.com/" }] } ] } diff --git a/src/App.vue b/src/App.vue index 3024a6cf57..97f955185b 100644 --- a/src/App.vue +++ b/src/App.vue @@ -4,7 +4,10 @@ :class="{ 'no-pointer-events': dragState }" > - + - - + @@ -13,9 +15,11 @@ > + - {{ $t('app.general.title.home') }} + {{ $t(homeLink.title) }} - - {{ $t('app.general.title.console') }} - + + - + - {{ $t('app.general.title.gcode_preview') }} - + + + {{ $t(item.title) }} + + + - + - {{ $t('app.general.title.jobs') }} - + + + + $menuAlt + + + {{ $t('app.general.title.more') }} + + + + + + + {{ item.icon }} + + + {{ $t(item.title) }} + + + + - - {{ $t('app.general.title.history') }} - + + - + - {{ $t('app.general.title.timelapse') }} - + + + + $bookmarkMultiple + + + {{ $t('app.general.title.bookmarks') }} + + + + + + + + + + {{ link.title }} + + + + - + - {{ $t('app.general.title.tune') }} - + + + {{ link.title }} + + + - + - {{ $t('app.general.title.diagnostics') }} - + + + + + + {{ isContextItemCollapsed ? '$eye' : '$eyeOff' }} + + + + + + {{ $t('app.general.label.show_in_sidebar') }} + + + {{ $t('app.general.label.collapse_to_more_menu') }} + + + {{ $t('app.general.label.collapse_to_bookmarks') }} + + + + - - {{ $t('app.general.title.configure') }} - + + + + $pencil + + + {{ $t('app.general.btn.edit') }} + + - + + + $delete + + + {{ $t('app.general.btn.delete') }} + + + + + + + $eyeOff + + + {{ $t('app.general.btn.hide') }} + + + + + + + - {{ $t('app.general.title.system') }} - + + + + $eye + + + {{ $t('app.general.label.show_all_in_sidebar') }} + + + + + $plus + + + {{ $t('app.setting.btn.add_nav_link') }} + + + + + + + {{ $t('app.general.title.settings') }} - + - - $apps - + + + $apps + + + + {{ $t('app.general.btn.adjust_layout') }} + + {{ $t('app.general.btn.adjust_layout') }} @@ -143,20 +366,226 @@ name="navigation" /> + + @@ -201,4 +965,13 @@ export default class AppNavDrawer extends Mixins(StateMixin, BrowserMixin) { :deep(.v-navigation-drawer.no-subnav > .v-navigation-drawer__border) { display: none; } + + // GPU acceleration hints for smoother transitions + :deep(.v-navigation-drawer) { + will-change: width, transform; + } + + :deep(.v-navigation-drawer__content) { + will-change: transform; + } diff --git a/src/components/layout/AppSettingsNav.vue b/src/components/layout/AppSettingsNav.vue index b2b3d2b450..af981dbd92 100644 --- a/src/components/layout/AppSettingsNav.vue +++ b/src/components/layout/AppSettingsNav.vue @@ -37,6 +37,7 @@ export default class AppSettingsNav extends Vue { { name: this.$t('app.setting.title.general'), hash: '#general', visible: true }, { name: this.$t('app.setting.title.warnings'), hash: '#warnings', visible: true }, { name: this.$t('app.setting.title.theme'), hash: '#theme', visible: true }, + { name: this.$t('app.setting.title.navigation'), hash: '#navigation', visible: true }, { name: this.$t('app.setting.title.authentication'), hash: '#auth', visible: true }, { name: this.$t('app.setting.title.console'), hash: '#console', visible: true }, { name: this.$t('app.setting.title.file_browser'), hash: '#browser', visible: true }, diff --git a/src/components/settings/navigation/NavLinkDialog.vue b/src/components/settings/navigation/NavLinkDialog.vue new file mode 100644 index 0000000000..1bf01e4747 --- /dev/null +++ b/src/components/settings/navigation/NavLinkDialog.vue @@ -0,0 +1,546 @@ + + + + + + {{ isEdit ? $t('app.general.label.edit_nav_link') : $t('app.general.label.add_nav_link') }} + + + + + + + + + + + + {{ $t('app.setting.label.url') }} + + + + + + + + + + {{ $t('app.setting.label.icon_type') }} + + + + + + + + + + + + ${{ item.value }} + + {{ item.text }} + + + + ${{ item.value }} + + {{ item.text }} + + + + + + + {{ $t('app.setting.label.custom_svg_icon') }} + + + + + {{ $t('app.setting.label.upload_svg') }} + + + + + $close + + + + + + {{ imageError }} + + + + + + {{ $t('app.setting.label.custom_image') }} + + + + + {{ $t('app.setting.label.upload_image') }} + + + + + $close + + + + + + {{ imageError }} + + + + + + + + + + + + + $circle + + {{ $t('app.setting.label.custom_color') }} + + + + + + + + + + + + + + + + + diff --git a/src/components/settings/navigation/NavigationSettings.vue b/src/components/settings/navigation/NavigationSettings.vue new file mode 100644 index 0000000000..7f086adc0d --- /dev/null +++ b/src/components/settings/navigation/NavigationSettings.vue @@ -0,0 +1,496 @@ + + + + {{ $t('app.setting.title.navigation') }} + + + + + + + + + + + $menu + + {{ $t('app.setting.btn.import_export_links') }} + + + + + + $download + + + {{ $t('app.setting.btn.export_links') }} + + + + + $fileUpload + + + {{ $t('app.setting.btn.import_links') }} + + + + + + + + $plus + + {{ $t('app.setting.btn.add_nav_link') }} + + + + + + + + + + + + + + + + + + + + {{ link.title }} + + + + {{ link.url }} + + + + {{ $t('app.setting.label.theme_nav_link') }} + + + + + {{ isThemeLinkHidden(link.id) ? '$eyeOff' : '$eye' }} + + + + + + + + + + + + + + + + {{ link.title }} + + + + {{ link.url }} + + + + + $edit + + + + + + $delete + + + + + + + + + + + + + + {{ $t('app.setting.btn.import_links') }} + + + + + + + + + + {{ $t('app.general.btn.cancel') }} + + + {{ $t('app.general.btn.save') }} + + + + + + + + + diff --git a/src/components/ui/AppNavExternalItem.vue b/src/components/ui/AppNavExternalItem.vue new file mode 100644 index 0000000000..b95541afb6 --- /dev/null +++ b/src/components/ui/AppNavExternalItem.vue @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/ui/AppNavItem.vue b/src/components/ui/AppNavItem.vue index 273c6a0b20..2ec57acb48 100644 --- a/src/components/ui/AppNavItem.vue +++ b/src/components/ui/AppNavItem.vue @@ -1,16 +1,18 @@ {{ icon }} @@ -36,7 +38,6 @@ import { Component, Mixins, Prop } from 'vue-property-decorator' import StateMixin from '@/mixins/state' import BrowserMixin from '@/mixins/browser' -import { eventTargetIsContentEditable, keyboardEventToKeyboardShortcut } from '@/util/event-helpers' import { Globals } from '@/globals' import isKeyOf from '@/util/is-key-of' @@ -54,6 +55,12 @@ export default class AppNavItem extends Mixins(StateMixin, BrowserMixin) { @Prop({ type: String }) readonly icon?: string + @Prop({ type: Boolean, default: false }) + readonly hideTooltip!: boolean + + @Prop({ type: String }) + readonly dataId?: string + get accelerator (): string | undefined { return isKeyOf(this.to, Globals.KEYBOARD_SHORTCUTS) ? Globals.KEYBOARD_SHORTCUTS[this.to] @@ -63,35 +70,6 @@ export default class AppNavItem extends Mixins(StateMixin, BrowserMixin) { get enableKeyboardShortcuts (): boolean { return this.$typedState.config.uiSettings.general.enableKeyboardShortcuts } - - handleKeyDown (event: KeyboardEvent) { - if ( - !this.enableKeyboardShortcuts || - !this.accelerator - ) { - return - } - - const shortcut = keyboardEventToKeyboardShortcut(event) - - if ( - shortcut === this.accelerator && - !eventTargetIsContentEditable(event) && - this.$route.name !== this.to - ) { - event.preventDefault() - - this.$router.push({ name: this.to }) - } - } - - mounted () { - window.addEventListener('keydown', this.handleKeyDown, false) - } - - beforeDestroy () { - window.removeEventListener('keydown', this.handleKeyDown) - } } diff --git a/src/components/ui/AppNavLinkIcon.vue b/src/components/ui/AppNavLinkIcon.vue new file mode 100644 index 0000000000..c469a1eb10 --- /dev/null +++ b/src/components/ui/AppNavLinkIcon.vue @@ -0,0 +1,126 @@ + + + + + + + + + + + + + {{ icon }} + + + + diff --git a/src/globals.ts b/src/globals.ts index ab53cc7307..0cf635e723 100644 --- a/src/globals.ts +++ b/src/globals.ts @@ -26,6 +26,7 @@ import { mdiArrowUp, mdiArrowDown, mdiArrowLeft, + mdiBookmarkMultiple, mdiArrowRight, mdiArrowCollapseDown, mdiViewGridOutline, @@ -196,7 +197,9 @@ import { mdiWrench, mdiVariable, mdiArrowDownBold, - mdiArrowUpBold + mdiArrowUpBold, + mdiEye, + mdiEyeOff } from '@mdi/js' import { @@ -438,6 +441,7 @@ export const Icons = Object.freeze({ printer3d: mdiPrinter3d, printer3dNozzle: mdiPrinter3dNozzleOutline, printer3dNozzleAlert: mdiPrinter3dNozzleAlertOutline, + bookmarkMultiple: mdiBookmarkMultiple, bedMesh: mdiViewGridOutline, host: mdiDesktopTower, history: mdiHistory, @@ -556,6 +560,8 @@ export const Icons = Object.freeze({ afcUnloadLane: mdiArrowUpBold, afcLoadLane: mdiArrowDownBold, afcEjectFilament: mdiEject, + eye: mdiEye, + eyeOff: mdiEyeOff, }) export const Waits = Object.freeze({ diff --git a/src/locales/en.yaml b/src/locales/en.yaml index 4d0f0513bd..b75e13504d 100644 --- a/src/locales/en.yaml +++ b/src/locales/en.yaml @@ -239,7 +239,7 @@ app: add_file: Add File add_printer: Add printer add_to_queue: Add to Queue - adjust_layout: Adjust dashboard layout + adjust_layout: Dashboard Layout adjusted: Adjusted all: All auth_unsure: Unsure why you're seeing this? @@ -261,6 +261,7 @@ app: forgot_password: Forgotten your password? go_to_file: Go to file heaters_off: Heaters off + hide: Hide job_queue: Job Queue load_all: Load all login: Login @@ -335,6 +336,7 @@ app: actual_time: Actual add_camera: Add Camera add_filter: Add Filter + add_nav_link: Add Navigation Link add_preset: Add Preset add_user: Add user all: All @@ -346,6 +348,8 @@ app: change_password: Change password clean_nozzle: Clean Nozzle clear_all: Clear all + collapse_to_bookmarks: Collapse to links + collapse_to_more_menu: Collapse to More menu color: Color command: Command confirm: Confirm @@ -360,6 +364,7 @@ app: disabled_while_printing: Disabled while printing edit_camera: Edit Camera edit_filter: Edit Filter + edit_nav_link: Edit Navigation Link edit_preset: Edit Preset edit_user: Edit user environment_facing: Environment Facing @@ -413,6 +418,8 @@ app: screw_name: Screw name screw_number: Screw %{index} services: Services + show_all_in_sidebar: Show all in sidebar + show_in_sidebar: Show in sidebar slicer: Slicer smooth_time: Smooth Time speed: Speed @@ -477,6 +484,7 @@ app: exists: Already exists invalid_number: Invalid Number invalid_url: Invalid URL + file_too_large: "File exceeds maximum size of %{size}" invalid_expression: Invalid Expression max: Max %{max} min: Min %{min} @@ -499,6 +507,12 @@ app: confirm_remove_console_filter: Are you sure you want to remove the console filter %{name}? confirm_remove_macro_category: Are you sure you want to remove the macro category %{name}? confirm_remove_printer: Are you sure you want to remove the printer %{name}? + confirm_open_nav_link: >- + Open external link? + %{url} + confirm_remove_nav_link: Are you sure you want to remove the navigation link %{name}? + confirm_import_links_replace: This will replace all existing custom links with %{count} imported link(s). Continue? + confirm_import_links_merge: This will add %{count} link(s) to your existing custom links. Continue? confirm_remove_thermal_preset: Are you sure you want to remove the thermal preset %{name}? confirm_remove_user: Are you sure you want to remove user %{username}? confirm_restore_backup: Are you sure you want to restore this backup? Moonraker will restart immediately after this request is processed. @@ -586,6 +600,8 @@ app: timelapse: Timelapse tool: Tool tune: Tune + bookmarks: Links + more: More tooltip: browse_metrics: Browse available metrics estop: Emergency Stop @@ -647,6 +663,10 @@ app: add_camera: Add Camera add_category: Add Category add_filter: Add Filter + add_nav_link: Add Link + import_export_links: Import/Export + export_links: Export Links + import_links: Import Links add_metric: Add Metric add_thermal_preset: Add Preset add_user: Add user @@ -680,6 +700,7 @@ app: auto_load_mobile_on_print_start: Automatically load file on mobile devices auto: Auto axes: Axes + built_in_icon: Built In Icon camera_flip_x: Flip horizontally camera_flip_y: Flip vertically camera_fullscreen_action: @@ -692,6 +713,13 @@ app: camera_url_stream: Camera Url Stream card: Card collector: Collector + color: Color + custom_color: Custom Color + custom_icon: Custom Icon (SVG) + custom_image: Custom Image + custom_svg_icon: Custom SVG Icon + upload_image: Upload Image + upload_svg: Upload SVG confirm_on_estop: Require confirm on Emergency Stop confirm_on_power_device_change: Require confirm on Device Power changes confirm_on_save_config_and_restart: Review Pending Configuration Changes before Save and Restart @@ -714,6 +742,7 @@ app: enable_xy_homing: Enable XY Homing expression: Expression extrusion_line_width: Extrusion Line Width + file: File firmware_restart: Firmware Restart flip_horizontal: Horizontal Flip flip_vertical: Vertical Flip @@ -728,6 +757,7 @@ app: gcode_coords: Use GCode Coordinates host_restart: Host Restart icon: Icon + icon_type: Icon Type invert_x_control: Invert X control invert_y_control: Invert Y control invert_z_control: Invert Z control @@ -746,6 +776,7 @@ app: never: Never none: None optional: Optional + position: Position power_toggle_in_top_nav: Power toggle in top navigation primary_color: Primary color print_eta_calculation: Print ETA calculation @@ -772,6 +803,7 @@ app: show_upload_and_print: Show Upload and Print button in top navigation solid: Solid starts_with: Starts with + theme: Theme theme_preset: Community preset thermal_preset_gcode: GCode thermal_preset_name: Preset Name @@ -784,12 +816,19 @@ app: toolhead_z_move_distances: Toolhead Z distance values type: Type unit: Unit + url: URL warn_on_cpu_throttled: Warn if CPU throttling is detected warn_on_stepper_driver_overheating: Warn if stepper driver is over-heating z_adjust_values: Z Adjust values date_format: Date format time_format: Time format text_sort_order: Text sort order + confirm_on_nav_link: Confirm Before Opening Links + open_nav_links_in_new_tab: Open Links in New Tab + link_icon_color: Link Icon Color + theme_nav_link: Theme Link + import_mode_replace: Replace existing links + import_mode_merge: Add to existing links drag_and_drop_functionality_for_files_and_folders: Drag-and-drop functionality for files and folders force_move_toggle_warning: Require confirm when activating FORCE_MOVE show_manual_probe_dialog_automatically: Show Manual Probe dialog automatically @@ -812,6 +851,7 @@ app: gcode_preview: Gcode Preview general: General macros: Macros + navigation: Navigation Links theme: Theme thermal_presets: Thermal Presets tool: Tool @@ -831,6 +871,24 @@ app: However, please note that these themes are not officially maintained by the respective brands. Any issues or inquiries related to the themes should be directed to the Fluidd team. The use of brand names and logos is for decorative and aesthetic purposes only, and no official association with or endorsement by the brands is implied. + nav_link_url_warning: 'See the tooltip for details.' + nav_link_url: >- + Include the protocol (e.g. https://example.com) for external links. + URLs without a protocol will be treated as relative paths by the browser. + nav_link_custom_icon: >- + Upload an SVG file (max 64 KB) to use as the link icon. + Use "File Colors" to preserve the original SVG colors, or choose + theme/custom color to override with a single color. + nav_link_custom_image: >- + Upload a PNG or JPEG image (max 64 KB) to use as the link icon. + icon_type: >- + Built In Icon: Choose from Material Design icons. Supports theme or custom colors. + Custom SVG Icon: Upload an SVG file. Supports file colors, theme color, or custom colors. + Custom Image: Upload a PNG or JPEG. Color override does not apply. + nav_link_position: >- + Controls the sort order among custom and theme links. + Lower numbers appear higher in the sidebar. + Links are inserted directly above the Settings icon. warn_on_cpu_throttled: CPU throttling can cause prints to fail warn_on_stepper_driver_overheating: Trinamic drivers only socket: diff --git a/src/scss/misc.scss b/src/scss/misc.scss index fbd06d2dbe..a8181d2a9d 100644 --- a/src/scss/misc.scss +++ b/src/scss/misc.scss @@ -10,28 +10,6 @@ pointer-events: none !important; } -// Apply a common scrollbar application to all elements inside the app -.v-application--wrap, -.v-dialog__content { - ::-webkit-scrollbar { - transition: all .5s; - width: 5px; - height: 8px; - z-index: 10; - } - - ::-webkit-scrollbar-track { - background: transparent; - } - - ::-webkit-scrollbar-thumb { - background: #b3ada7; - } - - ::-webkit-scrollbar-corner { - background: transparent; - } -} // Ensure the number arrows don't appear on text inputs. /* Chrome, Safari, Edge, Opera */ diff --git a/src/store/config/actions.ts b/src/store/config/actions.ts index d89be93ce2..15937eff48 100644 --- a/src/store/config/actions.ts +++ b/src/store/config/actions.ts @@ -1,6 +1,6 @@ import vuetify from '@/plugins/vuetify' import type { ActionTree } from 'vuex' -import type { ConfigState, SaveByPath, InitConfig, InstanceConfig, UiSettings, ThemeConfig, ConfiguredTableHeader } from './types' +import type { ConfigState, SaveByPath, InitConfig, InstanceConfig, UiSettings, ThemeConfig, ConfiguredTableHeader, CustomNavLink } from './types' import type { RootState } from '../types' import { SocketActions } from '@/api/socketActions' import { loadLocaleMessagesAsync, getStartingLocale } from '@/plugins/i18n' @@ -200,18 +200,51 @@ export const actions = { } }, - async updateTheme ({ state, dispatch }, payload: Partial) { + async updateTheme ({ state, commit, dispatch }, payload: Partial) { + // Check if theme preset is changing (logo changed) BEFORE updating state + const isLogoChanging = payload.logo && payload.logo.src !== state.uiSettings.theme.logo.src + const updatedTheme: ThemeConfig = { ...state.uiSettings.theme, ...payload } - dispatch('onThemeChange', updatedTheme) - - dispatch('saveByPath', { + // Save theme FIRST (synchronously) so getters evaluate with new theme + commit('setSaveByPath', { path: 'uiSettings.theme', - value: updatedTheme, - server: true + value: updatedTheme }) + + // Then clear positions - getter will now return new theme's link with -1 position + if (isLogoChanging) { + commit('setThemeLinkPositions', {}) + SocketActions.serverDatabasePostItem('uiSettings.navigation.themeLinkPositions', {}) + } + + // Apply Vuetify theme changes + dispatch('onThemeChange', updatedTheme) + + // Persist theme to database + SocketActions.serverDatabasePostItem('uiSettings.theme', updatedTheme) + }, + + async updateCustomNavLink ({ commit, state }, payload: CustomNavLink) { + commit('setCustomNavLink', payload) + SocketActions.serverDatabasePostItem('uiSettings.navigation.customLinks', state.uiSettings.navigation.customLinks) + }, + + async updateCustomNavLinkPositions ({ commit, state }, payload: { id: string; position: number }[]) { + commit('setCustomNavLinkPositions', payload) + SocketActions.serverDatabasePostItem('uiSettings.navigation.customLinks', state.uiSettings.navigation.customLinks) + }, + + async removeCustomNavLink ({ commit, state }, payload: { id: string }) { + commit('setRemoveCustomNavLink', payload) + SocketActions.serverDatabasePostItem('uiSettings.navigation.customLinks', state.uiSettings.navigation.customLinks) + }, + + async updateThemeLinkPositions ({ commit, state }, payload: Record) { + commit('setThemeLinkPositions', payload) + SocketActions.serverDatabasePostItem('uiSettings.navigation.themeLinkPositions', state.uiSettings.navigation.themeLinkPositions) } } satisfies ActionTree diff --git a/src/store/config/getters.ts b/src/store/config/getters.ts index e5d003df87..a4d067d151 100644 --- a/src/store/config/getters.ts +++ b/src/store/config/getters.ts @@ -1,5 +1,5 @@ import type { GetterTree } from 'vuex' -import type { ConfigState, TemperaturePreset } from './types' +import type { ConfigState, CustomNavLink, SvgIconPath, TemperaturePreset } from './types' import type { RootState } from '../types' import type { Heater, Fan } from '../printer/types' import type { AppDataTableHeader } from '@/types' @@ -107,6 +107,59 @@ export const getters = { })) }, + getThemeNavLinks: (state): CustomNavLink[] => { + const activePreset = state.hostConfig.themePresets.find( + p => p.logo.src === state.uiSettings.theme.logo.src + ) + if (!activePreset) return [] + + // Resolve links array: prefer preset.links, fall back to legacy preset.url + const links = activePreset.links ?? + (activePreset.url ? [{ title: activePreset.name, url: activePreset.url }] : []) + + if (links.length === 0) return [] + + const baseId = `preset-${activePreset.logo.src}` + + return links.map((link, index) => { + // Resolve icon per link via fallback chain + const customIcon: string | undefined | SvgIconPath[] = link.icon ?? + activePreset.logo.icon ?? + activePreset.icon ?? + activePreset.logo.src + + // Generate stable IDs: first link keeps legacy ID for backwards compat + const id = index === 0 ? baseId : `${baseId}-${index}` + + // Position: use stored position if available, otherwise group at top + const storedPosition = state.uiSettings.navigation?.themeLinkPositions?.[id] + const position = storedPosition ?? (-1 + index) + + return { + id, + title: link.title, + url: link.url, + icon: 'openInNew', + customIcon, + position + } + }) + }, + + getDbNavLinks: (state): CustomNavLink[] => { + return [...(state.uiSettings.navigation?.customLinks || [])] + .sort((a, b) => a.position - b.position) + }, + + getCustomNavLinks: (state, getters): CustomNavLink[] => { + const dbLinks = state.uiSettings.navigation?.customLinks || [] + const hidden = state.uiSettings.navigation?.hiddenThemeLinks || [] + const themeLinks = (getters.getThemeNavLinks as CustomNavLink[]) + .filter(l => !hidden.includes(l.id)) + const combined = [...dbLinks, ...themeLinks] + return combined.sort((a, b) => a.position - b.position) + }, + getTokenKeys: (state) => { const url = state.apiUrl const hash = (url) ? md5(url) : '' diff --git a/src/store/config/mutations.ts b/src/store/config/mutations.ts index c52cfe162f..40923eae06 100644 --- a/src/store/config/mutations.ts +++ b/src/store/config/mutations.ts @@ -1,6 +1,6 @@ import Vue from 'vue' import type { MutationTree } from 'vuex' -import type { ConfigState, UiSettings, SaveByPath, InstanceConfig, InitConfig, ConfiguredTableHeader } from './types' +import type { ConfigState, UiSettings, SaveByPath, InstanceConfig, InitConfig, ConfiguredTableHeader, CustomNavLink } from './types' import { defaultState } from './state' import { Globals } from '@/globals' import { cloneDeep, mergeWith, set } from 'lodash-es' @@ -209,5 +209,43 @@ export const mutations = { setUpdateThumbnailSizes (state, payload: { name: string; size: number }) { Vue.set(state.uiSettings.thumbnailSizes, payload.name, payload.size) + }, + + setCustomNavLink (state, payload: CustomNavLink) { + const link = { ...payload } + if (link.id === '') { + link.id = uuidv4() + state.uiSettings.navigation.customLinks.push(link) + } else { + const links = state.uiSettings.navigation.customLinks + const i = links.findIndex(l => l.id === link.id) + if (i >= 0) { + Vue.set(links, i, link) + } + } + }, + + setRemoveCustomNavLink (state, payload: { id: string }) { + const links = state.uiSettings.navigation.customLinks + const i = links.findIndex(link => link.id === payload.id) + if (i >= 0) { + links.splice(i, 1) + } + }, + + setCustomNavLinkPositions (state, payload: { id: string; position: number }[]) { + const links = state.uiSettings.navigation.customLinks + for (const { id, position } of payload) { + const index = links.findIndex(l => l.id === id) + if (index >= 0) { + // Use Vue.set to ensure reactivity + Vue.set(links[index], 'position', position) + } + } + }, + + setThemeLinkPositions (state, payload: Record) { + // Use Vue.set to ensure reactivity when updating nested object + Vue.set(state.uiSettings.navigation, 'themeLinkPositions', payload) } } satisfies MutationTree diff --git a/src/store/config/state.ts b/src/store/config/state.ts index ff677be8f9..698eccb973 100644 --- a/src/store/config/state.ts +++ b/src/store/config/state.ts @@ -163,6 +163,17 @@ export const defaultState = (): ConfigState => { showLaneInfinite: true, showUnitIcons: true, showTd1Color: true, + }, + navigation: { + customLinks: [], + hiddenThemeLinks: [], + collapsedSystemLinks: [], + systemLinkOrder: [], + collapsedCustomLinks: [], + themeLinkPositions: {}, + openNavLinksInNewTab: true, + confirmOnNavLink: false, + sidebarExpanded: false } } } diff --git a/src/store/config/types.ts b/src/store/config/types.ts index ecf26d5596..ab161c0cb2 100644 --- a/src/store/config/types.ts +++ b/src/store/config/types.ts @@ -1,5 +1,33 @@ import type { FileFilterType } from '../files/types' +export interface SvgIconPath { + d: string + fill?: string +} + +export interface CustomNavLink { + id: string + title: string + url: string + icon: string + customIcon?: string | SvgIconPath[] + customImage?: string // base64 data URI (e.g. "data:image/png;base64,...") + color?: string + position: number +} + +export interface NavigationConfig { + customLinks: CustomNavLink[] + hiddenThemeLinks: string[] + collapsedSystemLinks: string[] + systemLinkOrder: string[] + collapsedCustomLinks: string[] + themeLinkPositions: Record + openNavLinksInNewTab: boolean + confirmOnNavLink: boolean + sidebarExpanded: boolean +} + export interface ConfigState { [key: string]: any; appReady: boolean; @@ -27,6 +55,7 @@ export interface UiSettings { history: HistoryConfig; mmu: MmuConfig; afc: AfcConfig; + navigation: NavigationConfig; } export interface WarningsConfig { @@ -171,10 +200,21 @@ export interface ThemePreset { color: string; isDark: boolean; logo: ThemeLogo; + url?: string; + links?: ThemeLink[]; + icon?: SvgIconPath[]; +} + +export interface ThemeLink { + title: string + url: string + icon?: string } export interface ThemeLogo { src: string; + icon?: string; + background?: string; dark?: string; light?: string; } diff --git a/src/views/Settings.vue b/src/views/Settings.vue index f6943e3c37..96b4bc448e 100644 --- a/src/views/Settings.vue +++ b/src/views/Settings.vue @@ -5,6 +5,7 @@ > @@ -12,6 +13,7 @@ + @@ -38,6 +40,7 @@ import MacroSettings from '@/components/settings/macros/MacroSettings.vue' import GeneralSettings from '@/components/settings/GeneralSettings.vue' import PresetSettings from '@/components/settings/presets/PresetSettings.vue' import CameraSettings from '@/components/settings/cameras/CameraSettings.vue' +import NavigationSettings from '@/components/settings/navigation/NavigationSettings.vue' import ToolheadSettings from '@/components/settings/ToolheadSettings.vue' import ThemeSettings from '@/components/settings/ThemeSettings.vue' import VersionSettings from '@/components/settings/VersionSettings.vue' @@ -60,6 +63,7 @@ import WarningsSettings from '@/components/settings/WarningsSettings.vue' GeneralSettings, PresetSettings, CameraSettings, + NavigationSettings, ToolheadSettings, ThemeSettings, VersionSettings,