docspell/modules/webapp/src/main/elm/Comp/SourceManage.elm

483 lines
14 KiB
Elm

{-
Copyright 2020 Docspell Contributors
SPDX-License-Identifier: GPL-3.0-or-later
-}
module Comp.SourceManage exposing
( Model
, Msg(..)
, init
, update
, view2
)
import Api
import Api.Model.BasicResult exposing (BasicResult)
import Api.Model.SourceAndTags exposing (SourceAndTags)
import Api.Model.SourceList exposing (SourceList)
import Comp.Basic as B
import Comp.MenuBar as MB
import Comp.SourceForm
import Comp.SourceTable exposing (SelectMode(..))
import Comp.YesNoDimmer
import Data.Flags exposing (Flags)
import Data.UiSettings exposing (UiSettings)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onSubmit)
import Http
import Messages.Comp.SourceManage exposing (Texts)
import Ports
import QRCode
import Styles as S
type alias Model =
{ formModel : Comp.SourceForm.Model
, viewMode : SelectMode
, formError : FormError
, loading : Bool
, deleteConfirm : Comp.YesNoDimmer.Model
, sources : List SourceAndTags
}
type FormError
= FormErrorNone
| FormErrorHttp Http.Error
| FormErrorInvalid
| FormErrorSubmit String
init : Flags -> ( Model, Cmd Msg )
init flags =
let
( fm, fc ) =
Comp.SourceForm.init flags
in
( { formModel = fm
, viewMode = None
, formError = FormErrorNone
, loading = False
, deleteConfirm = Comp.YesNoDimmer.emptyModel
, sources = []
}
, Cmd.batch
[ Cmd.map FormMsg fc
, Ports.initClipboard appClipboardData
, Ports.initClipboard apiClipboardData
]
)
appClipboardData : ( String, String )
appClipboardData =
( "app-url", "#app-url-copy-to-clipboard-btn" )
apiClipboardData : ( String, String )
apiClipboardData =
( "api-url", "#api-url-copy-to-clipboard-btn" )
type Msg
= TableMsg Comp.SourceTable.Msg
| FormMsg Comp.SourceForm.Msg
| LoadSources
| SourceResp (Result Http.Error SourceList)
| InitNewSource
| Submit
| SubmitResp (Result Http.Error BasicResult)
| YesNoMsg Comp.YesNoDimmer.Msg
| RequestDelete
| SetTableView
--- Update
update : Flags -> Msg -> Model -> ( Model, Cmd Msg )
update flags msg model =
case msg of
TableMsg m ->
let
( tc, sel ) =
Comp.SourceTable.update flags m
( m2, c2 ) =
( { model
| viewMode = sel
, formError =
if Comp.SourceTable.isEdit sel then
model.formError
else
FormErrorNone
}
, Cmd.map TableMsg tc
)
( m3, c3 ) =
case sel of
Edit source ->
update flags (FormMsg (Comp.SourceForm.SetSource source)) m2
Display _ ->
( m2, Cmd.none )
None ->
( m2, Cmd.none )
in
( m3, Cmd.batch [ c2, c3 ] )
FormMsg m ->
let
( m2, c2 ) =
Comp.SourceForm.update flags m model.formModel
in
( { model | formModel = m2 }, Cmd.map FormMsg c2 )
LoadSources ->
( { model | loading = True }, Api.getSources flags SourceResp )
SourceResp (Ok sources) ->
( { model
| viewMode = None
, loading = False
, sources = sources.items
}
, Cmd.none
)
SourceResp (Err _) ->
( { model | loading = False }, Cmd.none )
SetTableView ->
( { model | viewMode = None }, Cmd.none )
InitNewSource ->
let
source =
Api.Model.SourceAndTags.empty
nm =
{ model | viewMode = Edit source, formError = FormErrorNone }
in
update flags (FormMsg (Comp.SourceForm.SetSource source)) nm
Submit ->
let
source =
Comp.SourceForm.getSource model.formModel
valid =
Comp.SourceForm.isValid model.formModel
in
if valid then
( { model | loading = True }, Api.postSource flags source SubmitResp )
else
( { model | formError = FormErrorInvalid }, Cmd.none )
SubmitResp (Ok res) ->
if res.success then
let
( m2, c2 ) =
update flags SetTableView model
( m3, c3 ) =
update flags LoadSources m2
in
( { m3 | loading = False }, Cmd.batch [ c2, c3 ] )
else
( { model | formError = FormErrorSubmit res.message, loading = False }, Cmd.none )
SubmitResp (Err err) ->
( { model | formError = FormErrorHttp err, loading = False }, Cmd.none )
RequestDelete ->
update flags (YesNoMsg Comp.YesNoDimmer.activate) model
YesNoMsg m ->
let
( cm, confirmed ) =
Comp.YesNoDimmer.update m model.deleteConfirm
src =
Comp.SourceForm.getSource model.formModel
cmd =
if confirmed then
Api.deleteSource flags src.source.id SubmitResp
else
Cmd.none
in
( { model | deleteConfirm = cm }, cmd )
--- View2
qrCodeView : Texts -> String -> Html msg
qrCodeView texts message =
QRCode.encode message
|> Result.map QRCode.toSvg
|> Result.withDefault
(Html.text texts.errorGeneratingQR)
view2 : Texts -> Flags -> UiSettings -> Model -> Html Msg
view2 texts flags settings model =
case model.viewMode of
None ->
viewTable2 texts model
Edit _ ->
div [] (viewForm2 texts flags settings model)
Display source ->
viewLinks2 texts flags settings source
viewTable2 : Texts -> Model -> Html Msg
viewTable2 texts model =
div [ class "relative flex flex-col" ]
[ MB.view
{ start = []
, end =
[ MB.PrimaryButton
{ tagger = InitNewSource
, title = texts.addSourceUrl
, icon = Just "fa fa-plus"
, label = texts.newSource
}
]
, rootClasses = "mb-4"
}
, Html.map TableMsg (Comp.SourceTable.view2 texts.sourceTable model.sources)
, B.loadingDimmer
{ active = model.loading
, label = texts.basics.loading
}
]
viewLinks2 : Texts -> Flags -> UiSettings -> SourceAndTags -> Html Msg
viewLinks2 texts flags _ source =
let
appUrl =
flags.config.baseUrl ++ "/app/upload/" ++ source.source.id
apiUrl =
flags.config.baseUrl ++ "/api/v1/open/upload/item/" ++ source.source.id
styleUrl =
"truncate px-2 py-2 border-0 border-t border-b border-r font-mono text-sm my-auto rounded-r border-gray-400 dark:border-bluegray-500"
in
div
[]
[ h2 [ class S.header2 ]
[ text (texts.publicUploads ++ ": ")
, text source.source.abbrev
, div [ class "opacity-50 text-sm" ]
[ text source.source.id
]
]
, MB.view
{ start =
[ MB.SecondaryButton
{ label = texts.basics.back
, icon = Just "fa fa-arrow-left"
, tagger = SetTableView
, title = texts.basics.backToList
}
]
, end = []
, rootClasses = "mb-4"
}
, p [ class "text-lg pt-2 opacity-75" ]
[ text texts.sourceInfoText
]
, p [ class "text-lg py-2 opacity-75" ]
[ text (texts.itemsCreatedInfo source.source.counter)
]
, h3
[ class S.header3
, class "mt-2"
]
[ text texts.publicUploadPage
]
, div [ class "" ]
[ div [ class "flex flex-row" ]
[ a
[ class S.secondaryBasicButtonPlain
, class "rounded-l border text-sm px-4 py-2"
, title texts.copyToClipboard
, href "#"
, Tuple.second appClipboardData
|> String.dropLeft 1
|> id
, attribute "data-clipboard-target" "#app-url"
]
[ i [ class "fa fa-copy" ] []
]
, a
[ class S.secondaryBasicButtonPlain
, class "px-4 py-2 border-0 border-t border-b border-r text-sm"
, href appUrl
, target "_blank"
, title texts.openInNewTab
]
[ i [ class "fa fa-external-link-alt" ] []
]
, div
[ id "app-url"
, class styleUrl
]
[ text appUrl
]
]
]
, div [ class "py-2" ]
[ div
[ class S.border
, class S.qrCode
]
[ qrCodeView texts appUrl
]
]
, h3
[ class S.header3
, class "mt-4"
]
[ text texts.publicUploadUrl
]
, div [ class "" ]
[ div [ class "flex flex-row" ]
[ a
[ class S.secondaryBasicButtonPlain
, class "px-4 py-2 rounded-l border text-sm"
, title texts.copyToClipboard
, href "#"
, Tuple.second apiClipboardData
|> String.dropLeft 1
|> id
, attribute "data-clipboard-target" "#api-url"
]
[ i [ class "fa fa-copy" ] []
]
, div
[ class styleUrl
, id "api-url"
]
[ text apiUrl
]
]
]
, div [ class "py-2" ]
[ div
[ class S.border
, class S.qrCode
]
[ qrCodeView texts apiUrl
]
]
]
viewForm2 : Texts -> Flags -> UiSettings -> Model -> List (Html Msg)
viewForm2 texts flags settings model =
let
newSource =
model.formModel.source.source.id == ""
dimmerSettings =
Comp.YesNoDimmer.defaultSettings texts.reallyDeleteSource
texts.basics.yes
texts.basics.no
in
[ if newSource then
h3 [ class S.header2 ]
[ text texts.createNewSource
]
else
h3 [ class S.header2 ]
[ text model.formModel.source.source.abbrev
, div [ class "opacity-50 text-sm" ]
[ text "Id: "
, text model.formModel.source.source.id
]
]
, Html.form
[ class "flex flex-col md:relative"
, onSubmit Submit
]
[ MB.view
{ start =
[ MB.PrimaryButton
{ tagger = Submit
, title = texts.basics.submitThisForm
, icon = Just "fa fa-save"
, label = texts.basics.submit
}
, MB.SecondaryButton
{ tagger = SetTableView
, title = texts.basics.backToList
, icon = Just "fa fa-arrow-left"
, label = texts.basics.cancel
}
]
, end =
if not newSource then
[ MB.DeleteButton
{ tagger = RequestDelete
, title = texts.deleteThisSource
, icon = Just "fa fa-trash"
, label = texts.basics.delete
}
]
else
[]
, rootClasses = "mb-4"
}
, Html.map FormMsg
(Comp.SourceForm.view2 flags texts.sourceForm settings model.formModel)
, div
[ classList
[ ( S.errorMessage, True )
, ( "hidden", model.formError == FormErrorNone )
]
]
[ case model.formError of
FormErrorNone ->
text ""
FormErrorHttp err ->
text (texts.httpError err)
FormErrorSubmit m ->
text m
FormErrorInvalid ->
text texts.correctFormErrors
]
, Html.map YesNoMsg
(Comp.YesNoDimmer.viewN True
dimmerSettings
model.deleteConfirm
)
, B.loadingDimmer
{ active = model.loading
, label = texts.basics.loading
}
]
]