Adds missing files, gets device

This commit is contained in:
Thomas Forgione 2020-10-04 20:20:16 +02:00
parent 56749e29c4
commit a72c5c867c
6 changed files with 883 additions and 435 deletions

View File

@ -12,7 +12,8 @@
<script src="js/main.js"></script> <script src="js/main.js"></script>
<script> <script>
var app = Elm.Main.init({ var app = Elm.Main.init({
node: document.getElementById('container') node: document.getElementById('container'),
flags: { width: window.innerWidth, height: window.innerHeight }
}); });
app.ports.registerVideo.subscribe(function(args) { app.ports.registerVideo.subscribe(function(args) {

View File

@ -1,7 +1,8 @@
module Core exposing (FullModel(..), Model, Msg(..), Page(..), init, update) module Core exposing (FullModel(..), Model, Msg(..), Page(..), init, subscriptions, update)
import Browser.Events as Events
import Browser.Navigation as Nav import Browser.Navigation as Nav
import Json.Decode as Decode import Element
import Ports import Ports
import Task import Task
import Time import Time
@ -10,7 +11,7 @@ import Url
type FullModel type FullModel
= Unloaded Url.Url Nav.Key = Unloaded Element.Device Url.Url Nav.Key
| Loaded Model | Loaded Model
@ -19,6 +20,7 @@ type alias Model =
, zone : Time.Zone , zone : Time.Zone
, page : Page , page : Page
, key : Nav.Key , key : Nav.Key
, device : Element.Device
} }
@ -35,25 +37,36 @@ type Msg
| PlaylistClicked Twitch.Playlist | PlaylistClicked Twitch.Playlist
| VideoClicked Twitch.Playlist Twitch.Video | VideoClicked Twitch.Playlist Twitch.Video
| UrlReceived Url.Url | UrlReceived Url.Url
| SizeReceived Int Int
init : Decode.Value -> Url.Url -> Nav.Key -> ( FullModel, Cmd Msg ) init : { width : Int, height : Int } -> Url.Url -> Nav.Key -> ( FullModel, Cmd Msg )
init _ url key = init { width, height } url key =
( Unloaded url key ( Unloaded (Element.classifyDevice { width = width, height = height }) url key
, Task.perform PlaylistsReceived Twitch.fetchPlaylists , Task.perform PlaylistsReceived Twitch.fetchPlaylists
) )
subscriptions : FullModel -> Sub Msg
subscriptions _ =
Events.onResize (\w h -> SizeReceived w h)
update : Msg -> FullModel -> ( FullModel, Cmd Msg ) update : Msg -> FullModel -> ( FullModel, Cmd Msg )
update msg model = update msg model =
case ( msg, model ) of case ( msg, model ) of
( Noop, _ ) -> ( Noop, _ ) ->
( model, Cmd.none ) ( model, Cmd.none )
( PlaylistsReceived ( playlists, zone ), Unloaded url key ) -> ( SizeReceived w h, Loaded m ) ->
( Loaded { m | device = Element.classifyDevice { width = w, height = h } }
, Cmd.none
)
( PlaylistsReceived ( playlists, zone ), Unloaded device url key ) ->
update update
(UrlReceived url) (UrlReceived url)
(Loaded { key = key, playlists = playlists, zone = zone, page = Home }) (Loaded { key = key, playlists = playlists, zone = zone, page = Home, device = device })
( HomeClicked, Loaded m ) -> ( HomeClicked, Loaded m ) ->
( model ( model

View File

@ -2,17 +2,16 @@ module Main exposing (main)
import Browser import Browser
import Core import Core
import Json.Decode as Decode
import Views import Views
main : Program Decode.Value Core.FullModel Core.Msg main : Program { width : Int, height : Int } Core.FullModel Core.Msg
main = main =
Browser.application Browser.application
{ init = Core.init { init = Core.init
, update = Core.update , update = Core.update
, view = Views.view , view = Views.view
, subscriptions = \_ -> Sub.none , subscriptions = Core.subscriptions
, onUrlChange = Core.UrlReceived , onUrlChange = Core.UrlReceived
, onUrlRequest = \_ -> Core.Noop , onUrlRequest = \_ -> Core.Noop
} }

View File

@ -4,18 +4,9 @@ import Browser
import Colors import Colors
import Consts import Consts
import Core import Core
import Element exposing (Element) import Element
import Element.Background as Background
import Element.Border as Border
import Element.Font as Font import Element.Font as Font
import Element.Input as Input import Views.Desktop as Desktop
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 : Core.FullModel -> Browser.Document Core.Msg
@ -27,7 +18,7 @@ view model =
, Font.size Consts.normalFontSize , Font.size Consts.normalFontSize
, Font.family [ Font.typeface "Cantarell" ] , Font.family [ Font.typeface "Cantarell" ]
] ]
(viewContent model) (Desktop.view model)
] ]
} }
@ -35,7 +26,7 @@ view model =
title : Core.FullModel -> String title : Core.FullModel -> String
title model = title model =
case model of case model of
Core.Unloaded _ _ -> Core.Unloaded _ _ _ ->
Consts.url Consts.url
Core.Loaded m -> Core.Loaded m ->
@ -48,413 +39,3 @@ title model =
Core.Video p v -> Core.Video p v ->
Consts.url ++ " - " ++ p.name ++ " - " ++ v.name 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)

427
src/Views/Desktop.elm Normal file
View File

@ -0,0 +1,427 @@
module Views.Desktop exposing (view)
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 -> Element Core.Msg
view 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)

427
src/Views/Mobile.elm Normal file
View File

@ -0,0 +1,427 @@
module Views.Mobile exposing (view)
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 -> Element Core.Msg
view 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)