elm-twitch/src/Views.elm

588 lines
17 KiB
Elm
Raw Normal View History

2020-10-03 18:44:16 +02:00
module Views exposing (view)
import Browser
2020-10-04 13:15:57 +02:00
import Colors
import Consts
2020-10-03 18:44:16 +02:00
import Core
2020-10-04 20:53:13 +02:00
import Element exposing (Element)
import Element.Background as Background
import Element.Border as Border
2020-10-16 12:03:07 +02:00
import Element.Events as Events
2020-10-04 13:15:57 +02:00
import Element.Font as Font
2020-10-04 20:53:13 +02:00
import Element.Input as Input
import Element.Keyed as Keyed
2020-10-16 12:03:07 +02:00
import Hover exposing (Hover)
2020-10-04 20:53:13 +02:00
import Html.Attributes
import Time
import TimeUtils
import Twitch
2020-10-08 16:18:22 +02:00
import Ui
2021-06-21 11:30:31 +02:00
import Video
import Video.Views
2020-10-03 18:44:16 +02:00
2020-11-06 11:43:28 +01:00
view : Core.Model -> Browser.Document Core.Msg
2020-10-03 18:44:16 +02:00
view model =
2020-10-04 20:53:13 +02:00
let
element =
2020-11-06 11:43:28 +01:00
case model.playlists of
[] ->
2020-10-04 21:09:13 +02:00
Element.el [ Element.padding 10, Element.centerX ] spinner
2020-10-04 20:53:13 +02:00
2020-11-06 11:43:28 +01:00
_ ->
viewContent model
2020-10-04 20:53:13 +02:00
in
2020-10-04 18:10:26 +02:00
{ title = title model
2020-10-04 15:24:04 +02:00
, body =
2020-10-04 18:10:26 +02:00
[ Element.layout
2021-04-03 23:33:01 +02:00
[ Font.color (Colors.font model.darkMode)
, Background.color (Colors.background model.darkMode)
2020-10-04 18:10:26 +02:00
, Font.size Consts.normalFontSize
, Font.family [ Font.typeface "Cantarell" ]
]
2020-10-04 20:53:13 +02:00
(Element.column
2020-10-06 11:09:37 +02:00
[ Element.width Element.fill, Element.height Element.fill ]
2021-04-03 23:33:01 +02:00
[ topBar model.darkSetting, element ]
2020-10-04 20:53:13 +02:00
)
2020-10-04 15:24:04 +02:00
]
2020-10-03 18:44:16 +02:00
}
2020-10-04 13:15:57 +02:00
2020-11-06 11:43:28 +01:00
title : Core.Model -> String
2020-10-04 18:10:26 +02:00
title model =
2020-11-06 11:43:28 +01:00
case model.page of
Core.Home _ ->
2020-10-04 18:10:26 +02:00
Consts.url
2020-11-06 11:43:28 +01:00
Core.Playlist p _ ->
Consts.url ++ " - " ++ p.name
2020-10-04 18:10:26 +02:00
2021-06-21 11:30:31 +02:00
Core.Video p v _ _ ->
2020-11-06 11:43:28 +01:00
Consts.url ++ " - " ++ p.name ++ " - " ++ v.name
2020-10-04 20:53:13 +02:00
viewContent : Core.Model -> Element Core.Msg
viewContent model =
case model.page of
2020-10-16 12:03:07 +02:00
Core.Home hover ->
2020-11-06 12:19:42 +01:00
playlistsView model.device model.playlists model.currentDate model.time hover
2020-10-04 20:53:13 +02:00
2020-10-16 12:03:07 +02:00
Core.Playlist playlist hover ->
2021-04-03 23:33:01 +02:00
videoMiniaturesView model.darkMode model.device model.zone model.currentDate model.time hover playlist
2020-10-04 20:53:13 +02:00
2021-06-21 11:30:31 +02:00
Core.Video playlist video v hover ->
videoView model.darkMode model.device model.zone model.currentDate model.time hover playlist video v
2020-10-04 20:53:13 +02:00
2021-04-03 23:33:01 +02:00
topBar : Maybe Bool -> Element Core.Msg
topBar darkSetting =
2020-10-04 20:53:13 +02:00
Element.row
[ Element.width Element.fill
, Background.color Colors.primary
, Font.color Colors.white
, Font.size Consts.homeFontSize
]
2021-04-03 23:33:01 +02:00
[ 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
}
2020-10-04 20:53:13 +02:00
homeButton : Element Core.Msg
homeButton =
2020-10-08 16:18:22 +02:00
Ui.link
2020-10-21 11:05:31 +02:00
[ Element.height Element.fill
2020-10-04 20:53:13 +02:00
, Font.bold
]
2020-10-21 11:05:31 +02:00
{ label = Element.el [ Element.padding 10 ] (Element.text Consts.name)
2020-10-08 16:18:22 +02:00
, url = "/"
2020-10-04 20:53:13 +02:00
}
2020-11-06 12:19:42 +01:00
playlistsView : Element.Device -> List Twitch.Playlist -> Time.Posix -> Time.Posix -> Maybe (Hover Twitch.Playlist) -> Element Core.Msg
playlistsView device playlists currentDate time hover =
2020-10-04 20:53:13 +02:00
let
empty =
Element.el [ Element.width Element.fill ] Element.none
views =
2020-11-06 12:19:42 +01:00
List.map (playlistView currentDate time hover) playlists
2020-10-04 20:53:13 +02:00
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
2020-11-06 12:19:42 +01:00
playlistView : Time.Posix -> Time.Posix -> Maybe (Hover Twitch.Playlist) -> Twitch.Playlist -> Element Core.Msg
playlistView currentDate time hover playlist =
2020-10-04 20:53:13 +02:00
let
2020-10-16 12:03:07 +02:00
key =
Twitch.playlistMiniatureUrl time Nothing playlist
2020-10-05 12:00:25 +02:00
src =
2020-10-16 12:03:07 +02:00
Twitch.playlistMiniatureUrl time hover playlist
2020-10-05 12:00:25 +02:00
2020-10-04 20:53:13 +02:00
image =
2020-10-16 12:03:07 +02:00
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")
2020-10-16 12:03:07 +02:00
, Events.onMouseEnter (Core.HoverPlaylist playlist)
, Events.onMouseLeave Core.Unhover
]
( key
2020-10-05 12:00:25 +02:00
, 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%")
2020-10-05 12:00:25 +02:00
, Element.inFront inFront
2020-11-06 12:19:42 +01:00
, Element.inFront new
2020-10-05 12:00:25 +02:00
]
{ description = "", src = src }
)
2020-10-04 20:53:13 +02:00
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
]
2020-11-06 12:19:42 +01:00
new =
if Time.posixToMillis currentDate - Twitch.playlistDate playlist > week then
Element.none
else
newBadge
2020-10-04 20:53:13 +02:00
display =
Element.column [ Element.width Element.fill, Element.spacing 10 ]
[ image
, Element.paragraph
[ Font.bold, Font.size Consts.titleFontSize ]
[ Element.text playlist.name ]
]
button =
2020-12-14 10:39:22 +01:00
Input.button [ Element.width Element.fill, Element.alignTop ]
2020-10-08 16:18:22 +02:00
{ label =
Ui.link [ Element.width Element.fill, Element.alignTop ]
{ label = display
, url = "/#" ++ playlist.url
}
, onPress = Nothing
2020-10-04 20:53:13 +02:00
}
in
button
2021-04-03 23:33:01 +02:00
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 =
2020-10-04 20:53:13 +02:00
let
empty =
Element.el [ Element.width Element.fill ] Element.none
views =
2021-04-03 23:33:01 +02:00
List.map (videoMiniatureView darkMode zone currentDate time hover playlist) playlist.videos
2020-10-04 20:53:13 +02:00
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
2020-11-06 12:19:42 +01:00
videoMiniature : Time.Posix -> Time.Posix -> Maybe (Hover Twitch.Video) -> Twitch.Playlist -> Twitch.Video -> Element Core.Msg
videoMiniature currentDate time hover playlist video =
2020-10-04 20:53:13 +02:00
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
]
2020-11-06 12:19:42 +01:00
date =
video.date
|> Maybe.map Time.posixToMillis
|> Maybe.withDefault 0
new =
if Time.posixToMillis currentDate - date > week then
Element.none
else
newBadge
2020-10-16 12:03:07 +02:00
key =
Twitch.videoMiniatureUrl time Nothing playlist video
2020-10-05 12:00:25 +02:00
src =
2020-10-16 12:03:07 +02:00
Twitch.videoMiniatureUrl time hover playlist video
2020-10-05 12:00:25 +02:00
2020-10-04 20:53:13 +02:00
image =
2020-10-16 12:03:07 +02:00
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")
2020-10-16 12:03:07 +02:00
]
( key
2020-10-05 12:00:25 +02:00
, 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%")
2020-10-05 12:00:25 +02:00
, Element.inFront inFront
2020-11-06 12:19:42 +01:00
, Element.inFront new
2020-10-05 12:00:25 +02:00
]
{ description = "", src = src }
)
2020-10-04 20:53:13 +02:00
label =
formatTime video.duration
in
image
2021-04-03 23:33:01 +02:00
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 =
2020-10-04 20:53:13 +02:00
let
display =
2021-06-22 09:21:37 +02:00
Element.column
[ Element.width Element.fill
, Element.spacing 10
, Events.onMouseEnter (Core.HoverVideo video)
, Events.onMouseLeave Core.Unhover
]
2020-11-06 12:19:42 +01:00
[ videoMiniature currentDate time hover playlist video
2021-06-22 09:21:37 +02:00
, videoDate darkMode zone video
2020-10-04 20:53:13 +02:00
]
button =
2020-10-08 16:18:22 +02:00
Ui.link [ Element.width Element.fill, Element.alignTop ]
2020-10-04 20:53:13 +02:00
{ label = display
2020-10-08 16:18:22 +02:00
, url = "/#" ++ playlist.url ++ video.url
2020-10-04 20:53:13 +02:00
}
in
button
2021-04-03 23:33:01 +02:00
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 =
2020-10-04 20:53:13 +02:00
let
label =
2021-06-22 09:21:37 +02:00
Element.row
[ Element.width Element.fill
, Element.spacing 10
, Events.onMouseEnter (Core.HoverVideo video)
, Events.onMouseLeave Core.Unhover
]
2020-10-04 20:53:13 +02:00
[ Element.el [ Element.width (Element.fillPortion 2) ]
2020-11-06 12:19:42 +01:00
(videoMiniature currentDate time hover playlist video)
2020-10-04 20:53:13 +02:00
, Element.el [ Element.width (Element.fillPortion 3), Element.paddingXY 0 10, Element.alignTop ]
2021-06-22 09:21:37 +02:00
(videoDate darkMode zone video)
2020-10-04 20:53:13 +02:00
]
in
2020-10-08 16:18:22 +02:00
if video == activeVideo then
Element.el
[ Element.width Element.fill
2021-04-03 23:33:01 +02:00
, Background.color (Colors.selected darkMode)
2020-10-08 16:18:22 +02:00
, Border.color Colors.primary
, Border.width 2
]
label
else
Ui.link [ Element.width Element.fill ]
{ label = label
, url = "/#" ++ playlist.url ++ video.url
}
2020-10-04 20:53:13 +02:00
2021-06-21 11:30:31 +02:00
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 =
2020-10-04 20:53:13 +02:00
let
( builder, contentPadding ) =
case device.class of
Element.Phone ->
2020-10-06 11:09:37 +02:00
( Element.column [ Element.width Element.fill, Element.height Element.fill, Element.spacing 10 ]
2020-10-04 20:53:13 +02:00
, 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
]
2021-12-09 17:34:53 +01:00
[ Video.Views.embedElement v |> Element.map Core.VideoMsg
2020-10-04 20:53:13 +02:00
, 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)
2020-10-06 11:09:37 +02:00
, Element.height Element.fill
, Element.scrollbarY
2020-10-04 20:53:13 +02:00
]
2021-04-03 23:33:01 +02:00
(List.map (videoInList darkMode zone currentDate time hover playlist video) playlist.videos)
2020-10-04 20:53:13 +02:00
]
2021-06-22 09:21:37 +02:00
videoDate : Bool -> Time.Zone -> Twitch.Video -> Element Core.Msg
videoDate darkMode zone video =
2020-10-04 20:53:13 +02:00
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
2021-04-03 23:33:01 +02:00
[ Font.color (Colors.detailFont darkMode)
2020-10-04 20:53:13 +02:00
]
[ 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)
2020-10-04 21:09:13 +02:00
spinner : Element Core.Msg
spinner =
2021-12-09 17:34:53 +01:00
Element.none
2020-11-06 12:19:42 +01:00
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