dashboard-rss/src/Client/Client.fs

119 lines
4.0 KiB
Forth

module Client
open Elmish
open Elmish.React
open Fable.Helpers.React
open Fable.Helpers.React.Props
open Fable.PowerPack.Fetch
open Shared
open Shared.Feed
type LoadedModel = {
items : Feed.Item array
searchIndex : Feed.Item Imports.Fuse.Fuse
searchResults : Feed.Item array }
type Model =
| Loading
| Loaded of LoadedModel
| Errored of exn
type Msg =
| NewItems of Feed.Item array
| SearchUpdate of string
| LoadError of exn
| LoadMore
| Refresh
module Server =
open Shared
open Fable.Remoting.Client
let api : FeedReaderProtocol =
Proxy.remoting<FeedReaderProtocol> {
use_route_builder Route.builder
}
let loadData start number =
Cmd.ofAsync
Server.api.getItems
(start, number)
NewItems
LoadError
let init () : Model * Cmd<Msg> =
Loading, loadData 0 20
let fuzzySearchOptions = [ Imports.Fuse.Keys [| "description"; "title"; "source" |] ]
// Takes the existing model and an array of items to add, and updates the model to include these items
let addItems (model : Model) (newItems : Feed.Item array) =
let originalItems =
match model with
| Loaded l -> l.items
| _ -> [||]
let updatedItems = Feed.merge originalItems newItems
let index = Imports.Fuse.make updatedItems fuzzySearchOptions
match model with
| Loaded l -> Loaded { l with searchResults = [||]; items = updatedItems; searchIndex = index }
| _ -> Loaded { searchResults = [||]; items = updatedItems; searchIndex = index }
let update (msg : Msg) (model : Model) : Model * Cmd<Msg> =
match msg, model with
| NewItems d, _ -> addItems model d, Cmd.none
| SearchUpdate s, Loaded m -> Loaded { m with searchResults = Imports.Fuse.search m.searchIndex s }, Cmd.none
| LoadError e, _ -> Errored e, Cmd.none
| Refresh, _-> model, loadData 0 20
| LoadMore, Loaded m -> model, loadData (Array.length m.items) 20
| _, _ -> model, Cmd.none
let loader = div [ Class "overlay" ] [ Imports.loadingSpinner [ Imports.Color "#000000" ] []; str (Imports.Funny.message ()) ]
let inlineDivider = str " - "
// Displays a Feed.Item
let viewItem { title = title; description = desc; time = time; link = url; source = source } =
li [ ClassName "feed-item" ]
[ div [ ClassName "item-title" ] [ str title ]
span [ ClassName "item-source" ] [ str source ]
inlineDivider
span [ ClassName "item-date" ] [ str <| time.LocalDateTime.ToString("HH:mm:ss dd/MM/yyyyy") ] // display date/time of article using local time
inlineDivider
a [ Href url; ClassName "item-link" ] [str url]
div [ ClassName "item-contents"; DangerouslySetInnerHTML { __html = Imports.sanitize desc } ] [] ]
let viewItems = Array.map viewItem >> Array.toList >> ul [ ClassName "feed-items" ]
let view (model : Model) (dispatch : Msg -> unit) =
match model with
| Loading -> loader
| Errored e -> div [ ClassName "overlay" ] [
div [] [ str <| sprintf "An error occured: %A" e ]
div [] [ str "A team of monkeys will arrive shortly to correct this." ]
button [ ClassName "retry-button"; OnClick (fun _ -> dispatch Refresh) ] [ str "Try Again" ] ]
| Loaded model ->
let items = if Array.length model.searchResults > 0 then model.searchResults else model.items
div []
[ div [ ClassName "controls-container" ] [
input [ OnChange (fun fe -> dispatch <| SearchUpdate fe.Value); Type "search" ; Class "search-bar"; Placeholder "Search Feeds" ]
button [ OnClick (fun _ -> dispatch Refresh); ClassName "refresh-button" ] [ str "Refresh" ] ]
viewItems items
button [ ClassName "load-more-button"; OnClick (fun _ -> dispatch LoadMore) ] [ str "Load More" ] ]
#if DEBUG
open Elmish.Debug
open Elmish.HMR
#endif
Program.mkProgram init update view
#if DEBUG
|> Program.withConsoleTrace
|> Program.withHMR
#endif
|> Program.withReact "app"
#if DEBUG
|> Program.withDebugger
#endif
|> Program.run