elm-twitch/src/Core.elm

265 lines
7.5 KiB
Elm

module Core exposing (Model, Msg(..), Page(..), init, subscriptions, update)
import Browser
import Browser.Events as Events
import Browser.Navigation as Nav
import Dict exposing (Dict)
import Element
import Hover exposing (Hover)
import Http
import Ports
import Task
import Time
import TimeZone
import Twitch
import Url
type alias Model =
{ playlists : List Twitch.Playlist
, zone : Time.Zone
, page : Page
, key : Nav.Key
, device : Element.Device
, time : Time.Posix
, currentDate : Time.Posix
, url : Url.Url
}
type Page
= Home (Maybe (Hover Twitch.Playlist))
| Playlist Twitch.Playlist (Maybe (Hover Twitch.Video))
| Video Twitch.Playlist Twitch.Video (Maybe (Hover Twitch.Video))
type Msg
= Noop
| PlaylistsReceived (List Twitch.Playlist)
| HomeClicked
| PlaylistClicked Twitch.Playlist
| VideoClicked Twitch.Playlist Twitch.Video
| UrlReceived Url.Url
| UrlRequested Browser.UrlRequest
| SizeReceived Int Int
| TimeZoneReceivedResult (Result TimeZone.Error ( String, Time.Zone ))
| TimeZoneReceived Time.Zone
| HoverPlaylist Twitch.Playlist
| HoverVideo Twitch.Video
| Unhover
| TimeReceived Time.Posix
| CurrentDateReceived Time.Posix
init : { width : Int, height : Int } -> Url.Url -> Nav.Key -> ( Model, Cmd Msg )
init { width, height } url key =
( Model
[]
Time.utc
(Home Nothing)
key
(Element.classifyDevice { width = width, height = height })
(Time.millisToPosix 0)
(Time.millisToPosix 0)
url
, Cmd.batch
[ Task.attempt TimeZoneReceivedResult TimeZone.getZone
, Task.perform CurrentDateReceived Time.now
, Twitch.fetchPlaylists resultToMsg
]
)
resultToMsg : Result Http.Error (List Twitch.Playlist) -> Msg
resultToMsg result =
case result of
Ok o ->
PlaylistsReceived o
Err _ ->
Noop
subscriptions : Model -> Sub Msg
subscriptions _ =
Sub.batch
[ Events.onResize (\w h -> SizeReceived w h)
, Time.every 200 TimeReceived
]
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Noop ->
( model, Cmd.none )
TimeZoneReceived z ->
( { model | zone = z }, Cmd.none )
TimeZoneReceivedResult (Ok ( _, zone )) ->
( { model | zone = zone }, Cmd.none )
TimeZoneReceivedResult (Err _) ->
( model, Task.perform TimeZoneReceived Time.here )
TimeReceived p ->
( { model | time = p }, Cmd.none )
CurrentDateReceived d ->
( { model | currentDate = d }, Cmd.none )
HoverPlaylist hover ->
case model.page of
Home Nothing ->
( { model | page = Home (Just (Hover.hover hover model.time)) }, Cmd.none )
_ ->
( model, Cmd.none )
HoverVideo hover ->
case model.page of
Playlist p Nothing ->
( { model | page = Playlist p (Just (Hover.hover hover model.time)) }, Cmd.none )
Video p v Nothing ->
( { model | page = Video p v (Just (Hover.hover v model.time)) }, Cmd.none )
_ ->
( model, Cmd.none )
Unhover ->
case model.page of
Home _ ->
( { model | page = Home Nothing }, Cmd.none )
Playlist p _ ->
( { model | page = Playlist p Nothing }, Cmd.none )
Video p v _ ->
( { model | page = Video p v Nothing }, Cmd.none )
SizeReceived w h ->
( { model | device = Element.classifyDevice { width = w, height = h } }
, Cmd.none
)
PlaylistsReceived playlists ->
update
(UrlReceived model.url)
{ model | playlists = playlists, page = Home Nothing }
HomeClicked ->
( model
, Nav.pushUrl model.key "#"
)
PlaylistClicked playlist ->
( model
, Nav.pushUrl model.key ("#" ++ playlist.url)
)
VideoClicked playlist video ->
( model
, Nav.pushUrl model.key ("#" ++ playlist.url ++ video.url)
)
UrlReceived url ->
if List.isEmpty model.playlists then
( { model | url = url }, Cmd.none )
else
let
splits =
String.split "?" (Maybe.withDefault "" url.fragment)
( split, args ) =
case splits of
h1 :: h2 :: _ ->
( String.split "/" h1, parseQueryString h2 )
h1 :: _ ->
( String.split "/" h1, Dict.empty )
_ ->
( [], Dict.empty )
time =
case Dict.get "t" args of
Just "0" ->
Nothing
Just t ->
Just t
_ ->
Nothing
( playlistName, videoName ) =
case split of
p :: v :: _ ->
( Just (p ++ "/"), Just (v ++ "/") )
p :: _ ->
( Just (p ++ "/"), Nothing )
_ ->
( Nothing, Nothing )
playlist =
List.head (List.filter (\x -> Just x.url == playlistName) model.playlists)
video =
case playlist of
Just p ->
List.head (List.filter (\x -> Just x.url == videoName) p.videos)
_ ->
Nothing
( page, cmd ) =
case ( playlist, video ) of
( Just p, Just v ) ->
( Video p v Nothing
, Ports.registerVideo ( Twitch.videoId v, "videos/" ++ p.url ++ v.url, time )
)
( Just p, Nothing ) ->
( Playlist p Nothing, Cmd.none )
_ ->
( Home Nothing, Cmd.none )
extraCmd =
case page of
Video _ _ _ ->
Cmd.none
_ ->
Ports.eraseVideo ()
in
( { model | page = page }, Cmd.batch [ cmd, extraCmd ] )
UrlRequested u ->
case u of
Browser.Internal url ->
( model, Nav.pushUrl model.key (Url.toString url) )
Browser.External s ->
( model, Nav.load s )
splitter : String -> Maybe ( String, String )
splitter input =
case String.split "=" input of
h :: t ->
Just ( h, String.join "=" t )
_ ->
Nothing
parseQueryString : String -> Dict String String
parseQueryString input =
Dict.fromList (List.filterMap splitter (String.split "&" input))