Version with global json

This commit is contained in:
Thomas Forgione 2020-10-05 11:26:11 +02:00
parent 00b68959e5
commit 98c34600d1
7 changed files with 130 additions and 315 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ js/main.js
js/main.tmp.js
js/main.min.js
deploy.sh
index.json

View File

@ -37,6 +37,12 @@
});
});
}
if (app.ports !== undefined && app.ports.eraseVideo !== undefined) {
app.ports.eraseVideo.subscribe(function() {
lastId = undefined;
});
}
</script>
</body>
</html>

28
indexify.js Normal file
View File

@ -0,0 +1,28 @@
const fs = require('fs');
const path = require('path');
const VIDEO_DIR = "videos";
const DESCRIPTION_FILE = "description.json";
let info = [];
for (let dir of fs.readdirSync(VIDEO_DIR)) {
let description = JSON.parse(fs.readFileSync(path.join(VIDEO_DIR, dir, DESCRIPTION_FILE)));
description.url = dir + "/";
description.videos = [];
for (let subdir of fs.readdirSync(path.join(VIDEO_DIR, dir))) {
if (subdir === DESCRIPTION_FILE) {
continue;
}
let subdescription = JSON.parse(fs.readFileSync(path.join(VIDEO_DIR, dir, subdir, DESCRIPTION_FILE)));
subdescription.url = subdir + "/";
description.videos.push(subdescription);
}
info.push(description);
}
fs.writeFileSync('index.json', JSON.stringify(info));

View File

