Nova pàgina de llistes

This commit is contained in:
marc
2025-12-21 17:09:59 +01:00
parent ac79785cf0
commit fdcda1b566
37 changed files with 323 additions and 33 deletions

View File

@@ -1,12 +1,13 @@
from folkugat_web.api.router import get_router
from . import auth, index, llista, sessio, sessions, tema, temes
from . import auth, index, llista, llistes, sessio, sessions, tema, temes
router = get_router()
router.include_router(auth.router)
router.include_router(index.router)
router.include_router(llista.router)
router.include_router(llistes.router)
router.include_router(sessio.router)
router.include_router(sessions.router)
router.include_router(tema.router)

View File

@@ -247,3 +247,23 @@ def set_tema_new(
entry_id=entry_id,
tema_id=new_tema.id,
)
@router.put("/api/llista/{playlist_id}/visible")
def set_visible(request: Request, logged_in: auth.RequireLogin, playlist_id: int):
new_playlist = playlists_service.set_visibility(playlist_id=playlist_id, hidden=False)
return playlist.visibility(
request=request,
logged_in=logged_in,
playlist=new_playlist,
)
@router.put("/api/llista/{playlist_id}/invisible")
def set_invisible(request: Request, logged_in: auth.RequireLogin, playlist_id: int):
new_playlist = playlists_service.set_visibility(playlist_id=playlist_id, hidden=True)
return playlist.visibility(
request=request,
logged_in=logged_in,
playlist=new_playlist,
)

View File

@@ -0,0 +1,5 @@
from folkugat_web.api.router import get_router
from . import index
router = get_router()
router.include_router(index.router)

View File

@@ -0,0 +1,26 @@
from fastapi import Request
from folkugat_web.api.router import get_router
from folkugat_web.fragments import llistes
from folkugat_web.services import auth
from folkugat_web.templates import templates
router = get_router()
@router.get("/llistes")
def page(request: Request, logged_in: auth.LoggedIn):
return templates.TemplateResponse(
"index.html",
{
"request": request,
"page_title": "Folkugat",
"content": "/api/content/llistes",
"logged_in": logged_in,
"animate": False,
}
)
@router.get("/api/content/llistes")
def content(request: Request, logged_in: auth.LoggedIn):
return llistes.llistes_pagina(request, logged_in)

View File

