mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-09-30 00:28:23 +00:00
Basic search view for shares
This commit is contained in:
@@ -5,28 +5,83 @@
|
||||
-}
|
||||
|
||||
|
||||
module Page.Share.Data exposing (Model, Msg, init)
|
||||
module Page.Share.Data exposing (Mode(..), Model, Msg(..), PageError(..), init)
|
||||
|
||||
import Api
|
||||
import Api.Model.ItemLightList exposing (ItemLightList)
|
||||
import Api.Model.ShareSecret exposing (ShareSecret)
|
||||
import Api.Model.ShareVerifyResult exposing (ShareVerifyResult)
|
||||
import Comp.ItemCardList
|
||||
import Comp.PowerSearchInput
|
||||
import Comp.SearchMenu
|
||||
import Data.Flags exposing (Flags)
|
||||
import Http
|
||||
|
||||
|
||||
type Mode
|
||||
= ModeInitial
|
||||
| ModePassword
|
||||
| ModeShare
|
||||
|
||||
|
||||
type PageError
|
||||
= PageErrorNone
|
||||
| PageErrorHttp Http.Error
|
||||
| PageErrorAuthFail
|
||||
|
||||
|
||||
type alias PasswordModel =
|
||||
{ password : String
|
||||
, passwordFailed : Bool
|
||||
}
|
||||
|
||||
|
||||
type alias Model =
|
||||
{}
|
||||
{ mode : Mode
|
||||
, verifyResult : ShareVerifyResult
|
||||
, passwordModel : PasswordModel
|
||||
, pageError : PageError
|
||||
, items : ItemLightList
|
||||
, searchMenuModel : Comp.SearchMenu.Model
|
||||
, powerSearchInput : Comp.PowerSearchInput.Model
|
||||
, searchInProgress : Bool
|
||||
, itemListModel : Comp.ItemCardList.Model
|
||||
}
|
||||
|
||||
|
||||
emptyModel : Flags -> Model
|
||||
emptyModel flags =
|
||||
{ mode = ModeInitial
|
||||
, verifyResult = Api.Model.ShareVerifyResult.empty
|
||||
, passwordModel =
|
||||
{ password = ""
|
||||
, passwordFailed = False
|
||||
}
|
||||
, pageError = PageErrorNone
|
||||
, items = Api.Model.ItemLightList.empty
|
||||
, searchMenuModel = Comp.SearchMenu.init flags
|
||||
, powerSearchInput = Comp.PowerSearchInput.init
|
||||
, searchInProgress = False
|
||||
, itemListModel = Comp.ItemCardList.init
|
||||
}
|
||||
|
||||
|
||||
init : Maybe String -> Flags -> ( Model, Cmd Msg )
|
||||
init shareId flags =
|
||||
case shareId of
|
||||
Just id ->
|
||||
let
|
||||
_ =
|
||||
Debug.log "share" id
|
||||
in
|
||||
( {}, Cmd.none )
|
||||
( emptyModel flags, Api.verifyShare flags (ShareSecret id Nothing) VerifyResp )
|
||||
|
||||
Nothing ->
|
||||
( {}, Cmd.none )
|
||||
( emptyModel flags, Cmd.none )
|
||||
|
||||
|
||||
type Msg
|
||||
= Msg
|
||||
= VerifyResp (Result Http.Error ShareVerifyResult)
|
||||
| SearchResp (Result Http.Error ItemLightList)
|
||||
| SetPassword String
|
||||
| SubmitPassword
|
||||
| SearchMenuMsg Comp.SearchMenu.Msg
|
||||
| PowerSearchMsg Comp.PowerSearchInput.Msg
|
||||
| ResetSearch
|
||||
| ItemListMsg Comp.ItemCardList.Msg
|
||||
|
60
modules/webapp/src/main/elm/Page/Share/Menubar.elm
Normal file
60
modules/webapp/src/main/elm/Page/Share/Menubar.elm
Normal file
@@ -0,0 +1,60 @@
|
||||
module Page.Share.Menubar exposing (view)
|
||||
|
||||
import Comp.Basic as B
|
||||
import Comp.MenuBar as MB
|
||||
import Comp.PowerSearchInput
|
||||
import Comp.SearchMenu
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onClick)
|
||||
import Messages.Page.Share exposing (Texts)
|
||||
import Page.Share.Data exposing (Model, Msg(..))
|
||||
import Styles as S
|
||||
|
||||
|
||||
view : Texts -> Model -> Html Msg
|
||||
view texts model =
|
||||
let
|
||||
btnStyle =
|
||||
S.secondaryBasicButton ++ " text-sm"
|
||||
|
||||
searchInput =
|
||||
Comp.SearchMenu.textSearchString
|
||||
model.searchMenuModel.textSearchModel
|
||||
|
||||
powerSearchBar =
|
||||
div
|
||||
[ class "relative flex flex-grow flex-row" ]
|
||||
[ Html.map PowerSearchMsg
|
||||
(Comp.PowerSearchInput.viewInput
|
||||
{ placeholder = texts.basics.searchPlaceholder
|
||||
, extraAttrs = []
|
||||
}
|
||||
model.powerSearchInput
|
||||
)
|
||||
, Html.map PowerSearchMsg
|
||||
(Comp.PowerSearchInput.viewResult [] model.powerSearchInput)
|
||||
]
|
||||
in
|
||||
MB.view
|
||||
{ end =
|
||||
[ MB.CustomElement <|
|
||||
B.secondaryBasicButton
|
||||
{ label = ""
|
||||
, icon =
|
||||
if model.searchInProgress then
|
||||
"fa fa-sync animate-spin"
|
||||
|
||||
else
|
||||
"fa fa-sync"
|
||||
, disabled = model.searchInProgress
|
||||
, handler = onClick ResetSearch
|
||||
, attrs = [ href "#" ]
|
||||
}
|
||||
]
|
||||
, start =
|
||||
[ MB.CustomElement <|
|
||||
powerSearchBar
|
||||
]
|
||||
, rootClasses = "mb-2 pt-1 dark:bg-bluegray-700 items-center text-sm"
|
||||
}
|
23
modules/webapp/src/main/elm/Page/Share/Results.elm
Normal file
23
modules/webapp/src/main/elm/Page/Share/Results.elm
Normal file
@@ -0,0 +1,23 @@
|
||||
module Page.Share.Results exposing (view)
|
||||
|
||||
import Comp.ItemCardList
|
||||
import Data.ItemSelection
|
||||
import Data.UiSettings exposing (UiSettings)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Messages.Page.Share exposing (Texts)
|
||||
import Page.Share.Data exposing (Model, Msg(..))
|
||||
|
||||
|
||||
view : Texts -> UiSettings -> Model -> Html Msg
|
||||
view texts settings model =
|
||||
let
|
||||
viewCfg =
|
||||
{ current = Nothing
|
||||
, selection = Data.ItemSelection.Inactive
|
||||
}
|
||||
in
|
||||
div []
|
||||
[ Html.map ItemListMsg
|
||||
(Comp.ItemCardList.view2 texts.itemCardList viewCfg settings model.itemListModel)
|
||||
]
|
32
modules/webapp/src/main/elm/Page/Share/Sidebar.elm
Normal file
32
modules/webapp/src/main/elm/Page/Share/Sidebar.elm
Normal file
@@ -0,0 +1,32 @@
|
||||
module Page.Share.Sidebar exposing (..)
|
||||
|
||||
import Comp.SearchMenu
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.UiSettings exposing (UiSettings)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Messages.Page.Share exposing (Texts)
|
||||
import Page.Share.Data exposing (Model, Msg(..))
|
||||
import Util.ItemDragDrop
|
||||
|
||||
|
||||
view : Texts -> Flags -> UiSettings -> Model -> Html Msg
|
||||
view texts flags settings model =
|
||||
div
|
||||
[ class "flex flex-col"
|
||||
]
|
||||
[ Html.map SearchMenuMsg
|
||||
(Comp.SearchMenu.viewDrop2 texts.searchMenu
|
||||
ddDummy
|
||||
flags
|
||||
settings
|
||||
model.searchMenuModel
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
ddDummy : Util.ItemDragDrop.DragDropData
|
||||
ddDummy =
|
||||
{ model = Util.ItemDragDrop.init
|
||||
, dropped = Nothing
|
||||
}
|
@@ -7,7 +7,15 @@
|
||||
|
||||
module Page.Share.Update exposing (UpdateResult, update)
|
||||
|
||||
import Api
|
||||
import Api.Model.ItemQuery
|
||||
import Comp.ItemCardList
|
||||
import Comp.PowerSearchInput
|
||||
import Comp.SearchMenu
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.ItemQuery as Q
|
||||
import Data.SearchMode
|
||||
import Data.UiSettings exposing (UiSettings)
|
||||
import Page.Share.Data exposing (..)
|
||||
|
||||
|
||||
@@ -18,6 +26,161 @@ type alias UpdateResult =
|
||||
}
|
||||
|
||||
|
||||
update : Flags -> String -> Msg -> Model -> UpdateResult
|
||||
update flags shareId msg model =
|
||||
UpdateResult model Cmd.none Sub.none
|
||||
update : Flags -> UiSettings -> String -> Msg -> Model -> UpdateResult
|
||||
update flags settings shareId msg model =
|
||||
case msg of
|
||||
VerifyResp (Ok res) ->
|
||||
if res.success then
|
||||
let
|
||||
eq =
|
||||
Api.Model.ItemQuery.empty
|
||||
|
||||
iq =
|
||||
{ eq | withDetails = Just True }
|
||||
in
|
||||
noSub
|
||||
( { model
|
||||
| pageError = PageErrorNone
|
||||
, mode = ModeShare
|
||||
, verifyResult = res
|
||||
, searchInProgress = True
|
||||
}
|
||||
, makeSearchCmd flags model
|
||||
)
|
||||
|
||||
else if res.passwordRequired then
|
||||
if model.mode == ModePassword then
|
||||
noSub
|
||||
( { model
|
||||
| pageError = PageErrorNone
|
||||
, passwordModel =
|
||||
{ password = ""
|
||||
, passwordFailed = True
|
||||
}
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
else
|
||||
noSub
|
||||
( { model
|
||||
| pageError = PageErrorNone
|
||||
, mode = ModePassword
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
else
|
||||
noSub
|
||||
( { model | pageError = PageErrorAuthFail }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
VerifyResp (Err err) ->
|
||||
noSub ( { model | pageError = PageErrorHttp err }, Cmd.none )
|
||||
|
||||
SearchResp (Ok list) ->
|
||||
update flags
|
||||
settings
|
||||
shareId
|
||||
(ItemListMsg (Comp.ItemCardList.SetResults list))
|
||||
{ model | searchInProgress = False }
|
||||
|
||||
SearchResp (Err err) ->
|
||||
noSub ( { model | pageError = PageErrorHttp err, searchInProgress = False }, Cmd.none )
|
||||
|
||||
SetPassword pw ->
|
||||
let
|
||||
pm =
|
||||
model.passwordModel
|
||||
in
|
||||
noSub ( { model | passwordModel = { pm | password = pw } }, Cmd.none )
|
||||
|
||||
SubmitPassword ->
|
||||
let
|
||||
secret =
|
||||
{ shareId = shareId
|
||||
, password = Just model.passwordModel.password
|
||||
}
|
||||
in
|
||||
noSub ( model, Api.verifyShare flags secret VerifyResp )
|
||||
|
||||
SearchMenuMsg lm ->
|
||||
let
|
||||
res =
|
||||
Comp.SearchMenu.update flags settings lm model.searchMenuModel
|
||||
|
||||
nextModel =
|
||||
{ model | searchMenuModel = res.model }
|
||||
|
||||
( initSearch, searchCmd ) =
|
||||
if res.stateChange && not model.searchInProgress then
|
||||
( True, makeSearchCmd flags nextModel )
|
||||
|
||||
else
|
||||
( False, Cmd.none )
|
||||
in
|
||||
noSub
|
||||
( { nextModel | searchInProgress = initSearch }
|
||||
, Cmd.batch [ Cmd.map SearchMenuMsg res.cmd, searchCmd ]
|
||||
)
|
||||
|
||||
PowerSearchMsg lm ->
|
||||
let
|
||||
res =
|
||||
Comp.PowerSearchInput.update lm model.powerSearchInput
|
||||
|
||||
nextModel =
|
||||
{ model | powerSearchInput = res.model }
|
||||
|
||||
( initSearch, searchCmd ) =
|
||||
case res.action of
|
||||
Comp.PowerSearchInput.NoAction ->
|
||||
( False, Cmd.none )
|
||||
|
||||
Comp.PowerSearchInput.SubmitSearch ->
|
||||
( True, makeSearchCmd flags nextModel )
|
||||
in
|
||||
{ model = { nextModel | searchInProgress = initSearch }
|
||||
, cmd = Cmd.batch [ Cmd.map PowerSearchMsg res.cmd, searchCmd ]
|
||||
, sub = Sub.map PowerSearchMsg res.subs
|
||||
}
|
||||
|
||||
ResetSearch ->
|
||||
let
|
||||
nm =
|
||||
{ model | powerSearchInput = Comp.PowerSearchInput.init }
|
||||
in
|
||||
update flags settings shareId (SearchMenuMsg Comp.SearchMenu.ResetForm) nm
|
||||
|
||||
ItemListMsg lm ->
|
||||
let
|
||||
( im, ic ) =
|
||||
Comp.ItemCardList.update flags lm model.itemListModel
|
||||
in
|
||||
noSub ( { model | itemListModel = im }, Cmd.map ItemListMsg ic )
|
||||
|
||||
|
||||
noSub : ( Model, Cmd Msg ) -> UpdateResult
|
||||
noSub ( m, c ) =
|
||||
UpdateResult m c Sub.none
|
||||
|
||||
|
||||
makeSearchCmd : Flags -> Model -> Cmd Msg
|
||||
makeSearchCmd flags model =
|
||||
let
|
||||
xq =
|
||||
Q.and
|
||||
[ Comp.SearchMenu.getItemQuery model.searchMenuModel
|
||||
, Maybe.map Q.Fragment model.powerSearchInput.input
|
||||
]
|
||||
|
||||
request mq =
|
||||
{ offset = Nothing
|
||||
, limit = Nothing
|
||||
, withDetails = Just True
|
||||
, query = Q.renderMaybe mq
|
||||
, searchMode = Just (Data.SearchMode.asString Data.SearchMode.Normal)
|
||||
}
|
||||
in
|
||||
Api.searchShare flags model.verifyResult.token (request xq) SearchResp
|
||||
|
@@ -7,32 +7,155 @@
|
||||
|
||||
module Page.Share.View exposing (viewContent, viewSidebar)
|
||||
|
||||
import Api.Model.VersionInfo exposing (VersionInfo)
|
||||
import Comp.Basic as B
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.Items
|
||||
import Data.UiSettings exposing (UiSettings)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onInput, onSubmit)
|
||||
import Messages.Page.Share exposing (Texts)
|
||||
import Page.Share.Data exposing (..)
|
||||
import Page.Share.Menubar as Menubar
|
||||
import Page.Share.Results as Results
|
||||
import Page.Share.Sidebar as Sidebar
|
||||
import Styles as S
|
||||
|
||||
|
||||
viewSidebar : Texts -> Bool -> Flags -> UiSettings -> Model -> Html Msg
|
||||
viewSidebar _ visible _ _ _ =
|
||||
viewSidebar texts visible flags settings model =
|
||||
div
|
||||
[ id "sidebar"
|
||||
, classList [ ( "hidden", not visible ) ]
|
||||
, class S.sidebar
|
||||
, class S.sidebarBg
|
||||
, classList [ ( "hidden", not visible || model.mode /= ModeShare ) ]
|
||||
]
|
||||
[ Sidebar.view texts flags settings model
|
||||
]
|
||||
[ text "sidebar" ]
|
||||
|
||||
|
||||
viewContent : Texts -> Flags -> UiSettings -> Model -> Html Msg
|
||||
viewContent texts flags _ model =
|
||||
viewContent : Texts -> Flags -> VersionInfo -> UiSettings -> Model -> Html Msg
|
||||
viewContent texts flags versionInfo uiSettings model =
|
||||
case model.mode of
|
||||
ModeInitial ->
|
||||
div
|
||||
[ id "content"
|
||||
, class "h-full w-full flex flex-col text-5xl"
|
||||
, class S.content
|
||||
]
|
||||
[ B.loadingDimmer
|
||||
{ active = model.pageError == PageErrorNone
|
||||
, label = ""
|
||||
}
|
||||
]
|
||||
|
||||
ModePassword ->
|
||||
passwordContent texts flags versionInfo model
|
||||
|
||||
ModeShare ->
|
||||
mainContent texts flags uiSettings model
|
||||
|
||||
|
||||
|
||||
--- Helpers
|
||||
|
||||
|
||||
mainContent : Texts -> Flags -> UiSettings -> Model -> Html Msg
|
||||
mainContent texts _ settings model =
|
||||
div
|
||||
[ id "content"
|
||||
, class "h-full flex flex-col"
|
||||
, class S.content
|
||||
]
|
||||
[ h1 [ class S.header1 ]
|
||||
[ text "Share Page!"
|
||||
[ h1
|
||||
[ class S.header1
|
||||
, classList [ ( "hidden", model.verifyResult.name == Nothing ) ]
|
||||
]
|
||||
[ text <| Maybe.withDefault "" model.verifyResult.name
|
||||
]
|
||||
, Menubar.view texts model
|
||||
, Results.view texts settings model
|
||||
]
|
||||
|
||||
|
||||
passwordContent : Texts -> Flags -> VersionInfo -> Model -> Html Msg
|
||||
passwordContent texts flags versionInfo model =
|
||||
div
|
||||
[ id "content"
|
||||
, class "h-full flex flex-col items-center justify-center w-full"
|
||||
, class S.content
|
||||
]
|
||||
[ div [ class ("flex flex-col px-4 sm:px-6 md:px-8 lg:px-10 py-8 rounded-md " ++ S.box) ]
|
||||
[ div [ class "self-center" ]
|
||||
[ img
|
||||
[ class "w-16 py-2"
|
||||
, src (flags.config.docspellAssetPath ++ "/img/logo-96.png")
|
||||
]
|
||||
[]
|
||||
]
|
||||
, div [ class "font-medium self-center text-xl sm:text-2xl" ]
|
||||
[ text texts.passwordRequired
|
||||
]
|
||||
, Html.form
|
||||
[ action "#"
|
||||
, onSubmit SubmitPassword
|
||||
, autocomplete False
|
||||
]
|
||||
[ div [ class "flex flex-col my-3" ]
|
||||
[ label
|
||||
[ for "password"
|
||||
, class S.inputLabel
|
||||
]
|
||||
[ text texts.password
|
||||
]
|
||||
, div [ class "relative" ]
|
||||
[ div [ class S.inputIcon ]
|
||||
[ i [ class "fa fa-lock" ] []
|
||||
]
|
||||
, input
|
||||
[ type_ "password"
|
||||
, name "password"
|
||||
, autocomplete False
|
||||
, autofocus True
|
||||
, tabindex 1
|
||||
, onInput SetPassword
|
||||
, value model.passwordModel.password
|
||||
, class ("pl-10 pr-4 py-2 rounded-lg" ++ S.textInput)
|
||||
, placeholder texts.password
|
||||
]
|
||||
[]
|
||||
]
|
||||
]
|
||||
, div [ class "flex flex-col my-3" ]
|
||||
[ button
|
||||
[ type_ "submit"
|
||||
, class S.primaryButton
|
||||
]
|
||||
[ text texts.passwordSubmitButton
|
||||
]
|
||||
]
|
||||
, div
|
||||
[ class S.errorMessage
|
||||
, classList [ ( "hidden", not model.passwordModel.passwordFailed ) ]
|
||||
]
|
||||
[ text texts.passwordFailed
|
||||
]
|
||||
]
|
||||
]
|
||||
, a
|
||||
[ class "inline-flex items-center mt-4 text-xs opacity-50 hover:opacity-90"
|
||||
, href "https://docspell.org"
|
||||
, target "_new"
|
||||
]
|
||||
[ img
|
||||
[ src (flags.config.docspellAssetPath ++ "/img/logo-mc-96.png")
|
||||
, class "w-3 h-3 mr-1"
|
||||
]
|
||||
[]
|
||||
, span []
|
||||
[ text "Docspell "
|
||||
, text versionInfo.version
|
||||
]
|
||||
]
|
||||
]
|
||||
|
Reference in New Issue
Block a user