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

View file

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

View file

@ -1,6 +1,7 @@
body {
position: relative;
min-height: 100vh;
overflow-y: scroll;
}
#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:before,
.loader:after {