API & Client - add a user preference for dark mode
This commit is contained in:
@ -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">
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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)",
|
||||
|
@ -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)",
|
||||
|
@ -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,
|
||||
|
@ -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',
|
||||
}
|
||||
|
@ -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
|
||||
},
|
||||
|
@ -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
|
||||
},
|
||||
}
|
||||
|
@ -17,4 +17,5 @@ export const state: IRootState = {
|
||||
},
|
||||
},
|
||||
appLoading: false,
|
||||
darkMode: null,
|
||||
}
|
||||
|
@ -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<
|
||||
|
@ -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 {
|
||||
|
Reference in New Issue
Block a user