From d54652264b4b3d4e662c22b6b9773885353d3532 Mon Sep 17 00:00:00 2001 From: qwqcode Date: Thu, 29 Aug 2024 20:56:34 +0800 Subject: [PATCH] feat(ui/sidebar): add dark mode toggle for dashboard (#790) --- ui/artalk-sidebar/src/App.vue | 19 ++++--- .../src/assets/icon-darkmode-off.svg | 1 + .../src/assets/icon-darkmode-on.svg | 1 + .../src/components/AppHeader.vue | 49 ++++++++++++++++++- .../src/components/AppNavigationDesktop.vue | 6 +-- ui/artalk-sidebar/src/global.ts | 12 ++++- ui/artalk-sidebar/src/stores/nav.ts | 16 +++++- ui/artalk-sidebar/src/style.scss | 12 +++++ ui/artalk/src/layer/sidebar-layer.ts | 18 +++---- 9 files changed, 108 insertions(+), 26 deletions(-) create mode 100644 ui/artalk-sidebar/src/assets/icon-darkmode-off.svg create mode 100644 ui/artalk-sidebar/src/assets/icon-darkmode-on.svg diff --git a/ui/artalk-sidebar/src/App.vue b/ui/artalk-sidebar/src/App.vue index ba1b5220..8982b442 100644 --- a/ui/artalk-sidebar/src/App.vue +++ b/ui/artalk-sidebar/src/App.vue @@ -1,20 +1,18 @@ @@ -65,7 +71,7 @@ const logout = () => { border-bottom: 1px solid var(--at-color-border); @media (min-width: 1024px) { - background: #f6f8fa; + background: var(--at-sidebar-header-bg); } .avatar { @@ -184,5 +190,46 @@ const logout = () => { height: 60px; margin-left: 10px; } + + .dark-mode-toggle { + display: none; + width: 60px; + height: 60px; + margin-left: 10px; + justify-content: center; + align-items: center; + user-select: none; + cursor: pointer; + + @media (min-width: 1024px) { + display: flex; + } + + &:hover { + &::after { + background-color: var(--at-color-bg-grey); + } + } + + &::after { + content: ''; + display: block; + transition: background-color 0.2s; + width: 35px; + height: 35px; + background-repeat: no-repeat; + background-position: center; + background-size: 60%; + border-radius: 50%; + } + + &[data-darkmode='on']::after { + background-image: url('@/assets/icon-darkmode-on.svg'); + } + + &[data-darkmode='off']::after { + background-image: url('@/assets/icon-darkmode-off.svg'); + } + } } diff --git a/ui/artalk-sidebar/src/components/AppNavigationDesktop.vue b/ui/artalk-sidebar/src/components/AppNavigationDesktop.vue index 3cac9361..41c927b5 100644 --- a/ui/artalk-sidebar/src/components/AppNavigationDesktop.vue +++ b/ui/artalk-sidebar/src/components/AppNavigationDesktop.vue @@ -58,7 +58,7 @@ $colorMain: #8ecee2; top: 61px; height: calc(100vh - 61px - 41px); padding: 20px; - background: #fff; + background: var(--at-color-bg); border-right: 1px solid var(--at-color-border); @media (max-width: 1023px) { @@ -82,7 +82,7 @@ $colorMain: #8ecee2; &.active { font-weight: bold; - color: #181a1e; + color: var(--at-color-deep); &::before { content: ''; @@ -99,7 +99,7 @@ $colorMain: #8ecee2; &:hover, &.active { - background: #f4f5f7; + background: var(--at-color-bg-grey-transl); } .icon { diff --git a/ui/artalk-sidebar/src/global.ts b/ui/artalk-sidebar/src/global.ts index a8927d21..ad28ba3f 100644 --- a/ui/artalk-sidebar/src/global.ts +++ b/ui/artalk-sidebar/src/global.ts @@ -37,13 +37,23 @@ function getBootParams() { is_admin: userFromURL.is_admin || false, } + let darkMode: boolean + if (p.get('darkMode') != null) { + darkMode = p.get('darkMode') == '1' + } else { + darkMode = + localStorage.getItem('ATK_SIDEBAR_DARK_MODE') != null + ? localStorage.getItem('ATK_SIDEBAR_DARK_MODE') == '1' + : window.matchMedia('(prefers-color-scheme: dark)').matches + } + return { user, pageKey: p.get('pageKey') || '', site: p.get('site') || '', view: p.get('view') || '', viewParams: null, - darkMode: p.get('darkMode') === '1', + darkMode, } } diff --git a/ui/artalk-sidebar/src/stores/nav.ts b/ui/artalk-sidebar/src/stores/nav.ts index 625f77e0..0e14e363 100644 --- a/ui/artalk-sidebar/src/stores/nav.ts +++ b/ui/artalk-sidebar/src/stores/nav.ts @@ -1,6 +1,6 @@ import { defineStore } from 'pinia' import type { ArtalkType } from 'artalk' -import { artalk } from '../global' +import { artalk, bootParams, getArtalk } from '@/global' import { useMobileWidth } from '@/hooks/MobileWidth' type TabsObj = { [name: string]: string } @@ -20,6 +20,14 @@ export const useNavStore = defineStore('nav', () => { const isPageLoading = ref(false) const scrollableArea = ref(null) + const darkMode = ref(bootParams.darkMode) + watch(darkMode, (val) => { + getArtalk()?.setDarkMode(val) + if (val != window.matchMedia('(prefers-color-scheme: dark)').matches) + localStorage.setItem('ATK_SIDEBAR_DARK_MODE', val ? '1' : '0') + else localStorage.removeItem('ATK_SIDEBAR_DARK_MODE') // enable auto switch + }) + const updateTabs = (aTabs: TabsObj, activeTab?: string) => { tabs.value = aTabs if (activeTab) curtTab.value = activeTab @@ -68,6 +76,10 @@ export const useNavStore = defineStore('nav', () => { searchResetEvent.value = searchResetEvt } + const toggleDarkMode = () => { + darkMode.value = !darkMode.value + } + useRouter().beforeEach((to, from) => { isSearchEnabled.value = false }) @@ -96,5 +108,7 @@ export const useNavStore = defineStore('nav', () => { searchResetEvent, enableSearch, isMobile, + darkMode, + toggleDarkMode, } }) diff --git a/ui/artalk-sidebar/src/style.scss b/ui/artalk-sidebar/src/style.scss index 2e9a3596..57a07f7f 100644 --- a/ui/artalk-sidebar/src/style.scss +++ b/ui/artalk-sidebar/src/style.scss @@ -11,6 +11,18 @@ body { font-family: $font-family; } +.artalk { + --at-sidebar-header-bg: #f6f8fa; +} + +.artalk.atk-dark-mode { + --at-sidebar-header-bg: #1e1e1e; + + .atk-comment-wrap.atk-openable:hover { + background: #1e1e1e; + } +} + .atk-sidebar { .atk-list { .atk-main-editor { diff --git a/ui/artalk/src/layer/sidebar-layer.ts b/ui/artalk/src/layer/sidebar-layer.ts index 111f401b..3b241af7 100644 --- a/ui/artalk/src/layer/sidebar-layer.ts +++ b/ui/artalk/src/layer/sidebar-layer.ts @@ -26,12 +26,12 @@ export default class SidebarLayer extends Component { // event this.ctx.on('user-changed', () => { - this.refreshOnShow = true + this.refreshWhenShow = true }) } - /** Refresh iFrame on show */ - private refreshOnShow = true + /** Refresh iFrame when show */ + private refreshWhenShow = true /** Animation timer */ private animTimer?: any = undefined @@ -45,8 +45,8 @@ export default class SidebarLayer extends Component { this.layer!.show() // init iframe - if (this.refreshOnShow) { - this.refreshOnShow = false + if (this.refreshWhenShow) { + this.refreshWhenShow = false this.$iframeWrap.innerHTML = '' this.$iframe = this.createIframe(conf.view) this.$iframeWrap.append(this.$iframe) @@ -57,9 +57,7 @@ export default class SidebarLayer extends Component { if (this.getDarkMode() !== iFrameSrc.includes('&darkMode=1')) { this.iframeLoad( $iframe, - this.getDarkMode() - ? iFrameSrc.concat('&darkMode=1') - : iFrameSrc.replace('&darkMode=1', ''), + iFrameSrc.replace(/&darkMode=\d/, `&darkMode=${Number(this.getDarkMode())}`), ) } } @@ -96,7 +94,7 @@ export default class SidebarLayer extends Component { }) ).data if (data.is_admin && !data.is_login) { - this.refreshOnShow = true + this.refreshWhenShow = true // show checker layer this.ctx.checkAdmin({ @@ -155,7 +153,7 @@ export default class SidebarLayer extends Component { } if (view) query.view = view - if (this.getDarkMode()) query.darkMode = '1' + query.darkMode = this.getDarkMode() ? '1' : '0' const urlParams = new URLSearchParams(query) this.iframeLoad($iframe, `${baseURL}?${urlParams.toString()}`)