forked from vikunja/frontend
feat: add color scheme setting #5
|
@ -21,6 +21,7 @@
|
|||
"@sentry/tracing": "6.14.1",
|
||||
"@sentry/vue": "6.14.1",
|
||||
"@vue/compat": "3.2.21",
|
||||
"@vueuse/core": "^6.8.0",
|
||||
"bulma-css-variables": "^0.9.33",
|
||||
"camel-case": "4.1.2",
|
||||
"codemirror": "5.63.3",
|
||||
|
|
|
@ -36,6 +36,7 @@ import ContentLinkShare from './components/home/contentLinkShare'
|
|||
import ContentNoAuth from './components/home/contentNoAuth'
|
||||
import {setLanguage} from './i18n'
|
||||
import AccountDeleteService from '@/services/accountDelete'
|
||||
import {useColorScheme} from '@/composables/useColorScheme'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'app',
|
||||
|
@ -63,6 +64,9 @@ export default defineComponent({
|
|||
|
||||
setLanguage()
|
||||
},
|
||||
setup() {
|
||||
useColorScheme()
|
||||
},
|
||||
created() {
|
||||
// Make sure to always load the home route when running with electron
|
||||
if (this.$route.fullPath.endsWith('frontend/index.html')) {
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import {computed, watch, readonly} from 'vue'
|
||||
import {useStorage, createSharedComposable, ColorSchemes, usePreferredColorScheme, tryOnMounted} from '@vueuse/core'
|
||||
|
||||
const STORAGE_KEY = 'color-scheme'
|
||||
|
||||
const DEFAULT_COLOR_SCHEME_SETTING: ColorSchemes = 'light'
|
||||
|
||||
const CLASS_DARK = 'dark'
|
||||
const CLASS_LIGHT = 'light'
|
||||
|
||||
// This is built upon the vueuse useDark
|
||||
// Main differences:
|
||||
// - usePreferredColorScheme
|
||||
// - doesn't allow setting via the `isDark` ref.
|
||||
// - instead the store is exposed
|
||||
// - value is synced via `createSharedComposable`
|
||||
// https://github.com/vueuse/vueuse/blob/main/packages/core/useDark/index.ts
|
||||
export const useColorScheme = createSharedComposable(() => {
|
||||
const store = useStorage<ColorSchemes>(STORAGE_KEY, DEFAULT_COLOR_SCHEME_SETTING)
|
||||
|
||||
const preferredColorScheme = usePreferredColorScheme()
|
||||
|
||||
const isDark = computed<boolean>(() => {
|
||||
if (store.value !== 'auto') {
|
||||
return store.value === 'dark'
|
||||
}
|
||||
|
||||
const autoColorScheme = preferredColorScheme.value === 'no-preference'
|
||||
? DEFAULT_COLOR_SCHEME_SETTING
|
||||
: preferredColorScheme.value
|
||||
return autoColorScheme === 'dark'
|
||||
})
|
||||
|
||||
function onChanged(v: boolean) {
|
||||
const el = window?.document.querySelector('html')
|
||||
el?.classList.toggle(CLASS_DARK, v)
|
||||
el?.classList.toggle(CLASS_LIGHT, !v)
|
||||
}
|
||||
|
||||
watch(isDark, onChanged, { flush: 'post' })
|
||||
|
||||
tryOnMounted(() => onChanged(isDark.value))
|
||||
|
||||
return {
|
||||
store,
|
||||
isDark: readonly(isDark),
|
||||
}
|
||||
})
|
|
@ -102,6 +102,15 @@
|
|||
"disabled": "Disabled",
|
||||
"todoist": "Todoist",
|
||||
"vikunja": "Vikunja"
|
||||
},
|
||||
"appearance": {
|
||||
"title": "Color Scheme",
|
||||
"setSuccess": "Saved change of color scheme to {colorScheme}",
|
||||
"colorScheme": {
|
||||
"light": "Light",
|
||||
"system": "System",
|
||||
"dark": "Dark"
|
||||
}
|
||||
}
|
||||
},
|
||||
"deletion": {
|
||||
|
|
|
@ -81,7 +81,7 @@
|
|||
--card-border-color: var(--grey-200);
|
||||
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
&.dark {
|
||||
// Light mode colours reversed for dark mode
|
||||
--grey-900-hsl: 210, 20%, 98%;
|
||||
--grey-900: hsl(var(--grey-900-hsl));
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
--shadow-lg: 0 15px 25px hsla(var(--grey-500-hsl), .12),
|
||||
0 5px 10px hsla(var(--grey-500-hsl), .05);
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
&.dark {
|
||||
// Even darker shadows for dark mode
|
||||
--shadow-xs: 0 1px 3px hsla(var(--grey-100-hsl),.12),
|
||||
0 1px 2px hsla(var(--grey-100-hsl), .24);
|
||||
|
|
|
@ -90,6 +90,21 @@
|
|||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="is-flex is-align-items-center">
|
||||
<span>
|
||||
{{ $t('user.settings.appearance.title') }}
|
||||
</span>
|
||||
<div class="select ml-2">
|
||||
<select v-model="activeColorSchemeSetting">
|
||||
<!-- TODO: use the Vikunja logo in color scheme as option buttons -->
|
||||
<option v-for="(title, schemeId) in colorSchemeSettings" :key="schemeId" :value="schemeId">
|
||||
{{ title }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<x-button
|
||||
:loading="userSettingsService.loading"
|
||||
|
@ -102,6 +117,9 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {computed, watch} from 'vue'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
|
||||
import {playSoundWhenDoneKey} from '@/helpers/playPop'
|
||||
import {availableLanguages, saveLanguage, getCurrentLanguage} from '@/i18n'
|
||||
import {getQuickAddMagicMode, setQuickAddMagicMode} from '@/helpers/quickAddMagicMode'
|
||||
|
@ -110,6 +128,29 @@ import {PrefixMode} from '@/modules/parseTaskText'
|
|||
import ListSearch from '@/components/tasks/partials/listSearch'
|
||||
import {createRandomID} from '@/helpers/randomId'
|
||||
import {playPop} from '@/helpers/playPop'
|
||||
import {useColorScheme} from '@/composables/useColorScheme'
|
||||
import {success} from '@/message'
|
||||
|
||||
function useColorSchemeSetting() {
|
||||
const { t } = useI18n()
|
||||
const colorSchemeSettings = computed(() => ({
|
||||
light: t('user.settings.appearance.colorScheme.light'),
|
||||
auto: t('user.settings.appearance.colorScheme.system'),
|
||||
dark: t('user.settings.appearance.colorScheme.dark'),
|
||||
}))
|
||||
|
||||
const {store} = useColorScheme()
|
||||
watch(store, (schemeId) => {
|
||||
success({message: t('user.settings.appearance.setSuccess', {
|
||||
colorScheme: colorSchemeSettings.value[schemeId],
|
||||
})})
|
||||
})
|
||||
|
||||
return {
|
||||
colorSchemeSettings,
|
||||
activeColorSchemeSetting: store,
|
||||
}
|
||||
}
|
||||
|
||||
function getPlaySoundWhenDoneSetting() {
|
||||
return localStorage.getItem(playSoundWhenDoneKey) === 'true' || localStorage.getItem(playSoundWhenDoneKey) === null
|
||||
|
@ -141,6 +182,13 @@ export default {
|
|||
return this.$store.getters['lists/getListById'](this.settings.defaultListId)
|
||||
},
|
||||
},
|
||||
|
||||
setup() {
|
||||
return {
|
||||
...useColorSchemeSetting(),
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.setTitle(`${this.$t('user.settings.general.title')} - ${this.$t('user.settings.title')}`)
|
||||
},
|
||||
|
|
20
yarn.lock
20
yarn.lock
|
@ -2844,6 +2844,21 @@
|
|||
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.21.tgz#4cd80c0e62cf65a7adab2449e86b6f0cb33a130b"
|
||||
integrity sha512-5EQmIPK6gw4UVYUbM959B0uPsJ58+xoMESCZs3N89XyvJ9e+fX4pqEPrOGV8OroIk3SbEvJcC+eYc8BH9JQrHA==
|
||||
|
||||
"@vueuse/core@^6.8.0":
|
||||
version "6.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-6.8.0.tgz#dc004bc30031e053e9ef5011203c4a80f00986ed"
|
||||
integrity sha512-C6KMBus29L/mVtA5eK26WAqj6tyPlugrKaPLi2uLtbV//BHjbxe1uo3gVXCc5SwouDEdc7zswlGPw/l0/++NRg==
|
||||
dependencies:
|
||||
"@vueuse/shared" "6.8.0"
|
||||
vue-demi "*"
|
||||
|
||||
"@vueuse/shared@6.8.0":
|
||||
version "6.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-6.8.0.tgz#50713b770941293e557a4eae9424cad2aad44a85"
|
||||
integrity sha512-+YjehQ8Qe4Qgyq8iTToVOzp4sZBAZvScv3AGJSMi6HYbe54+nyjrRfS8DN4fA0eUahyftHKZ00WKgMe7TS5N0w==
|
||||
dependencies:
|
||||
vue-demi "*"
|
||||
|
||||
abab@^2.0.3, abab@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a"
|
||||
|
@ -12097,6 +12112,11 @@ vue-advanced-cropper@2.6.3:
|
|||
debounce "^1.2.0"
|
||||
easy-bem "^1.0.2"
|
||||
|
||||
vue-demi@*:
|
||||
version "0.12.1"
|
||||
resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.12.1.tgz#f7e18efbecffd11ab069d1472d7a06e319b4174c"
|
||||
integrity sha512-QL3ny+wX8c6Xm1/EZylbgzdoDolye+VpCXRhI2hug9dJTP3OUJ3lmiKN3CsVV3mOJKwFi0nsstbgob0vG7aoIw==
|
||||
|
||||
vue-drag-resize@2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/vue-drag-resize/-/vue-drag-resize-2.0.3.tgz#1faf0813f43304205bb355fbb3dacc548dd9398a"
|
||||
|
|
Loading…
Reference in New Issue