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 import Html.Attributes import Json.Encode as Encode import Time import TimeUtils import Twitch import Ui view : Core.FullModel -> Browser.Document Core.Msg view model = let element = case model of Core.Unloaded _ _ _ _ -> Element.el [ Element.padding 10, Element.centerX ] spinner Core.Loaded m -> viewContent m in { title = title model , body = [ Element.layout [ Font.color Colors.blackFont , Font.size Consts.normalFontSize , Font.family [ Font.typeface "Cantarell" ] ] (Element.column [ Element.width Element.fill, Element.height Element.fill ] [ topBar, element ] ) ] } 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.Model -> Element Core.Msg viewContent model = case model.page of Core.Home hover -> playlistsView model.device model.playlists model.time hover Core.Playlist playlist hover -> videoMiniaturesView model.device model.zone model.time hover playlist Core.Video playlist video hover -> videoView model.device model.zone model.time hover 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 = Ui.link [ Element.padding Consts.homePadding , Element.height Element.fill , Font.bold ] { label = Element.text Consts.name , url = "/" } playlistsView : Element.Device -> List Twitch.Playlist -> Time.Posix -> Maybe (Hover Twitch.Playlist) -> Element Core.Msg playlistsView device playlists time hover = let empty = Element.el [ Element.width Element.fill ] Element.none views = List.map (playlistView 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 -> Maybe (Hover Twitch.Playlist) -> Twitch.Playlist -> Element Core.Msg playlistView 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.fill , Events.onMouseEnter (Core.HoverPlaylist playlist) , Events.onMouseLeave Core.Unhover ] ( key , Element.image [ Element.width Element.fill , Element.height Element.fill , Element.inFront inFront ] { 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 ] 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 ] { label = Ui.link [ Element.width Element.fill, Element.alignTop ] { label = display , url = "/#" ++ playlist.url } , onPress = Nothing } in button videoMiniaturesView : Element.Device -> Time.Zone -> Time.Posix -> Maybe (Hover Twitch.Video) -> Twitch.Playlist -> Element Core.Msg videoMiniaturesView device zone time hover playlist = let empty = Element.el [ Element.width Element.fill ] Element.none views = List.map (videoMiniatureView zone 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 -> Maybe (Hover Twitch.Video) -> Twitch.Playlist -> Twitch.Video -> Element Core.Msg videoMiniature 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 ] key = Twitch.videoMiniatureUrl time Nothing playlist video src = Twitch.videoMiniatureUrl time hover playlist video image = Keyed.el [ Element.width Element.fill , Element.height Element.fill , Events.onMouseEnter (Core.HoverVideo video) , Events.onMouseLeave Core.Unhover ] ( key , Element.image [ Element.width Element.fill , Element.height Element.fill , Element.inFront inFront ] { description = "", src = src } ) label = formatTime video.duration in image videoMiniatureView : Time.Zone -> Time.Posix -> Maybe (Hover Twitch.Video) -> Twitch.Playlist -> Twitch.Video -> Element Core.Msg videoMiniatureView zone time hover playlist video = let display = Element.column [ Element.width Element.fill, Element.spacing 10 ] [ videoMiniature time hover playlist video , videoDescription zone video ] button = Ui.link [ Element.width Element.fill, Element.alignTop ] { label = display , url = "/#" ++ playlist.url ++ video.url } in button videoInList : Time.Zone -> Time.Posix -> Maybe (Hover Twitch.Video) -> Twitch.Playlist -> Twitch.Video -> Twitch.Video -> Element Core.Msg videoInList zone time hover playlist activeVideo video = let label = Element.row [ Element.width Element.fill, Element.spacing 10 ] [ Element.el [ Element.width (Element.fillPortion 2) ] (videoMiniature time hover playlist video) , Element.el [ Element.width (Element.fillPortion 3), Element.paddingXY 0 10, Element.alignTop ] (videoDescription zone video) ] in if video == activeVideo then Element.el [ Element.width Element.fill , Background.color Colors.selected , Border.color Colors.primary , Border.width 2 ] label else Ui.link [ Element.width Element.fill ] { label = label , url = "/#" ++ playlist.url ++ video.url } videoView : Element.Device -> Time.Zone -> Time.Posix -> Maybe (Hover Twitch.Video) -> Twitch.Playlist -> Twitch.Video -> Element Core.Msg videoView device zone time hover playlist video = 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 , Element.height Element.fill ] [ 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 , 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 zone time hover 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 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.html (Html.div [ Html.Attributes.class "lds-spinner" ] [ Html.div [] [] , Html.div [] [] , Html.div [] [] , Html.div [] [] , Html.div [] [] , Html.div [] [] , Html.div [] [] , Html.div [] [] , Html.div [] [] , Html.div [] [] , Html.div [] [] , Html.div [] [] ] )