Skip to content

Commit

Permalink
feat: add support for build-in themes and add a Klipper theme
Browse files Browse the repository at this point in the history
Signed-off-by: Stefan Dej <[email protected]>
  • Loading branch information
meteyou committed Apr 28, 2024
1 parent c781074 commit 5125332
Show file tree
Hide file tree
Showing 13 changed files with 154 additions and 25 deletions.
3 changes: 2 additions & 1 deletion public/config.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"defaultLocale": "en",
"defaultTheme": "dark",
"defaultMode": "dark",
"defaultTheme": "mainsail",
"hostname": null,
"port": null,
"path": null,
Expand Down
15 changes: 15 additions & 0 deletions public/img/themes/sidebarLogo-klipper.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 4 additions & 4 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ export default class App extends Mixins(BaseMixin, ThemeMixin) {
return this.$store.state.printer.print_stats?.filename ?? ''
}
get theme(): string {
return this.$store.state.gui.uiSettings.theme
get mode(): string {
return this.$store.state.gui.uiSettings.mode
}
get logoColor(): string {
Expand Down Expand Up @@ -226,8 +226,8 @@ export default class App extends Mixins(BaseMixin, ThemeMixin) {
})
}
@Watch('theme')
themeChanged(newVal: string): void {
@Watch('mode')
modeChanged(newVal: string): void {
const dark = newVal !== 'light'
this.$vuetify.theme.dark = dark
Expand Down
22 changes: 19 additions & 3 deletions src/components/TheTopbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
<script lang="ts">
import { Mixins } from 'vue-property-decorator'
import BaseMixin from '@/components/mixins/base'
import { validGcodeExtensions } from '@/store/variables'
import { themes, validGcodeExtensions } from '@/store/variables'
import Component from 'vue-class-component'
import axios, { AxiosProgressEvent } from 'axios'
import { formatFilesize } from '@/plugins/helpers'
Expand All @@ -90,6 +90,7 @@ import { topbarHeight } from '@/store/variables'
import { mdiAlertOctagonOutline, mdiContentSave, mdiFileUpload, mdiClose, mdiCloseThick } from '@mdi/js'
import EmergencyStopDialog from '@/components/dialogs/EmergencyStopDialog.vue'
import InlineSvg from 'vue-inline-svg'
import ThemeMixin from '@/components/mixins/theme'
type uploadSnackbar = {
status: boolean
Expand All @@ -112,7 +113,7 @@ type uploadSnackbar = {
TheNotificationMenu,
},
})
export default class TheTopbar extends Mixins(BaseMixin) {
export default class TheTopbar extends Mixins(BaseMixin, ThemeMixin) {
mdiAlertOctagonOutline = mdiAlertOctagonOutline
mdiContentSave = mdiContentSave
mdiFileUpload = mdiFileUpload
Expand Down Expand Up @@ -192,8 +193,23 @@ export default class TheTopbar extends Mixins(BaseMixin) {
return this.$store.state.gui.uiSettings.boolHideUploadAndPrintButton ?? false
}
get themeObject() {
return themes.find((t) => t.name === this.theme) ?? null
}
get sidebarLogo(): string {
return this.$store.getters['files/getSidebarLogo']
let url = this.$store.getters['files/getSidebarLogo']
if (url !== '' || this.theme === 'mainsail') return url
// if no theme is set, return empty string to load the default logo
if (this.themeObject === null || (this.themeObject.sidebarLogo ?? false) === false) return ''
// return light logo if theme is light and sidebarLogo is set to both
if (this.themeObject.sidebarLogo === 'both' && this.themeMode === 'light')
return `/img/themes/sidebarLogo-${this.theme}-light.svg`
// return dark/generic theme logo
return `/img/themes/sidebarLogo-${this.theme}.svg`
}
get isSvgLogo() {
Expand Down
8 changes: 8 additions & 0 deletions src/components/mixins/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ export default class ThemeMixin extends Vue {
return this.fgColor(alpha, !this.$vuetify.theme.dark)
}

get theme() {
return this.$store.getters['gui/theme']
}

get themeMode() {
return this.$store.state.gui.uiSettings.mode ?? 'dark'
}

get fgColorHi() {
return this.fgColor(0.8)
}
Expand Down
60 changes: 53 additions & 7 deletions src/components/settings/SettingsUiSettingsTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
<div>
<v-card flat>
<v-card-text>
<settings-row
:title="$t('Settings.UiSettingsTab.Mode')"
:sub-title="$t('Settings.UiSettingsTab.ModeDescription')">
<v-select v-model="mode" :items="modes" class="mt-0" hide-details outlined dense />
</settings-row>
<v-divider class="my-2" />
<settings-row
:title="$t('Settings.UiSettingsTab.Theme')"
:sub-title="$t('Settings.UiSettingsTab.ThemeDescription')">
Expand Down Expand Up @@ -267,34 +273,42 @@

<script lang="ts">
import Component from 'vue-class-component'
import { Mixins } from 'vue-property-decorator'
import { Mixins, Watch } from 'vue-property-decorator'
import BaseMixin from '@/components/mixins/base'
import SettingsRow from '@/components/settings/SettingsRow.vue'
import { defaultLogoColor, defaultPrimaryColor, defaultBigThumbnailBackground } from '@/store/variables'
import { defaultLogoColor, defaultPrimaryColor, defaultBigThumbnailBackground, themes } from '@/store/variables'
import { Debounce } from 'vue-debounce-decorator'
import { mdiRestart, mdiTimerOutline } from '@mdi/js'
import { ServerPowerStateDevice } from '@/store/server/power/types'
import ThemeMixin from '@/components/mixins/theme'
@Component({
components: { SettingsRow },
})
export default class SettingsUiSettingsTab extends Mixins(BaseMixin) {
export default class SettingsUiSettingsTab extends Mixins(BaseMixin, ThemeMixin) {
mdiRestart = mdiRestart
mdiTimerOutline = mdiTimerOutline
defaultLogoColor = defaultLogoColor
defaultPrimaryColor = defaultPrimaryColor
defaultBigThumbnailBackground = defaultBigThumbnailBackground
get mode() {
return this.$store.state.gui.uiSettings.mode
}
set mode(newVal) {
this.$store.dispatch('gui/saveSetting', { name: 'uiSettings.mode', value: newVal })
}
get theme() {
return this.$store.state.gui.uiSettings.theme
return this.$store.getters['gui/theme']
}
set theme(newVal) {
set theme(newVal: string) {
this.$store.dispatch('gui/saveSetting', { name: 'uiSettings.theme', value: newVal })
}
get themes() {
get modes() {
return [
{
text: this.$t('Settings.UiSettingsTab.ThemeDark'),
Expand All @@ -307,6 +321,23 @@ export default class SettingsUiSettingsTab extends Mixins(BaseMixin) {
]
}
get themes() {
return themes.map((theme) => {
let text = theme.displayName
if (theme.type === 'community' && theme.name !== 'mainsail')
text = this.$t('Settings.UiSettingsTab.CommunityTheme', { name: theme.displayName }).toString()
if (theme.type === 'vendor')
text = this.$t('Settings.UiSettingsTab.VendorTheme', { name: theme.displayName }).toString()
return {
text,
value: theme.name,
}
})
}
get logoColor() {
return this.$store.state.gui.uiSettings.logo
}
Expand All @@ -315,6 +346,10 @@ export default class SettingsUiSettingsTab extends Mixins(BaseMixin) {
this.$store.dispatch('gui/saveSetting', { name: 'uiSettings.logo', value: newVal })
}
get defaultLogoColor() {
return themes.find((theme) => theme.name === this.theme)?.colorLogo ?? defaultLogoColor
}
get primaryColor() {
return this.$store.state.gui.uiSettings.primary
}
Expand Down Expand Up @@ -565,5 +600,16 @@ export default class SettingsUiSettingsTab extends Mixins(BaseMixin) {
updateBigThumbnailBackground(newVal: any) {
this.bigThumbnailBackground = this.clearColorObject(newVal)
}
@Watch('theme')
onThemeChanged(newVal: string) {
const theme = themes.find((theme) => theme.name === newVal)
// stop here when no theme was found with this name
if (!theme) return
// update logo color to theme logo color if the theme has a colorLogo
if (theme.colorLogo) this.logoColor = theme.colorLogo
}
}
</script>
8 changes: 6 additions & 2 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1152,6 +1152,7 @@
"BoolBigThumbnailDescription": "Display a large thumbnail in the status panel during a print.",
"BoolHideUploadAndPrintButton": "Hide Upload and Print Button",
"BoolHideUploadAndPrintButtonDescription": "Show or hide the \"Upload and Print\" button in the top bar.",
"CommunityTheme": "Community - {name}",
"ConfirmOnCoolDown": "Require confirm on CoolDown",
"ConfirmOnCoolDownDescription": "Show a confirmation dialog on CoolDown",
"ConfirmOnEmergencyStop": "Require confirm on Emergency Stop",
Expand Down Expand Up @@ -1181,6 +1182,8 @@
"Logo": "Logo",
"ManualProbeDialog": "Manual Probe Helper Dialog",
"ManualProbeDialogDescription": "Display helper dialog for PROBE_CALIBRATE or Z_ENDSTOP_CALIBRATE.",
"Mode": "Mode",
"ModeDescription": "Change the overall look and feel of the application.",
"NavigationStyle": "Navigation style",
"NavigationStyleDescription": "Change navigation appearance",
"NavigationStyleIconsAndText": "Icons + Text",
Expand All @@ -1196,9 +1199,10 @@
"TempchartHeightDescription": "Modify the height of the temperature chart on the Dashboard.",
"Theme": "Theme",
"ThemeDark": "Dark",
"ThemeDescription": "Change the overall look and feel of the application",
"ThemeDescription": "Change the overall look and feel of the application.",
"ThemeLight": "Light",
"UiSettings": "UI-Settings"
"UiSettings": "UI-Settings",
"VendorTheme": "Vendor - {name}"
},
"Update": "update",
"WebcamsTab": {
Expand Down
8 changes: 4 additions & 4 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { DatasetComponent, GridComponent, LegendComponent, TooltipComponent } fr
import 'vue-resize/dist/vue-resize.css'
// @ts-ignore
import VueResize from 'vue-resize'
import { defaultTheme } from './store/variables'
import { defaultMode } from './store/variables'

Vue.config.productionTip = false

Expand Down Expand Up @@ -77,9 +77,9 @@ const initLoad = async () => {
await setAndLoadLocale(file.defaultLocale as string)
}

// Handle theme outside of store init and before vue mount for consistency in dialog
const theme = file.defaultTheme ?? defaultTheme
vuetify.framework.theme.dark = theme !== 'light'
// Handle mode outside store init and before vue mount for consistency in dialog
const mode = file.defaultMode ?? defaultMode
vuetify.framework.theme.dark = mode !== 'light'
} catch (e) {
window.console.error('Failed to load config.json')
window.console.error(e)
Expand Down
12 changes: 11 additions & 1 deletion src/store/gui/getters.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
import { GetterTree } from 'vuex'
import { GuiState } from '@/store/gui/types'
import { GuiMacrosStateMacrogroup } from '@/store/gui/macros/types'
import { allDashboardPanels } from '@/store/variables'
import { allDashboardPanels, defaultTheme, themes } from '@/store/variables'
import { Theme } from '@/store/types'

// eslint-disable-next-line
export const getters: GetterTree<GuiState, any> = {
theme: (state) => {
const theme = state.uiSettings.theme

// return defaultTheme, if theme doesnt exists
if (themes.findIndex((tmp: Theme) => tmp.name === theme) === -1) return defaultTheme

return theme
},

getDatasetValue: (state) => (payload: { name: string; type: string }) => {
if (
payload.name in state.view.tempchart.datasetSettings &&
Expand Down
9 changes: 8 additions & 1 deletion src/store/gui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import { Module } from 'vuex'
import { actions } from '@/store/gui/actions'
import { mutations } from '@/store/gui/mutations'
import { getters } from '@/store/gui/getters'
import { defaultTheme, defaultLogoColor, defaultPrimaryColor, defaultBigThumbnailBackground } from '@/store/variables'
import {
defaultTheme,
defaultLogoColor,
defaultPrimaryColor,
defaultBigThumbnailBackground,
defaultMode,
} from '@/store/variables'

// load modules
import { console } from '@/store/gui/console'
Expand Down Expand Up @@ -149,6 +155,7 @@ export const getDefaultState = (): GuiState => {
entries: [],
},
uiSettings: {
mode: defaultMode,
theme: defaultTheme,
logo: defaultLogoColor,
primary: defaultPrimaryColor,
Expand Down
3 changes: 2 additions & 1 deletion src/store/gui/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ export interface GuiState {
presets?: GuiPresetsState
remoteprinters?: GuiRemoteprintersState
uiSettings: {
theme: 'dark' | 'light'
mode: 'dark' | 'light'
theme: string
logo: string
primary: string
displayCancelPrint: boolean
Expand Down
10 changes: 10 additions & 0 deletions src/store/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,13 @@ export interface ConfigJsonInstance {
port?: number
path?: string
}

export interface Theme {
type: 'community' | 'vendor'
name: string
displayName: string
colorLogo?: string
colorPrimary?: string
sidebarLogo?: false | true | 'both' // both means that it also has a light version
sidebarBackground?: false | true | 'both' // both means that it also has a light version
}
13 changes: 12 additions & 1 deletion src/store/variables.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export const defaultTheme = 'dark'
import { Theme } from '@/store/types'

export const defaultMode = 'dark'
export const defaultTheme = 'mainsail'
export const defaultLogoColor = '#D41216'
export const defaultPrimaryColor = '#2196f3'
export const defaultBigThumbnailBackground = '#1e1e1e'
Expand Down Expand Up @@ -139,3 +142,11 @@ export const genericLogfiles = ['klippy', 'moonraker', 'crowsnest', 'mmu', 'sona
* List of all rollover logfiles
*/
export const rolloverLogfiles = ['klipper', 'moonraker']

/*
* List of all Themes
*/
export const themes: Theme[] = [
{ type: 'community', name: 'mainsail', displayName: 'Mainsail', colorLogo: defaultLogoColor },
{ type: 'community', name: 'klipper', displayName: 'Klipper', colorLogo: '#b12f35', sidebarLogo: true },
]

0 comments on commit 5125332

Please sign in to comment.