diff --git a/folkugat_web/api/routes/temes/index.py b/folkugat_web/api/routes/temes/index.py
index 5f814a0..ca13f75 100644
--- a/folkugat_web/api/routes/temes/index.py
+++ b/folkugat_web/api/routes/temes/index.py
@@ -5,6 +5,7 @@ from fastapi import Request
from fastapi.params import Param
from folkugat_web.api.router import get_router
from folkugat_web.fragments import temes
+from folkugat_web.model.search import Order, OrderBy, OrderParams
from folkugat_web.services import auth
from folkugat_web.templates import templates
@@ -13,13 +14,21 @@ router = get_router()
@router.get("/temes")
def page(
- request: Request,
- logged_in: auth.LoggedIn,
- query: Annotated[str, Param()] = "",
- properties: Annotated[list[str] | None, Param()] = None,
+ request: Request,
+ logged_in: auth.LoggedIn,
+ query: Annotated[str, Param()] = "",
+ properties: Annotated[list[str] | None, Param()] = None,
+ order_by: OrderBy | None = None,
+ order: Order = Order.DESC,
):
properties = properties or []
- content_url = f"/api/content/temes?{temes.build_temes_params(query=query, properties=properties)}"
+ order_params = OrderParams(order_by=order_by, order=order) if order_by else None
+ temes_params = temes.build_temes_params(
+ query=query,
+ properties=properties,
+ order_params=order_params,
+ )
+ content_url = f"/api/content/temes?{temes_params}"
return templates.TemplateResponse(
"index.html",
{
@@ -38,13 +47,19 @@ def content(
logged_in: auth.LoggedIn,
query: Annotated[str, Param()] = "",
properties: Annotated[list[str] | None, Param()] = None,
+ order_by: OrderBy | None = None,
+ order: Order = Order.DESC,
):
properties = properties or []
+ if not query and not order_by:
+ order_by = OrderBy.TIMES_PLAYED
+ order_params = OrderParams(order_by=order_by, order=order) if order_by else None
return temes.temes_pagina(
request=request,
logged_in=logged_in,
query=query,
properties=properties,
+ order_params=order_params,
)
@@ -54,13 +69,17 @@ def busca(
logged_in: auth.LoggedIn,
query: Annotated[str, Param()],
properties: Annotated[list[str] | None, Param()] = None,
+ order_by: OrderBy | None = None,
+ order: Order = Order.DESC,
limit: int = 10,
offset: int = 0,
):
+ order_params = OrderParams(order_by=order_by, order=order) if order_by else None
return temes.temes_busca(
request=request,
query=query,
properties=properties or [],
+ order_params=order_params,
limit=limit,
offset=offset,
logged_in=logged_in,
diff --git a/folkugat_web/assets/static/css/main.css b/folkugat_web/assets/static/css/main.css
index 7bee737..3185b00 100644
--- a/folkugat_web/assets/static/css/main.css
+++ b/folkugat_web/assets/static/css/main.css
@@ -649,11 +649,6 @@ video {
margin-bottom: 0.75rem;
}
-.my-4 {
- margin-top: 1rem;
- margin-bottom: 1rem;
-}
-
.mb-3 {
margin-bottom: 0.75rem;
}
@@ -666,6 +661,10 @@ video {
margin-left: auto;
}
+.mr-1 {
+ margin-right: 0.25rem;
+}
+
.mr-2 {
margin-right: 0.5rem;
}
@@ -718,6 +717,10 @@ video {
min-height: 400px;
}
+.w-0 {
+ width: 0px;
+}
+
.w-1\/2 {
width: 50%;
}
@@ -988,10 +991,18 @@ video {
padding-bottom: 1rem;
}
+.pb-2 {
+ padding-bottom: 0.5rem;
+}
+
.pl-5 {
padding-left: 1.25rem;
}
+.pr-2 {
+ padding-right: 0.5rem;
+}
+
.pt-4 {
padding-top: 1rem;
}
@@ -1056,9 +1067,9 @@ video {
color: rgb(76 16 30 / var(--tw-text-opacity, 1));
}
-.text-gray-500 {
+.text-gray-400 {
--tw-text-opacity: 1;
- color: rgb(107 114 128 / var(--tw-text-opacity, 1));
+ color: rgb(156 163 175 / var(--tw-text-opacity, 1));
}
.text-red-400 {
diff --git a/folkugat_web/assets/templates/fragments/temes/pagina.html b/folkugat_web/assets/templates/fragments/temes/pagina.html
index d356930..59d0085 100644
--- a/folkugat_web/assets/templates/fragments/temes/pagina.html
+++ b/folkugat_web/assets/templates/fragments/temes/pagina.html
@@ -1,6 +1,7 @@
{% include "fragments/menu.html" %}
diff --git a/folkugat_web/assets/templates/fragments/temes/result.html b/folkugat_web/assets/templates/fragments/temes/result.html
index a145a8e..0ac34e4 100644
--- a/folkugat_web/assets/templates/fragments/temes/result.html
+++ b/folkugat_web/assets/templates/fragments/temes/result.html
@@ -8,27 +8,16 @@
{{ tema.title }}
-
- {% if tema.stats %}
- Tocat {{ tema.stats.times_played }}
- {% if tema.stats.times_played == 1 %}
- cop
- {% else %}
- cops
- {% endif %}
- {% else %}
- No s'ha tocat mai
- {% endif %}
-
{% if tema.properties %}
{% for property in tema.properties %}
+ {% set hx_vars = build_hx_vars({"properties": add_property_str(property.value)}, order_params_dict) %}
{{ property.value }}
@@ -38,9 +27,34 @@
{% endif %}
{% if tema.main_score() and tema.main_score().preview_url %}
-
{% endif %}
+
+
+ {% include "icons/music-box.svg" %}
+ {% if tema.stats %}
+ Tocat {{ tema.stats.times_played }}
+ {% if tema.stats.times_played == 1 %}
+ cop
+ {% else %}
+ cops
+ {% endif %}
+ {% else %}
+ No s'ha tocat mai
+ {% endif %}
+
+
+
+ {% if tema.stats and tema.stats.sessions_played %}
+ {% include "icons/calendar.svg" %}
+ {% set dn = get_date_names(tema.stats.sessions_played[0].date) %}
+ {{ dn.day }} {{ dn.month_name }} de {{ dn.year }}
+ {% endif %}
+
+
diff --git a/folkugat_web/assets/templates/fragments/temes/results.html b/folkugat_web/assets/templates/fragments/temes/results.html
index 48b6557..c77e6f7 100644
--- a/folkugat_web/assets/templates/fragments/temes/results.html
+++ b/folkugat_web/assets/templates/fragments/temes/results.html
@@ -12,38 +12,14 @@
{{ query }}
{% endif %}
-
- {% for property in properties %}
-
-
- {{ property }}
-
-
- {% endfor %}
- {% for property in property_results %}
-
-
- {{ property }}
-
-
- {% endfor %}
-
+
+
+ {% include "fragments/temes/search_params/order_by.html" %}
+ {% include "fragments/temes/search_params/filter.html" %}
+
+
+
{% for tema in temes %}
{% include "fragments/temes/result.html" %}
@@ -52,20 +28,22 @@
{% if prev_offset is not none or next_offset is not none %}
{% if prev_offset is not none %}
+ {% set hx_vars = build_hx_vars({"properties": properties_str, "offset": prev_offset}, order_params_dict) %}
{% endif %}
{% if next_offset is not none %}
+ {% set hx_vars = build_hx_vars({"properties": properties_str, "offset": next_offset}, order_params_dict) %}
diff --git a/folkugat_web/assets/templates/fragments/temes/search_params/filter.html b/folkugat_web/assets/templates/fragments/temes/search_params/filter.html
new file mode 100644
index 0000000..20df3d3
--- /dev/null
+++ b/folkugat_web/assets/templates/fragments/temes/search_params/filter.html
@@ -0,0 +1,40 @@
+{% if properties or property_results %}
+
+
Filtres:
+
+ {% for property in properties %}
+
+ {% set hx_vars = build_hx_vars({"properties": remove_property_str(property)}, order_params_dict) %}
+
+ {{ property }}
+
+
+ {% endfor %}
+ {% for property in property_results %}
+
+ {% set hx_vars = build_hx_vars({"properties": add_property_str(property)}, order_params_dict) %}
+
+ {{ property }}
+
+
+ {% endfor %}
+
+
+{% endif %}
diff --git a/folkugat_web/assets/templates/fragments/temes/search_params/order_by.html b/folkugat_web/assets/templates/fragments/temes/search_params/order_by.html
new file mode 100644
index 0000000..7ec579f
--- /dev/null
+++ b/folkugat_web/assets/templates/fragments/temes/search_params/order_by.html
@@ -0,0 +1,67 @@
+
+ {% set times_played_color = "text-gray-400" %}
+ {% set times_played_caret = "off" %}
+ {% set times_played_target_params = {"order_by": "'times_played'", "order": "'desc'"} %}
+
+ {% set last_played_color = "text-gray-400" %}
+ {% set last_played_caret = "off" %}
+ {% set last_played_target_params = {"order_by": "'last_played'", "order": "'desc'"} %}
+
+ {% if order_params is not none %}
+ {% if order_params.order_by == OrderBy.TIMES_PLAYED %}
+ {% set times_played_color = "text-beige" %}
+ {% if order_params.order == Order.ASC %}
+ {% set times_played_caret = "asc" %}
+ {% set times_played_target_params = {} %}
+ {% elif order_params.order == Order.DESC %}
+ {% set times_played_caret = "desc" %}
+ {% set times_played_target_params = {"order_by": "'times_played'", "order": "'asc'"} %}
+ {% endif %}
+ {% elif order_params.order_by == OrderBy.LAST_PLAYED %}
+ {% set last_played_color = "text-beige" %}
+ {% if order_params.order == Order.ASC %}
+ {% set last_played_caret = "asc" %}
+ {% set last_played_target_params = {} %}
+ {% elif order_params.order == Order.DESC %}
+ {% set last_played_caret = "desc" %}
+ {% set last_played_target_params = {"order_by": "'last_played'", "order": "'asc'"} %}
+ {% endif %}
+ {% endif %}
+ {% endif %}
+
+ {% set hx_vars = build_hx_vars({"properties": properties_str}, times_played_target_params) %}
+
+ {% include "icons/music-box.svg" %}
+ {% if times_played_caret == "asc" %}
+
+ {% elif times_played_caret == "desc" %}
+
+ {% else %}
+
+ {% endif %}
+
+
+ {% set hx_vars = build_hx_vars({"properties": properties_str}, last_played_target_params) %}
+
+ {% include "icons/calendar.svg" %}
+ {% if last_played_caret == "asc" %}
+
+ {% elif last_played_caret == "desc" %}
+
+ {% else %}
+
+ {% endif %}
+
+
diff --git a/folkugat_web/assets/templates/icons/calendar.svg b/folkugat_web/assets/templates/icons/calendar.svg
new file mode 100644
index 0000000..888b045
--- /dev/null
+++ b/folkugat_web/assets/templates/icons/calendar.svg
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/folkugat_web/assets/templates/icons/music-box.svg b/folkugat_web/assets/templates/icons/music-box.svg
new file mode 100644
index 0000000..cebbe27
--- /dev/null
+++ b/folkugat_web/assets/templates/icons/music-box.svg
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/folkugat_web/fragments/temes.py b/folkugat_web/fragments/temes.py
index c2f64d2..d3b0fde 100644
--- a/folkugat_web/fragments/temes.py
+++ b/folkugat_web/fragments/temes.py
@@ -5,7 +5,9 @@ from fastapi import Request
from folkugat_web.model import temes as model
from folkugat_web.model.lilypond.processing import RenderError
from folkugat_web.model.pagines import Pages
+from folkugat_web.model.search import Order, OrderBy, OrderParams
from folkugat_web.services import sessions as sessions_service
+from folkugat_web.services.sessions import get_date_names
from folkugat_web.services.temes import query as temes_q
from folkugat_web.services.temes import scores as scores_service
from folkugat_web.services.temes import search as temes_s
@@ -13,20 +15,48 @@ from folkugat_web.templates import templates
from folkugat_web.utils import FnChain
-def build_temes_params(query: str, properties: list[str]) -> str:
+def build_temes_params(
+ query: str,
+ properties: list[str],
+ order_params: OrderParams | None = None,
+) -> str:
content_params = [
("query", query),
*[("properties", prop) for prop in properties or []]
]
+ if order_params:
+ content_params.append(("order_by", order_params.order_by.value))
+ content_params.append(("order", order_params.order.value))
return urllib.parse.urlencode(content_params)
+def build_hx_vars(*vars_dicts: dict[str, str]) -> str:
+ result = ",".join(f"{var}:{value}" for hx_vars in vars_dicts for var, value in hx_vars.items())
+ return result
+
+
def build_property_str(properties: list[str]) -> str:
return json.dumps(properties).replace("'", "\\'").replace('"', "'")
-def temes_pagina(request: Request, logged_in: bool, query: str, properties: list[str]):
+def build_order_params_dict(order_params: OrderParams | None) -> dict[str, str]:
+ if not order_params:
+ return {}
+ return {
+ "order_by": f"'{order_params.order_by.value}'",
+ "order": f"'{order_params.order.value}'",
+ }
+
+
+def temes_pagina(
+ request: Request,
+ logged_in: bool,
+ query: str,
+ properties: list[str],
+ order_params: OrderParams | None,
+):
properties_str = build_property_str(properties)
+ order_params_dict = build_order_params_dict(order_params)
return templates.TemplateResponse(
"fragments/temes/pagina.html",
{
@@ -35,8 +65,14 @@ def temes_pagina(request: Request, logged_in: bool, query: str, properties: list
"query": query,
"properties": properties,
"properties_str": properties_str,
+ "order_params": order_params,
+ "order_params_dict": order_params_dict,
+ "build_hx_vars": build_hx_vars,
"property_results": [],
+ "Order": Order,
+ "OrderBy": OrderBy,
"Pages": Pages,
+ "get_date_names": get_date_names,
"menu_selected_id": Pages.Temes,
}
)
@@ -47,12 +83,14 @@ def temes_busca(
logged_in: bool,
query: str,
properties: list[str],
+ order_params: OrderParams | None = None,
offset: int = 0,
limit: int = 10,
):
temes = temes_s.busca_temes(
query=query,
properties=properties,
+ order_params=order_params,
hidden=logged_in,
limit=limit + 1,
offset=offset,
@@ -78,7 +116,12 @@ def temes_busca(
if prop not in properties
]
- temes_url = f"/temes?{build_temes_params(query, properties)}"
+ temes_params = build_temes_params(
+ query=query,
+ properties=properties,
+ order_params=order_params,
+ )
+ temes_url = f"/temes?{temes_params}"
def _add_property_str(prop: str) -> str:
properties_dedup = list(set(properties + [prop]))
@@ -97,13 +140,19 @@ def temes_busca(
"query": query,
"properties": properties,
"properties_str": build_property_str(properties),
+ "order_params": order_params,
+ "order_params_dict": build_order_params_dict(order_params),
"property_results": property_results,
"prev_offset": prev_offset,
"next_offset": next_offset,
"LinkType": model.LinkType,
"ContentType": model.ContentType,
+ "Order": Order,
+ "OrderBy": OrderBy,
+ "get_date_names": get_date_names,
"add_property_str": _add_property_str,
"remove_property_str": _remove_property_str,
+ "build_hx_vars": build_hx_vars,
},
headers={"HX-Replace-Url": temes_url},
)
diff --git a/folkugat_web/model/search.py b/folkugat_web/model/search.py
index 57a7acc..d10e3eb 100644
--- a/folkugat_web/model/search.py
+++ b/folkugat_web/model/search.py
@@ -1,4 +1,5 @@
import dataclasses
+import enum
from collections.abc import Iterable
from typing import Generic, Self, TypeVar
@@ -6,6 +7,22 @@ T = TypeVar("T")
NGrams = dict[int, list[str]]
+class Order(enum.Enum):
+ ASC = "asc"
+ DESC = "desc"
+
+
+class OrderBy(enum.Enum):
+ TIMES_PLAYED = "times_played"
+ LAST_PLAYED = "last_played"
+
+
+@dataclasses.dataclass(frozen=True)
+class OrderParams:
+ order_by: OrderBy
+ order: Order
+
+
@dataclasses.dataclass(order=True)
class SearchMatch:
distance: float
diff --git a/folkugat_web/services/temes/search.py b/folkugat_web/services/temes/search.py
index f79799a..1194a17 100644
--- a/folkugat_web/services/temes/search.py
+++ b/folkugat_web/services/temes/search.py
@@ -1,3 +1,4 @@
+import datetime
import time
from collections.abc import Iterable, Iterator
from sqlite3 import Connection
@@ -13,7 +14,7 @@ from folkugat_web.model import search as search_model
from folkugat_web.model import temes as model
from folkugat_web.services.temes import properties as properties_service
from folkugat_web.services.temes import query as query_service
-from folkugat_web.utils import FnChain
+from folkugat_web.utils import FnChain, identity
T = TypeVar("T")
@@ -69,6 +70,40 @@ def _sort_by_distance(qrs: Iterable[search_model.QueryResult[T]]) -> list[search
return sorted(qrs, key=lambda qr: qr.distance)
+def _sort_by_times_played_fn(order: search_model.Order) -> Callable[[list[model.Tema]], list[model.Tema]]:
+ reverse = order is search_model.Order.DESC
+
+ def _sort_by_times_played(temes: list[model.Tema]) -> list[model.Tema]:
+ return sorted(temes, key=lambda tema: tema.stats.times_played if tema.stats else 0, reverse=reverse)
+
+ return _sort_by_times_played
+
+
+def _sort_by_last_played_fn(order: search_model.Order) -> Callable[[list[model.Tema]], list[model.Tema]]:
+ reverse = order is search_model.Order.DESC
+
+ def _tune_last_played_key(tema: model.Tema) -> datetime.date:
+ if not tema.stats or not tema.stats.sessions_played:
+ return datetime.date.min
+ else:
+ return tema.stats.sessions_played[0].date
+
+ def _sort_by_last_played(temes: list[model.Tema]) -> list[model.Tema]:
+ return sorted(temes, key=_tune_last_played_key, reverse=reverse)
+
+ return _sort_by_last_played
+
+
+def _build_sort_fn(order_params: search_model.OrderParams | None) -> Callable[[list[model.Tema]], list[model.Tema]]:
+ if order_params is None:
+ return identity
+ match order_params.order_by:
+ case search_model.OrderBy.TIMES_PLAYED:
+ return _sort_by_times_played_fn(order=order_params.order)
+ case search_model.OrderBy.LAST_PLAYED:
+ return _sort_by_last_played_fn(order=order_params.order)
+
+
def _query_results_to_temes(
con: Connection
) -> Callable[[Iterable[search_model.QueryResult[int]]], Iterator[model.Tema]]:
@@ -105,6 +140,7 @@ def _apply_limit_offset(limit: int, offset: int) -> Callable[[Iterable[T]], list
def busca_temes(
query: str,
properties: list[str],
+ order_params: search_model.OrderParams | None = None,
hidden: bool = False,
limit: int = 10,
offset: int = 0,
@@ -124,6 +160,7 @@ def busca_temes(
properties_service.add_properties_to_temes |
_filter_properties(properties) |
query_service.temes_compute_stats |
+ _build_sort_fn(order_params=order_params) |
_apply_limit_offset(limit=limit, offset=offset)
).result()
logger.info(f"Temes search time: { int((time.time() - t0) * 1000) } ms")
diff --git a/folkugat_web/utils.py b/folkugat_web/utils.py
index 3216bee..dc46f7c 100644
--- a/folkugat_web/utils.py
+++ b/folkugat_web/utils.py
@@ -25,6 +25,10 @@ def groupby(
yield k, group_fn(g)
+def identity(t: T) -> T:
+ return t
+
+
U = TypeVar("U")
V = TypeVar("V")