Merge pull request #465 from DavidHenryThoreau/css

Add colors-dark.scss
This commit is contained in:
Sam
2023-12-20 11:01:55 +01:00
committed by GitHub
40 changed files with 1057 additions and 507 deletions
+2 -2
View File
@@ -7,11 +7,11 @@
<link rel="stylesheet" href="/static/css/fork-awesome.min.css"/>
<link rel="stylesheet" href="/static/css/leaflet.css"/>
<title>FitTrackee</title>
<script type="module" crossorigin src="/static/index-0EDbp-Wc.js"></script>
<script type="module" crossorigin src="/static/index-0v1Cinoq.js"></script>
<link rel="modulepreload" crossorigin href="/static/charts-_RwsDDkL.js">
<link rel="modulepreload" crossorigin href="/static/maps-ZyuCPqes.js">
<link rel="stylesheet" crossorigin href="/static/css/maps-B7qTrBCW.css">
<link rel="stylesheet" crossorigin href="/static/css/index-kyYSvvW8.css">
<link rel="stylesheet" crossorigin href="/static/css/index-86bdJKFy.css">
</head>
<body>
<div id="app"></div>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,34 @@
"""add dark theme preferences
Revision ID: 14f48e46f320
Revises: 24eb097614e4
Create Date: 2023-12-16 18:35:31.377007
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '14f48e46f320'
down_revision = '24eb097614e4'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('users', schema=None) as batch_op:
batch_op.add_column(
sa.Column('use_dark_mode', sa.Boolean(), nullable=True)
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('users', schema=None) as batch_op:
batch_op.drop_column('use_dark_mode')
# ### end Alembic commands ###
+2
View File
@@ -1459,6 +1459,7 @@ class TestUserPreferencesUpdate(ApiTestCaseMixin):
imperial_units=True,
display_ascent=False,
start_elevation_at_zero=False,
use_dark_mode=True,
use_raw_gpx_speed=True,
date_format='yyyy-MM-dd',
)
@@ -1478,6 +1479,7 @@ class TestUserPreferencesUpdate(ApiTestCaseMixin):
assert data['data']['timezone'] == 'America/New_York'
assert data['data']['date_format'] == 'yyyy-MM-dd'
assert data['data']['weekm'] is True
assert data['data']['use_dark_mode'] is True
@pytest.mark.parametrize(
'client_scope, can_access',
@@ -77,6 +77,12 @@ class TestUserSerializeAsAuthUser(UserModelAssertMixin):
assert serialized_user['timezone'] == user_1.timezone
assert serialized_user['weekm'] == user_1.weekm
assert serialized_user['display_ascent'] == user_1.display_ascent
assert (
serialized_user['start_elevation_at_zero']
== user_1.start_elevation_at_zero
)
assert serialized_user['use_raw_gpx_speed'] == user_1.use_raw_gpx_speed
assert serialized_user['use_dark_mode'] == user_1.use_dark_mode
def test_it_returns_workouts_infos(self, app: Flask, user_1: User) -> None:
serialized_user = user_1.serialize(user_1)
@@ -155,6 +161,9 @@ class TestUserSerializeAsAdmin(UserModelAssertMixin):
assert 'language' not in serialized_user
assert 'timezone' not in serialized_user
assert 'weekm' not in serialized_user
assert 'start_elevation_at_zero' not in serialized_user
assert 'use_raw_gpx_speed' not in serialized_user
assert 'use_dark_mode' not in serialized_user
def test_it_returns_workouts_infos(
self, app: Flask, user_1_admin: User, user_2: User
+9
View File
@@ -361,6 +361,7 @@ def get_authenticated_user_profile(
"total_ascent": 720.35,
"total_distance": 67.895,
"total_duration": "6:50:27",
"use_dark_mode": null,
"use_raw_gpx_speed": false,
"username": "sam",
"weekm": false
@@ -478,6 +479,7 @@ def edit_user(auth_user: User) -> Union[Dict, HttpResponse]:
"total_ascent": 720.35,
"total_distance": 67.895,
"total_duration": "6:50:27",
"use_dark_mode": null,
"use_raw_gpx_speed": false,
"username": "sam"
"weekm": true,
@@ -650,6 +652,7 @@ def update_user_account(auth_user: User) -> Union[Dict, HttpResponse]:
"total_ascent": 720.35,
"total_distance": 67.895,
"total_duration": "6:50:27",
"use_dark_mode": null,
"use_raw_gpx_speed": false,
"username": "sam"
"weekm": true,
@@ -878,6 +881,7 @@ def edit_user_preferences(auth_user: User) -> Union[Dict, HttpResponse]:
"total_ascent": 720.35,
"total_distance": 67.895,
"total_duration": "6:50:27",
"use_dark_mode": null,
"use_raw_gpx_speed": true,
"username": "sam"
"weekm": true,
@@ -892,6 +896,8 @@ def edit_user_preferences(auth_user: User) -> Union[Dict, HttpResponse]:
:<json string language: language preferences
:<json boolean start_elevation_at_zero: do elevation plots start at zero?
:<json string timezone: user time zone
:<json boolean use_dark_mode: Display interface with dark mode if true.
If null, it uses browser preferences.
:<json boolean use_raw_gpx_speed: Use unfiltered gpx to calculate speeds
:<json boolean weekm: does week start on Monday?
@@ -916,6 +922,7 @@ def edit_user_preferences(auth_user: User) -> Union[Dict, HttpResponse]:
'language',
'start_elevation_at_zero',
'timezone',
'use_dark_mode',
'use_raw_gpx_speed',
'weekm',
}
@@ -928,6 +935,7 @@ def edit_user_preferences(auth_user: User) -> Union[Dict, HttpResponse]:
language = get_language(post_data.get('language'))
start_elevation_at_zero = post_data.get('start_elevation_at_zero')
use_raw_gpx_speed = post_data.get('use_raw_gpx_speed')
use_dark_mode = post_data.get('use_dark_mode')
timezone = post_data.get('timezone')
weekm = post_data.get('weekm')
@@ -938,6 +946,7 @@ def edit_user_preferences(auth_user: User) -> Union[Dict, HttpResponse]:
auth_user.language = language
auth_user.start_elevation_at_zero = start_elevation_at_zero
auth_user.timezone = timezone
auth_user.use_dark_mode = use_dark_mode
auth_user.use_raw_gpx_speed = use_raw_gpx_speed
auth_user.weekm = weekm
db.session.commit()
+2
View File
@@ -63,6 +63,7 @@ class User(BaseModel):
db.Boolean, default=True, nullable=False
)
use_raw_gpx_speed = db.Column(db.Boolean, default=False, nullable=False)
use_dark_mode = db.Column(db.Boolean, default=False, nullable=True)
def __repr__(self) -> str:
return f'<User {self.username!r}>'
@@ -217,6 +218,7 @@ class User(BaseModel):
'language': self.language,
'start_elevation_at_zero': self.start_elevation_at_zero,
'timezone': self.timezone,
'use_dark_mode': self.use_dark_mode,
'use_raw_gpx_speed': self.use_raw_gpx_speed,
'weekm': self.weekm,
},
+1 -1
View File
@@ -123,7 +123,7 @@
.scroll-button {
background-color: var(--scroll-button-bg-color);
border-radius: $border-radius;
box-shadow: 1px 1px 3px lightgrey;
box-shadow: 1px 1px 3px var(--app-shadow-color);
display: none;
padding: 0 $default-padding;
@@ -70,14 +70,14 @@
.dropdown-list {
list-style-type: none;
background-color: #ffffff;
background-color: var(--dropdown-background-color);
padding: 0 !important;
margin-top: 5px;
margin-left: -20px !important;
position: absolute;
text-align: left;
border: solid 1px lightgrey;
box-shadow: 2px 2px 5px lightgrey;
border: solid 1px var(--dropdown-border-color);
box-shadow: 2px 2px 5px var(--app-shadow-color);
width: auto !important;
.dropdown-item {
@@ -51,6 +51,12 @@
width: 400px;
height: 225px;
z-index: 100;
filter: var(--map-display-hover-filter);
.map-attribution-text {
color: var(--map-display-hover-attribution-text);
background-color: var(--map-attribution-bg-color);
}
}
.bg-map-image {
@@ -59,6 +65,7 @@
opacity: 0.6;
height: 200px;
width: 100%;
filter: var(--map-filter);
}
.map-attribution {
@@ -69,7 +76,8 @@
}
.map-attribution-text {
background-color: rgba(255, 255, 255, 0.7);
color: var(--map-attribution-text);
background-color: var(--map-attribution-bg-color);
}
}
</style>
@@ -6,13 +6,16 @@
<script setup lang="ts">
import type { ChartOptions, LayoutItem } from 'chart.js'
import { computed, toRefs } from 'vue'
import { computed, type ComputedRef, toRefs } from 'vue'
import { Bar } from 'vue-chartjs'
import { useI18n } from 'vue-i18n'
import { useStore } from 'vuex'
import { ROOT_STORE } from '@/store/constants'
import type { IChartDataset } from '@/types/chart'
import type { TStatisticsDatasetKeys } from '@/types/statistics'
import { formatTooltipValue } from '@/utils/tooltip'
import { chartsColors } from '@/utils/workouts'
interface Props {
datasets: IChartDataset[]
@@ -32,8 +35,23 @@
useImperialUnits,
} = toRefs(props)
const store = useStore()
const { t } = useI18n()
const darkMode: ComputedRef<boolean | null> = computed(
() => store.getters[ROOT_STORE.GETTERS.DARK_MODE]
)
const lineColors = computed(() => ({
color: darkMode.value
? chartsColors.darkMode.line
: chartsColors.ligthMode.line,
}))
const textColors = computed(() => ({
color: darkMode.value
? chartsColors.darkMode.text
: chartsColors.ligthMode.text,
}))
const chartData = computed(() => ({
labels: labels.value,
// workaround to avoid dataset modification
@@ -53,12 +71,23 @@
stacked: true,
grid: {
drawOnChartArea: false,
...lineColors.value,
},
border: {
...lineColors.value,
},
ticks: {
...textColors.value,
},
},
y: {
stacked: displayedData.value !== 'average_speed',
grid: {
drawOnChartArea: false,
...lineColors.value,
},
border: {
...lineColors.value,
},
ticks: {
maxTicksLimit: 6,
@@ -71,6 +100,7 @@
getUnit(displayedData.value)
)
},
...textColors.value,
},
afterFit: function (scale: LayoutItem) {
scale.width = fullStats.value ? 90 : 60
@@ -34,7 +34,7 @@
padding: $default-padding * 0.5;
text-align: center;
text-transform: uppercase;
color: var(--app-color-light);
color: var(--calendar-day-color);
}
}
</style>
@@ -88,11 +88,11 @@
padding-left: 40px;
.more-workouts {
background: whitesmoke;
background: var(--calendar-workouts-color);
border-radius: 4px;
box-shadow:
0 4px 8px 0 rgba(0, 0, 0, 0.2),
0 6px 20px 0 rgba(0, 0, 0, 0.19);
0 4px 8px 0 var(--calendar-workouts-box-shadow-0),
0 6px 20px 0 var(--calendar-workouts-box-shadow-1);
position: absolute;
top: 52px;
left: 0;
+74 -11
View File
@@ -60,12 +60,12 @@
{{ authUser.username }}
</router-link>
<button
class="logout-button transparent"
class="nav-button logout-button transparent"
@click="updateDisplayModal(true)"
:aria-label="$t('user.LOGOUT')"
:title="$t('user.LOGOUT')"
>
<i class="fa fa-sign-out logout-fa" aria-hidden="true" />
<span class="logout-text">{{ $t('user.LOGOUT') }}</span>
<i class="fa fa-sign-out nav-button-fa" aria-hidden="true" />
<span class="nav-button-text">{{ $t('user.LOGOUT') }}</span>
</button>
</div>
<div class="nav-items-group" v-else>
@@ -76,6 +76,27 @@
{{ $t('user.REGISTER') }}
</router-link>
</div>
<div class="theme-button">
<button
class="nav-button transparent"
@click="toggleTheme"
:title="$t('user.TOGGLE_THEME')"
>
<i
v-if="darkTheme"
class="fa nav-button-fa fa-moon"
aria-hidden="true"
/>
<img
v-else
class="clear-theme"
src="/img/weather/clear-day.svg"
alt=""
aria-hidden="true"
/>
<span class="nav-button-text">{{ $t('user.TOGGLE_THEME') }}</span>
</button>
</div>
<Dropdown
v-if="availableLanguages && language"
class="nav-item"
@@ -93,7 +114,7 @@
</template>
<script setup lang="ts">
import { computed, ref, capitalize } from 'vue'
import { computed, ref, capitalize, onBeforeMount, watch } from 'vue'
import type { ComputedRef, Ref } from 'vue'
import UserPicture from '@/components/User/UserPicture.vue'
@@ -119,6 +140,12 @@
)
const isMenuOpen: Ref<boolean> = ref(false)
const displayModal: Ref<boolean> = ref(false)
const darkMode: ComputedRef<boolean | null> = computed(
() => store.getters[ROOT_STORE.GETTERS.DARK_MODE]
)
const darkTheme: ComputedRef<boolean> = computed(() => getDarkTheme())
onBeforeMount(() => setTheme())
function openMenu() {
isMenuOpen.value = true
@@ -141,6 +168,32 @@
function updateDisplayModal(display: boolean) {
displayModal.value = display
}
function getDarkTheme() {
if (
darkMode.value === null &&
window.matchMedia('(prefers-color-scheme: dark)').matches
) {
return true
}
return darkMode.value === true
}
function setTheme() {
if (darkTheme.value) {
document.body.setAttribute('data-theme', 'dark')
} else {
document.body.removeAttribute('data-theme')
}
}
function toggleTheme() {
store.commit(ROOT_STORE.MUTATIONS.UPDATE_DARK_MODE, !darkTheme.value)
}
watch(
() => darkTheme.value,
() => {
setTheme()
}
)
</script>
<style scoped lang="scss">
@@ -252,16 +305,22 @@
.nav-separator {
display: none;
}
.logout-button {
.nav-button {
padding: $default-padding * 0.5 $default-padding * 0.75;
margin-left: 2px;
.logout-fa {
.nav-button-fa {
display: block;
}
.logout-text {
.nav-button-text {
display: none;
}
}
.clear-theme {
filter: var(--workout-img-color);
height: 20px;
margin-bottom: -5px;
}
}
@media screen and (max-width: $medium-limit) {
@@ -323,15 +382,16 @@
display: flex;
flex-direction: column;
.logout-button {
.nav-button {
padding: $default-padding $default-padding $default-padding
$default-padding * 2.4;
color: var(--app-a-color);
text-align: left;
.logout-fa {
.nav-button-fa {
display: none;
width: 36px;
}
.logout-text {
.nav-button-text {
display: block;
}
}
@@ -361,6 +421,9 @@
padding: 0;
}
}
.theme-button {
margin-left: $default-padding * 2;
}
}
}
</style>
@@ -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;
}
}
@@ -92,7 +92,7 @@
.policy-content {
height: 500px;
border: 1px solid #ccc;
border: 1px solid var(--policy-border-color);
overflow: auto;
margin: $default-margin;
border-radius: $border-radius;
@@ -238,6 +238,7 @@
height: 150px;
.no-map {
line-height: 150px;
filter: var(--no-map-filter);
}
::v-deep(.bg-map-image) {
height: 150px;
@@ -59,8 +59,10 @@
import type { ComputedRef } from 'vue'
import { Line } from 'vue-chartjs'
import { useI18n } from 'vue-i18n'
import { useStore } from 'vuex'
import { htmlLegendPlugin } from '@/components/Workout/WorkoutDetail/WorkoutChart/legend'
import { ROOT_STORE } from '@/store/constants'
import type { TUnit } from '@/types/units'
import type { IAuthUserProfile } from '@/types/user'
import type {
@@ -69,7 +71,7 @@
TCoordinates,
} from '@/types/workouts'
import { units } from '@/utils/units'
import { getDatasets } from '@/utils/workouts'
import { chartsColors, getDatasets } from '@/utils/workouts'
interface Props {
authUser: IAuthUserProfile
@@ -79,13 +81,22 @@
const emit = defineEmits(['getCoordinates'])
const store = useStore()
const { t } = useI18n()
const { authUser, workoutData } = toRefs(props)
const darkMode: ComputedRef<boolean | null> = computed(
() => store.getters[ROOT_STORE.GETTERS.DARK_MODE]
)
const displayDistance = ref(true)
const beginElevationAtZero = ref(authUser.value.start_elevation_at_zero)
const datasets: ComputedRef<IWorkoutChartData> = computed(() =>
getDatasets(workoutData.value.chartData, t, authUser.value.imperial_units)
getDatasets(
workoutData.value.chartData,
t,
authUser.value.imperial_units,
darkMode.value !== false
)
)
const hasElevation = computed(
() => datasets.value && datasets.value.datasets.elevation.data.length > 0
@@ -106,6 +117,17 @@
const coordinates: ComputedRef<TCoordinates[]> = computed(
() => datasets.value.coordinates
)
const lineColors = computed(() => ({
color: darkMode.value
? chartsColors.darkMode.line
: chartsColors.ligthMode.line,
}))
const textColors = computed(() => ({
color: darkMode.value
? chartsColors.darkMode.text
: chartsColors.ligthMode.text,
}))
const options = computed<ChartOptions<'line'>>(() => ({
responsive: true,
maintainAspectRatio: false,
@@ -119,6 +141,10 @@
x: {
grid: {
drawOnChartArea: false,
...lineColors.value,
},
border: {
...lineColors.value,
},
ticks: {
count: 10,
@@ -127,6 +153,7 @@
? Number(value).toFixed(2)
: formatDuration(value)
},
...textColors.value,
},
type: 'linear',
bounds: 'data',
@@ -135,16 +162,25 @@
text: displayDistance.value
? t('workouts.DISTANCE') + ` (${fromKmUnit})`
: t('workouts.DURATION'),
...textColors.value,
},
},
ySpeed: {
grid: {
drawOnChartArea: false,
...lineColors.value,
},
border: {
...lineColors.value,
},
position: 'left',
title: {
display: true,
text: t('workouts.SPEED') + ` (${fromKmUnit}/h)`,
...textColors.value,
},
ticks: {
...textColors.value,
},
},
yElevation: {
@@ -152,11 +188,19 @@
display: hasElevation.value,
grid: {
drawOnChartArea: false,
...lineColors.value,
},
border: {
...lineColors.value,
},
position: 'right',
title: {
display: true,
text: t('workouts.ELEVATION') + ` (${fromMUnit})`,
...textColors.value,
},
ticks: {
...textColors.value,
},
},
},
@@ -143,6 +143,9 @@
.mountains {
padding-right: $default-padding * 0.5;
}
.mountains {
filter: var(--mountains-filter);
}
.workout-data {
padding: $default-padding * 0.5 0;
@@ -17,6 +17,7 @@
ref="workoutMap"
@ready="fitBounds(bounds)"
:use-global-leaflet="false"
class="map"
>
<LControlLayers />
<LControl
@@ -212,13 +213,23 @@
}
.no-map {
line-height: 400px;
filter: var(--no-map-filter);
}
.leaflet-container {
.map {
filter: var(--map-filter);
}
.map-control {
background: #ffffff;
background: var(--map-control-bg-color);
padding: 5px 10px;
border: 2px solid #bfc0ab;
border: 2px solid var(--map-control-border-color);
border-radius: 3px;
color: #000000;
color: var(--map-control-color);
&:hover {
background-color: var(--dropdown-hover-color);
}
}
}
::v-deep(.fullscreen) {
display: flex;
@@ -354,7 +354,7 @@
}
.static-map {
display: none;
box-shadow: 3px 3px 3px 1px lightgrey;
box-shadow: 3px 3px 3px 1px var(--workout-static-map-shadow-color);
}
}
.workout-title:hover .static-map {
@@ -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)",
@@ -136,6 +144,7 @@
"REVIEW": "review",
"SHOW_PASSWORD": "show password",
"THIS_USER_ACCOUNT_IS_INACTIVE": "This user account is inactive.",
"TOGGLE_THEME": "Toggle theme (Light or Dark mode)",
"USERNAME": "Username",
"USERNAME_INFO": "3 to 30 characters required, only alphanumeric characters and the underscore character \"_\" allowed.",
"USER_PICTURE": "user picture",
@@ -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)",
@@ -136,6 +144,7 @@
"REVIEW": "accepter",
"SHOW_PASSWORD": "afficher le mot de passe",
"THIS_USER_ACCOUNT_IS_INACTIVE": "Le compte de cet utilisateur est inactif.",
"TOGGLE_THEME": "Modifier le thème (Mode Clair ou Sombre)",
"USERNAME": "Nom d'utilisateur",
"USERNAME_INFO": "3 à 30 caractères requis, seuls les caractères alphanumériques et le caractère _ sont autorisés.",
"USER_PICTURE": "photo de l'utilisateur",
+11 -5
View File
@@ -1,7 +1,12 @@
@import 'colors';
@import 'colors-dark';
@import 'fonts';
@import 'vars';
html [data-theme='dark'] {
color-scheme: dark;
}
body {
margin: 0;
overflow-y: scroll;
@@ -55,6 +60,7 @@ select {
background-color: var(--input-bg-color);
border-radius: $border-radius;
border: solid 1px var(--input-border-color);
color: var(--input-color);
padding: $default-padding;
&:disabled {
@@ -102,13 +108,13 @@ button {
&:disabled,
&.confirm:disabled {
border-color: transparent;
border-color: var(--disabled-border-color);
color: var(--disabled-color);
}
}
&:hover {
background: var(--app-color);
background: var(--button-transparent-hover-color);
color: var(--button-hover-color);
}
@@ -128,7 +134,7 @@ button {
background: var(--button-cancel-bg-color);
color: var(--button-cancel-color);
&:hover {
background: var(--app-color);
background: var(--button-transparent-hover-color);
color: var(--button-hover-color);
}
}
@@ -137,7 +143,7 @@ button {
background: var(--button-confirm-bg-color);
color: var(--button-confirm-color);
&:hover {
background: var(--app-color);
background: var(--button-transparent-hover-color);
color: var(--button-hover-color);
}
}
@@ -194,7 +200,7 @@ button {
}
.form-info {
color: var(--alert-color);
color: var(--form-info);
font-size: 0.8em;
margin-top: -0.2 * $default-margin;
padding: 0 $default-padding * 1.5;
+113
View File
@@ -0,0 +1,113 @@
:root [data-theme='dark'] {
--dark-blue: #181a1b;
--light-grey: #cfd0d0;
--app-background-color: var(--dark-blue);
--app-color: var(--light-grey);
--app-color-light: #6f7070;
--app-a-color: var(--light-grey);
--app-shadow-color: #383d3f;
--app-loading-color: #f3f3f3;
--app-loading-top-color: var(--app-color);
--button-hover-color: var(--app-color);
--button-transparent-hover-color: #233240;
--button-cancel-bg-color: var(--dark-blue);
--button-cancel-color: var(--app-color);
--button-confirm-bg-color: var(--dark-blue);
--button-confirm-color: var(--app-color);
--button-danger-bg-color: var(--dark-blue);
--button-danger-color: #dc3545;
--button-danger-hover-bg-color: #dc3545;
--button-danger-hover-color: var(--dark-blue);
--card-border-color: #494f52;
--input-border-color: #494f52;
--input-bg-color: var(--dark-blue);
--input-color: var(--app-color);
--input-error-color: #dc3545;
--dropdown-hover-color: #233240;
--dropdown-background-color: var(--dark-blue);
--dropdown-border-color: var(--input-border-color);
--policy-border-color: #ccc;
--box-shadow-color: lightgrey;
--admin-disabled-input-color: var(--dark-blue);
--custom-checkbox-border-color: #665f54;
--custom-checkbox-checked-bg-color: #575e62;
--custom-checkbox-checked-color: #e8e6e3;
--calendar-border-color: var(--input-border-color);
--calendar-week-end-color: #1e2021;
--calendar-day-color: var(--app-color);
--calendar-today-color: #202324;
--calendar-workouts-color: #233240;
--calendar-workouts-box-shadow-0: rgba(0, 0, 0, 0.2);
--calendar-workouts-box-shadow-1: rgba(0, 0, 0, 0.19);
--modal-background-color: rgba(0, 0, 0, 0.3);
--nav-bar-background-color: var(--dark-blue);
--nav-bar-link-active: #ffffff;
--nav-border-color: var(--input-border-color);
--mobile-menu-selected-color: var(--dark-blue);
--mobile-menu-selected-bgcolor: #9da3af;
--footer-background-color: var(--dark-blue);
--footer-border-color: var(--input-border-color);
--footer-color: #9f968a;
--form-info: var(--app-color);
--alert-background-color: #d6dde3;
--alert-color: #3f3f3f;
--info-background-color: #33353a;
--info-color: var(--app-color);
--error-background-color: #4e0000;
--error-color: #ea464f;
--success-background-color: #24391c;
--success-color: #97cd97;
--disabled-background-color: var(--dark-blue);
--disabled-border-color: transparent;
--disabled-color: #727272;
--disabled-sport-color: #616161;
--scroll-button-bg-color: var(--dark-blue);
--workout-trophy-color: #daa520;
--workout-img-color: invert(22%) sepia(25%) saturate(646%) hue-rotate(169deg)
brightness(97%) contrast(96%);
--workout-no-map-bg-color: #eaeaea;
--workout-no-map-color: #585959;
--map-control-color: #000000;
--map-control-bg-color: #ffffff;
--map-control-border-color: #bfc0ab;
--map-attribution-text: #e8e8e8;
--map-display-hover-attribution-text: #444444;
--map-attribution-bg-color: none;
--map-filter: invert(1) hue-rotate(180deg) brightness(0.8) contrast(0.8);
--map-display-hover-filter: invert(1) hue-rotate(180deg) brightness(1.5)
contrast(0.6);
--map-layers-overlays: var(--app-color);
--map-control-bar: var(--app-color);
--no-map-filter: invert(1) brightness(1.5) contrast(0.9);
--workout-static-map-shadow-color: #d2d2d2;
--mountains-filter: invert(90%) sepia(19%) saturate(0%) hue-rotate(39deg)
brightness(86%) contrast(102%);
--cell-heading-bg-color: #383838;
--cell-heading-color: #eeeeee;
--svg-filter: drop-shadow(10px 10px 10px var(--app-shadow-color));
--password-bg-color: #d7dadf;
--password-color-weak: #831819;
--password-color-medium: #9e6906;
--password-color-good: #4b5826;
--password-color-strong: #4a8c32;
--scroll-thumb-color: #949697;
}
+29
View File
@@ -8,6 +8,7 @@
--app-loading-top-color: var(--app-color);
--button-hover-color: #ffffff;
--button-transparent-hover-color: var(--app-color);
--button-cancel-bg-color: #ffffff;
--button-cancel-color: var(--app-color);
--button-confirm-bg-color: #ffffff;
@@ -20,8 +21,14 @@
--card-border-color: #c4c7cf;
--input-border-color: #9da3af;
--input-bg-color: #ffffff;
--input-color: var(--app-color);
--input-error-color: #dc3545;
--dropdown-hover-color: #eff0f5;
--dropdown-background-color: #ffffff;
--dropdown-border-color: lightgrey;
--policy-border-color: #ccc;
--box-shadow-color: lightgrey;
--admin-disabled-input-color: #ffffff;
--custom-checkbox-border-color: #6d797a;
--custom-checkbox-checked-bg-color: #6d797a;
@@ -29,7 +36,11 @@
--calendar-border-color: #c4c7cf;
--calendar-week-end-color: #f5f5f5;
--calendar-day-color: var(--app-color-light);
--calendar-today-color: #eff1f3;
--calendar-workouts-color: whitesmoke;
--calendar-workouts-box-shadow-0: rgba(0, 0, 0, 0.2);
--calendar-workouts-box-shadow-1: rgba(0, 0, 0, 0.19);
--modal-background-color: rgba(0, 0, 0, 0.3);
@@ -44,6 +55,8 @@
--footer-border-color: #ebeef3;
--footer-color: #6f7070;
--form-info: var(--alert-color);
--alert-background-color: #d6dde3;
--alert-color: #3f3f3f;
--info-background-color: #e5e7ea;
@@ -54,6 +67,7 @@
--success-color: #306430;
--disabled-background-color: #e0e0e0;
--disabled-border-color: transparent;
--disabled-color: #727272;
--disabled-sport-color: #616161;
@@ -64,6 +78,21 @@
brightness(97%) contrast(96%);
--workout-no-map-bg-color: #eaeaea;
--workout-no-map-color: #585959;
--map-control-color: #000000;
--map-control-bg-color: #ffffff;
--map-control-border-color: #bfc0ab;
--map-attribution-text: var(--app-color);
--map-display-hover-attribution-text: initial;
--map-attribution-bg-color: rgba(255, 255, 255, 0.7);
--map-filter: initial;
--map-display-hover-filter: initial;
--map-layers-overlays: initial;
--map-control-bar: #bfc0ab;
--no-map-filter: initial;
--workout-static-map-shadow-color: var(--app-shadow-color);
--mountains-filter: invert(19%) sepia(9%) saturate(2921%) hue-rotate(169deg)
brightness(85%) contrast(80%);
--cell-heading-bg-color: #eeeeee;
--cell-heading-color: #696969;
@@ -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<
+2
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 {
+18 -5
View File
@@ -7,24 +7,37 @@ import type {
} from '@/types/workouts'
import { convertStatsDistance } from '@/utils/units'
export const chartsColors = {
ligthMode: {
// default chartjs values
text: '#666',
line: 'rgba(0, 0, 0, 0.1)',
},
darkMode: {
text: '#a1a1a1',
line: '#3f3f3f',
},
}
export const getDatasets = (
chartData: IWorkoutApiChartData[],
t: CallableFunction,
useImperialUnits: boolean
useImperialUnits: boolean,
useDarkMode: boolean = false
): IWorkoutChartData => {
const datasets: TWorkoutDatasets = {
speed: {
label: t('workouts.SPEED'),
backgroundColor: ['#FFFFFF'],
borderColor: ['#8884d8'],
backgroundColor: ['transparent'],
borderColor: [useDarkMode ? '#5f5c97' : '#8884d8'],
borderWidth: 2,
data: [],
yAxisID: 'ySpeed',
},
elevation: {
label: t('workouts.ELEVATION'),
backgroundColor: ['#e5e5e5'],
borderColor: ['#cccccc'],
backgroundColor: [useDarkMode ? '#303030' : '#e5e5e5'],
borderColor: [useDarkMode ? '#222222' : '#cccccc'],
borderWidth: 1,
fill: true,
data: [],
+2 -2
View File
@@ -72,8 +72,8 @@
&:disabled {
-webkit-appearance: none;
-moz-appearance: textfield;
background-color: white;
border-color: white;
background-color: var(--admin-disabled-input-color);
border-color: var(--admin-disabled-input-color);
color: var(--app-color);
}
}
@@ -21,7 +21,7 @@ describe('getDatasets', () => {
datasets: {
speed: {
label: 'vitesse',
backgroundColor: ['#FFFFFF'],
backgroundColor: ['transparent'],
borderColor: ['#8884d8'],
borderWidth: 2,
data: [],
@@ -81,7 +81,7 @@ describe('getDatasets', () => {
datasets: {
speed: {
label: 'speed',
backgroundColor: ['#FFFFFF'],
backgroundColor: ['transparent'],
borderColor: ['#8884d8'],
borderWidth: 2,
data: [2.89, 20.64, 13.03],
@@ -145,7 +145,7 @@ describe('getDatasets', () => {
datasets: {
speed: {
label: 'speed',
backgroundColor: ['#FFFFFF'],
backgroundColor: ['transparent'],
borderColor: ['#8884d8'],
borderWidth: 2,
data: [1.8, 12.83, 8.1],
@@ -183,6 +183,90 @@ describe('getDatasets', () => {
})
})
describe('getDatasets with dark mode', () => {
const testparams = [
{
description: 'it returns dark mode color',
inputParams: {
charData: [],
locale: 'fr',
useImperialUnits: false,
useDarkMode: true,
},
expected: {
distance_labels: [],
duration_labels: [],
datasets: {
speed: {
label: 'vitesse',
backgroundColor: ['transparent'],
borderColor: ['#5f5c97'],
borderWidth: 2,
data: [],
yAxisID: 'ySpeed',
},
elevation: {
label: 'altitude',
backgroundColor: ['#303030'],
borderColor: ['#222222'],
borderWidth: 1,
fill: true,
data: [],
yAxisID: 'yElevation',
},
},
coordinates: [],
},
},
{
description: 'it returns light mode color',
inputParams: {
charData: [],
locale: 'fr',
useImperialUnits: false,
useDarkMode: false,
},
expected: {
distance_labels: [],
duration_labels: [],
datasets: {
speed: {
label: 'vitesse',
backgroundColor: ['transparent'],
borderColor: ['#8884d8'],
borderWidth: 2,
data: [],
yAxisID: 'ySpeed',
},
elevation: {
label: 'altitude',
backgroundColor: ['#e5e5e5'],
borderColor: ['#cccccc'],
borderWidth: 1,
fill: true,
data: [],
yAxisID: 'yElevation',
},
},
coordinates: [],
},
},
]
testparams.map((testParams) => {
it(testParams.description, () => {
locale.value = testParams.inputParams.locale
expect(
getDatasets(
testParams.inputParams.charData,
t,
testParams.inputParams.useImperialUnits,
testParams.inputParams.useDarkMode
)
).toStrictEqual(testParams.expected)
})
})
})
describe('getDonutDatasets', () => {
const testparams = [
{