feat: add support for build-in themes and add a Klipper theme (#1859)
This commit is contained in:
parent
bee05b3519
commit
6fd32511dc
@ -1,6 +1,7 @@
|
||||
{
|
||||
"defaultLocale": "en",
|
||||
"defaultTheme": "dark",
|
||||
"defaultMode": "dark",
|
||||
"defaultTheme": "mainsail",
|
||||
"hostname": null,
|
||||
"port": null,
|
||||
"path": null,
|
||||
|
15
public/img/themes/sidebarLogo-klipper.svg
Normal file
15
public/img/themes/sidebarLogo-klipper.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.41421" viewBox="0 0 768 624" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m30 34v140l260 255 115-5h80l245-250v-140h-10l-335 340-345-340z" fill="#3c4b5a"/>
|
||||
<path d="m140 418-58 57 1 25v19l57 57h27v-1l-76-78.5 76-77.5v-1z" fill="#3c4b5a" stroke-width="1.03281"/>
|
||||
<path d="m191 418h24v158h-24z" style="clip-rule:evenodd;fill:#3c4b5a;fill-rule:nonzero;stroke-width:1.03281;stroke-linejoin:round;stroke-miterlimit:1.41421"/>
|
||||
<g fill="#3c4b5a">
|
||||
<path d="m21 426h30.015202v153h-30.015202z" fill-rule="nonzero" transform="matrix(1.03281 0 0 1.0326797 24.31099 -21.921569)"/>
|
||||
<path d="m247 469h24v107h-24z" stroke-width="1.196096"/>
|
||||
<path d="m371.98888 521.45953c0-10.8903-2.45322-19.36013-7.36207-25.4107-4.90765-6.05057-12.80741-9.07465-23.69929-9.07465-2.28639 0-4.6724.10081-7.16044.30241-2.48802.20161-4.94125.63842-7.36207 1.31044v60.29926c2.15197 1.47844 5.00967 2.85727 8.57188 4.13411 3.56342 1.27684 7.36208 1.91526 11.39597 1.91526 8.87554 0 15.36385-3.02409 19.46496-9.07465 4.10111-6.05057 6.15106-14.18439 6.15106-24.40148zm25.01112-.40321c0 8.06663-1.04178 15.46123-3.12654 22.18261-2.08476 6.72259-5.07568 12.50434-8.97515 17.34409-3.89947 4.83973-8.77472 8.60423-14.62333 11.29351-5.8498 2.68927-12.47254 4.0333-19.86702 4.0333-4.97606 0-9.54765-.60481-13.71597-1.81445-4.16832-1.21082-7.59731-2.55487-10.28698-4.03451v40.93913h-24.40501v-139.35264c4.97486-1.34524 11.09352-2.62207 18.35477-3.83171 7.26125-1.21083 14.92578-1.81565 22.99357-1.81565 8.33664 0 15.79953 1.27684 22.38866 3.83171 6.58794 2.55487 12.20251 6.21857 16.84131 10.9911s8.20221 10.5543 10.69024 17.34408c2.48684 6.78859 3.73145 14.4184 3.73145 22.88943z" fill-rule="nonzero" stroke-width="1.20012"/>
|
||||
<path d="m492.9892 521.45953c0-10.8903-2.4544-19.36013-7.36198-25.4107-4.90759-6.05057-12.80725-9.07465-23.699-9.07465-2.28636 0-4.67354.10081-7.16034.30241-2.488.20161-4.94119.63842-7.36197 1.31044v60.29926c2.15074 1.47844 5.00839 2.85727 8.57176 4.13411s7.36198 1.91526 11.39582 1.91526c8.87423 0 15.36246-3.02409 19.46352-9.07465 4.10106-6.05057 6.15219-14.18439 6.15219-24.40148zm25.0108-.40321c0 8.06663-1.04297 15.46123-3.1265 22.18261-2.08474 6.72259-5.07681 12.50434-8.97624 17.34409-3.89942 4.83973-8.77341 8.60423-14.62314 11.29351-5.84854 2.68927-12.47119 4.0333-19.86677 4.0333-4.9748 0-9.54633-.60481-13.7146-1.81445-4.16827-1.21082-7.59722-2.55487-10.28684-4.03451v40.93913h-24.40591v-139.35264c4.97599-1.34524 11.09337-2.62207 18.35454-3.83171 7.26115-1.21083 14.92558-1.81565 22.99328-1.81565 8.33773 0 15.80052 1.27684 22.38838 3.83171 6.58905 2.55487 12.20235 6.21857 16.84229 10.9911 4.63875 4.77253 8.20211 10.5543 10.69012 17.34408 2.48679 6.78859 3.73139 14.4184 3.73139 22.88943z" fill-rule="nonzero" stroke-width="1.200112"/>
|
||||
<path d="m537 521.00591c0-9.30123 1.37492-17.45591 4.12595-24.46521 2.74984-7.00931 6.40632-12.83838 10.96942-17.48839 4.56191-4.65121 9.79451-8.15587 15.699-10.51396 5.90328-2.3593 11.94191-3.53835 18.11469-3.53835 14.49054 0 25.79531 4.51527 33.91308 13.5458 8.11897 9.03174 12.17786 22.51016 12.17786 40.43768 0 1.34749-.0335 2.8646-.1006 4.54896-.0671 1.68555-.16768 3.20147-.30182 4.55015h-69.43942c.67068 8.49155 3.65646 15.06171 8.95733 19.71294 5.29967 4.65001 12.98149 6.97562 23.04547 6.97562 5.9033 0 11.30477-.5402 16.20202-1.61818 4.89726-1.07799 8.75493-2.22335 11.57304-3.43729l3.22052 20.01612c-1.34138.67495-3.18699 1.38237-5.53561 2.12349-2.34742.74112-5.03139 1.41485-8.05071 2.02242-3.01811.60637-6.27217 1.11168-9.76096 1.51592-3.4888.40425-7.04467.60637-10.66761.60637-9.25915 0-17.30985-1.38116-24.15211-4.14471-6.84346-2.76354-12.47968-6.60507-16.90744-11.52457-4.42778-4.91951-7.71537-10.7161-9.86278-17.38854-2.14622-6.67123-3.21932-13.98373-3.21932-21.93627zm70.64548-10.91821c0-3.36991-.46948-6.57138-1.40846-9.60442-.93897-3.03303-2.31508-5.66063-4.12595-7.88517-1.81207-2.22335-4.02535-3.97627-6.64226-5.25638-2.61689-1.28131-5.73561-1.92137-9.35855-1.92137-3.75708 0-7.04466.70743-9.86278 2.1235-2.8181 1.41484-5.19906 3.26883-7.14527 5.55956-1.94501 2.29192-3.45526 4.92071-4.52837 7.88516-1.0731 2.96567-1.81206 5.99871-2.21447 9.09912z" fill-rule="nonzero" stroke-width="1.200384"/>
|
||||
<path d="m717.7869 492.26931c-2.00567-.67394-4.78138-1.38159-8.32593-2.12292-3.54453-.74135-7.65736-1.11202-12.33845-1.11202-2.67542 0-5.51799.26957-8.52649.80874-3.0097.53915-5.11684 1.01091-6.32025 1.41528v84.74161h-24.27578v-100.71896c4.68109-1.75347 10.53216-3.40464 17.55439-4.95472 7.02224-1.55128 14.81332-2.32632 23.37323-2.32632 1.60574 0 3.47769.10109 5.61827.30328 2.13938.20218 4.27996.47176 6.42053.80873 2.13939.33697 4.2131.74255 6.21878 1.21431 2.00687.47176 3.6114.90983 4.8148 1.3142z" fill-rule="nonzero" stroke-width="1.198655"/>
|
||||
</g>
|
||||
<path d="m83.041215 464.92108h.001l-.04211 27.03092h11l73.000005-74v14.301l-63 64.199 63 65.5v14l-73.000002-75h-13c-2 0-4-2-4-4h-2v-33h2c0-1 1-2 3.00004-2 1.99996 0 2.99996 1 2.99996 2z" fill="var(--color-logo, #b12f35)" transform="translate(-.000108 .048)"/>
|
||||
<path d="m259 424h101v-65l-330-325h60l295 290 285-290h60l-320 325v84.99783c0 5.00217-5 10.00217-10 10.00217h-141c-12 0-14-10-14-15s2-15 14-15z" fill="var(--color-logo, #b12f35)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 5.2 KiB |
162
src/App.vue
162
src/App.vue
@ -81,10 +81,6 @@ export default class App extends Mixins(BaseMixin, ThemeMixin) {
|
||||
return this.$store.getters['getTitle']
|
||||
}
|
||||
|
||||
get mainBackground(): string {
|
||||
return this.$store.getters['files/getMainBackground']
|
||||
}
|
||||
|
||||
get naviDrawer(): boolean {
|
||||
return this.$store.state.naviDrawer
|
||||
}
|
||||
@ -98,8 +94,8 @@ export default class App extends Mixins(BaseMixin, ThemeMixin) {
|
||||
paddingLeft: '0',
|
||||
}
|
||||
|
||||
if (this.mainBackground !== null) {
|
||||
style.backgroundImage = 'url(' + this.mainBackground + ')'
|
||||
if (this.mainBgImage !== null) {
|
||||
style.backgroundImage = 'url(' + this.mainBgImage + ')'
|
||||
}
|
||||
|
||||
// overwrite padding left for the sidebar
|
||||
@ -127,8 +123,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 {
|
||||
@ -226,8 +222,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
|
||||
|
||||
@ -235,73 +231,105 @@ export default class App extends Mixins(BaseMixin, ThemeMixin) {
|
||||
doc.className = dark ? 'theme--dark' : 'theme--light'
|
||||
}
|
||||
|
||||
drawFavicon(val: number): void {
|
||||
async drawFavicon(val: number): Promise<void> {
|
||||
const favicon16: HTMLLinkElement | null = document.querySelector("link[rel*='icon'][sizes='16x16']")
|
||||
const favicon32: HTMLLinkElement | null = document.querySelector("link[rel*='icon'][sizes='32x32']")
|
||||
|
||||
if (favicon16 && favicon32) {
|
||||
if (this.progressAsFavicon && this.printerIsPrinting) {
|
||||
let faviconSize = 64
|
||||
// if no favicon is found, stop
|
||||
if (!favicon16 || !favicon32) return
|
||||
|
||||
let canvas = document.createElement('canvas')
|
||||
canvas.width = faviconSize
|
||||
canvas.height = faviconSize
|
||||
const context = canvas.getContext('2d')
|
||||
const centerX = canvas.width / 2
|
||||
const centerY = canvas.height / 2
|
||||
const radius = 32
|
||||
// if progressAsFavicon is enabled and the printer is printing, draw the progress as favicon
|
||||
if (this.progressAsFavicon && this.printerIsPrinting) {
|
||||
let faviconSize = 64
|
||||
|
||||
// draw the grey circle
|
||||
if (context) {
|
||||
context.beginPath()
|
||||
context.moveTo(centerX, centerY)
|
||||
context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false)
|
||||
context.closePath()
|
||||
context.fillStyle = '#ddd'
|
||||
context.fill()
|
||||
context.strokeStyle = 'rgba(200, 208, 218, 0.66)'
|
||||
context.stroke()
|
||||
let canvas = document.createElement('canvas')
|
||||
canvas.width = faviconSize
|
||||
canvas.height = faviconSize
|
||||
const context = canvas.getContext('2d')
|
||||
const centerX = canvas.width / 2
|
||||
const centerY = canvas.height / 2
|
||||
const radius = 32
|
||||
|
||||
// draw the green circle based on percentage
|
||||
let startAngle = 1.5 * Math.PI
|
||||
let endAngle = 0
|
||||
let unitValue = (Math.PI - 0.5 * Math.PI) / 25
|
||||
if (val >= 0 && val <= 25) endAngle = startAngle + val * unitValue
|
||||
else if (val > 25 && val <= 50) endAngle = startAngle + val * unitValue
|
||||
else if (val > 50 && val <= 75) endAngle = startAngle + val * unitValue
|
||||
else if (val > 75 && val <= 100) endAngle = startAngle + val * unitValue
|
||||
if (!context) return
|
||||
|
||||
context.beginPath()
|
||||
context.moveTo(centerX, centerY)
|
||||
context.arc(centerX, centerY, radius, startAngle, endAngle, false)
|
||||
context.closePath()
|
||||
context.fillStyle = this.logoColor
|
||||
context.fill()
|
||||
// draw the grey circle
|
||||
context.beginPath()
|
||||
context.moveTo(centerX, centerY)
|
||||
context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false)
|
||||
context.closePath()
|
||||
context.fillStyle = '#ddd'
|
||||
context.fill()
|
||||
context.strokeStyle = 'rgba(200, 208, 218, 0.66)'
|
||||
context.stroke()
|
||||
|
||||
favicon16.href = canvas.toDataURL('image/png')
|
||||
favicon32.href = canvas.toDataURL('image/png')
|
||||
}
|
||||
} else if (this.customFavicons) {
|
||||
const [favicon16Path, favicon32Path] = this.customFavicons
|
||||
favicon16.href = favicon16Path
|
||||
favicon32.href = favicon32Path
|
||||
} else {
|
||||
const favicon =
|
||||
'data:image/svg+xml;base64,' +
|
||||
window.btoa(`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 599.38 523.11" xml:space="preserve">
|
||||
<g>
|
||||
<path style="fill:${this.logoColor};" d="M382.29,142.98L132.98,522.82L0,522.68L344.3,0l0,0C352.18,49.06,365.2,97.68,382.29,142.98"/>
|
||||
<path style="fill:${this.logoColor};" d="M413.28,213.54L208.5,522.92l132.94,0.19l135.03-206.33l0,0C452.69,284.29,431.53,249.77,413.28,213.54 L413.28,213.54"/>
|
||||
<path style="fill:${this.logoColor};" d="M599.38,447.69l-49.25,75.42L417,522.82l101.6-153.67l0,0C543.48,397.35,570.49,423.61,599.38,447.69 L599.38,447.69z"/>
|
||||
</g>
|
||||
</svg>
|
||||
`)
|
||||
// draw the green circle based on percentage
|
||||
let startAngle = 1.5 * Math.PI
|
||||
let endAngle = 0
|
||||
let unitValue = (Math.PI - 0.5 * Math.PI) / 25
|
||||
if (val >= 0 && val <= 25) endAngle = startAngle + val * unitValue
|
||||
else if (val > 25 && val <= 50) endAngle = startAngle + val * unitValue
|
||||
else if (val > 50 && val <= 75) endAngle = startAngle + val * unitValue
|
||||
else if (val > 75 && val <= 100) endAngle = startAngle + val * unitValue
|
||||
|
||||
favicon16.href = favicon
|
||||
favicon32.href = favicon
|
||||
}
|
||||
context.beginPath()
|
||||
context.moveTo(centerX, centerY)
|
||||
context.arc(centerX, centerY, radius, startAngle, endAngle, false)
|
||||
context.closePath()
|
||||
context.fillStyle = this.logoColor
|
||||
context.fill()
|
||||
|
||||
favicon16.href = canvas.toDataURL('image/png')
|
||||
favicon32.href = canvas.toDataURL('image/png')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// if custom favicons are set, use them
|
||||
if (this.customFavicons) {
|
||||
const [favicon16Path, favicon32Path] = this.customFavicons
|
||||
favicon16.href = favicon16Path
|
||||
favicon32.href = favicon32Path
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// if a theme sidebar logo is set, use it
|
||||
if ((this.theme?.logo?.show ?? false) && this.sidebarLogo.endsWith('.svg')) {
|
||||
const response = await fetch(this.sidebarLogo)
|
||||
if (!response.ok) return
|
||||
|
||||
const text = await response.text()
|
||||
const modifiedSvg = text.replace(/fill="var\(--color-logo, #[0-9a-fA-F]{6}\)"/g, `fill="${this.logoColor}"`)
|
||||
|
||||
const blob = new Blob([modifiedSvg], { type: 'image/svg+xml' })
|
||||
const reader = new FileReader()
|
||||
|
||||
reader.onloadend = () => {
|
||||
const base64data = reader.result as string
|
||||
favicon16.href = base64data
|
||||
favicon32.href = base64data
|
||||
}
|
||||
|
||||
reader.readAsDataURL(blob)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// if no custom favicon is set, use the default one
|
||||
const favicon =
|
||||
'data:image/svg+xml;base64,' +
|
||||
window.btoa(`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 599.38 523.11" xml:space="preserve">
|
||||
<g>
|
||||
<path style="fill:${this.logoColor};" d="M382.29,142.98L132.98,522.82L0,522.68L344.3,0l0,0C352.18,49.06,365.2,97.68,382.29,142.98"/>
|
||||
<path style="fill:${this.logoColor};" d="M413.28,213.54L208.5,522.92l132.94,0.19l135.03-206.33l0,0C452.69,284.29,431.53,249.77,413.28,213.54 L413.28,213.54"/>
|
||||
<path style="fill:${this.logoColor};" d="M599.38,447.69l-49.25,75.42L417,522.82l101.6-153.67l0,0C543.48,397.35,570.49,423.61,599.38,447.69 L599.38,447.69z"/>
|
||||
</g>
|
||||
</svg>
|
||||
`)
|
||||
|
||||
favicon16.href = favicon
|
||||
favicon32.href = favicon
|
||||
}
|
||||
|
||||
@Watch('customFavicons')
|
||||
|
@ -3,7 +3,7 @@
|
||||
<v-app-bar app elevate-on-scroll :height="topbarHeight" class="topbar pa-0" clipped-left>
|
||||
<v-app-bar-nav-icon tile @click.stop="naviDrawer = !naviDrawer" />
|
||||
<router-link to="/">
|
||||
<inline-svg v-if="sidebarLogo && isSvgLogo" :src="'http:' + sidebarLogo" :class="logoClasses" />
|
||||
<inline-svg v-if="sidebarLogo && isSvgLogo" :src="sidebarLogo" :class="logoClasses" />
|
||||
<img v-else-if="sidebarLogo" :src="sidebarLogo" :class="logoClasses" alt="Logo" />
|
||||
<mainsail-logo v-else :color="logoColor" :class="logoClasses" router to="/" :ripple="false" />
|
||||
</router-link>
|
||||
@ -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
|
||||
@ -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
|
||||
@ -188,12 +189,8 @@ export default class TheTopbar extends Mixins(BaseMixin) {
|
||||
return this.$store.state.gui.uiSettings.boolHideUploadAndPrintButton ?? false
|
||||
}
|
||||
|
||||
get sidebarLogo(): string {
|
||||
return this.$store.getters['files/getSidebarLogo']
|
||||
}
|
||||
|
||||
get isSvgLogo() {
|
||||
return this.sidebarLogo.includes('.svg?timestamp=')
|
||||
return this.sidebarLogo.includes('.svg?timestamp=') || this.sidebarLogo.endsWith('.svg')
|
||||
}
|
||||
|
||||
get logoColor(): string {
|
||||
|
@ -12,6 +12,18 @@ export default class ThemeMixin extends Vue {
|
||||
return this.fgColor(alpha, !this.$vuetify.theme.dark)
|
||||
}
|
||||
|
||||
get themeName() {
|
||||
return this.$store.getters['gui/theme']
|
||||
}
|
||||
|
||||
get theme() {
|
||||
return this.$store.getters['gui/getTheme']
|
||||
}
|
||||
|
||||
get themeMode() {
|
||||
return this.$store.state.gui.uiSettings.mode ?? 'dark'
|
||||
}
|
||||
|
||||
get fgColorHi() {
|
||||
return this.fgColor(0.8)
|
||||
}
|
||||
@ -42,6 +54,40 @@ export default class ThemeMixin extends Vue {
|
||||
}
|
||||
|
||||
get sidebarBgImage() {
|
||||
if (this.theme.sidebarBackground?.show) {
|
||||
if (this.theme.sidebarBackground?.light && this.themeMode === 'light')
|
||||
return `/img/themes/sidebarBackground-${this.themeName}-light.png`
|
||||
|
||||
return `/img/themes/sidebarBackground-${this.themeName}.png`
|
||||
}
|
||||
|
||||
return this.$vuetify.theme.dark ? '/img/sidebar-background.svg' : '/img/sidebar-background-light.svg'
|
||||
}
|
||||
|
||||
get sidebarLogo(): string {
|
||||
const url = this.$store.getters['files/getSidebarLogo']
|
||||
if (url !== '' || this.themeName === 'mainsail') return url
|
||||
|
||||
// if no theme is set, return empty string to load the default logo
|
||||
if (!(this.theme.logo?.show ?? false)) return ''
|
||||
|
||||
// return light logo if theme is light and sidebarLogo is set to both
|
||||
if (this.theme.logo?.light && this.themeMode === 'light')
|
||||
return `/img/themes/sidebarLogo-${this.themeName}-light.svg`
|
||||
|
||||
// return dark/generic theme logo
|
||||
return `/img/themes/sidebarLogo-${this.themeName}.svg`
|
||||
}
|
||||
|
||||
get mainBgImage() {
|
||||
const url = this.$store.getters['files/getMainBackground']
|
||||
if (url || this.themeName === 'mainsail') return url
|
||||
|
||||
if (!this.theme.mainBackground?.show) return null
|
||||
|
||||
if (this.theme.mainBackground?.light && this.themeMode === 'light')
|
||||
return `/img/themes/mainBackground-${this.themeName}-light.png`
|
||||
|
||||
return `/img/themes/mainBackground-${this.themeName}.png`
|
||||
}
|
||||
}
|
||||
|
@ -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')">
|
||||
@ -276,34 +282,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 theme() {
|
||||
return this.$store.state.gui.uiSettings.theme
|
||||
get mode() {
|
||||
return this.$store.state.gui.uiSettings.mode
|
||||
}
|
||||
|
||||
set theme(newVal) {
|
||||
set mode(newVal) {
|
||||
this.$store.dispatch('gui/saveSetting', { name: 'uiSettings.mode', value: newVal })
|
||||
}
|
||||
|
||||
get theme() {
|
||||
return this.$store.getters['gui/theme']
|
||||
}
|
||||
|
||||
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'),
|
||||
@ -316,6 +330,15 @@ export default class SettingsUiSettingsTab extends Mixins(BaseMixin) {
|
||||
]
|
||||
}
|
||||
|
||||
get themes() {
|
||||
return themes.map((theme) => {
|
||||
return {
|
||||
text: theme.displayName,
|
||||
value: theme.name,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
get logoColor() {
|
||||
return this.$store.state.gui.uiSettings.logo
|
||||
}
|
||||
@ -324,6 +347,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.themeName)?.colorLogo ?? defaultLogoColor
|
||||
}
|
||||
|
||||
get primaryColor() {
|
||||
return this.$store.state.gui.uiSettings.primary
|
||||
}
|
||||
@ -582,5 +609,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>
|
||||
|
@ -1221,6 +1221,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",
|
||||
@ -1238,7 +1240,7 @@
|
||||
"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": "Customizes the branding of the interface.",
|
||||
"ThemeLight": "Light",
|
||||
"UiSettings": "UI-Settings"
|
||||
},
|
||||
|
@ -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
|
||||
|
||||
@ -80,9 +80,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)
|
||||
|
@ -1,10 +1,24 @@
|
||||
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): string => {
|
||||
const theme = state.uiSettings.theme
|
||||
|
||||
// return defaultTheme, if theme doesnt exists
|
||||
if (themes.findIndex((tmp: Theme) => tmp.name === theme) === -1) return defaultTheme
|
||||
|
||||
return theme
|
||||
},
|
||||
|
||||
getTheme: (state, getters): Theme => {
|
||||
return themes.find((theme: Theme) => theme.name === getters.theme) ?? themes[0]
|
||||
},
|
||||
|
||||
getDatasetValue: (state) => (payload: { name: string; type: string }) => {
|
||||
if (
|
||||
payload.name in state.view.tempchart.datasetSettings &&
|
||||
|
@ -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'
|
||||
@ -150,6 +156,7 @@ export const getDefaultState = (): GuiState => {
|
||||
entries: [],
|
||||
},
|
||||
uiSettings: {
|
||||
mode: defaultMode,
|
||||
theme: defaultTheme,
|
||||
logo: defaultLogoColor,
|
||||
primary: defaultPrimaryColor,
|
||||
|
@ -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
|
||||
|
@ -38,3 +38,22 @@ export interface ConfigJsonInstance {
|
||||
port?: number
|
||||
path?: string
|
||||
}
|
||||
|
||||
export interface Theme {
|
||||
name: string
|
||||
displayName: string
|
||||
colorLogo: string
|
||||
colorPrimary?: string
|
||||
logo?: {
|
||||
show: boolean
|
||||
light: boolean
|
||||
}
|
||||
sidebarBackground?: {
|
||||
show: boolean
|
||||
light: boolean
|
||||
}
|
||||
mainBackground?: {
|
||||
show: boolean
|
||||
light: boolean
|
||||
}
|
||||
}
|
||||
|
@ -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'
|
||||
@ -140,3 +143,16 @@ 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[] = [
|
||||
{ name: 'mainsail', displayName: 'Mainsail', colorLogo: defaultLogoColor },
|
||||
{
|
||||
name: 'klipper',
|
||||
displayName: 'Klipper',
|
||||
colorLogo: '#b12f35',
|
||||
logo: { show: true, light: false },
|
||||
},
|
||||
]
|
||||
|
Loading…
x
Reference in New Issue
Block a user