Add pace privacy controls & edit modal support

[backend] add hide_pace column to user_privacy_settings table
[backend] add hide_pace column to activities table
[backend] add logic for hide_pace column
[backend] add logic to only return activity values hidden if user_id = token_user_id
[frontend] add AlertComponent
[frontend] add pace column
[frontend] change US translations from "Velocity" to "Speed"
[frontend] added ability to change activity privacy settings on edit modal
This commit is contained in:
João Vitória Silva
2025-06-03 22:59:08 +01:00
parent e57db7d205
commit 093714fd76
24 changed files with 424 additions and 97 deletions

View File

@@ -197,6 +197,7 @@ def get_user_activities_with_pagination(
name_search: str | None = None,
sort_by: str | None = None,
sort_order: str | None = None,
user_is_owner: bool = False,
) -> list[activities_schema.Activity] | None:
try:
# Mapping from frontend sort keys to database model fields
@@ -311,6 +312,13 @@ def get_user_activities_with_pagination(
serialized_activities = []
if activities:
for activity in activities:
if not user_is_owner:
if activity.hide_start_time:
activity.start_time = None
if activity.hide_location:
activity.city = None
activity.town = None
activity.country = None
serialized_activities.append(
activities_utils.serialize_activity(activity)
)
@@ -364,6 +372,7 @@ def get_user_activities_per_timeframe(
start: datetime,
end: datetime,
db: Session,
user_is_owner: bool = False,
):
try:
# Get the activities from the database
@@ -384,6 +393,14 @@ def get_user_activities_per_timeframe(
for activity in activities:
activity = activities_utils.serialize_activity(activity)
if not user_is_owner:
if activity.hide_start_time:
activity.start_time = None
if activity.hide_location:
activity.city = None
activity.town = None
activity.country = None
# Return the activities
return activities
@@ -426,6 +443,12 @@ def get_user_following_activities_per_timeframe(
for activity in activities:
activity = activities_utils.serialize_activity(activity)
if activity.hide_start_time:
activity.start_time = None
if activity.hide_location:
activity.city = None
activity.town = None
activity.country = None
# Return the activities
return activities
@@ -477,6 +500,12 @@ def get_user_following_activities_with_pagination(
# Iterate and format the dates
for activity in activities:
activity = activities_utils.serialize_activity(activity)
if activity.hide_start_time:
activity.start_time = None
if activity.hide_location:
activity.city = None
activity.town = None
activity.country = None
# Return the activities
return activities

View File

@@ -171,6 +171,11 @@ class Activity(Base):
nullable=False,
comment="Hide activity speed",
)
hide_pace = Column(
Boolean,
nullable=False,
comment="Hide activity pace",
)
# Define a relationship to the User model
user = relationship("User", back_populates="activities")

View File

@@ -63,7 +63,7 @@ async def read_activities_user_activities_week(
if user_id == token_user_id:
# Get all user activities for the requested week if the user is the owner of the token
activities = activities_crud.get_user_activities_per_timeframe(
user_id, start_of_week, end_of_week, db
user_id, start_of_week, end_of_week, db, True
)
else:
# Get user following activities for the requested week if the user is not the owner of the token
@@ -107,7 +107,7 @@ async def read_activities_user_activities_this_week_distances(
if user_id == token_user_id:
# Get all user activities for the requested week if the user is the owner of the token
activities = activities_crud.get_user_activities_per_timeframe(
user_id, start_of_week, end_of_week, db
user_id, start_of_week, end_of_week, db, True
)
else:
# Get user following activities for the requested week if the user is not the owner of the token
@@ -148,7 +148,7 @@ async def read_activities_user_activities_this_month_distances(
if user_id == token_user_id:
# Get all user activities for the requested month if the user is the owner of the token
activities = activities_crud.get_user_activities_per_timeframe(
user_id, start_of_month, end_of_month, db
user_id, start_of_month, end_of_month, db, True
)
else:
# Get user following activities for the requested month if the user is not the owner of the token
@@ -193,7 +193,7 @@ async def read_activities_user_activities_this_month_number(
if user_id == token_user_id:
# Get all user activities for the requested month if the user is the owner of the token
activities = activities_crud.get_user_activities_per_timeframe(
user_id, start_of_month, end_of_month, db
user_id, start_of_month, end_of_month, db, True
)
else:
# Get user following activities for the requested month if the user is not the owner of the token
@@ -313,6 +313,10 @@ async def read_activities_user_activities_pagination(
check_scopes: Annotated[
Callable, Security(session_security.check_scopes, scopes=["activities:read"])
],
token_user_id: Annotated[
int,
Depends(session_security.get_user_id_from_access_token),
],
db: Annotated[
Session,
Depends(core_database.get_db),
@@ -335,6 +339,9 @@ async def read_activities_user_activities_pagination(
sort_by: str | None = Query(None),
sort_order: str | None = Query(None),
):
user_is_owner = True
if token_user_id != user_id:
user_is_owner = False
# Get and return the activities for the user with pagination and filters
return activities_crud.get_user_activities_with_pagination(
user_id=user_id,
@@ -347,6 +354,7 @@ async def read_activities_user_activities_pagination(
name_search=name_search,
sort_by=sort_by,
sort_order=sort_order,
user_is_owner=user_is_owner,
)

View File

@@ -46,6 +46,7 @@ class Activity(BaseModel):
hide_cadence: bool | None = None
hide_elevation: bool | None = None
hide_speed: bool | None = None
hide_pace: bool | None = None
class Config:
orm_mode = True

View File

@@ -223,6 +223,7 @@ def transform_schema_activity_to_model_activity(
hide_cadence=activity.hide_cadence,
hide_elevation=activity.hide_elevation,
hide_speed=activity.hide_speed,
hide_pace=activity.hide_pace,
)
return new_activity

View File

@@ -84,6 +84,12 @@ def upgrade() -> None:
nullable=False,
comment="Hide activity speed",
),
sa.Column(
"hide_activity_pace",
sa.Boolean(),
nullable=False,
comment="Hide activity pace",
),
sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
)
@@ -108,7 +114,8 @@ def upgrade() -> None:
hide_activity_power,
hide_activity_cadence,
hide_activity_elevation,
hide_activity_speed
hide_activity_speed,
hide_activity_pace
)
SELECT
id,
@@ -120,7 +127,8 @@ def upgrade() -> None:
FALSE,
FALSE,
FALSE,
FALSE
FALSE,
FALSE
FROM users;
"""
)
@@ -195,6 +203,12 @@ def upgrade() -> None:
"hide_speed", sa.Boolean(), nullable=True, comment="Hide activity speed"
),
)
op.add_column(
"activities",
sa.Column(
"hide_pace", sa.Boolean(), nullable=True, comment="Hide activity pace"
),
)
# Update all existing activities to set these columns to FALSE
connection.execute(
@@ -209,6 +223,7 @@ def upgrade() -> None:
hide_cadence = FALSE,
hide_elevation = FALSE,
hide_speed = FALSE
hide_pace = FALSE
WHERE hide_start_time IS NULL;
"""
)
@@ -223,11 +238,13 @@ def upgrade() -> None:
op.alter_column("activities", "hide_cadence", nullable=False)
op.alter_column("activities", "hide_elevation", nullable=False)
op.alter_column("activities", "hide_speed", nullable=False)
op.alter_column("activities", "hide_pace", nullable=False)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("activities", "hide_pace")
op.drop_column("activities", "hide_speed")
op.drop_column("activities", "hide_elevation")
op.drop_column("activities", "hide_cadence")

View File

@@ -178,6 +178,7 @@ def create_activity_objects(
hide_elevation=user_privacy_settings.hide_activity_elevation
or False,
hide_speed=user_privacy_settings.hide_activity_speed or False,
hide_pace=user_privacy_settings.hide_activity_pace or False,
),
"is_elevation_set": session_record["is_elevation_set"],
"ele_waypoints": session_record["ele_waypoints"],

View File

@@ -330,6 +330,7 @@ def parse_gpx_file(
hide_elevation=user_privacy_settings.hide_activity_elevation
or False,
hide_speed=user_privacy_settings.hide_activity_speed or False,
hide_pace=user_privacy_settings.hide_activity_pace or False,
)
# Generate activity laps

View File

@@ -338,6 +338,7 @@ def parse_activity(
hide_elevation=user_privacy_settings.hide_activity_elevation
or False,
hide_speed=user_privacy_settings.hide_activity_speed or False,
hide_pace=user_privacy_settings.hide_activity_pace or False,
)
# Fetch and process activity laps

View File

@@ -51,6 +51,7 @@ class UserMe(User):
hide_activity_cadence: bool | None = None
hide_activity_elevation: bool | None = None
hide_activity_speed: bool | None = None
hide_activity_pace: bool | None = None
class UserEditPassword(BaseModel):

View File

@@ -56,6 +56,7 @@ def create_user_privacy_settings(user_id: int, db: Session):
hide_activity_cadence=False,
hide_activity_elevation=False,
hide_activity_speed=False,
hide_activity_pace=False,
)
# Add the user privacy settings to the database

View File

@@ -73,6 +73,12 @@ class UsersPrivacySettings(Base):
default=False,
comment="Hide activity speed",
)
hide_activity_pace = Column(
Boolean,
nullable=False,
default=False,
comment="Hide activity pace",
)
# Define a relationship to the User model

View File

@@ -13,6 +13,7 @@ class UsersPrivacySettings(BaseModel):
hide_activity_cadence: bool | None = None
hide_activity_elevation: bool | None = None
hide_activity_speed: bool | None = None
hide_activity_pace: bool | None = None
class Config:
from_attributes = True

View File

@@ -1,72 +1,191 @@
<template>
<!-- Modal edit activity -->
<div class="modal fade" id="editActivityModal" tabindex="-1" aria-labelledby="editActivityModalComponent" aria-hidden="true">
<div class="modal fade" id="editActivityModal" tabindex="-1" aria-labelledby="editActivityModalComponent"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="editActivityModal">{{ $t("editActivityModalComponent.modalEditActivityTitle") }}</h1>
<h1 class="modal-title fs-5" id="editActivityModal">{{
$t("editActivityModalComponent.modalEditActivityTitle") }}</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form @submit.prevent="submitEditActivityForm">
<form @submit.prevent="submitEditActivityForm">
<div class="modal-body">
<!-- name fields -->
<label for="activityNameEdit"><b>* {{ $t("editActivityModalComponent.modalEditActivityNameLabel") }}</b></label>
<input class="form-control" type="text" name="activityNameEdit" :placeholder='$t("editActivityModalComponent.modalEditActivityNamePlaceholder")' maxlength="45" v-model="editActivityName" required>
<label for="activityNameEdit"><b>* {{
$t("editActivityModalComponent.modalEditActivityNameLabel") }}</b></label>
<input class="form-control" type="text" name="activityNameEdit"
:placeholder='$t("editActivityModalComponent.modalEditActivityNamePlaceholder")'
maxlength="45" v-model="editActivityName" required>
<!-- description fields -->
<label for="activityDescriptionEdit"><b>{{ $t("editActivityModalComponent.modalEditActivityDescriptionLabel") }}</b></label>
<input class="form-control" type="text" name="activityDescriptionEdit" :placeholder='$t("editActivityModalComponent.modalEditActivityDescriptionPlaceholder")' maxlength="2500" v-model="editActivityDescription">
<label for="activityDescriptionEdit"><b>{{
$t("editActivityModalComponent.modalEditActivityDescriptionLabel") }}</b></label>
<input class="form-control" type="text" name="activityDescriptionEdit"
:placeholder='$t("editActivityModalComponent.modalEditActivityDescriptionPlaceholder")'
maxlength="2500" v-model="editActivityDescription">
<!-- type fields -->
<label for="activityTypeEdit"><b>* {{ $t("editActivityModalComponent.modalEditActivityTypeLabel") }}</b></label>
<label for="activityTypeEdit"><b>* {{
$t("editActivityModalComponent.modalEditActivityTypeLabel") }}</b></label>
<select class="form-select" name="activityTypeEdit" v-model="editActivityType" required>
<option value="1">{{ $t("editActivityModalComponent.modalEditActivityTypeOption1") }}</option>
<option value="2">{{ $t("editActivityModalComponent.modalEditActivityTypeOption2") }}</option>
<option value="3">{{ $t("editActivityModalComponent.modalEditActivityTypeOption3") }}</option>
<option value="4">{{ $t("editActivityModalComponent.modalEditActivityTypeOption4") }}</option>
<option value="1">{{ $t("editActivityModalComponent.modalEditActivityTypeOption1") }}
</option>
<option value="2">{{ $t("editActivityModalComponent.modalEditActivityTypeOption2") }}
</option>
<option value="3">{{ $t("editActivityModalComponent.modalEditActivityTypeOption3") }}
</option>
<option value="4">{{ $t("editActivityModalComponent.modalEditActivityTypeOption4") }}
</option>
<hr>
<option value="5">{{ $t("editActivityModalComponent.modalEditActivityTypeOption5") }}</option>
<option value="6">{{ $t("editActivityModalComponent.modalEditActivityTypeOption6") }}</option>
<option value="7">{{ $t("editActivityModalComponent.modalEditActivityTypeOption7") }}</option>
<option value="27">{{ $t("editActivityModalComponent.modalEditActivityTypeOption27") }}</option>
<option value="5">{{ $t("editActivityModalComponent.modalEditActivityTypeOption5") }}
</option>
<option value="6">{{ $t("editActivityModalComponent.modalEditActivityTypeOption6") }}
</option>
<option value="7">{{ $t("editActivityModalComponent.modalEditActivityTypeOption7") }}
</option>
<option value="27">{{ $t("editActivityModalComponent.modalEditActivityTypeOption27") }}
</option>
<hr>
<option value="8">{{ $t("editActivityModalComponent.modalEditActivityTypeOption8") }}</option>
<option value="9">{{ $t("editActivityModalComponent.modalEditActivityTypeOption9") }}</option>
<option value="8">{{ $t("editActivityModalComponent.modalEditActivityTypeOption8") }}
</option>
<option value="9">{{ $t("editActivityModalComponent.modalEditActivityTypeOption9") }}
</option>
<hr>
<option value="18">{{ $t("editActivityModalComponent.modalEditActivityTypeOption18") }}</option>
<option value="18">{{ $t("editActivityModalComponent.modalEditActivityTypeOption18") }}
</option>
<hr>
<option value="10">{{ $t("editActivityModalComponent.modalEditActivityTypeOption10") }}</option>
<option value="19">{{ $t("editActivityModalComponent.modalEditActivityTypeOption19") }}</option>
<option value="20">{{ $t("editActivityModalComponent.modalEditActivityTypeOption20") }}</option>
<option value="10">{{ $t("editActivityModalComponent.modalEditActivityTypeOption10") }}
</option>
<option value="19">{{ $t("editActivityModalComponent.modalEditActivityTypeOption19") }}
</option>
<option value="20">{{ $t("editActivityModalComponent.modalEditActivityTypeOption20") }}
</option>
<hr>
<option value="11">{{ $t("editActivityModalComponent.modalEditActivityTypeOption11") }}</option>
<option value="12">{{ $t("editActivityModalComponent.modalEditActivityTypeOption12") }}</option>
<option value="11">{{ $t("editActivityModalComponent.modalEditActivityTypeOption11") }}
</option>
<option value="12">{{ $t("editActivityModalComponent.modalEditActivityTypeOption12") }}
</option>
<hr>
<option value="13">{{ $t("editActivityModalComponent.modalEditActivityTypeOption13") }}</option>
<option value="13">{{ $t("editActivityModalComponent.modalEditActivityTypeOption13") }}
</option>
<hr>
<option value="14">{{ $t("editActivityModalComponent.modalEditActivityTypeOption14") }}</option>
<option value="14">{{ $t("editActivityModalComponent.modalEditActivityTypeOption14") }}
</option>
<hr>
<option value="15">{{ $t("editActivityModalComponent.modalEditActivityTypeOption15") }}</option>
<option value="16">{{ $t("editActivityModalComponent.modalEditActivityTypeOption16") }}</option>
<option value="17">{{ $t("editActivityModalComponent.modalEditActivityTypeOption17") }}</option>
<option value="15">{{ $t("editActivityModalComponent.modalEditActivityTypeOption15") }}
</option>
<option value="16">{{ $t("editActivityModalComponent.modalEditActivityTypeOption16") }}
</option>
<option value="17">{{ $t("editActivityModalComponent.modalEditActivityTypeOption17") }}
</option>
<hr>
<option value="21">{{ $t("editActivityModalComponent.modalEditActivityTypeOption21") }}</option>
<option value="22">{{ $t("editActivityModalComponent.modalEditActivityTypeOption22") }}</option>
<option value="23">{{ $t("editActivityModalComponent.modalEditActivityTypeOption23") }}</option>
<option value="24">{{ $t("editActivityModalComponent.modalEditActivityTypeOption24") }}</option>
<option value="25">{{ $t("editActivityModalComponent.modalEditActivityTypeOption25") }}</option>
<option value="26">{{ $t("editActivityModalComponent.modalEditActivityTypeOption26") }}</option>
<option value="21">{{ $t("editActivityModalComponent.modalEditActivityTypeOption21") }}
</option>
<option value="22">{{ $t("editActivityModalComponent.modalEditActivityTypeOption22") }}
</option>
<option value="23">{{ $t("editActivityModalComponent.modalEditActivityTypeOption23") }}
</option>
<option value="24">{{ $t("editActivityModalComponent.modalEditActivityTypeOption24") }}
</option>
<option value="25">{{ $t("editActivityModalComponent.modalEditActivityTypeOption25") }}
</option>
<option value="26">{{ $t("editActivityModalComponent.modalEditActivityTypeOption26") }}
</option>
</select>
<!-- visibility fields -->
<label for="activityVisibilityEdit"><b>* {{ $t("editActivityModalComponent.modalEditActivityVisibilityLabel") }}</b></label>
<select class="form-select" name="activityVisibilityEdit" v-model="editActivityVisibility" required>
<option value="0">{{ $t("editActivityModalComponent.modalEditActivityVisibilityOption0") }}</option>
<option value="1">{{ $t("editActivityModalComponent.modalEditActivityVisibilityOption1") }}</option>
<option value="2">{{ $t("editActivityModalComponent.modalEditActivityVisibilityOption2") }}</option>
<label for="activityVisibilityEdit"><b>* {{
$t("editActivityModalComponent.modalEditActivityVisibilityLabel") }}</b></label>
<select class="form-select" name="activityVisibilityEdit" v-model="editActivityVisibility"
required>
<option value="0">{{ $t("editActivityModalComponent.modalEditActivityVisibilityOption0") }}
</option>
<option value="1">{{ $t("editActivityModalComponent.modalEditActivityVisibilityOption1") }}
</option>
<option value="2">{{ $t("editActivityModalComponent.modalEditActivityVisibilityOption2") }}
</option>
</select>
<!-- hide start time fields -->
<label for="activityHideStartTimeEdit"><b>* {{
$t("editActivityModalComponent.modalEditActivityHideStartTimeLabel") }}</b></label>
<select class="form-select" name="activityHideStartTimeEdit" v-model="editActivityHideStartTime"
required>
<option :value="true">{{ $t("generalItems.yes") }}</option>
<option :value="false">{{ $t("generalItems.no") }}</option>
</select>
<!-- hide location fields -->
<label for="activityHideLocationEdit"><b>* {{
$t("editActivityModalComponent.modalEditActivityHideLocationLabel") }}</b></label>
<select class="form-select" name="activityHideLocationEdit" v-model="editActivityHideLocation"
required>
<option :value="true">{{ $t("generalItems.yes") }}</option>
<option :value="false">{{ $t("generalItems.no") }}</option>
</select>
<!-- hide map fields -->
<label for="activityHideMapEdit"><b>* {{
$t("editActivityModalComponent.modalEditActivityHideMapLabel") }}</b></label>
<select class="form-select" name="activityHideMapEdit" v-model="editActivityHideMap" required>
<option :value="true">{{ $t("generalItems.yes") }}</option>
<option :value="false">{{ $t("generalItems.no") }}</option>
</select>
<!-- hide HR fields -->
<label for="activityHideHrEdit"><b>* {{
$t("editActivityModalComponent.modalEditActivityHideHrLabel") }}</b></label>
<select class="form-select" name="activityHideHrEdit" v-model="editActivityHideHr" required>
<option :value="true">{{ $t("generalItems.yes") }}</option>
<option :value="false">{{ $t("generalItems.no") }}</option>
</select>
<!-- hide power fields -->
<label for="activityHidePowerEdit"><b>* {{
$t("editActivityModalComponent.modalEditActivityHidePowerLabel") }}</b></label>
<select class="form-select" name="activityHidePowerEdit" v-model="editActivityHidePower"
required>
<option :value="true">{{ $t("generalItems.yes") }}</option>
<option :value="false">{{ $t("generalItems.no") }}</option>
</select>
<!-- hide cadence fields -->
<label for="activityHideCadenceEdit"><b>* {{
$t("editActivityModalComponent.modalEditActivityHideCadenceLabel") }}</b></label>
<select class="form-select" name="activityHideCadenceEdit" v-model="editActivityHideCadence"
required>
<option :value="true">{{ $t("generalItems.yes") }}</option>
<option :value="false">{{ $t("generalItems.no") }}</option>
</select>
<!-- hide elevation fields -->
<div v-if="!activityTypeIsSwimming(activity)">
<label for="activityHideElevationEdit"><b>* {{
$t("editActivityModalComponent.modalEditActivityHideElevationLabel") }}</b></label>
<select class="form-select" name="activityHideElevationEdit" v-model="editActivityHideElevation"
required>
<option :value="true">{{ $t("generalItems.yes") }}</option>
<option :value="false">{{ $t("generalItems.no") }}</option>
</select>
</div>
<!-- hide speed fields -->
<div v-if="activityTypeIsCycling(activity)">
<label for="activityHideSpeedEdit"><b>* {{
$t("editActivityModalComponent.modalEditActivityHideSpeedLabel") }}</b></label>
<select class="form-select" name="activityHideSpeedEdit" v-model="editActivityHideSpeed"
required>
<option :value="true">{{ $t("generalItems.yes") }}</option>
<option :value="false">{{ $t("generalItems.no") }}</option>
</select>
</div>
<!-- hide pace fields -->
<div v-if="activityTypeIsRunning(activity) || activityTypeIsWalking(activity) || activityTypeIsSwimming(activity)">
<label for="activityHidePaceEdit"><b>* {{
$t("editActivityModalComponent.modalEditActivityHidePaceLabel") }}</b></label>
<select class="form-select" name="activityHidePaceEdit" v-model="editActivityHidePace"
required>
<option :value="true">{{ $t("generalItems.yes") }}</option>
<option :value="false">{{ $t("generalItems.no") }}</option>
</select>
</div>
<p>* {{ $t("generalItems.requiredField") }}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ $t("generalItems.buttonClose") }}</button>
<button type="submit" class="btn btn-success" data-bs-dismiss="modal">{{ $t("editActivityModalComponent.modalEditActivityTitle") }}</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{
$t("generalItems.buttonClose") }}</button>
<button type="submit" class="btn btn-success" data-bs-dismiss="modal">{{
$t("editActivityModalComponent.modalEditActivityTitle") }}</button>
</div>
</form>
</div>
@@ -81,56 +200,89 @@ import { useI18n } from "vue-i18n";
import { push } from "notivue";
// Importing the services
import { activities } from "@/services/activitiesService";
// Importing the utils
import { activityTypeIsRunning, activityTypeIsCycling, activityTypeIsWalking, activityTypeIsSwimming } from "@/utils/activityUtils";
export default {
components: {},
props: {
activity: {
type: Object,
required: true,
},
},
emits: ["activityEditedFields"],
setup(props, { emit }) {
const { t } = useI18n();
const editActivityDescription = ref(props.activity.description);
const editActivityName = ref(props.activity.name);
const editActivityType = ref(props.activity.activity_type);
const editActivityVisibility = ref(props.activity.visibility);
components: {},
props: {
activity: {
type: Object,
required: true,
},
},
emits: ["activityEditedFields"],
setup(props, { emit }) {
const { t } = useI18n();
const editActivityDescription = ref(props.activity.description);
const editActivityName = ref(props.activity.name);
const editActivityType = ref(props.activity.activity_type);
const editActivityVisibility = ref(props.activity.visibility);
const editActivityHideStartTime = ref(props.activity.hide_start_time);
const editActivityHideLocation = ref(props.activity.hide_location);
const editActivityHideMap = ref(props.activity.hide_map);
const editActivityHideHr = ref(props.activity.hide_hr);
const editActivityHidePower = ref(props.activity.hide_power);
const editActivityHideCadence = ref(props.activity.hide_cadence);
const editActivityHideElevation = ref(props.activity.hide_elevation);
const editActivityHideSpeed = ref(props.activity.hide_speed);
const editActivityHidePace = ref(props.activity.hide_pace);
async function submitEditActivityForm() {
try {
const data = {
id: props.activity.id,
description: editActivityDescription.value,
name: editActivityName.value,
activity_type: editActivityType.value,
visibility: editActivityVisibility.value,
};
async function submitEditActivityForm() {
try {
const data = {
id: props.activity.id,
description: editActivityDescription.value,
name: editActivityName.value,
activity_type: editActivityType.value,
visibility: editActivityVisibility.value,
hide_start_time: editActivityHideStartTime.value,
hide_location: editActivityHideLocation.value,
hide_map: editActivityHideMap.value,
hide_hr: editActivityHideHr.value,
hide_power: editActivityHidePower.value,
hide_cadence: editActivityHideCadence.value,
hide_elevation: editActivityHideElevation.value,
hide_speed: editActivityHideSpeed.value,
hide_pace: editActivityHidePace.value,
};
// Call the service to edit the activity
await activities.editActivity(data);
// Call the service to edit the activity
await activities.editActivity(data);
// show success toast
push.success(t("editActivityModalComponent.successActivityEdit"));
// show success toast
push.success(t("editActivityModalComponent.successActivityEdit"));
// Emit the activityEditedFields event to the parent component
emit("activityEditedFields", data);
} catch (error) {
// If there is an error, set the error message and show the error alert.
push.error(
`${t("editActivityModalComponent.errorActivityEdit")} - ${error}`,
);
}
}
// Emit the activityEditedFields event to the parent component
emit("activityEditedFields", data);
} catch (error) {
// If there is an error, set the error message and show the error alert.
push.error(
`${t("editActivityModalComponent.errorActivityEdit")} - ${error}`,
);
}
}
return {
editActivityDescription,
editActivityName,
editActivityType,
editActivityVisibility,
submitEditActivityForm,
};
},
return {
editActivityDescription,
editActivityName,
editActivityType,
editActivityVisibility,
editActivityHideStartTime,
editActivityHideLocation,
editActivityHideMap,
editActivityHideHr,
editActivityHidePower,
editActivityHideCadence,
editActivityHideElevation,
editActivityHideSpeed,
editActivityHidePace,
submitEditActivityForm,
activityTypeIsCycling,
activityTypeIsRunning,
activityTypeIsWalking,
activityTypeIsSwimming,
};
},
};
</script>

View File

@@ -0,0 +1,30 @@
<template>
<div class="alert alert-dismissible fade show" :class="`alert-${type}`" role="alert">
<font-awesome-icon :icon="['fas', `${symbol}`]" class="me-1"/>
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close" v-if="dismissible"></button>
</div>
</template>
<script setup>
const props = defineProps({
message: {
type: String,
required: true
},
type: {
type: String,
default: 'info'
},
symbol: {
type: String,
default: 'circle-info'
},
dismissible: {
type: Boolean,
default: false
}
});
</script>

View File

@@ -287,6 +287,13 @@
<option :value="false">{{ $t("generalItems.no") }}</option>
</select>
</form>
<form>
<label for="activityPace">{{ $t("settingsUserProfileZone.defaultActivityPace") }}</label>
<select class="form-select" name="activityPace" v-model="activityPace" required>
<option :value="true">{{ $t("generalItems.yes") }}</option>
<option :value="false">{{ $t("generalItems.no") }}</option>
</select>
</form>
</div>
</div>
<!-- Edit profile section -->
@@ -361,6 +368,7 @@ const activityPower = ref(authStore.user.hide_activity_power);
const activityCadence = ref(authStore.user.hide_activity_cadence);
const activityElevation = ref(authStore.user.hide_activity_elevation);
const activitySpeed = ref(authStore.user.hide_activity_speed);
const activityPace = ref(authStore.user.hide_activity_pace);
async function submitDeleteUserPhoto() {
try {
@@ -438,6 +446,7 @@ async function updateUserPrivacySettings() {
hide_activity_cadence: activityCadence.value,
hide_activity_elevation: activityElevation.value,
hide_activity_speed: activitySpeed.value,
hide_activity_pace: activityPace.value,
};
try {
// Update the user privacy settings in the DB
@@ -453,6 +462,7 @@ async function updateUserPrivacySettings() {
authStore.user.hide_activity_cadence = activityCadence.value;
authStore.user.hide_activity_elevation = activityElevation.value;
authStore.user.hide_activity_speed = activitySpeed.value;
authStore.user.hide_activity_pace = activityPace.value;
push.success(t("settingsUserProfileZone.successUpdateUserPrivacySettings"));
} catch (error) {
@@ -536,6 +546,7 @@ watch(
activityCadence,
activityElevation,
activitySpeed,
activityPace,
],
async () => {
if (!isMounted.value || isLoading.value) return;

View File

@@ -7,7 +7,7 @@
"labelGraphPower": "Power",
"labelGraphCadence": "Cadence",
"labelGraphElevation": "Elevation",
"labelGraphVelocity": "Velocity",
"labelGraphVelocity": "Speed",
"labelGraphPace": "Pace",
"labelDownsampling": "Data downsampled to ~200 points",
"errorMessageProcessingActivityStreams": "Error processing activity streams",

View File

@@ -7,5 +7,7 @@
"successMessageGearAdded": "Gear added to activity",
"successMessageGearDeleted": "Gear deleted from activity",
"errorMessageDeleteGear": "Error deleting gear from activity",
"errorMessageActivityNotFound": "Activity not found"
"errorMessageActivityNotFound": "Activity not found",
"alertPrivacyMessage": "You have hidden information in this activity. You can see it, but others cannot."
}

View File

@@ -7,7 +7,7 @@
"labelGraphPower": "Power",
"labelGraphCadence": "Cadence",
"labelGraphElevation": "Elevation",
"labelGraphVelocity": "Velocity",
"labelGraphVelocity": "Speed",
"labelGraphPace": "Pace",
"labelDownsampling": "Data downsampled to ~200 points",
"errorMessageProcessingActivityStreams": "Error processing activity streams",

View File

@@ -36,6 +36,15 @@
"modalEditActivityVisibilityOption0": "Public",
"modalEditActivityVisibilityOption1": "Followers",
"modalEditActivityVisibilityOption2": "Private",
"modalEditActivityHideStartTimeLabel": "Hide start time",
"modalEditActivityHideLocationLabel": "Hide location",
"modalEditActivityHideMapLabel": "Hide map",
"modalEditActivityHideHrLabel": "Hide heart rate",
"modalEditActivityHidePowerLabel": "Hide power",
"modalEditActivityHideCadenceLabel": "Hide cadence",
"modalEditActivityHideElevationLabel": "Hide elevation",
"modalEditActivityHideSpeedLabel": "Hide speed",
"modalEditActivityHidePaceLabel": "Hide pace",
"successActivityEdit": "Activity edited successfully",
"errorActivityEdit": "Error editing activity"
}

View File

@@ -41,8 +41,8 @@
"unitsSpm": "spm",
"labelElevationInMeters": "Elevation in meters",
"labelElevationInFeet": "Elevation in feet",
"labelVelocityInKmH": "Velocity in km/h",
"labelVelocityInMph": "Velocity in mph",
"labelVelocityInKmH": "Speed in km/h",
"labelVelocityInMph": "Speed in mph",
"labelPaceInMinKm": "Pace in min/km",
"labelPaceInMin100m": "Pace in min/100m",
"labelPaceInMinMile": "Pace in min/mile",

View File

@@ -31,6 +31,7 @@ export const useAuthStore = defineStore('auth', {
hide_activity_cadence: false,
hide_activity_elevation: false,
hide_activity_speed: false,
hide_activity_pace: false,
},
isAuthenticated: false,
user_websocket: null,
@@ -93,6 +94,7 @@ export const useAuthStore = defineStore('auth', {
hide_activity_cadence: false,
hide_activity_elevation: false,
hide_activity_speed: false,
hide_activity_pace: false,
};
if (this.user_websocket && this.user_websocket.readyState === WebSocket.OPEN) {
this.user_websocket.close();

View File

@@ -137,6 +137,36 @@ export function activityTypeIsSwimming(activity) {
return activity.activity_type === 8 || activity.activity_type === 9;
}
/**
* Checks if the activity type is a running activity.
*
* @param {object} activity - The activity object.
* @returns {boolean} True if the type of the activity is running, false otherwise.
*/
export function activityTypeIsRunning(activity) {
return activity.activity_type === 1 || activity.activity_type === 2 || activity.activity_type === 3;
}
/**
* Checks if the activity type is a running activity.
*
* @param {object} activity - The activity object.
* @returns {boolean} True if the type of the activity is cycling, false otherwise.
*/
export function activityTypeIsCycling(activity) {
return activity.activity_type === 4 || activity.activity_type === 5 || activity.activity_type === 6 || activity.activity_type === 7 || activity.activity_type === 27;
}
/**
* Checks if the activity type is a running activity.
*
* @param {object} activity - The activity object.
* @returns {boolean} True if the type of the activity is walking, false otherwise.
*/
export function activityTypeIsWalking(activity) {
return activity.activity_type === 11 || activity.activity_type === 12;
}
/**
* Formats the pace of an activity based on its type and the specified unit system.
*

View File

@@ -4,6 +4,7 @@
<div v-else>
<ActivitySummaryComponent v-if="activity" :activity="activity" :source="'activity'" @activityEditedFields="updateActivityFieldsOnEdit" :units="units" />
<AlertComponent v-if="activity && activity.user_id === authStore.user.id" :message="alertPrivacyMessage" :dismissible="true" class="mt-2"/>
</div>
<!-- map zone -->
@@ -106,6 +107,7 @@ import LoadingComponent from "@/components/GeneralComponents/LoadingComponent.vu
import BackButtonComponent from "@/components/GeneralComponents/BackButtonComponent.vue";
import ModalComponent from "@/components/Modals/ModalComponent.vue";
import AddGearToActivityModalComponent from "@/components/Activities/Modals/AddGearToActivityModalComponent.vue";
import AlertComponent from "@/components/GeneralComponents/AlertComponent.vue";
// Importing the services
import { gears } from "@/services/gearsService";
import { activities } from "@/services/activitiesService";
@@ -125,6 +127,7 @@ export default {
BackButtonComponent,
ModalComponent,
AddGearToActivityModalComponent,
AlertComponent,
},
setup() {
const { t } = useI18n();
@@ -143,6 +146,7 @@ export default {
const units = ref(1);
const activityActivityExerciseTitles = ref([]);
const activityActivitySets = ref([]);
const alertPrivacyMessage = ref(null);
async function submitDeleteGearFromActivity() {
try {
@@ -176,6 +180,15 @@ export default {
activity.value.description = data.description;
activity.value.activity_type = data.activity_type;
activity.value.visibility = data.visibility;
activity,value.hide_start_time = data.hide_start_time;
activity,value.location = data.location;
activity,value.hide_map = data.hide_map;
activity,value.hide_hr = data.hide_hr;
activity.value.hide_power = data.hide_power;
activity.value.hide_cadence = data.hide_cadence;
activity.value.hide_elevation = data.hide_elevation;
activity.value.hide_speed = data.hide_speed;
activity.value.hide_pace = data.hide_pace;
}
onMounted(async () => {
@@ -327,6 +340,10 @@ export default {
}
isLoading.value = false;
console.log("Activity loaded:", activity.value);
if (authStore.user.id === activity.value.user_id) {
alertPrivacyMessage.value = t("activityView.alertPrivacyMessage");
}
});
return {
@@ -346,6 +363,7 @@ export default {
activityActivityWorkoutSteps,
activityActivityExerciseTitles,
activityActivitySets,
alertPrivacyMessage,
};
},
};