Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions mytral/blueprints/exercise_types_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from mytral import forms
from mytral import muscle_groups as mg
from mytral import settings as user_settings
from mytral import utils
from mytral.blobstore import EntityPhotoService
from mytral.blobstore import exceptions as blob_exc
from mytral.blobstore import validation as blob_validation
Expand Down Expand Up @@ -321,8 +322,7 @@ def settings_exercises_create():
elif flask.request.method == "POST":
form = UpdateExerciseTypeForm()
if form.validate_on_submit():
tags_str = form.tags.data or ""
tags_list = [tag.strip() for tag in tags_str.split(",") if tag.strip()]
tags_list = utils.parse_tags_csv(form.tags.data)
muscle_groups_csv = flask.request.form.get("muscle_groups", "")
muscle_groups_list = mg.parse_muscle_groups_csv(muscle_groups_csv)
muscle_groups_secondary_csv = flask.request.form.get(
Expand Down Expand Up @@ -507,8 +507,7 @@ def settings_exercises_update(key: str):
elif flask.request.method == "POST":
form = UpdateExerciseTypeForm()
if form.validate_on_submit():
tags_str = form.tags.data or ""
tags_list = [tag.strip() for tag in tags_str.split(",") if tag.strip()]
tags_list = utils.parse_tags_csv(form.tags.data)
muscle_groups_csv = flask.request.form.get("muscle_groups", "")
muscle_groups_list = mg.parse_muscle_groups_csv(muscle_groups_csv)
muscle_groups_secondary_csv = flask.request.form.get(
Expand Down
12 changes: 12 additions & 0 deletions mytral/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from wtforms import validators

from mytral import commons
from mytral import utils
from mytral.backends import entities


Expand Down Expand Up @@ -658,6 +659,16 @@ class CreateActivityForm(flask_wtf.FlaskForm):
validators=[],
default="",
)
tags = wtforms.StringField(
render_kw={"placeholder": "trail, morning, with friends"},
label="Tags (comma-separated)",
description=(
"Labels to make the activity easy to find, separated by commas "
"(e.g. trail, morning, with friends)"
),
validators=[validators.Optional()],
default="",
)

###

Expand Down Expand Up @@ -1354,6 +1365,7 @@ def from_activity_form(
entity.gears = list(form.gears.data) if form.gears.data else []
entity.outfit = form.outfit.data or ""
entity.formula = form.formula.data or ""
entity.tags = utils.parse_tags_csv(form.tags.data)
if form.exercises.entries:
for exercise_form in form.exercises.entries:
entity.exercises.append(from_activity_exercise_form(exercise_form))
Expand Down
13 changes: 11 additions & 2 deletions mytral/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2732,6 +2732,7 @@ def _update_activity(key: str, template: str):
)
form.outfit.data = db_entity.outfit if hasattr(db_entity, "outfit") else ""
form.formula.data = db_entity.formula
form.tags.data = ", ".join(db_entity.tags) if db_entity.tags else ""

# exercises
if db_entity.exercises:
Expand Down Expand Up @@ -3818,6 +3819,7 @@ def list_activities_year(year):
filter_intensity = flask.request.args.get("intensity", "")
filter_type = flask.request.args.get("type", "")
filter_source = flask.request.args.get("source", "")
filter_tag = flask.request.args.get("tag", "")
aspect = flask.request.args.get("aspect", "feed")

# apply filters
Expand Down Expand Up @@ -3847,6 +3849,8 @@ def list_activities_year(year):
activities = [a for a in activities if a.ranked]
if filter_source:
activities = [a for a in activities if a.src == filter_source]
if filter_tag:
activities = [a for a in activities if filter_tag in (a.tags or [])]

# get sort parameters
sort_by = flask.request.args.get("sort", "when")
Expand Down Expand Up @@ -3916,6 +3920,7 @@ def list_activities_year(year):
)
unique_intensities = sorted(set(a.intensity for a in all_activities if a.intensity))
unique_sources = sorted(set(a.src for a in all_activities if a.src))
unique_tags = sorted({tag for a in all_activities for tag in (a.tags or [])})

