API & Client - add a user preference for dark mode

This commit is contained in:
Sam
2023-12-16 21:18:09 +01:00
parent 3653239022
commit 3be787de7f
17 changed files with 161 additions and 13 deletions

View File

@ -107,7 +107,7 @@
</template>
<script setup lang="ts">
import { computed, ref, capitalize, onBeforeMount } from 'vue'
import { computed, ref, capitalize, onBeforeMount, watch } from 'vue'
import type { ComputedRef, Ref } from 'vue'
import UserPicture from '@/components/User/UserPicture.vue'
@ -133,12 +133,18 @@
)
const isMenuOpen: Ref<boolean> = ref(false)
const displayModal: Ref<boolean> = ref(false)
const darkTheme: Ref<boolean> = ref(false)
const darkMode: ComputedRef<boolean | null> = computed(
() => store.getters[ROOT_STORE.GETTERS.DARK_MODE]
)
const darkTheme: ComputedRef<boolean> = computed(
() => darkMode.value !== false
)
const themeIcon: ComputedRef<string> = computed(() =>
darkTheme.value ? 'fa-moon' : 'fa-sun-o'
)
onBeforeMount(() => initTheme())
onBeforeMount(() => setTheme())
function openMenu() {
isMenuOpen.value = true
@ -163,22 +169,21 @@
}
function setTheme() {
if (darkTheme.value) {
darkTheme.value = true
document.body.setAttribute('data-theme', 'dark')
} else {
document.body.removeAttribute('data-theme')
}
}
function initTheme() {
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
darkTheme.value = true
}
setTheme()
}
function toggleTheme() {
darkTheme.value = !darkTheme.value
setTheme()
store.commit(ROOT_STORE.MUTATIONS.UPDATE_DARK_MODE, !darkTheme.value)
}
watch(
() => darkTheme.value,
() => {
setTheme()
}
)
</script>
<style scoped lang="scss">

View File

@ -4,6 +4,8 @@
<dl>
<dt>{{ $t('user.PROFILE.LANGUAGE') }}:</dt>
<dd>{{ userLanguage }}</dd>
<dt>{{ $t('user.PROFILE.THEME_MODE.LABEL') }}:</dt>
<dd>{{ $t(`user.PROFILE.THEME_MODE.VALUES.${darkMode}`) }}</dd>
<dt>{{ $t('user.PROFILE.TIMEZONE') }}:</dt>
<dd>{{ timezone }}</dd>
<dt>{{ $t('user.PROFILE.DATE_FORMAT') }}:</dt>
@ -95,6 +97,13 @@
const display_ascent = computed(() =>
props.user.display_ascent ? 'DISPLAYED' : 'HIDDEN'
)
const darkMode = computed(() =>
props.user.use_dark_mode === true
? 'DARK'
: props.user.use_dark_mode === false
? 'LIGHT'
: 'DEFAULT'
)
</script>
<style lang="scss" scoped>

View File

@ -18,6 +18,22 @@
</option>
</select>
</label>
<label class="form-items">
{{ $t('user.PROFILE.THEME_MODE.LABEL') }}
<select
id="use_dark_mode"
v-model="userForm.use_dark_mode"
:disabled="loading"
>
<option
v-for="mode in useDarkMode"
:value="mode.value"
:key="mode.label"
>
{{ $t(`user.PROFILE.THEME_MODE.VALUES.${mode.label}`) }}
</option>
</select>
</label>
<label class="form-items">
{{ $t('user.PROFILE.TIMEZONE') }}
<TimezoneDropdown
@ -195,6 +211,7 @@
weekm: false,
start_elevation_at_zero: false,
use_raw_gpx_speed: false,
use_dark_mode: false,
})
const weekStart = [
{
@ -246,6 +263,20 @@
value: true,
},
]
const useDarkMode = [
{
label: 'DARK',
value: true,
},
{
label: 'DEFAULT',
value: null,
},
{
label: 'LIGHT',
value: false,
},
]
const loading = computed(
() => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING]
)
@ -279,6 +310,7 @@
userForm.timezone = user.timezone ? user.timezone : 'Europe/Paris'
userForm.date_format = user.date_format ? user.date_format : 'dd/MM/yyyy'
userForm.weekm = user.weekm ? user.weekm : false
userForm.use_dark_mode = user.use_dark_mode
}
function updateProfile() {
store.dispatch(AUTH_USER_STORE.ACTIONS.UPDATE_USER_PREFERENCES, userForm)
@ -339,7 +371,8 @@
}
#language,
#date_format {
#date_format,
#use_dark_mode {
padding: $default-padding * 0.5;
}
}

View File

