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 import Video import Video.Events 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 , darkMode : Bool , darkSetting : Maybe Bool } type Page = Home (Maybe (Hover Twitch.Playlist)) | Playlist Twitch.Playlist (Maybe (Hover Twitch.Video)) | Video Twitch.Playlist Twitch.Video Video.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 | DarkMode Bool | DarkModeClicked | VideoMsg Video.Msg init : { width : Int, height : Int, darkMode : Bool, darkSetting : Maybe Bool } -> Url.Url -> Nav.Key -> ( Model, Cmd Msg ) init { width, height, darkMode, darkSetting } url key = ( Model [] Time.utc (Home Nothing) key (Element.classifyDevice { width = width, height = height }) (Time.millisToPosix 0) (Time.millisToPosix 0) url darkMode darkSetting , 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 model = Sub.batch [ Events.onResize (\w h -> SizeReceived w h) , Time.every 200 TimeReceived , Ports.darkMode DarkMode , Sub.map VideoMsg (case model.page of Video _ _ v _ -> Video.Events.subs v _ -> Sub.none ) ] 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 x Nothing -> ( { model | page = Video p v x (Just (Hover.hover hover 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 x _ -> ( { model | page = Video p v x 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 "#" ) DarkModeClicked -> let next = nextDarkSetting model.darkSetting in ( { model | darkSetting = next }, Ports.setDarkMode next ) 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 ) -> let ( el, videoCommand ) = Video.fromConfig { url = "/videos/" ++ p.url ++ v.url ++ "/manifest.m3u8" , id = "video" , autoplay = True , enableMiniatures = True , startTime = Nothing } in ( Video p v el Nothing, Cmd.map VideoMsg videoCommand ) ( Just p, Nothing ) -> ( Playlist p Nothing, Cmd.none ) _ -> ( Home Nothing, Cmd.none ) in ( { model | page = page }, cmd ) UrlRequested u -> case u of Browser.Internal url -> ( model, Nav.pushUrl model.key (Url.toString url) ) Browser.External s -> ( model, Nav.load s ) DarkMode dark -> ( { model | darkMode = dark }, Cmd.none ) VideoMsg vMsg -> let ( newPage, newCommand ) = case model.page of Video a b v c -> let ( newVideo, cmd ) = Video.update vMsg v in ( Video a b newVideo c, cmd ) _ -> ( model.page, Cmd.none ) in ( { model | page = newPage }, Cmd.map VideoMsg newCommand ) 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)) nextDarkSetting : Maybe Bool -> Maybe Bool nextDarkSetting current = case current of Nothing -> Just True Just True -> Just False Just False -> Nothing