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.Font as Font import Element.Input as Input import Element.Keyed as Keyed import Html import Html.Attributes import Json.Encode as Encode import Time import TimeUtils import Twitch view : Core.FullModel -> Browser.Document Core.Msg view model = { title = title model , body = [ Element.layout [ Font.color Colors.blackFont , Font.size Consts.normalFontSize , Font.family [ Font.typeface "Cantarell" ] ] (viewContent model) ] } title : Core.FullModel -> String title model = case model of Core.Unloaded _ _ -> Consts.url Core.Loaded m -> case m.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.FullModel -> Element Core.Msg viewContent model = let content = case model of Core.Unloaded _ _ -> Element.none Core.Loaded submodel -> mainView submodel in Element.column [ Element.width Element.fill ] [ topBar, content ] mainView : Core.Model -> Element Core.Msg mainView model = case model.page of Core.Home -> playlistsView model.playlists Core.Playlist playlist -> videoMiniaturesView model.zone playlist Core.Video playlist video -> videoView model.zone playlist video topBar : Element Core.Msg topBar = Element.row [ Element.width Element.fill , Background.color Colors.primary , Font.color Colors.white , Font.size Consts.homeFontSize ] [ homeButton ] homeButton : Element Core.Msg homeButton = Input.button [ Element.padding Consts.homePadding , Element.height Element.fill , Element.mouseOver [ Background.color Colors.primaryOver ] , Font.bold ] { label = Element.text Consts.name , onPress = Just Core.HomeClicked } playlistsView : List Twitch.Playlist -> Element Core.Msg playlistsView playlists = let empty = Element.el [ Element.width Element.fill ] Element.none views = List.map playlistView playlists grouped = group 4 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 : Twitch.Playlist -> Element Core.Msg playlistView playlist = let image = Element.image [ Element.width Element.fill , Element.height Element.fill , Element.inFront inFront ] { description = "", src = Twitch.playlistMiniatureUrl playlist } 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 ] 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 = display , onPress = Just (Core.PlaylistClicked playlist) } in button videoMiniaturesView : Time.Zone -> Twitch.Playlist -> Element Core.Msg videoMiniaturesView zone playlist = let empty = Element.el [ Element.width Element.fill ] Element.none views = List.map (videoMiniatureView zone playlist) playlist.videos grouped = group 4 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 : Twitch.Video -> Element Core.Msg videoMiniature 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 ] image = Element.image [ Element.width Element.fill , Element.height Element.fill , Element.inFront inFront ] { description = "", src = Twitch.videoMiniatureUrl video } label = formatTime video.duration in image videoMiniatureView : Time.Zone -> Twitch.Playlist -> Twitch.Video -> Element Core.Msg videoMiniatureView zone playlist video = let display = Element.column [ Element.width Element.fill, Element.spacing 10 ] [ videoMiniature video , videoDescription zone video ] button = Input.button [ Element.width Element.fill, Element.alignTop ] { label = display , onPress = Just (Core.VideoClicked playlist video) } in button videoInList : Time.Zone -> Twitch.Playlist -> Twitch.Video -> Twitch.Video -> Element Core.Msg videoInList zone playlist activeVideo video = let ( msg, attr ) = if video == activeVideo then ( Nothing , [ Element.width Element.fill , Background.color Colors.selected , Border.color Colors.primary , Border.width 2 ] ) else ( Just (Core.VideoClicked playlist video), [ Element.width Element.fill ] ) label = Element.row [ Element.width Element.fill, Element.spacing 10 ] [ Element.el [ Element.width (Element.fillPortion 2) ] (videoMiniature video) , Element.el [ Element.width (Element.fillPortion 3), Element.paddingXY 0 10, Element.alignTop ] (videoDescription zone video) ] in Input.button attr { label = label, onPress = msg } videoView : Time.Zone -> Twitch.Playlist -> Twitch.Video -> Element Core.Msg videoView zone playlist video = Element.row [ Element.padding 10, Element.width Element.fill, Element.spacing 10 ] [ Element.column [ Element.width (Element.fillPortion 2) , Element.spacing 10 , Element.alignTop ] [ 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") ] ( video.url , Element.html (Html.video [ Html.Attributes.id (Twitch.videoId video) , Html.Attributes.class "video-js" , Html.Attributes.class "vjs-default-skin" , Html.Attributes.class "wf" , Html.Attributes.property "data-setup" (Encode.string "{\"fluid\": true}") , Html.Attributes.style "position" "absolute" , Html.Attributes.style "top" "0" , Html.Attributes.style "height" "100%" , Html.Attributes.controls True , Html.Attributes.autoplay True ] [] ) ) , Element.paragraph [ Font.size Consts.homeFontSize , Font.bold , Element.paddingEach { top = 10, left = 0, bottom = 0, right = 0 } ] [ Element.text video.name ] , case video.date of Just date -> Element.paragraph [ Font.size Consts.titleFontSize ] [ Element.text ("Diffusé le " ++ formatDate zone date) ] _ -> Element.none ] , Element.column [ Element.alignTop, Element.spacing 10, Element.width (Element.fillPortion 1) ] (List.map (videoInList zone playlist video) playlist.videos) ] videoDescription : Time.Zone -> Twitch.Video -> Element Core.Msg videoDescription 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.greyFont ] [ 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 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)