Advertisement
azdab

Issue with parsing of int representation of DateTime

Apr 12th, 2022
1,321
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
F# 8.46 KB | None | 0 0
  1. module App
  2.  
  3. open Elmish
  4. open Elmish.React
  5. open Feliz
  6. open Fable.SimpleHttp
  7. open Fable.DateFunctions
  8. open Thoth.Json
  9. open System
  10.  
  11. module Async =
  12.     let map f op = async {
  13.         let! x = op
  14.         let value = f x
  15.         return value
  16.     }
  17.  
  18. module Cmd =
  19.     let fromAsync (operation:Async<'msg>) : Cmd<'msg> =
  20.         let delayedCmd (dispatch : 'msg -> unit) : unit =
  21.            let delayedDispatch = async {
  22.                let! msg = operation
  23.                dispatch msg
  24.            }
  25.  
  26.            Async.StartImmediate delayedDispatch
  27.  
  28.        Cmd.ofSub delayedCmd
  29.  
  30. [<AutoOpen>]
  31. module AsyncOperations =
  32.    type Deferred<'t> =
  33.         | HasNotStartedYet
  34.         | InProgress
  35.         | Resolved of 't
  36.  
  37.    type AsyncOperationStatus<'t> =
  38.         | Started
  39.         | Finished of 't
  40.  
  41.  
  42. type HackernewsItem = {
  43.    Time : int
  44. }
  45.  
  46. type DeferredResult<'t> = Deferred<Result<'t, string>>
  47.  
  48. type DeferredStoryItem = DeferredResult<HackernewsItem>
  49.  
  50. type State = {
  51.    StoryItems: DeferredResult<Map<int,DeferredStoryItem>>
  52. }
  53.  
  54. type Msg =
  55.    | LoadStoryItems of AsyncOperationStatus<Result<int list, string>>
  56.    | LoadedStoryItem of int * Result<HackernewsItem, string>
  57.  
  58. let init() =
  59.    let initialState = {
  60.        StoryItems = HasNotStartedYet
  61.        }
  62.    let initialCmd = Cmd.ofMsg (LoadStoryItems Started)
  63.    initialState, initialCmd
  64.  
  65. let itemDecoder : Decoder<HackernewsItem> =
  66.    Decode.object (fun fields -> {
  67.        Time = fields.Required.At ["time"] Decode.int
  68.    })
  69.  
  70. let storiesEndpoint = "https://hacker-news.firebaseio.com/v0/topstories.json"
  71.  
  72. let loadStoryItem (itemId:int) = async {
  73.    let rnd = System.Random()
  74.    let endpoint = sprintf "https://hacker-news.firebaseio.com/v0/item/%d.json" itemId
  75.    do! Async.Sleep (rnd.Next(1000, 3000))
  76.    let! (status, responseText) = Http.get endpoint
  77.    match status with
  78.    | 200 ->
  79.        match Decode.fromString itemDecoder responseText with
  80.        | Ok storyItem ->
  81.            return LoadedStoryItem(itemId, Ok storyItem)
  82.        | Error errorMsg ->
  83.            return LoadedStoryItem(itemId, Error errorMsg)
  84.    | _ ->
  85.        return LoadedStoryItem(itemId, Error("Http error while loading " + string itemId))
  86. }
  87.  
  88. let loadStoryItems = async {
  89.    let endpoint = storiesEndpoint
  90.    let! (status, responseText) = Http.get endpoint
  91.    
  92.    match status with
  93.    | 200 ->
  94.        let storyIds = Decode.fromString (Decode.list Decode.int) responseText
  95.        
  96.        match storyIds with
  97.        | Ok storyIds ->
  98.            let storyItems =
  99.                storyIds
  100.                |> List.truncate 10
  101.                
  102.            return LoadStoryItems(Finished (Ok storyItems))
  103.        
  104.        | Error errorMsg ->
  105.            return LoadStoryItems (Finished (Error errorMsg))
  106.  
  107.    | _ ->
  108.        return LoadStoryItems (Finished (Error("Could not load the IDs of the story items.")))
  109. }
  110.  
  111. let startLoading (state:State) =
  112.    { state with StoryItems = InProgress }
  113.  
  114. let update msg state =
  115.    match msg with
  116.    | LoadStoryItems Started ->
  117.        let nextState = { state with StoryItems = InProgress }
  118.        let nextCmd = Cmd.fromAsync loadStoryItems
  119.        nextState, nextCmd
  120.  
  121.    | LoadStoryItems (Finished (Ok storyIds)) ->
  122.        let storiesMap = Map.ofList [ for id in storyIds -> id, Deferred.InProgress ]
  123.        let nextState = { state with StoryItems = Resolved(Ok storiesMap) }
  124.        nextState, Cmd.batch [for id in storyIds -> Cmd.fromAsync (loadStoryItem id)]
  125.  
  126.    | LoadStoryItems (Finished (Error error)) ->
  127.        let nextState = { state with StoryItems = Resolved (Error error) }
  128.        nextState, Cmd.none
  129.  
  130.    | LoadedStoryItem (itemId, Ok item) ->
  131.        match state.StoryItems with
  132.        | Resolved (Ok storiesMap) ->
  133.            let modifiedStoriesMap =
  134.                storiesMap
  135.                |> Map.remove itemId
  136.                |> Map.add itemId (Resolved (Ok item))
  137.  
  138.            let nextState = { state with StoryItems = Resolved(Ok modifiedStoriesMap) }
  139.            nextState, Cmd.none
  140.  
  141.        | _ ->
  142.            state, Cmd.none
  143.  
  144.    | LoadedStoryItem (itemId, Error error) ->
  145.        match state.StoryItems with
  146.        | Resolved (Ok storiesMap) ->
  147.            let modifiedStoriesMap =
  148.                storiesMap
  149.                |> Map.remove itemId
  150.                |> Map.add itemId (Resolved (Error error))
  151.  
  152.            let nextState = { state with StoryItems = Resolved (Ok modifiedStoriesMap) }
  153.            nextState, Cmd.none
  154.  
  155.        | _ ->
  156.            state, Cmd.none
  157.  
  158.  
  159. let renderError (errorMsg : string) =
  160.    Html.h1 [
  161.        prop.style [ style.color.red ]
  162.        prop.text errorMsg
  163.    ]
  164.  
  165. let div (classes: string list) (children: ReactElement list) =
  166.  Html.div [
  167.    prop.className classes
  168.    prop.children children
  169.  ]
  170.  
  171. let (|Years|Months|Weeks|Days|Hours|Minutes|Seconds|) (time:int) =
  172.    let time = DateTime.Parse time
  173.    match (DateTime.Now.DifferenceInYears time) with
  174.    | value when value > 0 ->
  175.        Years value
  176.    | _ ->
  177.        match (DateTime.Now.DifferenceInMonths time) with
  178.        | value when value > 0 ->
  179.            Months value
  180.        | _ ->
  181.            match (DateTime.Now.DifferenceInWeeks time) with
  182.            | value when value > 0 ->
  183.                Weeks value
  184.            | _ ->
  185.                match (DateTime.Now.DifferenceInDays time) with
  186.                | value when value > 0 ->
  187.                    Days value
  188.                | _ ->
  189.                    match (DateTime.Now.DifferenceInHours time) with
  190.                    | value when value > 0 ->
  191.                        Hours value
  192.                    | _ ->
  193.                        match (DateTime.Now.DifferenceInMinutes time) with
  194.                        | value when value > 0 ->
  195.                            Minutes value
  196.                        | value ->
  197.                            Seconds value
  198.  
  199. let renderItemTime item =
  200.    match item.Time with
  201.    | Years y -> sprintf "%d years ago" y
  202.    | Months m -> sprintf "%d months ago" m
  203.    | Weeks w -> sprintf "%d weeks ago" w
  204.    | Days d -> sprintf "%d days ago" d
  205.    | Hours h -> sprintf "%d hours ago" h
  206.    | Minutes m -> sprintf "%d minutes ago" m
  207.    | Seconds s -> sprintf "%d seconds ago" s
  208.  
  209. let renderItemContent item =
  210.  Html.div [
  211.    prop.children [
  212.      div [ "columns"; "is-mobile" ] [
  213.        div [ "column"; "is-narrow" ] [
  214.          Html.div [
  215.            prop.className [ "icon" ]
  216.            prop.style [ style.marginLeft 20 ]
  217.            prop.children [
  218.              Html.i [prop.className "fa fa-poll fa-2x"]
  219.            ]
  220.          ]
  221.        ]
  222.  
  223.        div ["column"; "is-narrow"] [
  224.            Html.div [
  225.                prop.children [
  226.                    Html.span [
  227.                        prop.style [style.marginLeft 10; style.marginRight 10 ]
  228.                        prop.text (renderItemTime item)
  229.                    ]
  230.                ]
  231.            ]
  232.        ]
  233.      ]
  234.    ]
  235.  ]
  236.  
  237. let spinner =
  238.    Html.div [
  239.        prop.style [ style.textAlign.center; style.marginTop 20 ]
  240.        prop.children [
  241.            Html.i [
  242.                prop.className "fa fa-cog fa-spin fa-2x"
  243.            ]
  244.        ]
  245.    ]
  246.  
  247. let renderStoryItem (itemId:int) storyItem =
  248.    let renderedItem =
  249.        match storyItem with
  250.        | HasNotStartedYet -> Html.none
  251.        | InProgress -> spinner
  252.        | Resolved (Error errorMsg) -> renderError errorMsg
  253.        | Resolved (Ok item) -> renderItemContent item
  254.  
  255.    Html.div [
  256.        prop.key itemId
  257.        prop.className "box"
  258.        prop.style [style.marginTop 15; style.marginBottom 15]
  259.        prop.children [ renderedItem ]
  260.    ]
  261.  
  262. let renderStories items =
  263.    match items with
  264.    | HasNotStartedYet -> Html.none
  265.    | InProgress -> spinner
  266.    | Resolved (Error errorMsg) -> renderError errorMsg
  267.    | Resolved (Ok items) ->
  268.        items
  269.        |> Map.toList
  270.        |> List.sortByDescending (fun (_, item) -> item)
  271.        |> List.map (fun (id, storyItem) -> renderStoryItem id storyItem)
  272.        |> Html.div
  273.  
  274.  
  275. let render (state: State) (dispatch: Msg -> unit) =
  276.  Html.div [
  277.    prop.style [ style.padding 20 ]
  278.    prop.children [
  279.        Html.h1 [
  280.            prop.className "title"
  281.            prop.text "Elmish Hackernews"
  282.        ]
  283.  
  284.        renderStories state.StoryItems
  285.    ]
  286.  ]
  287.  
  288. Program.mkProgram init update render
  289. |> Program.withReactSynchronous "elmish-app"
  290. |> Program.run
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement