moosicbox_app_native_ui 0.1.1

MoosicBox App UI package
#![allow(clippy::module_name_repetitions)]

use hyperchad_transformer_models::{ImageLoading, LayoutOverflow};
use maud::{Markup, PreEscaped, html};
use moosicbox_music_models::{
    AlbumType, ApiSource,
    api::{ApiAlbum, ApiArtist},
    id::Id,
};

use crate::{
    formatting::{AlbumTypeFormat as _, ApiSourceFormat},
    page, pre_escaped, public_img,
    state::State,
};

#[must_use]
pub fn artist_page_url(artist_id: &str) -> PreEscaped<String> {
    pre_escaped!("/artists?artistId={artist_id}")
}

fn artist_cover_url(artist: &ApiArtist, width: u16, height: u16) -> String {
    if artist.contains_cover {
        format!(
            "{}/files/artists/{}/{width}x{height}?moosicboxProfile=master",
            std::env::var("MOOSICBOX_HOST")
                .as_deref()
                .unwrap_or("http://localhost:8500"),
            artist.artist_id
        )
    } else {
        public_img!("album.svg").to_string()
    }
}

fn artist_cover_img(artist: &ApiArtist, size: u16) -> Markup {
    #[allow(clippy::cast_sign_loss)]
    #[allow(clippy::cast_possible_truncation)]
    let request_size = (f64::from(size) * 1.33).round() as u16;

    html! {
        img loading=(ImageLoading::Lazy) src=(artist_cover_url(&artist, request_size, request_size)) sx-width=(size) sx-height=(size);
    }
}

#[must_use]
pub fn artist_page_content(artist: &ApiArtist) -> Markup {
    fn source_html(artist_id: &Id, source: ApiSource, album_type: AlbumType, size: u16) -> Markup {
        html! {
            div
                hx-get=(pre_escaped!("/artists/albums-list?artistId={artist_id}&size={size}&source={source}&albumType={album_type}"))
                hx-trigger="load"
                sx-hidden=(true)
            {}
        }
    }

    let size = 200;

    let mut sources = vec![];

    {
        let artist_id = artist.artist_id.clone();
        let source = ApiSource::Library;
        sources.extend(vec![
            source_html(&artist_id, source, AlbumType::Lp, size),
            source_html(&artist_id, source, AlbumType::EpsAndSingles, size),
            source_html(&artist_id, source, AlbumType::Compilations, size),
        ]);
    }

    for source in &*artist.api_sources {
        let artist_id = source.id.clone();
        let source = source.source;
        sources.extend(vec![
            source_html(&artist_id, source, AlbumType::Lp, size),
            source_html(&artist_id, source, AlbumType::EpsAndSingles, size),
            source_html(&artist_id, source, AlbumType::Compilations, size),
        ]);
    }

    html! {
        div sx-padding-x=(60) sx-padding-y=(20) {
            div sx-padding-y=(20) {
                "Back"
            }
            div sx-dir="row" {
                div sx-width=(size) sx-height=(size) sx-padding-right=(15) {
                    (artist_cover_img(&artist, size))
                }
                div {
                    h1 { (artist.title) }
                }
            }
            @for source in sources {
                (source)
            }
        }
    }
}

#[must_use]
pub fn artist(state: &State, artist: &ApiArtist) -> Markup {
    page(state, &artist_page_content(artist))
}

#[must_use]
pub fn artists_page_content(artists: &[ApiArtist]) -> Markup {
    let size: u16 = 200;
    #[allow(clippy::cast_sign_loss)]
    #[allow(clippy::cast_possible_truncation)]
    let request_size = (f64::from(size) * 1.33).round() as u16;

    html! {
        div
            sx-dir="row"
            sx-overflow-x=(LayoutOverflow::Wrap { grid: true })
            sx-grid-cell-size=(size)
            sx-justify-content="space-evenly"
            sx-gap=(15)
            sx-padding-x=(30)
            sx-padding-y=(15)
        {
            @for artist in artists {
                a href=(artist_page_url(&artist.artist_id.to_string())) sx-width=(size) {
                    div sx-width=(size) {
                        img loading=(ImageLoading::Lazy) src=(artist_cover_url(artist, request_size, request_size)) sx-width=(size) sx-height=(size);
                        (artist.title)
                    }
                }
            }
        }
    }
}

#[must_use]
pub fn artists(state: &State, artists: &[ApiArtist]) -> Markup {
    page(state, &artists_page_content(artists))
}

#[must_use]
pub fn albums_list(
    albums: &[ApiAlbum],
    source: ApiSource,
    album_type: AlbumType,
    size: u16,
) -> Markup {
    if albums.is_empty() {
        return html! {};
    }

    html! {
        div sx-padding-y=(20) {
            h2 {
                (album_type.into_formatted())
                @if source == ApiSource::Library {
                    " in "
                } @else {
                    " on "
                }
                (source.into_formatted())
            }
            div
                sx-dir="row"
                sx-overflow-x=(LayoutOverflow::Wrap { grid: true })
                sx-grid-cell-size=(size)
                sx-justify-content="space-evenly"
                sx-gap=(15)
                sx-padding-y=(15)
            {
                (crate::albums::show_albums(albums.iter(), size))
            }
        }
    }
}