# calculate year statistics for the cards
year_total_distance = sum(a.distance for a in activities if a.distance)
Expand Down Expand Up @@ -3997,11 +4002,13 @@ def list_activities_year(year):
unique_activity_types=unique_activity_types,
unique_intensities=unique_intensities,
unique_sources=unique_sources,
unique_tags=unique_tags,
filter_activity_type=filter_activity_type,
filter_gear=filter_gear,
filter_intensity=filter_intensity,
filter_type=filter_type,
filter_source=filter_source,
filter_tag=filter_tag,
sort_by=sort_by,
sort_order=sort_order,
aspect=aspect,
Expand Down Expand Up @@ -4037,12 +4044,14 @@ def search_activities():
sort_by_when=True,
)

# filter by case-insensitive substring match on name and description
# filter by case-insensitive substring match on name, description and tags
q_lower = q.lower()
activities = [
a
for a in activities
if q_lower in (a.name or "").lower() or q_lower in (a.description or "").lower()
if q_lower in (a.name or "").lower()
or q_lower in (a.description or "").lower()
or any(q_lower in tag.lower() for tag in (a.tags or []))
]

activities_weekdays = {
Expand Down
1 change: 1 addition & 0 deletions mytral/templates/activity-create.html
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ <h3 class="card-title">Activity Details</h3>
<div class="card-body">
{{ field_macros.add_field(form.name) }}
{{ field_macros.add_field(form.description) }}
{{ field_macros.add_field(form.tags) }}
</div>
</div>
</div>
Expand Down
10 changes: 10 additions & 0 deletions mytral/templates/activity-get.html
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,16 @@ <h2 class="mb-3">
{{ activity_entity.description|markdown }}
</div>

{% if activity_entity.tags %}
<div class="mb-4">
{% for tag in activity_entity.tags %}
<a href="{{ url_for('list_activities_year', year=activity_entity.when_year, tag=tag) }}" class="text-decoration-none">
<span class="badge bg-{{ tag|tag_to_color }} text-{{ tag|tag_to_color }}-fg">{{ tag }}</span>
</a>
Comment thread
Copilot marked this conversation as resolved.
{% endfor %}
</div>
{% endif %}

{% if activity_entity.distance %}
<div class="row g-3 mb-4">
<div class="col-sm-6 col-lg-3">
Expand Down
25 changes: 23 additions & 2 deletions mytral/templates/activity-list-year.html
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ <h2 class="page-title">
<div class="btn-list">
{% if activities|length > 0 %}
<div class="btn-group">
<a href="{{ url_for('list_activities_year', year=year, aspect='list', activity_type=filter_activity_type, gear=filter_gear, intensity=filter_intensity, type=filter_type, source=filter_source, sort=sort_by, order=sort_order) }}" class="btn btn-outline-primary">
<a href="{{ url_for('list_activities_year', year=year, aspect='list', activity_type=filter_activity_type, gear=filter_gear, intensity=filter_intensity, type=filter_type, source=filter_source, tag=filter_tag, sort=sort_by, order=sort_order) }}" class="btn btn-outline-primary">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-table" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M3 5a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-14z"></path>
Expand All @@ -68,7 +68,7 @@ <h2 class="page-title">
</svg>
List
</a>
<a href="{{ url_for('list_activities_year', year=year, aspect='feed', activity_type=filter_activity_type, gear=filter_gear, intensity=filter_intensity, type=filter_type, source=filter_source, sort=sort_by, order=sort_order) }}" class="btn btn-outline-primary">
<a href="{{ url_for('list_activities_year', year=year, aspect='feed', activity_type=filter_activity_type, gear=filter_gear, intensity=filter_intensity, type=filter_type, source=filter_source, tag=filter_tag, sort=sort_by, order=sort_order) }}" class="btn btn-outline-primary">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-layout-cards" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M4 4m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z"></path>
Expand Down Expand Up @@ -209,6 +209,18 @@ <h2 class="page-title">
{% endfor %}
</select>
</div>

<div class="col-md-2">
<label class="form-label">Tag</label>
<select class="form-select" name="tag" onchange="this.form.submit()">
<option value="">All</option>
{% for tag in unique_tags %}
<option value="{{ tag }}" {% if tag == filter_tag %}selected{% endif %}>
{{ tag }}
</option>
{% endfor %}
</select>
</div>
</div>

<div class="row g-3 mt-2">
Expand Down Expand Up @@ -438,6 +450,15 @@ <h3 class="mb-1">
<span class="mx-2">·</span>
<a href="{{ url_for('settings_activity_types_get', key=activity.activity_type_key) }}" class="text-reset">{{ activity_types.name(activity.activity_type_key) }}</a>
</div>
{% if activity.tags %}
<div class="mt-1">
{% for tag in activity.tags %}
<a href="{{ url_for('list_activities_year', year=year, aspect=aspect, tag=tag) }}" class="text-decoration-none">
<span class="badge bg-{{ tag|tag_to_color }} text-{{ tag|tag_to_color }}-fg">{{ tag }}</span>
</a>
Comment thread
Copilot marked this conversation as resolved.
{% endfor %}
</div>
{% endif %}
</div>
<div class="text-end">
{% if activity.commute %}
Expand Down
1 change: 1 addition & 0 deletions mytral/templates/activity-update-all.html
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ <h3 class="card-title">Activity Details</h3>
<div class="card-body">
{{ field_macros.add_field(form.name) }}
{{ field_macros.add_field(form.description) }}
{{ field_macros.add_field(form.tags) }}
</div>
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions mytral/templates/activity-update.html
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ <h3 class="card-title">Details</h3>
<div class="card-body">
{{ field_macros.add_field(form.name) }}
{{ field_macros.add_field(form.description) }}
{{ field_macros.add_field(form.tags) }}
</div>
</div>
</div>
Expand Down
13 changes: 13 additions & 0 deletions mytral/templates/macros/activity_list_table.html
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,19 @@
{{ gear.to_dict().get(gear_key, {}).name }}
{% endif %}{% if not loop.last %}, {% endif %}
{% endfor %}
{% if activity.tags %}
<div class="mt-1">
{% for tag in activity.tags %}
{% if year %}
<a href="{{ url_for('list_activities_year', year=year, aspect='list', tag=tag) }}" class="text-decoration-none">
<span class="badge bg-{{ tag|tag_to_color }} text-{{ tag|tag_to_color }}-fg">{{ tag }}</span>
</a>
{% else %}
<span class="badge bg-{{ tag|tag_to_color }} text-{{ tag|tag_to_color }}-fg">{{ tag }}</span>
{% endif %}
Comment thread
dvorka marked this conversation as resolved.
{% endfor %}
</div>
{% endif %}
</td>
<td>
{{ activity_types.emoji(activity.activity_type_key) }}
Expand Down
22 changes: 22 additions & 0 deletions mytral/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,25 @@ def tag_to_color(tag: str) -> str:
color_index = tag_hash % len(colors)

return colors[color_index]


def parse_tags_csv(tags_csv: str | None) -> list[str]:
"""Parse a comma-separated tags string into a normalized list.

Parameters
----------
tags_csv : str | None
raw comma-separated tags input (may be None or empty)

Returns
-------
list[str]
trimmed, non-empty tags in input order with duplicates removed

"""
tags: list[str] = []
for tag in (tags_csv or "").split(","):
tag = tag.strip()
if tag and tag not in tags:
tags.append(tag)
return tags
Comment thread
dvorka marked this conversation as resolved.
Loading
Loading