module Views exposing (view) import Browser import Colors import Consts import Core import Element exposing (Element) import Element.Background as Background import Element.Border as Border import Element.Events as Events import Element.Font as Font import Element.Input as Input import Element.Keyed as Keyed import Hover exposing (Hover) import Html.Attributes import Time import TimeUtils import Twitch import Ui import Video import Video.Views view : Core.Model -> Browser.Document Core.Msg view model = let element = case model.playlists of [] -> Element.el [ Element.padding 10, Element.centerX ] spinner _ -> viewContent model in { title = title model , body = [ Element.layout [ Font.color (Colors.font model.darkMode) , Background.color (Colors.background model.darkMode) , Font.size Consts.normalFontSize , Font.family [ Font.typeface "Cantarell" ] ] (Element.column [ Element.width Element.fill, Element.height Element.fill ] [ topBar model.darkSetting, element ] ) ] } title : Core.Model -> String title model = case model.page of Core.Home _ -> Consts.url Core.Playlist p _ -> Consts.url ++ " - " ++ p.name Core.Video p v _ _ -> Consts.url ++ " - " ++ p.name ++ " - " ++ v.name viewContent : Core.Model -> Element Core.Msg viewContent model = case model.page of Core.Home hover -> playlistsView model.device model.playlists model.currentDate model.time hover Core.Playlist playlist hover -> videoMiniaturesView model.darkMode model.device model.zone model.currentDate model.time hover playlist Core.Video playlist video v hover -> videoView model.darkMode model.device model.zone model.currentDate model.time hover playlist video v topBar : Maybe Bool -> Element Core.Msg topBar darkSetting = Element.row [ Element.width Element.fill , Background.color Colors.primary , Font.color Colors.white , Font.size Consts.homeFontSize ] [ homeButton, mode darkSetting ] mode : Maybe Bool -> Element Core.Msg mode current = let ( label, icon ) = case current of Nothing -> ( "Par défaut", "🌓" ) Just True -> ( "Mode nuit", "🌑" ) Just False -> ( "Mode jour", "🌕" ) in Input.button [ Element.height Element.fill, Element.alignRight, Element.padding 10 ] { label = Element.row [ Element.spacing 5 ] [ Element.el [ Font.size Consts.titleFontSize ] (Element.text label) , Element.el [ Font.size Consts.homeFontSize ] (Element.text icon) ] , onPress = Just Core.DarkModeClicked } homeButton : Element Core.Msg homeButton = Ui.link [ Element.height Element.fill , Font.bold ] { label = Element.el [ Element.padding 10 ] (Element.text Consts.name) , url = "/" } playlistsView : Element.Device -> List Twitch.Playlist -> Time.Posix -> Time.Posix -> Maybe (Hover Twitch.Playlist) -> Element Core.Msg playlistsView device playlists currentDate time hover = let empty = Element.el [ Element.width Element.fill ] Element.none views = List.map (playlistView currentDate time hover) playlists grouped = group (numberOfVideosPerRow device) views rows = grouped |> List.map (\x -> List.map (Maybe.withDefault empty) x) |> List.map (Element.row [ Element.spacing 10, Element.width Element.fill ]) final = Element.column [ Element.padding 10, Element.spacing 10, Element.width Element.fill ] rows in final playlistView : Time.Posix -> Time.Posix -> Maybe (Hover Twitch.Playlist) -> Twitch.Playlist -> Element Core.Msg playlistView currentDate time hover playlist = let key = Twitch.playlistMiniatureUrl time Nothing playlist src = Twitch.playlistMiniatureUrl time hover playlist image = Keyed.el [ Element.width Element.fill , Element.height (Element.px 0) , Element.htmlAttribute (Html.Attributes.style "padding-top" "56.25%") , Element.htmlAttribute (Html.Attributes.style "position" "relative") , Events.onMouseEnter (Core.HoverPlaylist playlist) , Events.onMouseLeave Core.Unhover ] ( key , Element.image [ Element.width Element.fill , Element.htmlAttribute (Html.Attributes.style "position" "absolute") , Element.htmlAttribute (Html.Attributes.style "top" "0") , Element.htmlAttribute (Html.Attributes.style "height" "100%") , Element.inFront inFront , Element.inFront new ] { description = "", src = src } ) length = List.length playlist.videos label = String.fromInt length ++ " video" ++ (if length > 1 then "s" else "" ) inFront = Element.text label |> Element.el [ Background.color Colors.greyBackground , Border.rounded 5 , Element.padding 5 , Font.color Colors.white ] |> Element.el [ Element.alignBottom , Element.alignRight , Element.padding 5 ] new = if Time.posixToMillis currentDate - Twitch.playlistDate playlist > week then Element.none else newBadge display = Element.column [ Element.width Element.fill, Element.spacing 10 ] [ image , Element.paragraph [ Font.bold, Font.size Consts.titleFontSize ] [ Element.text playlist.name ] ] button = Input.button [ Element.width Element.fill, Element.alignTop ] { label = Ui.link [ Element.width Element.fill, Element.alignTop ] { label = display , url = "/#" ++ playlist.url } , onPress = Nothing } in button videoMiniaturesView : Bool -> Element.Device -> Time.Zone -> Time.Posix -> Time.Posix -> Maybe (Hover Twitch.Video) -> Twitch.Playlist -> Element Core.Msg videoMiniaturesView darkMode device zone currentDate time hover playlist = let empty = Element.el [ Element.width Element.fill ] Element.none views = List.map (videoMiniatureView darkMode zone currentDate time hover playlist) playlist.videos grouped = group (numberOfVideosPerRow device) views rows = grouped |> List.map (\x -> List.map (Maybe.withDefault empty) x) |> List.map (Element.row [ Element.spacing 10, Element.width Element.fill ]) final = Element.column [ Element.padding 10, Element.spacing 10, Element.width Element.fill ] rows in final videoMiniature : Time.Posix -> Time.Posix -> Maybe (Hover Twitch.Video) -> Twitch.Playlist -> Twitch.Video -> Element Core.Msg videoMiniature currentDate time hover playlist video = let inFront = Element.text label |> Element.el [ Background.color Colors.greyBackground , Border.rounded 5 , Element.padding 5 , Font.color Colors.white ] |> Element.el [ Element.alignBottom , Element.alignRight , Element.padding 5 ] date = video.date |> Maybe.map Time.posixToMillis |> Maybe.withDefault 0 new = if Time.posixToMillis currentDate - date > week then Element.none else newBadge key = Twitch.videoMiniatureUrl time Nothing playlist video src = Twitch.videoMiniatureUrl time hover playlist video image = Keyed.el [ Element.width Element.fill , Element.height (Element.px 0) , Element.htmlAttribute (Html.Attributes.style "padding-top" "56.25%") , Element.htmlAttribute (Html.Attributes.style "position" "relative") ] ( key , Element.image [ Element.width Element.fill , Element.htmlAttribute (Html.Attributes.style "position" "absolute") , Element.htmlAttribute (Html.Attributes.style "top" "0") , Element.htmlAttribute (Html.Attributes.style "height" "100%") , Element.inFront inFront , Element.inFront new ] { description = "", src = src } ) label = formatTime video.duration in image videoMiniatureView : Bool -> Time.Zone -> Time.Posix -> Time.Posix -> Maybe (Hover Twitch.Video) -> Twitch.Playlist -> Twitch.Video -> Element Core.Msg videoMiniatureView darkMode zone currentDate time hover playlist video = let display = Element.column [ Element.width Element.fill , Element.spacing 10 , Events.onMouseEnter (Core.HoverVideo video) , Events.onMouseLeave Core.Unhover ] [ videoMiniature currentDate time hover playlist video , videoDate darkMode zone video ] button = Ui.link [ Element.width Element.fill, Element.alignTop ] { label = display , url = "/#" ++ playlist.url ++ video.url } in button videoInList : Bool -> Time.Zone -> Time.Posix -> Time.Posix -> Maybe (Hover Twitch.Video) -> Twitch.Playlist -> Twitch.Video -> Twitch.Video -> Element Core.Msg videoInList darkMode zone currentDate time hover playlist activeVideo video = let label = Element.row [ Element.width Element.fill , Element.spacing 10 , Events.onMouseEnter (Core.HoverVideo video) , Events.onMouseLeave Core.Unhover ] [ Element.el [ Element.width (Element.fillPortion 2) ] (videoMiniature currentDate time hover playlist video) , Element.el [ Element.width (Element.fillPortion 3), Element.paddingXY 0 10, Element.alignTop ] (videoDate darkMode zone video) ] in if video == activeVideo then Element.el [ Element.width Element.fill , Background.color (Colors.selected darkMode) , Border.color Colors.primary , Border.width 2 ] label else Ui.link [ Element.width Element.fill ] { label = label , url = "/#" ++ playlist.url ++ video.url } videoView : Bool -> Element.Device -> Time.Zone -> Time.Posix -> Time.Posix -> Maybe (Hover Twitch.Video) -> Twitch.Playlist -> Twitch.Video -> Video.Video -> Element Core.Msg videoView darkMode device zone currentDate time hover playlist video v = let ( builder, contentPadding ) = case device.class of Element.Phone -> ( Element.column [ Element.width Element.fill, Element.height Element.fill, Element.spacing 10 ] , Element.paddingXY 10 0 ) _ -> ( Element.row [ Element.padding 10, Element.width Element.fill, Element.spacing 10 ] , Element.padding 0 ) in builder [ Element.column [ Element.width (Element.fillPortion 2) , Element.spacing 10 , Element.alignTop ] [ Video.Views.embedElement v |> Element.map Core.VideoMsg , Element.paragraph [ Font.size Consts.homeFontSize , Font.bold , contentPadding ] [ Element.text video.name ] , case video.date of Just date -> Element.paragraph [ contentPadding, Font.size Consts.titleFontSize ] [ Element.text ("Diffusé le " ++ formatDate zone date) ] _ -> Element.none ] , Element.column [ contentPadding , Element.alignTop , Element.spacing 10 , Element.width (Element.fillPortion 1) , Element.height Element.fill , Element.scrollbarY ] (List.map (videoInList darkMode zone currentDate time hover playlist video) playlist.videos) ] videoDate : Bool -> Time.Zone -> Twitch.Video -> Element Core.Msg videoDate darkMode zone video = Element.column [ Element.spacing 10 ] [ Element.paragraph [ Font.bold , Font.size Consts.titleFontSize ] [ Element.text video.name ] , case video.date of Just date -> Element.paragraph [ Font.color (Colors.detailFont darkMode) ] [ Element.text ("Diffusé le " ++ formatDate zone date) ] _ -> Element.none ] formatDate : Time.Zone -> Time.Posix -> String formatDate zone time = let day = Time.toDay zone time |> String.fromInt |> TimeUtils.pad2 month = Time.toMonth zone time |> TimeUtils.monthToString year = Time.toYear zone time |> String.fromInt |> TimeUtils.pad2 hours = Time.toHour zone time |> String.fromInt minutes = Time.toMinute zone time |> String.fromInt |> TimeUtils.pad2 in day ++ "/" ++ month ++ "/" ++ year ++ " à " ++ hours ++ "h" ++ minutes formatTime : Int -> String formatTime time = let hours = toHours time minutes = toMinutes time seconds = toSeconds time hoursString = String.fromInt hours minutesString = if minutes < 10 then "0" ++ String.fromInt minutes else String.fromInt minutes secondsString = if seconds < 10 then "0" ++ String.fromInt seconds else String.fromInt seconds in hoursString ++ ":" ++ minutesString ++ ":" ++ secondsString toHours : Int -> Int toHours i = i // 3600 toMinutes : Int -> Int toMinutes i = modBy 3600 i // 60 toSeconds : Int -> Int toSeconds i = modBy 60 i numberOfVideosPerRow : Element.Device -> Int numberOfVideosPerRow device = case device.class of Element.Phone -> 1 Element.Tablet -> 3 _ -> 4 group : Int -> List a -> List (List (Maybe a)) group size list = let grouped = List.map (List.map Just) (groupAux size list []) groupedRev = List.reverse grouped ( firstFixed, tail ) = case groupedRev of h :: t -> ( List.concat [ h, List.repeat (size - List.length h) Nothing ], t ) [] -> ( [], [] ) fixed = (firstFixed :: tail) |> List.reverse in fixed groupAux : Int -> List a -> List (List a) -> List (List a) groupAux size list acc = if List.isEmpty list then List.reverse acc else let groupHead = List.take size list groupTail = List.drop size list in groupAux size groupTail (groupHead :: acc) spinner : Element Core.Msg spinner = Element.none newBadge : Element Core.Msg newBadge = Element.text "NOUV." |> Element.el [ Background.color Colors.red , Border.rounded 5 , Element.padding 5 , Font.color Colors.white , Font.bold ] |> Element.el [ Element.alignBottom , Element.alignLeft , Element.padding 5 ] week : Int week = 1000 * 60 * 60 * 24 * 7