@ -115,6 +115,14 @@
"PROFILE": "profile",
"SPORTS": "sports"
},
"THEME_MODE": {
"LABEL": "Theme mode",
"VALUES": {
"DARK": "Dark",
"DEFAULT": "Browser preference",
"LIGHT": "Light"
}
},
"TIMEZONE": "Timezone",
"UNITS": {
"IMPERIAL": "Imperial system (ft, mi, mph, °F)",

View File

@ -115,6 +115,14 @@
"PROFILE": "profil",
"SPORTS": "sports"
},
"THEME_MODE": {
"LABEL": "Thème",
"VALUES": {
"DARK": "Sombre",
"DEFAULT": "Préférence du navigateur",
"LIGHT": "Clair"
}
},
"TIMEZONE": "Fuseau horaire",
"UNITS": {
"IMPERIAL": "Système impérial (ft, mi, mph, °F)",

View File

@ -139,6 +139,10 @@ export const actions: ActionTree<IAuthUserState, IRootState> &
res.data.data.language
)
}
context.commit(
ROOT_STORE.MUTATIONS.UPDATE_DARK_MODE,
res.data.data.use_dark_mode
)
context.dispatch(SPORTS_STORE.ACTIONS.GET_SPORTS)
} else {
handleError(context, null)
@ -270,6 +274,10 @@ export const actions: ActionTree<IAuthUserState, IRootState> &
AUTH_USER_STORE.MUTATIONS.UPDATE_AUTH_USER_PROFILE,
res.data.data
)
context.commit(
ROOT_STORE.MUTATIONS.UPDATE_DARK_MODE,
res.data.data.use_dark_mode
)
context
.dispatch(
ROOT_STORE.ACTIONS.UPDATE_APPLICATION_LANGUAGE,

View File

@ -10,6 +10,7 @@ export enum RootGetters {
APP_CONFIG = 'APP_CONFIG',
APP_LOADING = 'APP_LOADING',
APP_STATS = 'APP_STATS',
DARK_MODE = 'DARK_MODE',
ERROR_MESSAGES = 'ERROR_MESSAGES',
LANGUAGE = 'LANGUAGE',
LOCALE = 'LOCALE', // date-fns
@ -22,5 +23,6 @@ export enum RootMutations {
UPDATE_APPLICATION_LOADING = 'UPDATE_APPLICATION_LOADING',
UPDATE_APPLICATION_PRIVACY_POLICY = 'UPDATE_APPLICATION_PRIVACY_POLICY',
UPDATE_APPLICATION_STATS = 'UPDATE_APPLICATION_STATS',
UPDATE_DARK_MODE = 'UPDATE_DARK_MODE',
UPDATE_LANG = 'UPDATE_LANG',
}

View File

@ -13,6 +13,9 @@ export const getters: GetterTree<IRootState, IRootState> & IRootGetters = {
[ROOT_STORE.GETTERS.APP_STATS]: (state: IRootState) => {
return state.application.statistics
},
[ROOT_STORE.GETTERS.DARK_MODE]: (state: IRootState) => {
return state.darkMode
},
[ROOT_STORE.GETTERS.ERROR_MESSAGES]: (state: IRootState) => {
return state.errorMessages
},

View File

@ -45,4 +45,10 @@ export const mutations: MutationTree<IRootState> & TRootMutations = {
state.language = language
state.locale = localeFromLanguage[language]
},
[ROOT_STORE.MUTATIONS.UPDATE_DARK_MODE](
state: IRootState,
darkMode: boolean | null
) {
state.darkMode = darkMode
},
}

View File

@ -17,4 +17,5 @@ export const state: IRootState = {
},
},
appLoading: false,
darkMode: null,
}

View File

@ -22,6 +22,7 @@ export interface IRootState {
errorMessages: string | string[] | null
application: IApplication
appLoading: boolean
darkMode: boolean | null
}
export interface IRootActions {
@ -51,6 +52,8 @@ export interface IRootGetters {
[ROOT_STORE.GETTERS.APP_STATS](state: IRootState): IAppStatistics
[ROOT_STORE.GETTERS.DARK_MODE](state: IRootState): boolean | null
[ROOT_STORE.GETTERS.ERROR_MESSAGES](
state: IRootState
): string | string[] | null
@ -83,6 +86,10 @@ export type TRootMutations<S = IRootState> = {
statistics: IAppStatistics
): void
[ROOT_STORE.MUTATIONS.UPDATE_LANG](state: S, language: TLanguage): void
[ROOT_STORE.MUTATIONS.UPDATE_DARK_MODE](
state: S,
darkMode: boolean | null
): void
}
export type TRootStoreModule<S = IRootState> = Omit<

View File

@ -35,6 +35,7 @@ export interface IAuthUserProfile extends IUserProfile {
timezone: string
date_format: string
weekm: boolean
use_dark_mode: boolean | null
}
export interface IUserPayload {
@ -73,6 +74,7 @@ export interface IUserPreferencesPayload {
timezone: string
date_format: string
weekm: boolean
use_dark_mode: boolean | null
}
export interface IUserSportPreferencesPayload {