diff --git a/folkugat_web/api/routes/tema/lyrics.py b/folkugat_web/api/routes/tema/lyrics.py index 556cd5f..392eb17 100644 --- a/folkugat_web/api/routes/tema/lyrics.py +++ b/folkugat_web/api/routes/tema/lyrics.py @@ -33,11 +33,25 @@ def set_lyric( lyric_id: int, title: Annotated[str, Form()], content: Annotated[str, Form()], + max_columns: Annotated[str | None, Form()] = None, ): lyric = lyrics_service.get_lyric_by_id(lyric_id=lyric_id, tema_id=tema_id) if not lyric: raise HTTPException(status_code=404, detail="Could not find lyric!") - new_lyric = lyrics_service.update_lyric(lyric=lyric, title=title, content=content) + + # Parse max_columns from string to int if provided + max_columns_int = None + if max_columns is not None and max_columns.strip(): + try: + max_columns_int = int(max_columns.strip()) + except ValueError: + raise HTTPException(status_code=400, detail="max_columns must be a valid integer") + + try: + new_lyric = lyrics_service.update_lyric(lyric=lyric, title=title, content=content, max_columns=max_columns_int) + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) + return lyrics_fragments.lyric(request=request, logged_in=logged_in, lyric=new_lyric) diff --git a/folkugat_web/assets/static/css/main.css b/folkugat_web/assets/static/css/main.css index c96d85a..65cddec 100644 --- a/folkugat_web/assets/static/css/main.css +++ b/folkugat_web/assets/static/css/main.css @@ -737,6 +737,10 @@ video { width: 50%; } +.w-16 { + width: 4rem; +} + .w-full { width: 100%; } diff --git a/folkugat_web/assets/templates/fragments/tema/editor/lyric.html b/folkugat_web/assets/templates/fragments/tema/editor/lyric.html index c754056..5e6d9e4 100644 --- a/folkugat_web/assets/templates/fragments/tema/editor/lyric.html +++ b/folkugat_web/assets/templates/fragments/tema/editor/lyric.html @@ -7,6 +7,16 @@ rounded bg-brown px-2 " /> + {{ lyric.title }} + {% if logged_in and lyric.max_columns %} + {{ lyric.max_columns }} columnes + {% endif %} {% if logged_in %} - {{ lyric.content.replace('\n', '') | safe }} + {{ (lyric.content.replace('~', '')).replace('\n', '') | safe }} diff --git a/folkugat_web/assets/templates/lilypond/single_tune.ly b/folkugat_web/assets/templates/lilypond/single_tune.ly index c307532..9420cbd 100644 --- a/folkugat_web/assets/templates/lilypond/single_tune.ly +++ b/folkugat_web/assets/templates/lilypond/single_tune.ly @@ -38,7 +38,7 @@ \hspace #1 \center-column { {% for line in paragraph.lines %} - \line { {{ line | safe }} } + {% if line %} \line { {{ line | safe }} } {% else %} \vspace #1 {% endif %} {% endfor %} } {% endfor %} diff --git a/folkugat_web/dal/sql/temes/lyrics.py b/folkugat_web/dal/sql/temes/lyrics.py index 1b1364d..edf5d81 100644 --- a/folkugat_web/dal/sql/temes/lyrics.py +++ b/folkugat_web/dal/sql/temes/lyrics.py @@ -4,7 +4,7 @@ from typing import TypedDict from folkugat_web.dal.sql import Connection, get_connection from folkugat_web.model import temes as model -LyricRowTuple = tuple[int, int, str, str] +LyricRowTuple = tuple[int, int, str, str, int | None] class LyricRowDict(TypedDict): @@ -12,6 +12,7 @@ class LyricRowDict(TypedDict): tema_id: int title: str content: str + max_columns: int | None def lyric_to_row(lyric: model.Lyrics) -> LyricRowDict: @@ -20,6 +21,7 @@ def lyric_to_row(lyric: model.Lyrics) -> LyricRowDict: "tema_id": lyric.tema_id, "title": lyric.title, "content": lyric.content, + "max_columns": lyric.max_columns, } @@ -29,6 +31,7 @@ def row_to_lyric(row: LyricRowTuple) -> model.Lyrics: tema_id=row[1], title=row[2], content=row[3], + max_columns=row[4], ) @@ -63,7 +66,7 @@ def get_lyrics(lyric_id: int | None = None, tema_id: int | None = None, con: Con query = f""" SELECT - id, tema_id, title, content + id, tema_id, title, content, max_columns FROM tema_lyrics {filter_clause} """ @@ -77,9 +80,9 @@ def insert_lyric(lyric: model.Lyrics, con: Connection | None = None) -> model.Ly data = lyric_to_row(lyric) query = f""" INSERT INTO tema_lyrics - (id, tema_id, title, content) + (id, tema_id, title, content, max_columns) VALUES - (:id, :tema_id, :title, :content) + (:id, :tema_id, :title, :content, :max_columns) RETURNING * """ with get_connection(con) as con: @@ -95,6 +98,7 @@ def create_lyric(tema_id: int, title: str | None = None, con: Connection | None tema_id=tema_id, title=title or "", content="", + max_columns=None, ) return insert_lyric(new_lyric, con=con) @@ -104,7 +108,7 @@ def update_lyric(lyric: model.Lyrics, con: Connection | None = None): query = """ UPDATE tema_lyrics SET - tema_id = :tema_id, title = :title, content = :content + tema_id = :tema_id, title = :title, content = :content, max_columns = :max_columns WHERE id = :id """ diff --git a/folkugat_web/model/lilypond/score.py b/folkugat_web/model/lilypond/score.py index 27691b5..d14a5f1 100644 --- a/folkugat_web/model/lilypond/score.py +++ b/folkugat_web/model/lilypond/score.py @@ -32,13 +32,16 @@ class LyricsText: @classmethod def from_lyrics(cls, lyrics: temes.Lyrics, max_cols: int = 3) -> Self: + # Use stored max_columns if available, otherwise use the provided max_cols + effective_max_cols = lyrics.max_columns if lyrics.max_columns is not None else max_cols + # Remove ~ characters before processing paragraphs = [ - LyricsParagraph(lines=par_str.splitlines()) + LyricsParagraph(lines=[line.replace("~", "") for line in par_str.splitlines()]) for par_str in lyrics.content.split("\n\n") if par_str ] return cls(lines=[ LyricsLine(paragraphs=list(line_pars)) - for line_pars in batched(paragraphs, max_cols) + for line_pars in batched(paragraphs, effective_max_cols) ]) def hash(self) -> bytes: diff --git a/folkugat_web/model/temes.py b/folkugat_web/model/temes.py index cb46d74..cc55bd4 100644 --- a/folkugat_web/model/temes.py +++ b/folkugat_web/model/temes.py @@ -60,6 +60,7 @@ class Lyrics: tema_id: int title: str content: str + max_columns: int | None = None @dataclasses.dataclass diff --git a/folkugat_web/services/temes/lyrics.py b/folkugat_web/services/temes/lyrics.py index a2a6659..26ac3c3 100644 --- a/folkugat_web/services/temes/lyrics.py +++ b/folkugat_web/services/temes/lyrics.py @@ -17,6 +17,7 @@ def _sub(pattern: str, sub: str) -> Callable[[str], str]: def _clean_string(lyrics_str: str) -> str: return ( FnChain.transform(lyrics_str) | + # _sub(r"~", "") | _sub(r"\t", " ") | _sub(r" +", " ") | _sub(r" \n", "\n") | @@ -39,8 +40,13 @@ def get_lyric_by_id(lyric_id: int, tema_id: int | None = None) -> model.Lyrics | return next(iter(lyrics_dal.get_lyrics(lyric_id=lyric_id, tema_id=tema_id)), None) -def update_lyric(lyric: model.Lyrics, title: str, content: str): - lyric = dataclasses.replace(lyric, title=title.strip(), content=_clean_string(content)) +def update_lyric(lyric: model.Lyrics, title: str, content: str, max_columns: int | None = None): + # Validate max_columns if provided + if max_columns is not None: + if not isinstance(max_columns, int) or max_columns < 1 or max_columns > 10: + raise ValueError("max_columns must be an integer between 1 and 10") + + lyric = dataclasses.replace(lyric, title=title.strip(), content=_clean_string(content), max_columns=max_columns) lyrics_dal.update_lyric(lyric=lyric) return lyric diff --git a/scripts/08_add_lyrics_max_columns.py b/scripts/08_add_lyrics_max_columns.py new file mode 100644 index 0000000..a713d8a --- /dev/null +++ b/scripts/08_add_lyrics_max_columns.py @@ -0,0 +1,8 @@ +from folkugat_web.dal.sql import get_connection + +with get_connection() as con: + cur = con.cursor() + alter_query = """ ALTER TABLE tema_lyrics ADD COLUMN max_columns INTEGER""" + _ = cur.execute(alter_query) + +print("DONE!") \ No newline at end of file