Refactor the loading and request management (#230)

This commit is contained in:
Marek Fajkus 2020-11-29 00:02:59 +01:00 committed by GitHub
parent ecf71f5932
commit 060daed760
Failed to generate hash of commit
3 changed files with 225 additions and 207 deletions

View file

@ -108,50 +108,68 @@ updateWith toPage toMsg model ( subModel, subCmd ) =
) )
submitQuery : attemptQuery : ( Model, Cmd Msg ) -> ( Model, Cmd Msg )
Model attemptQuery (( model, _ ) as pair) =
-> ( Model, Cmd Msg )
-> ( Model, Cmd Msg )
submitQuery old ( new, cmd ) =
let let
triggerSearch _ newModel msg makeRequest = -- We intentially throw away Cmd
if -- because we don't want to perform any effects
(newModel.query /= Nothing) -- in this cases where route itself doesn't change
&& (newModel.query /= Just "") noEffects =
&& List.member newModel.channel Search.channels Tuple.mapSecond (always Cmd.none)
then
( new submitQuery msg makeRequest searchModel =
, Cmd.batch Tuple.mapSecond
[ cmd (\cmd ->
, makeRequest Cmd.batch
new.elasticsearch [ cmd
newModel.channel , Cmd.map msg <|
(Maybe.withDefault "" newModel.query) makeRequest
newModel.from model.elasticsearch
newModel.size searchModel.channel
newModel.sort (Maybe.withDefault "" searchModel.query)
|> Cmd.map msg searchModel.from
] searchModel.size
searchModel.sort
]
) )
pair
in
case model.page of
Packages searchModel ->
if Search.shouldLoad searchModel then
submitQuery PackagesMsg Page.Packages.makeRequest searchModel
else else
( new, cmd ) noEffects pair
in
case ( old.page, new.page ) of
( Packages oldModel, Packages newModel ) ->
triggerSearch oldModel newModel PackagesMsg Page.Packages.makeRequest
( NotFound, Packages newModel ) -> Options searchModel ->
triggerSearch newModel newModel PackagesMsg Page.Packages.makeRequest if Search.shouldLoad searchModel then
submitQuery OptionsMsg Page.Options.makeRequest searchModel
( Options oldModel, Options newModel ) -> else
triggerSearch oldModel newModel OptionsMsg Page.Options.makeRequest noEffects pair
( NotFound, Options newModel ) -> _ ->
triggerSearch newModel newModel OptionsMsg Page.Options.makeRequest pair
( _, _ ) ->
( new, cmd ) pageMatch : Page -> Page -> Bool
pageMatch m1 m2 =
case ( m1, m2 ) of
( NotFound, NotFound ) ->
True
( Home _, Home _ ) ->
True
( Packages _, Packages _ ) ->
True
( Options _, Options _ ) ->
True
_ ->
False
changeRouteTo : changeRouteTo :
@ -159,49 +177,6 @@ changeRouteTo :
-> Url.Url -> Url.Url
-> ( Model, Cmd Msg ) -> ( Model, Cmd Msg )
changeRouteTo currentModel url = changeRouteTo currentModel url =
let
attempteQuery ( newModel, cmd ) =
let
-- We intentially throw away Cmd
-- because we don't want to perform any effects
-- in this cases where route itself doesn't change
noEffects =
( newModel, Cmd.none )
in
case ( currentModel.route, newModel.route ) of
( Route.Packages arg1, Route.Packages arg2 ) ->
if
(arg1.channel /= arg2.channel)
|| (arg1.query /= arg2.query)
|| (arg1.from /= arg2.from)
|| (arg1.size /= arg2.size)
|| (arg1.sort /= arg2.sort)
then
submitQuery newModel ( newModel, cmd )
else
noEffects
( Route.Options arg1, Route.Options arg2 ) ->
if
(arg1.channel /= arg2.channel)
|| (arg1.query /= arg2.query)
|| (arg1.from /= arg2.from)
|| (arg1.size /= arg2.size)
|| (arg1.sort /= arg2.sort)
then
submitQuery newModel ( newModel, cmd )
else
noEffects
( a, b ) ->
if a /= b then
submitQuery newModel ( newModel, cmd )
else
noEffects
in
case Route.fromUrl url of case Route.fromUrl url of
Nothing -> Nothing ->
( { currentModel | page = NotFound } ( { currentModel | page = NotFound }
@ -212,6 +187,13 @@ changeRouteTo currentModel url =
let let
model = model =
{ currentModel | route = route } { currentModel | route = route }
avoidReinit ( newModel, cmd ) =
if pageMatch currentModel.page newModel.page then
( model, Cmd.none )
else
( newModel, cmd )
in in
case route of case route of
Route.NotFound -> Route.NotFound ->
@ -234,7 +216,8 @@ changeRouteTo currentModel url =
in in
Page.Packages.init searchArgs modelPage Page.Packages.init searchArgs modelPage
|> updateWith Packages PackagesMsg model |> updateWith Packages PackagesMsg model
|> attempteQuery |> avoidReinit
|> attemptQuery
Route.Options searchArgs -> Route.Options searchArgs ->
let let
@ -248,7 +231,8 @@ changeRouteTo currentModel url =
in in
Page.Options.init searchArgs modelPage Page.Options.init searchArgs modelPage
|> updateWith Options OptionsMsg model |> updateWith Options OptionsMsg model
|> attempteQuery |> avoidReinit
|> attemptQuery
update : Msg -> Model -> ( Model, Cmd Msg ) update : Msg -> Model -> ( Model, Cmd Msg )
@ -263,7 +247,13 @@ update msg model =
Browser.External href -> Browser.External href ->
( model ( model
, Browser.Navigation.load href , case href of
-- ignore links with no `href` attribute
"" ->
Cmd.none
_ ->
Browser.Navigation.load href
) )
( ChangedUrl url, _ ) -> ( ChangedUrl url, _ ) ->

View file

@ -12,6 +12,7 @@ module Search exposing
, init , init
, makeRequest , makeRequest
, makeRequestBody , makeRequestBody
, shouldLoad
, update , update
, view , view
) )
@ -148,10 +149,25 @@ init args model =
|> fromSortId |> fromSortId
|> Maybe.withDefault Relevance |> Maybe.withDefault Relevance
} }
|> ensureLoading
, Browser.Dom.focus "search-query-input" |> Task.attempt (\_ -> NoOp) , Browser.Dom.focus "search-query-input" |> Task.attempt (\_ -> NoOp)
) )
shouldLoad : Model a -> Bool
shouldLoad model =
model.result == RemoteData.Loading
ensureLoading : Model a -> Model a
ensureLoading model =
if model.query /= Nothing && model.query /= Just "" && List.member model.channel channels then
{ model | result = RemoteData.Loading }
else
model
-- --------------------------- -- ---------------------------
-- UPDATE -- UPDATE
@ -166,6 +182,7 @@ type Msg a
| QueryInputSubmit | QueryInputSubmit
| QueryResponse (RemoteData.WebData (SearchResult a)) | QueryResponse (RemoteData.WebData (SearchResult a))
| ShowDetails String | ShowDetails String
| ChangePage Int
update : update :
@ -186,19 +203,15 @@ update toRoute navKey msg model =
| sort = fromSortId sortId |> Maybe.withDefault Relevance | sort = fromSortId sortId |> Maybe.withDefault Relevance
, from = 0 , from = 0
} }
|> ensureLoading
|> pushUrl toRoute navKey |> pushUrl toRoute navKey
ChannelChange channel -> ChannelChange channel ->
{ model { model
| channel = channel | channel = channel
, result =
if model.query == Nothing || model.query == Just "" then
RemoteData.NotAsked
else
RemoteData.Loading
, from = 0 , from = 0
} }
|> ensureLoading
|> pushUrl toRoute navKey |> pushUrl toRoute navKey
QueryInput query -> QueryInput query ->
@ -207,10 +220,8 @@ update toRoute navKey msg model =
) )
QueryInputSubmit -> QueryInputSubmit ->
{ model { model | from = 0 }
| result = RemoteData.Loading |> ensureLoading
, from = 0
}
|> pushUrl toRoute navKey |> pushUrl toRoute navKey
QueryResponse result -> QueryResponse result ->
@ -229,6 +240,11 @@ update toRoute navKey msg model =
} }
|> pushUrl toRoute navKey |> pushUrl toRoute navKey
ChangePage from ->
{ model | from = from }
|> ensureLoading
|> pushUrl toRoute navKey
pushUrl : Route.SearchRoute -> Browser.Navigation.Key -> Model a -> ( Model a, Cmd msg ) pushUrl : Route.SearchRoute -> Browser.Navigation.Key -> Model a -> ( Model a, Cmd msg )
pushUrl toRoute navKey model = pushUrl toRoute navKey model =
@ -487,20 +503,26 @@ view { toRoute, categoryName } title model viewSuccess outMsg =
] ]
] ]
] ]
, case model.result of , div [] <|
RemoteData.NotAsked -> case model.result of
div [] [ text "" ] RemoteData.NotAsked ->
[ text "" ]
RemoteData.Loading -> RemoteData.Loading ->
div [ class "loader" ] [ text "Loading..." ] [ p [] [ em [] [ text "Searching..." ] ]
, div []
[ viewSortSelection outMsg model
, viewPager outMsg model 0 toRoute
]
, div [ class "loader-wrapper" ] [ div [ class "loader" ] [ text "Loading..." ] ]
, viewPager outMsg model 0 toRoute
]
RemoteData.Success result -> RemoteData.Success result ->
if result.hits.total.value == 0 then if result.hits.total.value == 0 then
div []
[ h4 [] [ text <| "No " ++ categoryName ++ " found!" ] ] [ h4 [] [ text <| "No " ++ categoryName ++ " found!" ] ]
else else
div []
[ p [] [ p []
[ em [] [ em []
[ text [ text
@ -525,138 +547,128 @@ view { toRoute, categoryName } title model viewSuccess outMsg =
) )
] ]
] ]
, form [ class "form-horizontal pull-right" ] , div []
[ div [ viewSortSelection outMsg model
[ class "control-group" , viewPager outMsg model result.hits.total.value toRoute
]
[ label [ class "control-label" ] [ text "Sort by:" ]
, div
[ class "controls" ]
[ select
[ onInput (\x -> outMsg (SortChange x))
]
(List.map
(\sort ->
option
[ selected (model.sort == sort)
, value (toSortId sort)
]
[ text <| toSortTitle sort ]
)
sortBy
)
]
]
] ]
, viewPager outMsg model result toRoute
, viewSuccess model.channel model.show result , viewSuccess model.channel model.show result
, viewPager outMsg model result toRoute , viewPager outMsg model result.hits.total.value toRoute
] ]
RemoteData.Failure error -> RemoteData.Failure error ->
let let
( errorTitle, errorMessage ) = ( errorTitle, errorMessage ) =
case error of case error of
Http.BadUrl text -> Http.BadUrl text ->
( "Bad Url!", text ) ( "Bad Url!", text )
Http.Timeout -> Http.Timeout ->
( "Timeout!", "Request to the server timeout." ) ( "Timeout!", "Request to the server timeout." )
Http.NetworkError -> Http.NetworkError ->
( "Network Error!", "A network request bonsaisearch.net domain failed. This is either due to a content blocker or a networking issue." ) ( "Network Error!", "A network request bonsaisearch.net domain failed. This is either due to a content blocker or a networking issue." )
Http.BadStatus code -> Http.BadStatus code ->
( "Bad Status", "Server returned " ++ String.fromInt code ) ( "Bad Status", "Server returned " ++ String.fromInt code )
Http.BadBody text -> Http.BadBody text ->
( "Bad Body", text ) ( "Bad Body", text )
in in
div [ class "alert alert-error" ] [ div [ class "alert alert-error" ]
[ h4 [] [ text errorTitle ] [ h4 [] [ text errorTitle ]
, text errorMessage , text errorMessage
]
] ]
] ]
viewSortSelection : (Msg a -> b) -> Model a -> Html b
viewSortSelection outMsg model =
form [ class "form-horizontal pull-right sort-form" ]
[ div [ class "control-group sort-group" ]
[ label [ class "control-label" ] [ text "Sort by:" ]
, div
[ class "controls" ]
[ select
[ onInput (\x -> outMsg (SortChange x))
]
(List.map
(\sort ->
option
[ selected (model.sort == sort)
, value (toSortId sort)
]
[ text <| toSortTitle sort ]
)
sortBy
)
]
]
]
viewPager : viewPager :
(Msg a -> b) (Msg a -> b)
-> Model a -> Model a
-> SearchResult a -> Int
-> Route.SearchRoute -> Route.SearchRoute
-> Html b -> Html b
viewPager _ model result toRoute = viewPager outMsg model total toRoute =
ul [ class "pager" ] Html.map outMsg <|
[ li ul [ class "pager" ]
[ classList [ li [ classList [ ( "disabled", model.from == 0 ) ] ]
[ ( "disabled", model.from == 0 ) [ a
] [ Html.Events.onClick <|
] if model.from == 0 then
[ a NoOp
[ href <|
if model.from == 0 then
""
else else
createUrl toRoute { model | from = 0 } ChangePage 0
]
[ text "First" ]
] ]
[ text "First" ] , li [ classList [ ( "disabled", model.from == 0 ) ] ]
] [ a
, li [ Html.Events.onClick <|
[ classList if model.from - model.size < 0 then
[ ( "disabled", model.from == 0 ) NoOp
]
]
[ a
[ href <|
if model.from - model.size < 0 then
""
else else
createUrl toRoute { model | from = model.from - model.size } ChangePage <| model.from - model.size
]
[ text "Previous" ]
] ]
[ text "Previous" ] , li [ classList [ ( "disabled", model.from + model.size >= total ) ] ]
] [ a
, li [ Html.Events.onClick <|
[ classList if model.from + model.size >= total then
[ ( "disabled", model.from + model.size >= result.hits.total.value ) NoOp
]
]
[ a
[ href <|
if model.from + model.size >= result.hits.total.value then
""
else else
createUrl toRoute { model | from = model.from + model.size } ChangePage <| model.from + model.size
]
[ text "Next" ]
] ]
[ text "Next" ] , li [ classList [ ( "disabled", model.from + model.size >= total ) ] ]
] [ a
, li [ Html.Events.onClick <|
[ classList if model.from + model.size >= total then
[ ( "disabled", model.from + model.size >= result.hits.total.value ) NoOp
]
]
[ a
[ href <|
if model.from + model.size >= result.hits.total.value then
""
else else
let let
remainder = remainder =
if remainderBy model.size result.hits.total.value == 0 then if remainderBy model.size total == 0 then
1 1
else else
0 0
in in
createUrl toRoute ChangePage <| ((total // model.size) - remainder) * model.size
{ model | from = ((result.hits.total.value // model.size) - remainder) * model.size } ]
[ text "Last" ]
] ]
[ text "Last" ]
] ]
]

View file

@ -1,6 +1,7 @@
body { body {
position: relative; position: relative;
min-height: 100vh; min-height: 100vh;
overflow-y: scroll;
} }
#content { #content {
@ -89,6 +90,21 @@ header .navbar.navbar-static-top {
} }
} }
.sort-form,
.sort-form > .sort-group {
margin-bottom: 0;
}
.pager {
& > li > a {
cursor: pointer;
margin: 0 2px;
}
}
.loader-wrapper {
height: 200px;
overflow: hidden;
}
.loader, .loader,
.loader:before, .loader:before,
.loader:after { .loader:after {