elm-twitch/src/Views.elm

546 lines
15 KiB
Elm

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.height Element.fill
, Font.bold
]
{ label = Element.el [ Element.padding 10 ] (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 [] []
]
)