@ -4,8 +4,8 @@ import Browser.Events as Events
import Browser.Navigation as Nav
import Dict exposing (Dict)
import Element
import Http
import Ports
import Task
import Time
import Twitch
import Url
@ -27,19 +27,16 @@ type alias Model =
type Page
= Home
| Loading
| Playlist Twitch.PlaylistWithVideos
| Video Twitch.PlaylistWithVideos Twitch.Video
| Playlist Twitch.Playlist
| Video Twitch.Playlist Twitch.Video
type Msg
= Noop
| PlaylistsReceived ( List Twitch.Playlist, Time.Zone )
| HomeClicked
| PlaylistClicked String
| PlaylistReceived Twitch.PlaylistWithVideos
| PlaylistReceivedVideo String (Maybe Int) Twitch.PlaylistWithVideos
| VideoClicked Twitch.PlaylistWithVideos Twitch.Video
| PlaylistClicked Twitch.Playlist
| VideoClicked Twitch.Playlist Twitch.Video
| UrlReceived Url.Url
| SizeReceived Int Int
@ -47,10 +44,20 @@ type Msg
init : { width : Int, height : Int } -> Url.Url -> Nav.Key -> ( FullModel, Cmd Msg )
init { width, height } url key =
( Unloaded (Element.classifyDevice { width = width, height = height }) url key
, Task.perform PlaylistsReceived Twitch.fetchPlaylists
, Twitch.fetchPlaylists resultToMsg
)
resultToMsg : Result Http.Error (List Twitch.Playlist) -> Msg
resultToMsg result =
case result of
Ok o ->
PlaylistsReceived ( o, Time.utc )
_ ->
Noop
subscriptions : FullModel -> Sub Msg
subscriptions _ =
Events.onResize (\w h -> SizeReceived w h)
@ -79,12 +86,12 @@ update msg model =
( PlaylistClicked playlist, Loaded m ) ->
( model
, Nav.pushUrl m.key ("#" ++ playlist)
, Nav.pushUrl m.key ("#" ++ playlist.url)
)
( VideoClicked playlist video, Loaded m ) ->
( model
, Nav.pushUrl m.key ("#" ++ playlist.url ++ Twitch.videoName video)
, Nav.pushUrl m.key ("#" ++ playlist.url ++ video.url)
)
( UrlReceived url, Loaded m ) ->
@ -95,14 +102,10 @@ update msg model =
( split, args ) =
case splits of
h1 :: h2 :: _ ->
( String.split "/" h1 |> List.filter (not << String.isEmpty)
, parseQueryString h2
)
( String.split "/" h1, parseQueryString h2 )
h1 :: _ ->
( String.split "/" h1 |> List.filter (not << String.isEmpty)
, Dict.empty
)
( String.split "/" h1, Dict.empty )
_ ->
( [], Dict.empty )
@ -130,72 +133,38 @@ update msg model =
( Nothing, Nothing )
playlist =
case m.page of
Playlist p ->
Just p
List.head (List.filter (\x -> Just x.url == playlistName) m.playlists)
Video p _ ->
Just p
_ ->
Nothing
realVideo =
video =
case playlist of
Just p ->
case List.head (List.filter (\x -> Just (Twitch.videoName x) == videoName) p.videos) of
Just v ->
Just v
_ ->
Nothing
List.head (List.filter (\x -> Just x.url == videoName) p.videos)
_ ->
Nothing
( page, cmd ) =
case ( ( playlist, realVideo ), ( playlistName, videoName ) ) of
( ( Just p, _ ), ( Just _, Nothing ) ) ->
( Playlist p
, Cmd.none
case ( playlist, video ) of
( Just p, Just v ) ->
( Video p v
, Ports.registerVideo ( Twitch.videoId v, "videos/" ++ p.url ++ v.url, time )
)
( ( Just p, Just video ), ( Just _, Just _ ) ) ->
( Video p video
, Ports.registerVideo ( Twitch.videoId video, video.url, time )
)
( ( _, _ ), ( Just name, Nothing ) ) ->
( Loading
, Task.perform
PlaylistReceived
(Twitch.fetchPlaylistWithVideos name)
)
( ( _, _ ), ( Just name, Just video ) ) ->
( Loading
, Task.perform
(PlaylistReceivedVideo video time)
(Twitch.fetchPlaylistWithVideos name)
)
( Just p, Nothing ) ->
( Playlist p, Cmd.none )
_ ->
( Home, Cmd.none )
extraCmd =
case page of
Video _ _ ->
Cmd.none
_ ->
Ports.eraseVideo ()
in
( Loaded { m | page = page }, cmd )
( PlaylistReceived p, Loaded m ) ->
( Loaded { m | page = Playlist p }, Cmd.none )
( PlaylistReceivedVideo video time playlist, Loaded m ) ->
case List.head (List.filter (\x -> Twitch.videoName x == video) playlist.videos) of
Just v ->
( Loaded { m | page = Video playlist v }
, Ports.registerVideo ( Twitch.videoId v, v.url, time )
)
_ ->
( Loaded { m | page = Home }, Cmd.none )
( Loaded { m | page = page }, Cmd.batch [ cmd, extraCmd ] )
_ ->
( model, Cmd.none )

View File

@ -1,4 +1,7 @@
port module Ports exposing (registerVideo)
port module Ports exposing (eraseVideo, registerVideo)
port registerVideo : ( String, String, Maybe Int ) -> Cmd msg
port eraseVideo : () -> Cmd msg

View File

@ -1,8 +1,7 @@
module Twitch exposing
( Playlist
, PlaylistWithVideos
, Video
, fetchPlaylistWithVideos
, decodePlaylists
, fetchPlaylists
, playlistMiniatureUrl
, videoId
@ -10,28 +9,12 @@ module Twitch exposing
, videoName
)
import Html.Parser
import Http
import Iso8601
import Json.Decode as Decode
import Task exposing (Task)
import Time
type alias Playlist =
{ url : String
, name : String
, videos : List String
}
type alias PlaylistWithVideos =
{ url : String
, name : String
, videos : List Video
}
type alias Video =
{ name : String
, url : String
@ -40,9 +23,46 @@ type alias Video =
}
type alias Playlist =
{ url : String
, name : String
, videos : List Video
}
decodeVideo : Decode.Decoder Video
decodeVideo =
Decode.map4 Video
(Decode.field "title" Decode.string)
(Decode.field "url" Decode.string)
(Decode.field "duration" Decode.int)
(Decode.maybe (Decode.field "date" Iso8601.decoder))
decodePlaylist : Decode.Decoder Playlist
decodePlaylist =
Decode.map3 Playlist
(Decode.field "url" Decode.string)
(Decode.field "title" Decode.string)
(Decode.field "videos" (Decode.map (List.sortBy .url >> List.reverse) (Decode.list decodeVideo)))
decodePlaylists : Decode.Decoder (List Playlist)
decodePlaylists =
Decode.map (List.sortBy .url >> List.reverse) (Decode.list decodePlaylist)
fetchPlaylists : (Result Http.Error (List Playlist) -> msg) -> Cmd msg
fetchPlaylists resultToMsg =
Http.get
{ url = "/index.json"
, expect = Http.expectJson resultToMsg decodePlaylists
}
videoName : Video -> String
videoName video =
String.join "/" (List.drop 3 (String.split "/" video.url))
video.url
videoId : Video -> String
@ -50,222 +70,16 @@ videoId video =
String.dropLeft 1 video.url |> String.replace "/" "-"
get : { url : String, resolver : Http.Resolver x a } -> Task x a
get { url, resolver } =
Http.task
{ body = Http.emptyBody
, headers = []
, method = "GET"
, resolver = resolver
, timeout = Nothing
, url = url
}
playlistMiniatureUrl : Playlist -> String
playlistMiniatureUrl playlist =
case List.head (List.reverse playlist.videos) of
Just v ->
"videos/" ++ playlist.url ++ v ++ "miniature-050.png"
"videos/" ++ playlist.url ++ v.url ++ "miniature-050.png"
_ ->
""
videoMiniatureUrl : Video -> String
videoMiniatureUrl video =
video.url ++ "miniature-050.png"
sortPlaylistWithVideos : PlaylistWithVideos -> PlaylistWithVideos
sortPlaylistWithVideos playlist =
{ playlist | videos = List.sortBy .url playlist.videos |> List.reverse }
sortPlaylists : List Playlist -> List Playlist
sortPlaylists playlists =
List.sortBy .url playlists |> List.reverse
fetchPlaylists : Task x ( List Playlist, Time.Zone )
fetchPlaylists =
fetchPlaylistPath
|> Task.andThen fetchPlaylistsMapper
|> Task.map sortPlaylists
|> Task.andThen fetchTimezone
fetchTimezone : List Playlist -> Task x ( List Playlist, Time.Zone )
fetchTimezone playlists =
Task.map (\zone -> ( playlists, zone )) Time.here
fetchPlaylistPath : Task x (List String)
fetchPlaylistPath =
get
{ url = "/videos"
, resolver = Http.stringResolver parseHrefs
}
fetchPlaylist : String -> Task x Playlist
fetchPlaylist name =
fetchPlaylistName name
|> Task.andThen
(\a ->
fetchPlaylistVideoPaths name
-- |> Task.andThen (\c -> fetchVideos name c)
|> Task.map (Playlist name a)
)
fetchPlaylistWithVideos : String -> Task x PlaylistWithVideos
fetchPlaylistWithVideos name =
fetchPlaylistName name
|> Task.andThen
(\a ->
fetchPlaylistVideoPaths name
|> Task.andThen (\c -> fetchVideos name c)
|> Task.map (PlaylistWithVideos name a)
|> Task.map sortPlaylistWithVideos
)
fetchVideo : String -> String -> Task x Video
fetchVideo playlist video =
let
url =
"/videos/" ++ playlist ++ video
in
get
{ url = url ++ "description.json"
, resolver = Http.stringResolver (parseVideo url)
}
fetchVideos : String -> List String -> Task x (List Video)
fetchVideos playlist videos =
Task.sequence (List.map (fetchVideo playlist) videos)
fetchPlaylistName : String -> Task x String
fetchPlaylistName path =
get
{ url = "/videos/" ++ path ++ "description.json"
, resolver = Http.stringResolver parsePlaylistName
}
fetchPlaylistVideoPaths : String -> Task x (List String)
fetchPlaylistVideoPaths name =
get
{ url = "/videos/" ++ name
, resolver = Http.stringResolver parseHrefs
}
parseVideo : String -> Http.Response String -> Result x Video
parseVideo url result =
case result of
Http.GoodStatus_ _ content ->
case Decode.decodeString (decodeVideo url) content of
Ok v ->
Ok v
_ ->
Ok { name = "", url = url, duration = 0, date = Nothing }
_ ->
Ok { name = "", url = url, duration = 0, date = Nothing }
fetchPlaylistsMapper : List String -> Task x (List Playlist)
fetchPlaylistsMapper names =
Task.sequence (List.map fetchPlaylist names)
parsePlaylistName : Http.Response String -> Result x String
parsePlaylistName result =
case result of
Http.GoodStatus_ _ content ->
case Decode.decodeString decodePlaylistName content of
Ok p ->
Ok p
_ ->
Ok ""
_ ->
Ok ""
parseHrefs : Http.Response String -> Result x (List String)
parseHrefs result =
case result of
Http.GoodStatus_ _ content ->
let
withoutDoctype =
if String.startsWith "<!doctype" (String.toLower content) then
String.lines content |> List.drop 1 |> String.join "\n"
else
content
decoded =
Html.Parser.run withoutDoctype
hrefs =
Result.map findHrefs decoded
in
case hrefs of
Ok h ->
Ok h
_ ->
Ok []
_ ->
Ok []
findHrefsAux : List String -> Html.Parser.Node -> List String
findHrefsAux acc node =
case node of
Html.Parser.Element string (( key, value ) :: t) nodes ->
let
newAcc =
if key == "href" && String.endsWith "/" value && value /= "../" then
value :: acc
else
acc
in
findHrefsAux newAcc (Html.Parser.Element string t nodes)
Html.Parser.Element string [] (h :: t) ->
let
attrs =
findHrefsAux [] h
in
findHrefsAux (acc ++ attrs) (Html.Parser.Element string [] t)
_ ->
acc
findHrefs : List Html.Parser.Node -> List String
findHrefs x =
findHrefsAux [] (Html.Parser.Element "" [] x)
decodePlaylistName : Decode.Decoder String
decodePlaylistName =
Decode.field "title" Decode.string
decodeVideo : String -> Decode.Decoder Video
decodeVideo url =
Decode.map3 (\x y -> Video x url y)
(Decode.field "title" Decode.string)
(Decode.map Basics.round (Decode.field "duration" Decode.float))
(Decode.maybe (Decode.field "date" Iso8601.decoder))
videoMiniatureUrl : Playlist -> Video -> String
videoMiniatureUrl playlist video =
"videos/" ++ playlist.url ++ video.url ++ "miniature-050.png"

View File

@ -55,9 +55,6 @@ title model =
Core.Home ->
Consts.url
Core.Loading ->
Consts.url
Core.Playlist p ->
Consts.url ++ " - " ++ p.name
@ -71,9 +68,6 @@ viewContent model =
Core.Home ->
playlistsView model.device model.playlists
Core.Loading ->
Element.el [ Element.padding 10, Element.centerX ] spinner
Core.Playlist playlist ->
videoMiniaturesView model.device model.zone playlist
@ -177,13 +171,13 @@ playlistView playlist =
button =
Input.button [ Element.width Element.fill, Element.alignTop ]
{ label = display
, onPress = Just (Core.PlaylistClicked playlist.url)
, onPress = Just (Core.PlaylistClicked playlist)
}
in
button
videoMiniaturesView : Element.Device -> Time.Zone -> Twitch.PlaylistWithVideos -> Element Core.Msg
videoMiniaturesView : Element.Device -> Time.Zone -> Twitch.Playlist -> Element Core.Msg
videoMiniaturesView device zone playlist =
let
empty =
@ -206,8 +200,8 @@ videoMiniaturesView device zone playlist =
final
videoMiniature : Twitch.Video -> Element Core.Msg
videoMiniature video =
videoMiniature : Twitch.Playlist -> Twitch.Video -> Element Core.Msg
videoMiniature playlist video =
let
inFront =
Element.text label
@ -229,7 +223,7 @@ videoMiniature video =
, Element.height Element.fill
, Element.inFront inFront
]
{ description = "", src = Twitch.videoMiniatureUrl video }
{ description = "", src = Twitch.videoMiniatureUrl playlist video }
label =
formatTime video.duration
@ -237,12 +231,12 @@ videoMiniature video =
image
videoMiniatureView : Time.Zone -> Twitch.PlaylistWithVideos -> Twitch.Video -> Element Core.Msg
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
[ videoMiniature playlist video
, videoDescription zone video
]
@ -255,7 +249,7 @@ videoMiniatureView zone playlist video =
button
videoInList : Time.Zone -> Twitch.PlaylistWithVideos -> Twitch.Video -> Twitch.Video -> Element Core.Msg
videoInList : Time.Zone -> Twitch.Playlist -> Twitch.Video -> Twitch.Video -> Element Core.Msg
videoInList zone playlist activeVideo video =
let
( msg, attr ) =
@ -274,7 +268,7 @@ videoInList zone playlist activeVideo video =
label =
Element.row [ Element.width Element.fill, Element.spacing 10 ]
[ Element.el [ Element.width (Element.fillPortion 2) ]
(videoMiniature video)
(videoMiniature playlist video)
, Element.el [ Element.width (Element.fillPortion 3), Element.paddingXY 0 10, Element.alignTop ]
(videoDescription zone video)
]
@ -282,7 +276,7 @@ videoInList zone playlist activeVideo video =
Input.button attr { label = label, onPress = msg }
videoView : Element.Device -> Time.Zone -> Twitch.PlaylistWithVideos -> Twitch.Video -> Element Core.Msg
videoView : Element.Device -> Time.Zone -> Twitch.Playlist -> Twitch.Video -> Element Core.Msg
videoView device zone playlist video =
let
( builder, contentPadding ) =