@@ -899,6 +899,10 @@ video {
justify-content: center;
}
.justify-between {
justify-content: space-between;
}
.gap-2 {
gap: 0.5rem;
}
@@ -1019,6 +1023,11 @@ video {
padding-bottom: 1rem;
}
.py-8 {
padding-top: 2rem;
padding-bottom: 2rem;
}
.pb-2 {
padding-bottom: 0.5rem;
}
@@ -1085,6 +1094,10 @@ video {
color: rgb(178 124 9 / var(--tw-text-opacity, 1));
}
.text-beige\/70 {
color: rgb(178 124 9 / 0.7);
}
.text-black {
--tw-text-opacity: 1;
color: rgb(0 0 0 / var(--tw-text-opacity, 1));
@@ -1134,6 +1147,10 @@ video {
transition-duration: 200ms;
}
.hover\:underline:hover {
text-decoration-line: underline;
}
.focus\:outline-none:focus {
outline: 2px solid transparent;
outline-offset: 2px;

View File

@@ -15,5 +15,6 @@
hx-swap="outerHTML">
<i class="fa fa-pencil" aria-hidden="true"></i>
</button>
{% include "fragments/llista/visibility.html" %}
{% endif %}
</div>

View File

@@ -1,14 +1,14 @@
{% include "fragments/menu.html" %}
<div class="flex justify-center">
<div class="m-12 grow max-w-4xl">
<div id="llista-name">
{% include "fragments/playlist/name.html" %}
<div id="playlist-name">
{% include "fragments/llista/name.html" %}
</div>
{% include "fragments/playlist/score.html" %}
<div class="text-left">
{% set playlist_id = playlist.id %}
{% set playlist = playlist %}
{% include "fragments/playlist/playlist.html" %}
{% include "fragments/llista/playlist.html" %}
</div>
{% include "fragments/llista/score.html" %}
</div>
</div>

View File

@@ -1,8 +1,7 @@
<ul id="llista-{{ playlist_id }}" class="">
{% for set_entry in playlist.sets %}
{% set set_id = set_entry.id %}
{% include "fragments/playlist/set_entry.html" %}
{% endfor %}
{% include "fragments/llista/set_entry.html" %} {% endfor %}
</ul>
{% if logged_in %}
<div class="flex flex-col items-center">

View File

@@ -0,0 +1,2 @@
{% include "fragments/menu.html" %}
{% include "fragments/llista/set/set_page.html" %}

View File

@@ -13,14 +13,14 @@
{% for tema_in_set in set.temes %}
{% if tema_in_set.tema is not none %}
{% set tema = tema_in_set.tema %}
{% include "fragments/playlist/set/tema_title.html" %}
{% include "fragments/llista/set/tema_title.html" %}
{% else %}
<h3 class="text-center text-3xl p-4"> <i>Desconegut</i> </h3>
{% endif %}
{% endfor %}
<div class="mx-12">
<hr class="h-px mt-1 mb-3 bg-beige border-0">
{% include "fragments/playlist/set/set_score.html"%}
{% include "fragments/llista/set/set_score.html"%}
</div>
{% else %}
{% for tema_in_set in set.temes %}
@@ -31,7 +31,7 @@
{% endif %}
{% if tema_in_set.tema is not none %}
{% set tema = tema_in_set.tema %}
{% include "fragments/playlist/set/tema.html"%}
{% include "fragments/llista/set/tema.html"%}
{% else %}
<h3 class="text-center text-3xl p-4">
<i>Desconegut</i>

View File

@@ -1,4 +1,4 @@
{% include "fragments/playlist/set/tema_title.html" %}
{% include "fragments/llista/set/tema_title.html" %}
<div class="mx-12 text-left">
{% if tema.main_score() is not none %}

View File

@@ -36,9 +36,9 @@ hx-get="/api/llista/{{ playlist_id }}/set/{{ set_id }}"
class="flex flex-col items-start w-full">
{% for tema_entry in set_entry.temes %}
{% if new_entry %}
{% include "fragments/playlist/tema_editor.html" %}
{% include "fragments/llista/tema_editor.html" %}
{% else %}
{% include "fragments/playlist/tema_entry.html" %}
{% include "fragments/llista/tema_entry.html" %}
{% endif %}
{% endfor %}
</ol>

View File

@@ -0,0 +1,15 @@
{% if not playlist.hidden %}
<button title="Llista visible (amaga-la)"
class="text-beige text-2xl mx-2"
hx-put="/api/llista/{{ playlist.id }}/invisible"
hx-swap="outerHTML">
<i class="fa fa-eye" aria-hidden="true"></i>
</button>
{% else %}
<button title="Llista invisible (mostra-la)"
class="text-beige text-2xl mx-2"
hx-put="/api/llista/{{ playlist.id }}/visible"
hx-swap="outerHTML">
<i class="fa fa-eye-slash" aria-hidden="true"></i>
</button>
{% endif %}

View File

@@ -0,0 +1,16 @@
{% include "fragments/menu.html" %}
<div class="p-12 text-center">
<h3 class="text-3xl text-beige p-4">Llistes de Repertori</h3>
{% if playlists %}
<div class="flex flex-col items-center">
{% for playlist in playlists %}
{% include "fragments/llistes/playlist_entry.html" %}
{% endfor %}
</div>
{% else %}
<div class="text-center py-8">
<p class="text-beige/70">No hi ha llistes disponibles.</p>
</div>
{% endif %}
</div>

View File

@@ -0,0 +1,24 @@
<div class="
border rounded border-beige
p-2 m-2 w-full max-w-xl
">
<div class="flex justify-between items-start">
<div class="flex-1">
<p class="text-xl text-white">
<a href="/llista/{{ playlist.id }}">
{% if playlist.name %}
{{ playlist.name }}
{% else %}
Llista de temes #{{ playlist.id }}
{% endif %}
</a>
</p>
</div>
{% if logged_in %}
<div class="ml-2">
{% include "fragments/llista/visibility.html" %}
</div>
{% endif %}
</div>
</div>

View File

@@ -27,6 +27,19 @@
</button>
</li>
{% if menu_selected_id == Pages.Llistes %}
<li class="p-2 text-beige">
<button>
{% else %}
<li class="p-2">
<button hx-get="/api/content/llistes"
hx-replace-url="/llistes"
hx-target="#content">
{% endif %}
Llistes
</button>
</li>
</ul>
<hr class="h-px bg-beige border-0">
<div hx-get="/api/sessions/live"

View File

@@ -1,2 +0,0 @@
{% include "fragments/menu.html" %}
{% include "fragments/playlist/set/set_page.html" %}

View File

@@ -46,7 +46,7 @@
</h4>
{% set playlist_id = session.slowjam.id %}
{% set playlist = session.slowjam %}
{% include "fragments/playlist/playlist.html" %}
{% include "fragments/llista/playlist.html" %}
</div>
{% endif %}
{% if logged_in or (session.setlist and session.setlist.sets) %}
@@ -58,7 +58,7 @@
</h4>
{% set playlist_id = session.setlist.id %}
{% set playlist = session.setlist %}
{% include "fragments/playlist/playlist.html" %}
{% include "fragments/llista/playlist.html" %}
</div>
{% endif %}
</div>

View File

@@ -2,7 +2,7 @@ from typing import TypedDict
from folkugat_web.model import playlists as model
PlaylistRowTuple = tuple[int, str | None]
PlaylistRowTuple = tuple[int, str | None, int]
PlaylistEntryRowTuple = tuple[int, int, int, int | None]
@@ -29,3 +29,12 @@ def row_to_playlist_entry(row: PlaylistEntryRowTuple) -> model.PlaylistEntry:
set_id=row[2],
tema_id=row[3],
)
def row_to_playlist(row: PlaylistRowTuple) -> model.Playlist:
return model.Playlist(
id=row[0],
name=row[1],
sets=[], # Empty for list view
hidden=bool(row[2]),
)

View File

@@ -11,7 +11,8 @@ def create_playlists_table(con: Connection):
query = """
CREATE TABLE IF NOT EXISTS playlists (
id INTEGER PRIMARY KEY,
name TEXT
name TEXT,
hidden INTEGER NOT NULL DEFAULT 1
)
"""
cur = con.cursor()

View File

@@ -69,3 +69,35 @@ def get_playlist_name(playlist_id: int, con: Connection | None = None) -> str |
_ = cur.execute(query, data)
row = cur.fetchone()
return row[0] if row else None
def get_all_playlists(logged_in: bool = False, con: Connection | None = None) -> Iterator[model.Playlist]:
if logged_in:
# Show all playlists for logged in users
query = """
SELECT id, name, hidden
FROM playlists
ORDER BY id ASC
"""
else:
# Show only visible playlists for non-logged in users
query = """
SELECT id, name, hidden
FROM playlists
WHERE hidden = 0
ORDER BY id ASC
"""
with get_connection(con) as con:
cur = con.cursor()
_ = cur.execute(query)
rows = cur.fetchall()
for row in rows:
# Convert to Playlist model without sets for list view
yield model.Playlist(
id=row[0],
name=row[1],
sets=[], # Empty sets list for now
hidden=bool(row[2])
)

View File

@@ -120,3 +120,20 @@ def update_playlist_name(
cur = con.cursor()
_ = cur.execute(query, data)
return
def update_playlist_visibility(
playlist_id: int,
hidden: bool,
con: Connection | None = None,
):
query = """
UPDATE playlists SET
hidden = :hidden
WHERE id = :id
"""
data = dict(id=playlist_id, hidden=1 if hidden else 0)
with get_connection(con) as con:
cur = con.cursor()
_ = cur.execute(query, data)
return

View File

@@ -0,0 +1 @@
from folkugat_web.fragments import llistes

View File

@@ -0,0 +1,17 @@
from folkugat_web.model.pagines import Pages
from folkugat_web.services import playlists as playlists_service
from folkugat_web.templates import templates
def llistes_pagina(request, logged_in):
playlists = playlists_service.get_all_playlists(logged_in=logged_in)
return templates.TemplateResponse(
"fragments/llistes/pagina.html",
{
"request": request,
"logged_in": logged_in,
"playlists": playlists,
"Pages": Pages,
"menu_selected_id": Pages.Llistes,
}
)

View File

@@ -16,7 +16,7 @@ def add_set(
):
new_set = playlists_service.add_set(playlist_id=playlist_id)
return templates.TemplateResponse(
"fragments/playlist/set_entry.html",
"fragments/llista/set_entry.html",
{
"request": request,
"logged_in": logged_in,
@@ -32,7 +32,7 @@ def get_set(request: Request, playlist_id: int, set_id: int, logged_in: bool):
set_entry = playlists_service.get_set(playlist_id=playlist_id, set_id=set_id)
if set_entry:
return templates.TemplateResponse(
"fragments/playlist/set_entry.html",
"fragments/llista/set_entry.html",
{
"request": request,
"logged_in": logged_in,
@@ -55,7 +55,7 @@ def add_tema(request: Request, playlist_id: int, set_id: int, logged_in: bool):
new_tema = playlists_service.add_tema(playlist_id=playlist_id, set_id=set_id)
new_tema = playlists_service.add_tema_to_tema_in_set(new_tema)
return templates.TemplateResponse(
"fragments/playlist/tema_editor.html",
"fragments/llista/tema_editor.html",
{
"request": request,
"logged_in": logged_in,
@@ -70,7 +70,7 @@ def get_tema(request: Request, playlist_id: int, set_id: int, entry_id: int, log
tema_entry = playlists_service.get_tema(entry_id=entry_id)
tema_entry = playlists_service.add_tema_to_tema_in_set(tema_entry)
return templates.TemplateResponse(
"fragments/playlist/tema_entry.html",
"fragments/llista/tema_entry.html",
{
"request": request,
"logged_in": logged_in,
@@ -85,7 +85,7 @@ def get_tema_editor(request: Request, playlist_id: int, set_id: int, entry_id: i
tema_entry = playlists_service.get_tema(entry_id=entry_id)
tema_entry = playlists_service.add_tema_to_tema_in_set(tema_entry)
return templates.TemplateResponse(
"fragments/playlist/tema_editor.html",
"fragments/llista/tema_editor.html",
{
"request": request,
"logged_in": logged_in,
@@ -146,7 +146,7 @@ def busca_tema(
offset=0,
)
return templates.TemplateResponse(
"fragments/playlist/tema_results.html",
"fragments/llista/tema_results.html",
{
"request": request,
"playlist_id": playlist_id,
@@ -163,7 +163,7 @@ def set_tema(request: Request, logged_in: bool, playlist_id: int, set_id: int, e
tema_entry = playlists_service.get_tema(entry_id=entry_id)
tema_entry = playlists_service.add_tema_to_tema_in_set(tema_entry)
return templates.TemplateResponse(
"fragments/playlist/tema_entry.html",
"fragments/llista/tema_entry.html",
{
"request": request,
"logged_in": logged_in,
@@ -182,7 +182,7 @@ async def pagina(request: Request, playlist_id: int, logged_in: bool):
playlist = playlists_service.add_temes_to_playlist(playlist)
playlist = await playlists_service.add_playlist_score_to_playlist(playlist)
return templates.TemplateResponse(
"fragments/playlist/pagina.html",
"fragments/llista/pagina.html",
{
"request": request,
"logged_in": logged_in,
@@ -200,7 +200,7 @@ def name(request: Request, playlist_id: int, logged_in: bool):
from fastapi import HTTPException
raise HTTPException(status_code=404, detail="Could not find playlist")
return templates.TemplateResponse(
"fragments/playlist/name.html",
"fragments/llista/name.html",
{
"request": request,
"logged_in": logged_in,
@@ -216,7 +216,7 @@ def name_editor(request: Request, playlist_id: int, logged_in: bool):
from fastapi import HTTPException
raise HTTPException(status_code=404, detail="Could not find playlist")
return templates.TemplateResponse(
"fragments/playlist/editor/name.html",
"fragments/llista/editor/name.html",
{
"request": request,
"logged_in": logged_in,
@@ -224,3 +224,14 @@ def name_editor(request: Request, playlist_id: int, logged_in: bool):
"playlist": playlist,
}
)
def visibility(request: Request, logged_in: bool, playlist):
return templates.TemplateResponse(
"fragments/llista/visibility.html",
{
"request": request,
"logged_in": logged_in,
"playlist": playlist,
}
)

View File

@@ -13,7 +13,7 @@ async def pagina(request: Request, playlist_id: int, set_id: int, logged_in: boo
set_ = playlists_service.add_temes_to_set(set_)
set_ = await playlists_service.add_set_score_to_set(set_)
return templates.TemplateResponse(
"fragments/playlist/set/pagina.html",
"fragments/llista/set/pagina.html",
{
"request": request,
"logged_in": logged_in,
@@ -36,7 +36,7 @@ async def live(request: Request, logged_in: bool):
set_ = playlists_service.add_temes_to_set(playlist.sets[-1])
set_ = await playlists_service.add_set_score_to_set(set_)
return templates.TemplateResponse(
"fragments/playlist/set/pagina.html",
"fragments/llista/set/pagina.html",
{
"request": request,
"logged_in": logged_in,
@@ -59,7 +59,7 @@ async def live_set(request: Request, logged_in: bool):
set_ = playlists_service.add_temes_to_set(playlist.sets[-1])
set_ = await playlists_service.add_set_score_to_set(set_)
return templates.TemplateResponse(
"fragments/playlist/set/set_page.html",
"fragments/llista/set/set_page.html",
{
"request": request,
"logged_in": logged_in,

View File

@@ -4,3 +4,4 @@ import enum
class Pages(enum.IntEnum):
Sessions = 0
Temes = 1
Llistes = 2

View File

@@ -87,6 +87,7 @@ class Playlist:
id: int
name: str | None
sets: list[Set]
hidden: bool = True
playlist_score: PlaylistScore | None = None
def to_playlist_entries(self) -> Iterator[PlaylistEntry]:

View File

@@ -216,3 +216,20 @@ async def add_playlist_score_to_playlist(playlist: playlists.Playlist) -> playli
)
else:
return playlist
def get_all_playlists(logged_in: bool = False) -> list[playlists.Playlist]:
return list(query.get_all_playlists(logged_in=logged_in))
def set_visibility(playlist_id: int, hidden: bool) -> playlists.Playlist:
with get_connection() as con:
playlist = get_playlist(playlist_id=playlist_id, con=con)
if playlist is None:
raise ValueError(f"No playlist found with playlist_id = {playlist_id}!")
# Update hidden status in database
write.update_playlist_visibility(playlist_id=playlist_id, hidden=hidden, con=con)
# Return updated playlist
return dataclasses.replace(playlist, hidden=hidden)

View File

@@ -0,0 +1,46 @@
#!/usr/bin/env python3
"""
Migration script to add hidden column to playlists table and set all existing playlists to hidden.
"""
import sqlite3
import sys
from pathlib import Path
# Add the project root to the path so we can import project modules
project_root = Path(__file__).parent
sys.path.insert(0, str(project_root))
from folkugat_web.config.db import DB_FILE
def main():
print(f"Connecting to database at: {DB_FILE}")
with sqlite3.connect(DB_FILE) as con:
cur = con.cursor()
# Check if hidden column already exists
cur.execute("PRAGMA table_info(playlists)")
columns = [column[1] for column in cur.fetchall()]
if 'hidden' not in columns:
print("Adding hidden column to playlists table...")
cur.execute("ALTER TABLE playlists ADD COLUMN hidden INTEGER NOT NULL DEFAULT 1")
print("Column added successfully.")
else:
print("Hidden column already exists.")
# Set all existing playlists to hidden (1)
print("Setting all existing playlists to hidden...")
cur.execute("UPDATE playlists SET hidden = 1")
updated_count = cur.rowcount
print(f"Updated {updated_count} playlists to hidden.")
con.commit()
print("Migration completed successfully!")
if __name__ == "__main__":
main()