119 lines
4.0 KiB
Forth
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
|