mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-05 22:55:58 +00:00
Add a load-more button to item list
This commit is contained in:
parent
e5b90eff34
commit
b150269528
@ -57,7 +57,7 @@ init key url flags =
|
|||||||
, key = key
|
, key = key
|
||||||
, page = page
|
, page = page
|
||||||
, version = Api.Model.VersionInfo.empty
|
, version = Api.Model.VersionInfo.empty
|
||||||
, homeModel = Page.Home.Data.emptyModel
|
, homeModel = Page.Home.Data.init flags
|
||||||
, loginModel = Page.Login.Data.emptyModel
|
, loginModel = Page.Login.Data.emptyModel
|
||||||
, manageDataModel = Page.ManageData.Data.emptyModel
|
, manageDataModel = Page.ManageData.Data.emptyModel
|
||||||
, collSettingsModel = Page.CollectiveSettings.Data.emptyModel
|
, collSettingsModel = Page.CollectiveSettings.Data.emptyModel
|
||||||
|
@ -14,9 +14,11 @@ import Api.Model.ItemLightList exposing (ItemLightList)
|
|||||||
import Data.Direction
|
import Data.Direction
|
||||||
import Data.Flags exposing (Flags)
|
import Data.Flags exposing (Flags)
|
||||||
import Data.Icons as Icons
|
import Data.Icons as Icons
|
||||||
|
import Data.Items
|
||||||
import Html exposing (..)
|
import Html exposing (..)
|
||||||
import Html.Attributes exposing (..)
|
import Html.Attributes exposing (..)
|
||||||
import Html.Events exposing (onClick)
|
import Html.Events exposing (onClick)
|
||||||
|
import Ports
|
||||||
import Util.List
|
import Util.List
|
||||||
import Util.String
|
import Util.String
|
||||||
import Util.Time
|
import Util.Time
|
||||||
@ -29,6 +31,7 @@ type alias Model =
|
|||||||
|
|
||||||
type Msg
|
type Msg
|
||||||
= SetResults ItemLightList
|
= SetResults ItemLightList
|
||||||
|
| AddResults ItemLightList
|
||||||
| SelectItem ItemLight
|
| SelectItem ItemLight
|
||||||
|
|
||||||
|
|
||||||
@ -64,6 +67,28 @@ update _ msg model =
|
|||||||
in
|
in
|
||||||
( newModel, Cmd.none, Nothing )
|
( newModel, Cmd.none, Nothing )
|
||||||
|
|
||||||
|
AddResults list ->
|
||||||
|
if list.groups == [] then
|
||||||
|
( model, Cmd.none, Nothing )
|
||||||
|
|
||||||
|
else
|
||||||
|
let
|
||||||
|
firstNew =
|
||||||
|
Data.Items.first list
|
||||||
|
|
||||||
|
scrollCmd =
|
||||||
|
case firstNew of
|
||||||
|
Just item ->
|
||||||
|
Ports.scrollToElem item.id
|
||||||
|
|
||||||
|
Nothing ->
|
||||||
|
Cmd.none
|
||||||
|
|
||||||
|
newModel =
|
||||||
|
{ model | results = Data.Items.concat model.results list }
|
||||||
|
in
|
||||||
|
( newModel, scrollCmd, Nothing )
|
||||||
|
|
||||||
SelectItem item ->
|
SelectItem item ->
|
||||||
( model, Cmd.none, Just item )
|
( model, Cmd.none, Just item )
|
||||||
|
|
||||||
@ -123,6 +148,7 @@ viewItem item =
|
|||||||
[ ( "ui fluid card", True )
|
[ ( "ui fluid card", True )
|
||||||
, ( newColor, not isConfirmed )
|
, ( newColor, not isConfirmed )
|
||||||
]
|
]
|
||||||
|
, id item.id
|
||||||
, href "#"
|
, href "#"
|
||||||
, onClick (SelectItem item)
|
, onClick (SelectItem item)
|
||||||
]
|
]
|
||||||
|
67
modules/webapp/src/main/elm/Data/Items.elm
Normal file
67
modules/webapp/src/main/elm/Data/Items.elm
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
module Data.Items exposing
|
||||||
|
( concat
|
||||||
|
, first
|
||||||
|
, length
|
||||||
|
)
|
||||||
|
|
||||||
|
import Api.Model.ItemLight exposing (ItemLight)
|
||||||
|
import Api.Model.ItemLightGroup exposing (ItemLightGroup)
|
||||||
|
import Api.Model.ItemLightList exposing (ItemLightList)
|
||||||
|
import Util.List
|
||||||
|
|
||||||
|
|
||||||
|
concat : ItemLightList -> ItemLightList -> ItemLightList
|
||||||
|
concat l0 l1 =
|
||||||
|
let
|
||||||
|
lastOld =
|
||||||
|
lastGroup l0
|
||||||
|
|
||||||
|
firstNew =
|
||||||
|
List.head l1.groups
|
||||||
|
in
|
||||||
|
case ( lastOld, firstNew ) of
|
||||||
|
( Nothing, Nothing ) ->
|
||||||
|
l0
|
||||||
|
|
||||||
|
( Just _, Nothing ) ->
|
||||||
|
l0
|
||||||
|
|
||||||
|
( Nothing, Just _ ) ->
|
||||||
|
l1
|
||||||
|
|
||||||
|
( Just o, Just n ) ->
|
||||||
|
if o.name == n.name then
|
||||||
|
let
|
||||||
|
ng =
|
||||||
|
ItemLightGroup o.name (o.items ++ n.items)
|
||||||
|
|
||||||
|
prev =
|
||||||
|
Util.List.dropRight 1 l0.groups
|
||||||
|
|
||||||
|
suff =
|
||||||
|
List.drop 1 l1.groups
|
||||||
|
in
|
||||||
|
ItemLightList (prev ++ [ ng ] ++ suff)
|
||||||
|
|
||||||
|
else
|
||||||
|
ItemLightList (l0.groups ++ l1.groups)
|
||||||
|
|
||||||
|
|
||||||
|
first : ItemLightList -> Maybe ItemLight
|
||||||
|
first list =
|
||||||
|
List.head list.groups
|
||||||
|
|> Maybe.map .items
|
||||||
|
|> Maybe.withDefault []
|
||||||
|
|> List.head
|
||||||
|
|
||||||
|
|
||||||
|
length : ItemLightList -> Int
|
||||||
|
length list =
|
||||||
|
List.map (\g -> List.length g.items) list.groups
|
||||||
|
|> List.sum
|
||||||
|
|
||||||
|
|
||||||
|
lastGroup : ItemLightList -> Maybe ItemLightGroup
|
||||||
|
lastGroup list =
|
||||||
|
List.reverse list.groups
|
||||||
|
|> List.head
|
@ -2,13 +2,19 @@ module Page.Home.Data exposing
|
|||||||
( Model
|
( Model
|
||||||
, Msg(..)
|
, Msg(..)
|
||||||
, ViewMode(..)
|
, ViewMode(..)
|
||||||
, emptyModel
|
, doSearchCmd
|
||||||
|
, init
|
||||||
, itemNav
|
, itemNav
|
||||||
|
, resultsBelowLimit
|
||||||
|
, searchLimit
|
||||||
)
|
)
|
||||||
|
|
||||||
|
import Api
|
||||||
import Api.Model.ItemLightList exposing (ItemLightList)
|
import Api.Model.ItemLightList exposing (ItemLightList)
|
||||||
import Comp.ItemCardList
|
import Comp.ItemCardList
|
||||||
import Comp.SearchMenu
|
import Comp.SearchMenu
|
||||||
|
import Data.Flags exposing (Flags)
|
||||||
|
import Data.Items
|
||||||
import Http
|
import Http
|
||||||
|
|
||||||
|
|
||||||
@ -18,16 +24,22 @@ type alias Model =
|
|||||||
, searchInProgress : Bool
|
, searchInProgress : Bool
|
||||||
, viewMode : ViewMode
|
, viewMode : ViewMode
|
||||||
, menuCollapsed : Bool
|
, menuCollapsed : Bool
|
||||||
|
, searchOffset : Int
|
||||||
|
, moreAvailable : Bool
|
||||||
|
, moreInProgress : Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
emptyModel : Model
|
init : Flags -> Model
|
||||||
emptyModel =
|
init _ =
|
||||||
{ searchMenuModel = Comp.SearchMenu.emptyModel
|
{ searchMenuModel = Comp.SearchMenu.emptyModel
|
||||||
, itemListModel = Comp.ItemCardList.init
|
, itemListModel = Comp.ItemCardList.init
|
||||||
, searchInProgress = False
|
, searchInProgress = False
|
||||||
, viewMode = Listing
|
, viewMode = Listing
|
||||||
, menuCollapsed = False
|
, menuCollapsed = False
|
||||||
|
, searchOffset = 0
|
||||||
|
, moreAvailable = True
|
||||||
|
, moreInProgress = False
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -39,6 +51,7 @@ type Msg
|
|||||||
| ItemSearchResp (Result Http.Error ItemLightList)
|
| ItemSearchResp (Result Http.Error ItemLightList)
|
||||||
| DoSearch
|
| DoSearch
|
||||||
| ToggleSearchMenu
|
| ToggleSearchMenu
|
||||||
|
| LoadMore
|
||||||
|
|
||||||
|
|
||||||
type ViewMode
|
type ViewMode
|
||||||
@ -58,3 +71,32 @@ itemNav id model =
|
|||||||
{ prev = Maybe.map .id prev
|
{ prev = Maybe.map .id prev
|
||||||
, next = Maybe.map .id next
|
, next = Maybe.map .id next
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
searchLimit : Int
|
||||||
|
searchLimit =
|
||||||
|
90
|
||||||
|
|
||||||
|
|
||||||
|
doSearchCmd : Flags -> Int -> Comp.SearchMenu.Model -> Cmd Msg
|
||||||
|
doSearchCmd flags offset model =
|
||||||
|
let
|
||||||
|
smask =
|
||||||
|
Comp.SearchMenu.getItemSearch model
|
||||||
|
|
||||||
|
mask =
|
||||||
|
{ smask
|
||||||
|
| limit = searchLimit
|
||||||
|
, offset = offset
|
||||||
|
}
|
||||||
|
in
|
||||||
|
Api.itemSearch flags mask ItemSearchResp
|
||||||
|
|
||||||
|
|
||||||
|
resultsBelowLimit : Model -> Bool
|
||||||
|
resultsBelowLimit model =
|
||||||
|
let
|
||||||
|
len =
|
||||||
|
Data.Items.length model.itemListModel.results
|
||||||
|
in
|
||||||
|
len < searchLimit
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
module Page.Home.Update exposing (update)
|
module Page.Home.Update exposing (update)
|
||||||
|
|
||||||
import Api
|
|
||||||
import Browser.Navigation as Nav
|
import Browser.Navigation as Nav
|
||||||
import Comp.ItemCardList
|
import Comp.ItemCardList
|
||||||
import Comp.SearchMenu
|
import Comp.SearchMenu
|
||||||
@ -21,7 +20,11 @@ update key flags msg model =
|
|||||||
model
|
model
|
||||||
|
|
||||||
ResetSearch ->
|
ResetSearch ->
|
||||||
update key flags (SearchMenuMsg Comp.SearchMenu.ResetForm) model
|
let
|
||||||
|
nm =
|
||||||
|
{ model | searchOffset = 0 }
|
||||||
|
in
|
||||||
|
update key flags (SearchMenuMsg Comp.SearchMenu.ResetForm) nm
|
||||||
|
|
||||||
SearchMenuMsg m ->
|
SearchMenuMsg m ->
|
||||||
let
|
let
|
||||||
@ -57,32 +60,71 @@ update key flags msg model =
|
|||||||
|
|
||||||
ItemSearchResp (Ok list) ->
|
ItemSearchResp (Ok list) ->
|
||||||
let
|
let
|
||||||
|
noff =
|
||||||
|
model.searchOffset + searchLimit
|
||||||
|
|
||||||
m =
|
m =
|
||||||
{ model | searchInProgress = False, viewMode = Listing }
|
{ model
|
||||||
|
| searchInProgress = False
|
||||||
|
, moreInProgress = False
|
||||||
|
, searchOffset = noff
|
||||||
|
, viewMode = Listing
|
||||||
|
, moreAvailable = list.groups /= []
|
||||||
|
}
|
||||||
in
|
in
|
||||||
|
if list.groups == [] then
|
||||||
|
( m, Cmd.none )
|
||||||
|
|
||||||
|
else if model.searchOffset == 0 then
|
||||||
update key flags (ItemCardListMsg (Comp.ItemCardList.SetResults list)) m
|
update key flags (ItemCardListMsg (Comp.ItemCardList.SetResults list)) m
|
||||||
|
|
||||||
|
else
|
||||||
|
update key flags (ItemCardListMsg (Comp.ItemCardList.AddResults list)) m
|
||||||
|
|
||||||
ItemSearchResp (Err _) ->
|
ItemSearchResp (Err _) ->
|
||||||
( { model | searchInProgress = False }, Cmd.none )
|
( { model
|
||||||
|
| searchInProgress = False
|
||||||
|
}
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
DoSearch ->
|
DoSearch ->
|
||||||
doSearch flags model
|
let
|
||||||
|
nm =
|
||||||
|
{ model | searchOffset = 0 }
|
||||||
|
in
|
||||||
|
doSearch flags nm
|
||||||
|
|
||||||
ToggleSearchMenu ->
|
ToggleSearchMenu ->
|
||||||
( { model | menuCollapsed = not model.menuCollapsed }
|
( { model | menuCollapsed = not model.menuCollapsed }
|
||||||
, Cmd.none
|
, Cmd.none
|
||||||
)
|
)
|
||||||
|
|
||||||
|
LoadMore ->
|
||||||
|
if model.moreAvailable then
|
||||||
|
doSearchMore flags model
|
||||||
|
|
||||||
|
else
|
||||||
|
( model, Cmd.none )
|
||||||
|
|
||||||
|
|
||||||
doSearch : Flags -> Model -> ( Model, Cmd Msg )
|
doSearch : Flags -> Model -> ( Model, Cmd Msg )
|
||||||
doSearch flags model =
|
doSearch flags model =
|
||||||
let
|
let
|
||||||
smask =
|
cmd =
|
||||||
Comp.SearchMenu.getItemSearch model.searchMenuModel
|
doSearchCmd flags model.searchOffset model.searchMenuModel
|
||||||
|
|
||||||
mask =
|
|
||||||
{ smask | limit = 100 }
|
|
||||||
in
|
in
|
||||||
( { model | searchInProgress = True, viewMode = Listing }
|
( { model | searchInProgress = True, viewMode = Listing }
|
||||||
, Api.itemSearch flags mask ItemSearchResp
|
, cmd
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
doSearchMore : Flags -> Model -> ( Model, Cmd Msg )
|
||||||
|
doSearchMore flags model =
|
||||||
|
let
|
||||||
|
cmd =
|
||||||
|
doSearchCmd flags model.searchOffset model.searchMenuModel
|
||||||
|
in
|
||||||
|
( { model | moreInProgress = True, viewMode = Listing }
|
||||||
|
, cmd
|
||||||
)
|
)
|
||||||
|
@ -61,6 +61,7 @@ view model =
|
|||||||
, not model.menuCollapsed
|
, not model.menuCollapsed
|
||||||
)
|
)
|
||||||
, ( "sixteen wide column", model.menuCollapsed )
|
, ( "sixteen wide column", model.menuCollapsed )
|
||||||
|
, ( "item-card-list", True )
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
[ div
|
[ div
|
||||||
@ -90,6 +91,36 @@ view model =
|
|||||||
Detail ->
|
Detail ->
|
||||||
div [] []
|
div [] []
|
||||||
]
|
]
|
||||||
|
, div
|
||||||
|
[ classList
|
||||||
|
[ ( "sixteen wide column", True )
|
||||||
|
]
|
||||||
|
]
|
||||||
|
[ div [ class "ui basic center aligned segment" ]
|
||||||
|
[ button
|
||||||
|
[ classList
|
||||||
|
[ ( "ui basic tiny button", True )
|
||||||
|
, ( "disabled", not model.moreAvailable )
|
||||||
|
, ( "hidden invisible", resultsBelowLimit model )
|
||||||
|
]
|
||||||
|
, disabled (not model.moreAvailable || model.moreInProgress || model.searchInProgress)
|
||||||
|
, title "Load more items"
|
||||||
|
, href "#"
|
||||||
|
, onClick LoadMore
|
||||||
|
]
|
||||||
|
[ if model.moreInProgress then
|
||||||
|
i [ class "loading spinner icon" ] []
|
||||||
|
|
||||||
|
else
|
||||||
|
i [ class "angle double down icon" ] []
|
||||||
|
, if model.moreAvailable then
|
||||||
|
text "Load more…"
|
||||||
|
|
||||||
|
else
|
||||||
|
text "That's all"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
port module Ports exposing
|
port module Ports exposing
|
||||||
( removeAccount
|
( removeAccount
|
||||||
|
, scrollToElem
|
||||||
, setAccount
|
, setAccount
|
||||||
, setAllProgress
|
, setAllProgress
|
||||||
, setProgress
|
, setProgress
|
||||||
@ -18,3 +19,6 @@ port setProgress : ( String, Int ) -> Cmd msg
|
|||||||
|
|
||||||
|
|
||||||
port setAllProgress : ( String, Int ) -> Cmd msg
|
port setAllProgress : ( String, Int ) -> Cmd msg
|
||||||
|
|
||||||
|
|
||||||
|
port scrollToElem : String -> Cmd msg
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
module Util.List exposing
|
module Util.List exposing
|
||||||
( distinct
|
( distinct
|
||||||
|
, dropRight
|
||||||
, find
|
, find
|
||||||
, findIndexed
|
, findIndexed
|
||||||
, findNext
|
, findNext
|
||||||
@ -80,3 +81,10 @@ findNext pred list =
|
|||||||
|> Maybe.map Tuple.second
|
|> Maybe.map Tuple.second
|
||||||
|> Maybe.map (\i -> i + 1)
|
|> Maybe.map (\i -> i + 1)
|
||||||
|> Maybe.andThen (get list)
|
|> Maybe.andThen (get list)
|
||||||
|
|
||||||
|
|
||||||
|
dropRight : Int -> List a -> List a
|
||||||
|
dropRight n list =
|
||||||
|
List.reverse list
|
||||||
|
|> List.drop n
|
||||||
|
|> List.reverse
|
||||||
|
@ -30,3 +30,18 @@ elmApp.ports.setAllProgress.subscribe(function(input) {
|
|||||||
$("."+id).progress({percent: percent});
|
$("."+id).progress({percent: percent});
|
||||||
}, 100);
|
}, 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
elmApp.ports.scrollToElem.subscribe(function(id) {
|
||||||
|
if (id && id != "") {
|
||||||
|
window.setTimeout(function() {
|
||||||
|
var el = document.getElementById(id);
|
||||||
|
if (el) {
|
||||||
|
if (el["scrollIntoViewIfNeeded"]) {
|
||||||
|
el.scrollIntoViewIfNeeded();
|
||||||
|
} else {
|
||||||
|
el.scrollIntoView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 20);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user