Merge pull request #358 from jat255/max_speed_preference

Add user preference for filtering of GPX speed data
This commit is contained in:
Sam 2023-05-28 17:03:57 +02:00 committed by GitHub
commit a74c03d14a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 275 additions and 27 deletions

View File

@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="/favicon.ico"><![endif]--><link rel="stylesheet" href="/static/css/fork-awesome.min.css"/><link rel="stylesheet" href="/static/css/leaflet.css"/><title>FitTrackee</title><script defer="defer" src="/static/js/chunk-vendors.a33476ea.js"></script><script defer="defer" src="/static/js/app.464dd65e.js"></script><link href="/static/css/app.fa4567f8.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="/img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/img/icons/favicon-16x16.png"><link rel="manifest" href="/manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="fittrackee_client"><link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="/img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><noscript><strong>We're sorry but FitTrackee doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html> <!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="/favicon.ico"><![endif]--><link rel="stylesheet" href="/static/css/fork-awesome.min.css"/><link rel="stylesheet" href="/static/css/leaflet.css"/><title>FitTrackee</title><script defer="defer" src="/static/js/chunk-vendors.a33476ea.js"></script><script defer="defer" src="/static/js/app.f72ca9c7.js"></script><link href="/static/css/app.3193986e.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="/img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/img/icons/favicon-16x16.png"><link rel="manifest" href="/manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="fittrackee_client"><link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="/img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><noscript><strong>We're sorry but FitTrackee doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>

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

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

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,34 @@
"""Add user prefrence for gpx speed calculation
Revision ID: eff1c16c43eb
Revises: db58d195c5bf
Create Date: 2023-05-14 22:12:56.244291
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'eff1c16c43eb'
down_revision = 'db58d195c5bf'
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_raw_gpx_speed', sa.Boolean(), nullable=True))
op.execute("UPDATE users SET use_raw_gpx_speed = false")
op.alter_column('users', 'use_raw_gpx_speed', nullable=False)
# ### 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_raw_gpx_speed')
# ### end Alembic commands ###

View File

@ -59,6 +59,24 @@ def user_1_full() -> User:
return user return user
@pytest.fixture()
def user_1_raw_speed() -> User:
user = User(username='test', email='test@test.com', password='12345678')
user.first_name = 'John'
user.last_name = 'Doe'
user.bio = 'just a random guy'
user.location = 'somewhere'
user.language = 'en'
user.timezone = 'America/New_York'
user.birth_date = datetime.datetime.strptime('01/01/1980', '%d/%m/%Y')
user.is_active = True
user.use_raw_gpx_speed = True
user.accepted_policy = datetime.datetime.utcnow()
db.session.add(user)
db.session.commit()
return user
@pytest.fixture() @pytest.fixture()
def user_1_paris() -> User: def user_1_paris() -> User:
user = User(username='test', email='test@test.com', password='12345678') user = User(username='test', email='test@test.com', password='12345678')

View File

@ -1459,6 +1459,7 @@ class TestUserPreferencesUpdate(ApiTestCaseMixin):
imperial_units=True, imperial_units=True,
display_ascent=False, display_ascent=False,
start_elevation_at_zero=False, start_elevation_at_zero=False,
use_raw_gpx_speed=True,
date_format='yyyy-MM-dd', date_format='yyyy-MM-dd',
) )
), ),
@ -1471,6 +1472,7 @@ class TestUserPreferencesUpdate(ApiTestCaseMixin):
assert data['message'] == 'user preferences updated' assert data['message'] == 'user preferences updated'
assert data['data']['display_ascent'] is False assert data['data']['display_ascent'] is False
assert data['data']['start_elevation_at_zero'] is False assert data['data']['start_elevation_at_zero'] is False
assert data['data']['use_raw_gpx_speed'] is True
assert data['data']['imperial_units'] is True assert data['data']['imperial_units'] is True
assert data['data']['language'] == expected_language assert data['data']['language'] == expected_language
assert data['data']['timezone'] == 'America/New_York' assert data['data']['timezone'] == 'America/New_York'

View File

@ -54,7 +54,7 @@ class TestStoppedSpeedThreshold:
) )
assert gpx_track_segment_mock.call_args_list[0] == call( assert gpx_track_segment_mock.call_args_list[0] == call(
stopped_speed_threshold=expected_threshold stopped_speed_threshold=expected_threshold, raw=False
) )
gpx_track_segment_mock.assert_called_with( gpx_track_segment_mock.assert_called_with(
expected_threshold, # stopped_speed_threshold expected_threshold, # stopped_speed_threshold
@ -88,7 +88,7 @@ class TestStoppedSpeedThreshold:
) )
assert gpx_track_segment_mock.call_args_list[0] == call( assert gpx_track_segment_mock.call_args_list[0] == call(
stopped_speed_threshold=expected_threshold stopped_speed_threshold=expected_threshold, raw=False
) )
gpx_track_segment_mock.assert_called_with( gpx_track_segment_mock.assert_called_with(
expected_threshold, # stopped_speed_threshold expected_threshold, # stopped_speed_threshold
@ -98,6 +98,43 @@ class TestStoppedSpeedThreshold:
) )
class TestUseRawGpxSpeed:
@pytest.mark.parametrize('input_use_raw_gpx_speed', [True, False])
def test_it_calls_get_moving_data_with_user_use_raw_gpx_speed_preference(
self,
app: Flask,
user_1: User,
gpx_file_storage: FileStorage,
sport_1_cycling: Sport,
input_use_raw_gpx_speed: bool,
) -> None:
user_1.use_raw_gpx_speed = input_use_raw_gpx_speed
with patch(
'fittrackee.workouts.utils.workouts.get_new_file_path',
return_value='/tmp/fitTrackee/uploads/test.png',
), patch(
'gpxpy.gpx.GPXTrackSegment.get_moving_data',
return_value=moving_data,
) as gpx_track_segment_mock:
process_files(
auth_user=user_1,
folders=folders,
workout_data={'sport_id': sport_1_cycling.id},
workout_file=gpx_file_storage,
)
assert gpx_track_segment_mock.call_args_list[0] == call(
stopped_speed_threshold=sport_1_cycling.stopped_speed_threshold,
raw=input_use_raw_gpx_speed,
)
gpx_track_segment_mock.assert_called_with(
sport_1_cycling.stopped_speed_threshold, # stopped_speed_threshold
False, # raw
IGNORE_TOP_SPEED_PERCENTILES, # speed_extreemes_percentiles
True, # ignore_nonstandard_distances
)
class TestGetGpxInfoStopTime: class TestGetGpxInfoStopTime:
def test_stop_time_equals_to_0_when_gpx_file_contains_one_segment( def test_stop_time_equals_to_0_when_gpx_file_contains_one_segment(
self, gpx_file: str self, gpx_file: str

View File

@ -279,6 +279,37 @@ class TestPostWorkoutWithGpx(ApiTestCaseMixin, CallArgsMixin):
assert 'just a workout' == data['data']['workouts'][0]['title'] assert 'just a workout' == data['data']['workouts'][0]['title']
assert_workout_data_with_gpx(data) assert_workout_data_with_gpx(data)
def test_it_adds_a_workout_with_gpx_file_raw_speed(
self,
app: Flask,
user_1_raw_speed: User,
sport_1_cycling: Sport,
gpx_file: str,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_raw_speed.email
)
response = client.post(
'/api/workouts',
data=dict(
file=(BytesIO(str.encode(gpx_file)), 'example.gpx'),
data='{"sport_id": 1}',
),
headers=dict(
content_type='multipart/form-data',
Authorization=f'Bearer {auth_token}',
),
)
data = json.loads(response.data.decode())
assert response.status_code == 201
assert 'created' in data['status']
assert len(data['data']['workouts']) == 1
# max speed should be slightly higher than that tested in
# assert_workout_data_with_gpx
assert data['data']['workouts'][0]['max_speed'] == pytest.approx(5.25)
def test_it_returns_ha_record_when_a_workout_without_gpx_exists( def test_it_returns_ha_record_when_a_workout_without_gpx_exists(
self, self,
app: Flask, app: Flask,

View File

@ -879,6 +879,7 @@ def edit_user_preferences(auth_user: User) -> Union[Dict, HttpResponse]:
"total_ascent": 720.35, "total_ascent": 720.35,
"total_distance": 67.895, "total_distance": 67.895,
"total_duration": "6:50:27", "total_duration": "6:50:27",
"use_raw_gpx_speed": true,
"username": "sam" "username": "sam"
"weekm": true, "weekm": true,
}, },
@ -892,6 +893,7 @@ def edit_user_preferences(auth_user: User) -> Union[Dict, HttpResponse]:
:<json string language: language preferences :<json string language: language preferences
:<json boolean start_elevation_at_zero: do elevation plots start at zero? :<json boolean start_elevation_at_zero: do elevation plots start at zero?
:<json string timezone: user time zone :<json string timezone: user time zone
:<json boolean use_raw_gpx_speed: Use unfiltered gpx to calculate speeds
:<json boolean weekm: does week start on Monday? :<json boolean weekm: does week start on Monday?
:reqheader Authorization: OAuth 2.0 Bearer Token :reqheader Authorization: OAuth 2.0 Bearer Token
@ -915,6 +917,7 @@ def edit_user_preferences(auth_user: User) -> Union[Dict, HttpResponse]:
'language', 'language',
'start_elevation_at_zero', 'start_elevation_at_zero',
'timezone', 'timezone',
'use_raw_gpx_speed',
'weekm', 'weekm',
} }
if not post_data or not post_data.keys() >= user_mandatory_data: if not post_data or not post_data.keys() >= user_mandatory_data:
@ -925,6 +928,7 @@ def edit_user_preferences(auth_user: User) -> Union[Dict, HttpResponse]:
imperial_units = post_data.get('imperial_units') imperial_units = post_data.get('imperial_units')
language = get_language(post_data.get('language')) language = get_language(post_data.get('language'))
start_elevation_at_zero = post_data.get('start_elevation_at_zero') start_elevation_at_zero = post_data.get('start_elevation_at_zero')
use_raw_gpx_speed = post_data.get('use_raw_gpx_speed')
timezone = post_data.get('timezone') timezone = post_data.get('timezone')
weekm = post_data.get('weekm') weekm = post_data.get('weekm')
@ -935,6 +939,7 @@ def edit_user_preferences(auth_user: User) -> Union[Dict, HttpResponse]:
auth_user.language = language auth_user.language = language
auth_user.start_elevation_at_zero = start_elevation_at_zero auth_user.start_elevation_at_zero = start_elevation_at_zero
auth_user.timezone = timezone auth_user.timezone = timezone
auth_user.use_raw_gpx_speed = use_raw_gpx_speed
auth_user.weekm = weekm auth_user.weekm = weekm
db.session.commit() db.session.commit()

View File

@ -62,6 +62,7 @@ class User(BaseModel):
start_elevation_at_zero = db.Column( start_elevation_at_zero = db.Column(
db.Boolean, default=True, nullable=False db.Boolean, default=True, nullable=False
) )
use_raw_gpx_speed = db.Column(db.Boolean, default=False, nullable=False)
def __repr__(self) -> str: def __repr__(self) -> str:
return f'<User {self.username!r}>' return f'<User {self.username!r}>'
@ -216,6 +217,7 @@ class User(BaseModel):
'language': self.language, 'language': self.language,
'start_elevation_at_zero': self.start_elevation_at_zero, 'start_elevation_at_zero': self.start_elevation_at_zero,
'timezone': self.timezone, 'timezone': self.timezone,
'use_raw_gpx_speed': self.use_raw_gpx_speed,
'weekm': self.weekm, 'weekm': self.weekm,
}, },
} }

View File

@ -73,6 +73,7 @@ def get_gpx_info(
stopped_speed_threshold: float, stopped_speed_threshold: float,
update_map_data: Optional[bool] = True, update_map_data: Optional[bool] = True,
update_weather_data: Optional[bool] = True, update_weather_data: Optional[bool] = True,
use_raw_gpx_speed: bool = False,
) -> Tuple: ) -> Tuple:
""" """
Parse and return gpx, map and weather data from gpx file Parse and return gpx, map and weather data from gpx file
@ -128,7 +129,8 @@ def get_gpx_info(
if update_map_data: if update_map_data:
map_data.append([point.longitude, point.latitude]) map_data.append([point.longitude, point.latitude])
moving_data = segment.get_moving_data( moving_data = segment.get_moving_data(
stopped_speed_threshold=stopped_speed_threshold stopped_speed_threshold=stopped_speed_threshold,
raw=use_raw_gpx_speed,
) )
if moving_data: if moving_data:
calculated_max_speed = moving_data.max_speed calculated_max_speed = moving_data.max_speed

View File

@ -299,10 +299,12 @@ def process_one_gpx_file(
absolute_gpx_filepath = None absolute_gpx_filepath = None
absolute_map_filepath = None absolute_map_filepath = None
try: try:
gpx_data, map_data, weather_data = get_gpx_info(
params['file_path'], stopped_speed_threshold
)
auth_user = params['auth_user'] auth_user = params['auth_user']
gpx_data, map_data, weather_data = get_gpx_info(
gpx_file=params['file_path'],
stopped_speed_threshold=stopped_speed_threshold,
use_raw_gpx_speed=auth_user.use_raw_gpx_speed,
)
workout_date, _ = get_workout_datetime( workout_date, _ = get_workout_datetime(
workout_date=gpx_data['start'], workout_date=gpx_data['start'],
date_str_format=None if gpx_data else '%Y-%m-%d %H:%M', date_str_format=None if gpx_data else '%Y-%m-%d %H:%M',

View File

@ -20,7 +20,31 @@
<dt>{{ $t('user.PROFILE.ASCENT_DATA') }}:</dt> <dt>{{ $t('user.PROFILE.ASCENT_DATA') }}:</dt>
<dd>{{ $t(`common.${display_ascent}`) }}</dd> <dd>{{ $t(`common.${display_ascent}`) }}</dd>
<dt>{{ $t('user.PROFILE.ELEVATION_CHART_START.LABEL') }}:</dt> <dt>{{ $t('user.PROFILE.ELEVATION_CHART_START.LABEL') }}:</dt>
<dd>{{ $t(`user.PROFILE.ELEVATION_CHART_START.${user.start_elevation_at_zero ? 'ZERO' : 'MIN_ALT'}`) }}</dd> <dd>
{{
$t(
`user.PROFILE.ELEVATION_CHART_START.${
user.start_elevation_at_zero ? 'ZERO' : 'MIN_ALT'
}`
)
}}
</dd>
<dt>{{ $t('user.PROFILE.USE_RAW_GPX_SPEED.LABEL') }}:</dt>
<dd>
{{
$t(
`user.PROFILE.USE_RAW_GPX_SPEED.${
user.use_raw_gpx_speed ? 'RAW_SPEED' : 'FILTERED_SPEED'
}`
)
}}
</dd>
<div class="info-box raw-speed-help">
<span>
<i class="fa fa-info-circle" aria-hidden="true" />
{{ $t('user.PROFILE.USE_RAW_GPX_SPEED.HELP') }}
</span>
</div>
</dl> </dl>
<div class="profile-buttons"> <div class="profile-buttons">
<button @click="$router.push('/profile/edit/preferences')"> <button @click="$router.push('/profile/edit/preferences')">

View File

@ -104,7 +104,10 @@
{{ $t('user.PROFILE.ELEVATION_CHART_START.LABEL') }} {{ $t('user.PROFILE.ELEVATION_CHART_START.LABEL') }}
</span> </span>
<div class="checkboxes"> <div class="checkboxes">
<label v-for="status in startElevationAtZeroData" :key="status.label"> <label
v-for="status in startElevationAtZeroData"
:key="status.label"
>
<input <input
type="radio" type="radio"
:id="status.label" :id="status.label"
@ -119,6 +122,32 @@
</label> </label>
</div> </div>
</div> </div>
<div class="form-items form-checkboxes">
<span class="checkboxes-label">
{{ $t('user.PROFILE.USE_RAW_GPX_SPEED.LABEL') }}
</span>
<div class="checkboxes">
<label v-for="status in useRawGpxSpeed" :key="status.label">
<input
type="radio"
:id="status.label"
:name="status.label"
:checked="status.value === userForm.use_raw_gpx_speed"
:disabled="loading"
@input="updateUseRawGpxSpeed(status.value)"
/>
<span class="checkbox-label">
{{ $t(`user.PROFILE.USE_RAW_GPX_SPEED.${status.label}`) }}
</span>
</label>
</div>
<div class="info-box raw-speed-help">
<span>
<i class="fa fa-info-circle" aria-hidden="true" />
{{ $t('user.PROFILE.USE_RAW_GPX_SPEED.HELP') }}
</span>
</div>
</div>
<div class="form-buttons"> <div class="form-buttons">
<button class="confirm" type="submit"> <button class="confirm" type="submit">
{{ $t('buttons.SUBMIT') }} {{ $t('buttons.SUBMIT') }}
@ -193,12 +222,22 @@
const startElevationAtZeroData = [ const startElevationAtZeroData = [
{ {
label: 'ZERO', label: 'ZERO',
value: true value: true,
}, },
{ {
label: 'MIN_ALT', label: 'MIN_ALT',
value: false value: false,
} },
]
const useRawGpxSpeed = [
{
label: 'FILTERED_SPEED',
value: false,
},
{
label: 'RAW_SPEED',
value: true,
},
] ]
const loading = computed( const loading = computed(
() => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING] () => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING]
@ -222,7 +261,12 @@
function updateUserForm(user: IAuthUserProfile) { function updateUserForm(user: IAuthUserProfile) {
userForm.display_ascent = user.display_ascent userForm.display_ascent = user.display_ascent
userForm.start_elevation_at_zero = user.start_elevation_at_zero ? user.start_elevation_at_zero : false userForm.start_elevation_at_zero = user.start_elevation_at_zero
? user.start_elevation_at_zero
: false
userForm.use_raw_gpx_speed = user.use_raw_gpx_speed
? user.use_raw_gpx_speed
: false
userForm.imperial_units = user.imperial_units ? user.imperial_units : false userForm.imperial_units = user.imperial_units ? user.imperial_units : false
userForm.language = user.language ? user.language : 'en' userForm.language = user.language ? user.language : 'en'
userForm.timezone = user.timezone ? user.timezone : 'Europe/Paris' userForm.timezone = user.timezone ? user.timezone : 'Europe/Paris'
@ -238,6 +282,9 @@
function updateStartElevationAtZero(value: boolean) { function updateStartElevationAtZero(value: boolean) {
userForm.start_elevation_at_zero = value userForm.start_elevation_at_zero = value
} }
function updateUseRawGpxSpeed(value: boolean) {
userForm.use_raw_gpx_speed = value
}
function updateAscentDisplay(value: boolean) { function updateAscentDisplay(value: boolean) {
userForm.display_ascent = value userForm.display_ascent = value
} }

View File

@ -116,6 +116,11 @@
"IMPERIAL": "Imperiales System (ft, mi, mph, °F)", "IMPERIAL": "Imperiales System (ft, mi, mph, °F)",
"LABEL": "Einheiten für die Distanz", "LABEL": "Einheiten für die Distanz",
"METRIC": "Metrisches System (m, km, m/s, °C)" "METRIC": "Metrisches System (m, km, m/s, °C)"
},
"USE_RAW_GPX_SPEED": {
"FILTERED_SPEED": "Gefiltert",
"LABEL": "Höchstgeschwindigkeitsstrategie",
"RAW_SPEED": "Rau"
} }
}, },
"READ_AND_ACCEPT_PRIVACY_POLICY": "Ich habe die {0} gelesen und stimme ihr zu.", "READ_AND_ACCEPT_PRIVACY_POLICY": "Ich habe die {0} gelesen und stimme ihr zu.",

View File

@ -68,12 +68,12 @@
"EDIT": "Edit profile", "EDIT": "Edit profile",
"EDIT_PREFERENCES": "Edit preferences", "EDIT_PREFERENCES": "Edit preferences",
"EDIT_SPORTS_PREFERENCES": "Edit sports preferences", "EDIT_SPORTS_PREFERENCES": "Edit sports preferences",
"ERRORED_EMAIL_UPDATE": "Please {0} to change your email address again or contact the administrator",
"ELEVATION_CHART_START": { "ELEVATION_CHART_START": {
"LABEL": "Elevation chart starts at", "LABEL": "Elevation chart starts at",
"ZERO": "Zero", "MIN_ALT": "Minimum altitude",
"MIN_ALT": "Minimum altitude" "ZERO": "Zero"
}, },
"ERRORED_EMAIL_UPDATE": "Please {0} to change your email address again or contact the administrator",
"FIRST_DAY_OF_WEEK": "First day of week", "FIRST_DAY_OF_WEEK": "First day of week",
"FIRST_NAME": "First name", "FIRST_NAME": "First name",
"LANGUAGE": "Language", "LANGUAGE": "Language",
@ -116,6 +116,12 @@
"IMPERIAL": "Imperial system (ft, mi, mph, °F)", "IMPERIAL": "Imperial system (ft, mi, mph, °F)",
"LABEL": "Units for distance", "LABEL": "Units for distance",
"METRIC": "Metric system (m, km, m/s, °C)" "METRIC": "Metric system (m, km, m/s, °C)"
},
"USE_RAW_GPX_SPEED": {
"FILTERED_SPEED": "Filtered",
"HELP": "If filtered, it excludes extreme values (which may be GPS errors) when calculating the maximum speed.",
"LABEL": "GPX max speed strategy",
"RAW_SPEED": "Raw"
} }
}, },
"READ_AND_ACCEPT_PRIVACY_POLICY": "I have read and agree to the {0}.", "READ_AND_ACCEPT_PRIVACY_POLICY": "I have read and agree to the {0}.",

View File

@ -116,6 +116,11 @@
"IMPERIAL": "Sistema Imperial (ft, mi, mph, ºF)", "IMPERIAL": "Sistema Imperial (ft, mi, mph, ºF)",
"LABEL": "Unidades de distancia", "LABEL": "Unidades de distancia",
"METRIC": "Sistema Métrico (m, km, m/s, ºC)" "METRIC": "Sistema Métrico (m, km, m/s, ºC)"
},
"USE_RAW_GPX_SPEED": {
"FILTERED_SPEED": "Filtrado",
"LABEL": "Estrategia de velocidad máxima",
"RAW_SPEED": "Crudo"
} }
}, },
"READ_AND_ACCEPT_PRIVACY_POLICY": "He leído y aceptado la {0}.", "READ_AND_ACCEPT_PRIVACY_POLICY": "He leído y aceptado la {0}.",

View File

@ -70,8 +70,8 @@
"EDIT_SPORTS_PREFERENCES": "Modifier les préférences des sports", "EDIT_SPORTS_PREFERENCES": "Modifier les préférences des sports",
"ELEVATION_CHART_START": { "ELEVATION_CHART_START": {
"LABEL": "Début de l'axe pour le graphe affichant l'altitude", "LABEL": "Début de l'axe pour le graphe affichant l'altitude",
"ZERO": "0", "MIN_ALT": "Altitude minimale",
"MIN_ALT": "Altitude minimale" "ZERO": "0"
}, },
"ERRORED_EMAIL_UPDATE": "Veuillez vous {0} pour changer de nouveau votre adresse électronique ou contacter l'administrateur", "ERRORED_EMAIL_UPDATE": "Veuillez vous {0} pour changer de nouveau votre adresse électronique ou contacter l'administrateur",
"FIRST_DAY_OF_WEEK": "Premier jour de la semaine", "FIRST_DAY_OF_WEEK": "Premier jour de la semaine",
@ -116,6 +116,12 @@
"IMPERIAL": "Système impérial (ft, mi, mph, °F)", "IMPERIAL": "Système impérial (ft, mi, mph, °F)",
"LABEL": "Unités pour les distances", "LABEL": "Unités pour les distances",
"METRIC": "Système métrique (m, km, m/s, °C)" "METRIC": "Système métrique (m, km, m/s, °C)"
},
"USE_RAW_GPX_SPEED": {
"FILTERED_SPEED": "Filtré",
"HELP": "Si filtré, les valeurs extrêmes (qui peuvent être des erreurs GPS) sont exclues lors du calcul de la vitesse maximale.",
"LABEL": "Stratégie de vitesse maximale",
"RAW_SPEED": "Brut"
} }
}, },
"READ_AND_ACCEPT_PRIVACY_POLICY": "J'ai lu et accepte la {0}.", "READ_AND_ACCEPT_PRIVACY_POLICY": "J'ai lu et accepte la {0}.",

View File

@ -116,6 +116,11 @@
"IMPERIAL": "Sistema Imperial (ft, mi, mph, ºF)", "IMPERIAL": "Sistema Imperial (ft, mi, mph, ºF)",
"LABEL": "Unidades de distancia", "LABEL": "Unidades de distancia",
"METRIC": "Sistema Métrico (m, km, m/s, ºC)" "METRIC": "Sistema Métrico (m, km, m/s, ºC)"
},
"USE_RAW_GPX_SPEED": {
"FILTERED_SPEED": "Filtrado",
"LABEL": "Estratexia de velocidade máxima",
"RAW_SPEED": "Cru"
} }
}, },
"READ_AND_ACCEPT_PRIVACY_POLICY": "Lin e acepto a {0}.", "READ_AND_ACCEPT_PRIVACY_POLICY": "Lin e acepto a {0}.",

View File

@ -97,6 +97,11 @@
"IMPERIAL": "Sistema imperiale (ft, mi, mph, °F)", "IMPERIAL": "Sistema imperiale (ft, mi, mph, °F)",
"LABEL": "Unità per la distanza", "LABEL": "Unità per la distanza",
"METRIC": "Sistema metrico (m, km, m/s, °C)" "METRIC": "Sistema metrico (m, km, m/s, °C)"
},
"USE_RAW_GPX_SPEED": {
"FILTERED_SPEED": "Filtrato",
"LABEL": "Strategia di massima velocità",
"RAW_SPEED": "Greggio"
} }
}, },
"REGISTER": "Registra", "REGISTER": "Registra",

View File

@ -47,7 +47,12 @@
"TABS": { "TABS": {
"PROFILE": "profil" "PROFILE": "profil"
}, },
"TIMEZONE": "Tidssone" "TIMEZONE": "Tidssone",
"USE_RAW_GPX_SPEED": {
"FILTERED_SPEED": "Filtrert",
"LABEL": "Maks hastighet strategi",
"RAW_SPEED": "Rå"
}
}, },
"RESET_PASSWORD": "Tilbakestill passordet ditt", "RESET_PASSWORD": "Tilbakestill passordet ditt",
"SHOW_PASSWORD": "vis passord", "SHOW_PASSWORD": "vis passord",

View File

@ -116,6 +116,11 @@
"IMPERIAL": "Imperialistisch systeem (ft, mi, mph, °F)", "IMPERIAL": "Imperialistisch systeem (ft, mi, mph, °F)",
"LABEL": "Eenheid voor afstand", "LABEL": "Eenheid voor afstand",
"METRIC": "Metrisch systeem (m, km, m/s, °C)" "METRIC": "Metrisch systeem (m, km, m/s, °C)"
},
"USE_RAW_GPX_SPEED": {
"FILTERED_SPEED": "Gefilterd",
"LABEL": "Strategie voor maximale snelheid",
"RAW_SPEED": "Rauw"
} }
}, },
"READ_AND_ACCEPT_PRIVACY_POLICY": "Ik heb het {0} gelezen en goedgekeurd.", "READ_AND_ACCEPT_PRIVACY_POLICY": "Ik heb het {0} gelezen en goedgekeurd.",

View File

@ -29,6 +29,7 @@ export interface IAuthUserProfile extends IUserProfile {
display_ascent: boolean display_ascent: boolean
imperial_units: boolean imperial_units: boolean
start_elevation_at_zero: boolean start_elevation_at_zero: boolean
use_raw_gpx_speed: boolean
language: string | null language: string | null
timezone: string timezone: string
date_format: string date_format: string
@ -65,6 +66,7 @@ export interface IAdminUserPayload {
export interface IUserPreferencesPayload { export interface IUserPreferencesPayload {
display_ascent: boolean display_ascent: boolean
start_elevation_at_zero: boolean start_elevation_at_zero: boolean
use_raw_gpx_speed: boolean
imperial_units: boolean imperial_units: boolean
language: string language: string
timezone: string timezone: string