Basic ui for addons

This commit is contained in:
eikek 2022-05-08 14:01:41 +02:00
parent 7fdd78ad06
commit 73747c4ea3
33 changed files with 2881 additions and 13 deletions

View File

@ -18,6 +18,14 @@ module Api exposing
, addShare
, addTag
, addTagsMultiple
, addonRunConfigDelete
, addonRunConfigGet
, addonRunConfigSet
, addonRunExistingItem
, addonsDelete
, addonsGetAll
, addonsInstall
, addonsUpdate
, attachmentPreviewURL
, bookmarkNameExists
, cancelJob
@ -211,6 +219,11 @@ module Api exposing
, versionInfo
)
import Api.Model.AddonList exposing (AddonList)
import Api.Model.AddonRegister exposing (AddonRegister)
import Api.Model.AddonRunConfig exposing (AddonRunConfig)
import Api.Model.AddonRunConfigList exposing (AddonRunConfigList)
import Api.Model.AddonRunExistingItem exposing (AddonRunExistingItem)
import Api.Model.AttachmentMeta exposing (AttachmentMeta)
import Api.Model.AuthResult exposing (AuthResult)
import Api.Model.BasicResult exposing (BasicResult)
@ -3156,6 +3169,99 @@ shareDownloadAllLink flags id =
--- Addons
addonsGetAll : Flags -> (Result Http.Error AddonList -> msg) -> Cmd msg
addonsGetAll flags receive =
Http2.authGet
{ url = flags.config.baseUrl ++ "/api/v1/sec/addon/archive"
, account = getAccount flags
, expect = Http.expectJson receive Api.Model.AddonList.decoder
}
addonsDelete : Flags -> String -> (Result Http.Error BasicResult -> msg) -> Cmd msg
addonsDelete flags addonId receive =
Http2.authDelete
{ url = flags.config.baseUrl ++ "/api/v1/sec/addon/archive/" ++ addonId
, account = getAccount flags
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
}
addonsInstall : Flags -> AddonRegister -> (Result Http.Error BasicResult -> msg) -> Cmd msg
addonsInstall flags addon receive =
Http2.authPost
{ url = flags.config.baseUrl ++ "/api/v1/sec/addon/archive"
, account = getAccount flags
, body = Http.jsonBody (Api.Model.AddonRegister.encode addon)
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
}
addonsUpdate : Flags -> String -> (Result Http.Error BasicResult -> msg) -> Cmd msg
addonsUpdate flags addonId receive =
Http2.authPut
{ url = flags.config.baseUrl ++ "/api/v1/sec/addon/archive/" ++ addonId
, account = getAccount flags
, body = Http.emptyBody
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
}
addonRunConfigGet : Flags -> (Result Http.Error AddonRunConfigList -> msg) -> Cmd msg
addonRunConfigGet flags receive =
Http2.authGet
{ url = flags.config.baseUrl ++ "/api/v1/sec/addon/run-config"
, account = getAccount flags
, expect = Http.expectJson receive Api.Model.AddonRunConfigList.decoder
}
addonRunConfigSet :
Flags
-> AddonRunConfig
-> (Result Http.Error BasicResult -> msg)
-> Cmd msg
addonRunConfigSet flags cfg receive =
if cfg.id == "" then
Http2.authPost
{ url = flags.config.baseUrl ++ "/api/v1/sec/addon/run-config"
, account = getAccount flags
, body = Http.jsonBody (Api.Model.AddonRunConfig.encode cfg)
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
}
else
Http2.authPut
{ url = flags.config.baseUrl ++ "/api/v1/sec/addon/run-config/" ++ cfg.id
, account = getAccount flags
, body = Http.jsonBody (Api.Model.AddonRunConfig.encode cfg)
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
}
addonRunConfigDelete : Flags -> String -> (Result Http.Error BasicResult -> msg) -> Cmd msg
addonRunConfigDelete flags id receive =
Http2.authDelete
{ url = flags.config.baseUrl ++ "/api/v1/sec/addon/run-config/" ++ id
, account = getAccount flags
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
}
addonRunExistingItem : Flags -> AddonRunExistingItem -> (Result Http.Error BasicResult -> msg) -> Cmd msg
addonRunExistingItem flags input receive =
Http2.authPost
{ url = flags.config.baseUrl ++ "/api/v1/sec/addon/run/existingitem"
, account = getAccount flags
, body = Http.jsonBody (Api.Model.AddonRunExistingItem.encode input)
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
}
--- Helper

View File

@ -14,6 +14,7 @@ import Api
import App.Data exposing (..)
import Browser exposing (UrlRequest(..))
import Browser.Navigation as Nav
import Comp.AddonArchiveManage
import Comp.DownloadAll
import Data.AppEvent exposing (AppEvent(..))
import Data.Environment as Env
@ -345,6 +346,9 @@ updateWithSub msg model =
Ok (JobsWaiting n) ->
( { model | jobsWaiting = max 0 n }, Cmd.none, Sub.none )
Ok (AddonInstalled info) ->
updateManageData (Page.ManageData.Data.AddonArchiveMsg <| Comp.AddonArchiveManage.addonInstallResult info) model
Err _ ->
( model, Cmd.none, Sub.none )
@ -640,7 +644,7 @@ updateManageData : Page.ManageData.Data.Msg -> Model -> ( Model, Cmd Msg, Sub Ms
updateManageData lmsg model =
let
( lm, lc, ls ) =
Page.ManageData.Update.update model.flags lmsg model.manageDataModel
Page.ManageData.Update.update model.flags model.uiSettings lmsg model.manageDataModel
in
( { model | manageDataModel = lm }
, Cmd.map ManageDataMsg lc

View File

@ -0,0 +1,106 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Comp.AddonArchiveForm exposing (Model, Msg, get, init, initWith, update, view)
import Api.Model.Addon exposing (Addon)
import Comp.Basic as B
import Data.Flags exposing (Flags)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onInput)
import Messages.Comp.AddonArchiveForm exposing (Texts)
import Styles as S
import Util.Maybe
type alias Model =
{ addon : Addon
, url : Maybe String
}
init : ( Model, Cmd Msg )
init =
( { addon = Api.Model.Addon.empty
, url = Nothing
}
, Cmd.none
)
initWith : Addon -> ( Model, Cmd Msg )
initWith a =
( { addon = a
, url = a.url
}
, Cmd.none
)
isValid : Model -> Bool
isValid model =
model.url /= Nothing
get : Model -> Maybe Addon
get model =
let
a =
model.addon
in
if isValid model then
Just
{ a
| url = model.url
}
else
Nothing
type Msg
= SetUrl String
update : Flags -> Msg -> Model -> ( Model, Cmd Msg )
update _ msg model =
case msg of
SetUrl url ->
( { model | url = Util.Maybe.fromString url }, Cmd.none )
--- View
view : Texts -> Model -> Html Msg
view texts model =
div
[ class "flex flex-col" ]
[ div [ class "mb-4" ]
[ label
[ class S.inputLabel
]
[ text texts.addonUrl
, B.inputRequired
]
, input
[ type_ "text"
, placeholder texts.addonUrlPlaceholder
, class S.textInput
, classList [ ( "disabled", model.addon.id /= "" ) ]
, value (model.url |> Maybe.withDefault "")
, onInput SetUrl
, disabled (model.addon.id /= "")
]
[]
, span [ class "text-sm opacity-75" ]
[ text texts.installInfoText
]
]
]

View File

@ -0,0 +1,429 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Comp.AddonArchiveManage exposing (Model, Msg, addonInstallResult, init, loadAddons, update, view)
import Api
import Api.Model.Addon exposing (Addon)
import Api.Model.AddonList exposing (AddonList)
import Api.Model.AddonRegister exposing (AddonRegister)
import Api.Model.BasicResult exposing (BasicResult)
import Comp.AddonArchiveForm
import Comp.AddonArchiveTable
import Comp.Basic as B
import Comp.ItemDetail.Model exposing (Msg(..))
import Comp.MenuBar as MB
import Data.Flags exposing (Flags)
import Data.ServerEvent exposing (AddonInfo)
import Data.UiSettings exposing (UiSettings)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
import Http
import Markdown
import Messages.Comp.AddonArchiveManage exposing (Texts)
import Page exposing (Page(..))
import Styles as S
type FormError
= FormErrorNone
| FormErrorHttp Http.Error
| FormErrorInvalid
| FormErrorSubmit String
type ViewMode
= Table
| Form
type DeleteConfirm
= DeleteConfirmOff
| DeleteConfirmOn
type alias Model =
{ viewMode : ViewMode
, addons : List Addon
, formModel : Comp.AddonArchiveForm.Model
, loading : Bool
, formError : FormError
, deleteConfirm : DeleteConfirm
}
init : Flags -> ( Model, Cmd Msg )
init flags =
let
( fm, fc ) =
Comp.AddonArchiveForm.init
in
( { viewMode = Table
, addons = []
, formModel = fm
, loading = False
, formError = FormErrorNone
, deleteConfirm = DeleteConfirmOff
}
, Cmd.batch
[ Cmd.map FormMsg fc
, Api.addonsGetAll flags LoadAddonsResp
]
)
type Msg
= LoadAddons
| TableMsg Comp.AddonArchiveTable.Msg
| FormMsg Comp.AddonArchiveForm.Msg
| InitNewAddon
| SetViewMode ViewMode
| Submit
| RequestDelete
| CancelDelete
| DeleteAddonNow String
| LoadAddonsResp (Result Http.Error AddonList)
| AddAddonResp (Result Http.Error BasicResult)
| UpdateAddonResp (Result Http.Error BasicResult)
| DeleteAddonResp (Result Http.Error BasicResult)
| AddonInstallResp AddonInfo
loadAddons : Msg
loadAddons =
LoadAddons
addonInstallResult : AddonInfo -> Msg
addonInstallResult info =
AddonInstallResp info
--- update
update : Flags -> Msg -> Model -> ( Model, Cmd Msg, Sub Msg )
update flags msg model =
case msg of
InitNewAddon ->
let
( bm, bc ) =
Comp.AddonArchiveForm.init
nm =
{ model
| viewMode = Form
, formError = FormErrorNone
, formModel = bm
}
in
( nm, Cmd.map FormMsg bc, Sub.none )
SetViewMode vm ->
( { model | viewMode = vm, formError = FormErrorNone }
, if vm == Table then
Api.addonsGetAll flags LoadAddonsResp
else
Cmd.none
, Sub.none
)
FormMsg lm ->
let
( fm, fc ) =
Comp.AddonArchiveForm.update flags lm model.formModel
in
( { model | formModel = fm, formError = FormErrorNone }
, Cmd.map FormMsg fc
, Sub.none
)
TableMsg lm ->
let
action =
Comp.AddonArchiveTable.update lm
in
case action of
Comp.AddonArchiveTable.Selected addon ->
let
( bm, bc ) =
Comp.AddonArchiveForm.initWith addon
in
( { model
| viewMode = Form
, formError = FormErrorNone
, formModel = bm
}
, Cmd.map FormMsg bc
, Sub.none
)
RequestDelete ->
( { model | deleteConfirm = DeleteConfirmOn }, Cmd.none, Sub.none )
CancelDelete ->
( { model | deleteConfirm = DeleteConfirmOff }, Cmd.none, Sub.none )
DeleteAddonNow id ->
( { model | deleteConfirm = DeleteConfirmOff, loading = True }
, Api.addonsDelete flags id DeleteAddonResp
, Sub.none
)
LoadAddons ->
( { model | loading = True }
, Api.addonsGetAll flags LoadAddonsResp
, Sub.none
)
LoadAddonsResp (Ok list) ->
( { model | loading = False, addons = list.items, formError = FormErrorNone }
, Cmd.none
, Sub.none
)
LoadAddonsResp (Err err) ->
( { model | loading = False, formError = FormErrorHttp err }, Cmd.none, Sub.none )
AddonInstallResp info ->
if info.success then
( { model | loading = False, viewMode = Table }, Api.addonsGetAll flags LoadAddonsResp, Sub.none )
else
( { model | loading = False, formError = FormErrorSubmit info.message }, Cmd.none, Sub.none )
Submit ->
case Comp.AddonArchiveForm.get model.formModel of
Just data ->
if data.id /= "" then
( { model | loading = True }
, Api.addonsUpdate flags data.id UpdateAddonResp
, Sub.none
)
else
( { model | loading = True }
, Api.addonsInstall
flags
(AddonRegister <| Maybe.withDefault "" data.url)
AddAddonResp
, Sub.none
)
Nothing ->
( { model | formError = FormErrorInvalid }, Cmd.none, Sub.none )
AddAddonResp (Ok res) ->
if res.success then
( model, Cmd.none, Sub.none )
else
( { model | loading = False, formError = FormErrorSubmit res.message }, Cmd.none, Sub.none )
AddAddonResp (Err err) ->
( { model | loading = False, formError = FormErrorHttp err }, Cmd.none, Sub.none )
UpdateAddonResp (Ok res) ->
if res.success then
( model, Cmd.none, Sub.none )
else
( { model | loading = False, formError = FormErrorSubmit res.message }, Cmd.none, Sub.none )
UpdateAddonResp (Err err) ->
( { model | loading = False, formError = FormErrorHttp err }, Cmd.none, Sub.none )
DeleteAddonResp (Ok res) ->
if res.success then
update flags (SetViewMode Table) { model | loading = False }
else
( { model | formError = FormErrorSubmit res.message, loading = False }, Cmd.none, Sub.none )
DeleteAddonResp (Err err) ->
( { model | formError = FormErrorHttp err, loading = False }, Cmd.none, Sub.none )
--- view
view : Texts -> UiSettings -> Flags -> Model -> Html Msg
view texts settings flags model =
if model.viewMode == Table then
viewTable texts model
else
viewForm texts settings flags model
viewTable : Texts -> Model -> Html Msg
viewTable texts model =
div [ class "flex flex-col" ]
[ MB.view
{ start =
[]
, end =
[ MB.PrimaryButton
{ tagger = InitNewAddon
, title = texts.createNewAddonArchive
, icon = Just "fa fa-plus"
, label = texts.newAddonArchive
}
]
, rootClasses = "mb-4"
, sticky = True
}
, div
[ class "flex flex-col"
]
[ Html.map TableMsg
(Comp.AddonArchiveTable.view texts.addonArchiveTable model.addons)
]
, B.loadingDimmer
{ label = ""
, active = model.loading
}
]
viewForm : Texts -> UiSettings -> Flags -> Model -> Html Msg
viewForm texts _ _ model =
let
newAddon =
model.formModel.addon.id == ""
isValid =
Comp.AddonArchiveForm.get model.formModel /= Nothing
in
div [ class "relative" ]
[ Html.form []
[ if newAddon then
h1 [ class S.header2 ]
[ text texts.createNewAddonArchive
]
else
h1 [ class S.header2 ]
[ text (Comp.AddonArchiveForm.get model.formModel |> Maybe.map .name |> Maybe.withDefault "Update")
]
, MB.view
{ start =
[ MB.SecondaryButton
{ tagger = SetViewMode Table
, title = texts.basics.backToList
, icon = Just "fa fa-arrow-left"
, label = texts.basics.back
}
]
, end =
if not newAddon then
[ MB.DeleteButton
{ tagger = RequestDelete
, title = texts.deleteThisAddonArchive
, icon = Just "fa fa-trash"
, label = texts.basics.delete
}
]
else
[]
, rootClasses = "mb-4"
, sticky = True
}
, div
[ classList
[ ( "hidden", model.formError == FormErrorNone )
]
, class "my-2"
, class S.errorMessage
]
[ case model.formError of
FormErrorNone ->
text ""
FormErrorHttp err ->
text (texts.httpError err)
FormErrorInvalid ->
text texts.correctFormErrors
FormErrorSubmit m ->
text m
]
, div []
[ Html.map FormMsg (Comp.AddonArchiveForm.view texts.addonArchiveForm model.formModel)
]
, MB.view
{ start =
[ MB.PrimaryButton
{ tagger = Submit
, title = texts.installNow
, icon =
if newAddon then
Just "fa fa-save"
else
Just "fa fa-arrows-rotate"
, label =
if newAddon then
texts.installNow
else
texts.updateNow
}
]
, end = []
, rootClasses = "mb-4"
, sticky = False
}
, div
[ class "mb-4"
, classList [ ( "hidden", newAddon ) ]
]
[ label [ class S.inputLabel ] [ text texts.description ]
, case model.formModel.addon.description of
Just desc ->
Markdown.toHtml [ class "markdown-preview" ] desc
Nothing ->
div [ class "italic" ] [ text "-" ]
]
, B.loadingDimmer
{ active = model.loading
, label = texts.basics.loading
}
, B.contentDimmer
(model.deleteConfirm == DeleteConfirmOn)
(div [ class "flex flex-col" ]
[ div [ class "text-lg" ]
[ i [ class "fa fa-info-circle mr-2" ] []
, text texts.reallyDeleteAddonArchive
]
, div [ class "mt-4 flex flex-row items-center" ]
[ B.deleteButton
{ label = texts.basics.yes
, icon = "fa fa-check"
, disabled = False
, handler = onClick (DeleteAddonNow model.formModel.addon.id)
, attrs = [ href "#" ]
}
, B.secondaryButton
{ label = texts.basics.no
, icon = "fa fa-times"
, disabled = False
, handler = onClick CancelDelete
, attrs = [ href "#", class "ml-2" ]
}
]
]
)
]
]

View File

@ -0,0 +1,72 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Comp.AddonArchiveTable exposing (..)
import Api.Model.Addon exposing (Addon)
import Comp.Basic as B
import Html exposing (Html, div, table, tbody, td, text, th, thead, tr)
import Html.Attributes exposing (class)
import Messages.Comp.AddonArchiveTable exposing (Texts)
import Styles as S
type Msg
= SelectAddon Addon
type TableAction
= Selected Addon
--- Update
update : Msg -> TableAction
update msg =
case msg of
SelectAddon addon ->
Selected addon
--- View
view : Texts -> List Addon -> Html Msg
view texts addons =
table [ class S.tableMain ]
[ thead []
[ tr []
[ th [ class "" ] []
, th [ class "text-left" ]
[ text texts.basics.name
]
, th [ class "text-left" ]
[ text texts.version
]
]
]
, tbody []
(List.map (renderAddonLine texts) addons)
]
renderAddonLine : Texts -> Addon -> Html Msg
renderAddonLine texts addon =
tr
[ class S.tableRow
]
[ B.editLinkTableCell texts.basics.edit (SelectAddon addon)
, td [ class "text-left py-4 md:py-2" ]
[ text addon.name
]
, td [ class "text-left" ]
[ text addon.version
]
]

View File

@ -0,0 +1,709 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Comp.AddonRunConfigForm exposing (Model, Msg, get, init, initWith, update, view)
import Api
import Api.Model.Addon exposing (Addon)
import Api.Model.AddonList exposing (AddonList)
import Api.Model.AddonRef exposing (AddonRef)
import Api.Model.AddonRunConfig exposing (AddonRunConfig)
import Api.Model.User exposing (User)
import Api.Model.UserList exposing (UserList)
import Comp.Basic as B
import Comp.CalEventInput
import Comp.Dropdown
import Comp.MenuBar as MB
import Data.AddonTrigger exposing (AddonTrigger)
import Data.CalEvent exposing (CalEvent)
import Data.DropdownStyle as DS
import Data.Flags exposing (Flags)
import Data.TimeZone exposing (TimeZone)
import Data.UiSettings exposing (UiSettings)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick, onInput)
import Http
import Markdown
import Messages.Comp.AddonRunConfigForm exposing (Texts)
import Process
import Styles as S
import Task
import Util.List
import Util.String
type alias Model =
{ runConfig : AddonRunConfig
, name : String
, enabled : Bool
, userDropdown : Comp.Dropdown.Model User
, userId : Maybe String
, userList : List User
, scheduleModel : Maybe Comp.CalEventInput.Model
, schedule : Maybe CalEvent
, triggerDropdown : Comp.Dropdown.Model AddonTrigger
, addons : List AddonRef
, selectedAddon : Maybe AddonConfigModel
, existingAddonDropdown : Comp.Dropdown.Model Addon
, existingAddons : List Addon
, configApplied : Bool
}
type alias AddonConfigModel =
{ ref : AddonRef
, position : Int
, args : String
, readMore : Bool
}
getRef : AddonConfigModel -> AddonRef
getRef cfg =
let
a =
cfg.ref
in
{ a | args = cfg.args }
emptyModel : Model
emptyModel =
{ runConfig = Api.Model.AddonRunConfig.empty
, name = ""
, enabled = True
, userDropdown = Comp.Dropdown.makeSingle
, userId = Nothing
, userList = []
, scheduleModel = Nothing
, schedule = Nothing
, triggerDropdown =
Comp.Dropdown.makeMultipleList
{ options = Data.AddonTrigger.all, selected = [] }
, addons = []
, selectedAddon = Nothing
, existingAddonDropdown = Comp.Dropdown.makeSingle
, existingAddons = []
, configApplied = False
}
init : Flags -> ( Model, Cmd Msg )
init flags =
( emptyModel
, Cmd.batch
[ Api.getUsers flags UserListResp
, Api.addonsGetAll flags AddonListResp
]
)
initWith : Flags -> AddonRunConfig -> ( Model, Cmd Msg )
initWith flags a =
let
ce =
Maybe.andThen Data.CalEvent.fromEvent a.schedule
ceInit =
Maybe.map (Comp.CalEventInput.init flags) ce
triggerModel =
Comp.Dropdown.makeMultipleList
{ options = Data.AddonTrigger.all
, selected = Data.AddonTrigger.fromList a.trigger
}
in
( { emptyModel
| runConfig = a
, name = a.name
, enabled = a.enabled
, scheduleModel = Maybe.map Tuple.first ceInit
, schedule = ce
, triggerDropdown = triggerModel
, userId = a.userId
, addons = a.addons
}
, Cmd.batch
[ Api.getUsers flags UserListResp
, Api.addonsGetAll flags AddonListResp
, Maybe.map Tuple.second ceInit
|> Maybe.map (Cmd.map ScheduleMsg)
|> Maybe.withDefault Cmd.none
]
)
isValid : Model -> Bool
isValid model =
model.name
/= ""
&& (Comp.Dropdown.getSelected model.triggerDropdown
|> List.isEmpty
|> not
)
&& (List.isEmpty model.addons
|> not
)
get : Model -> Maybe AddonRunConfig
get model =
let
a =
model.runConfig
in
if isValid model then
Just
{ a
| name = model.name
, enabled = model.enabled
, schedule = Maybe.map Data.CalEvent.makeEvent model.schedule
, trigger =
Comp.Dropdown.getSelected model.triggerDropdown
|> List.map Data.AddonTrigger.asString
, userId = model.userId
, addons = model.addons
}
else
Nothing
type Msg
= SetName String
| UserListResp (Result Http.Error UserList)
| AddonListResp (Result Http.Error AddonList)
| ScheduleMsg Comp.CalEventInput.Msg
| UserDropdownMsg (Comp.Dropdown.Msg User)
| TriggerDropdownMsg (Comp.Dropdown.Msg AddonTrigger)
| AddonDropdownMsg (Comp.Dropdown.Msg Addon)
| Configure Int AddonRef
| Up Int
| Down Int
| Remove Int
| ToggleEnabled
| ConfigSetArgs String
| ConfigApply
| ConfigCancel
| AddSelectedAddon
| ConfigToggleReadMore
| ConfigArgsUpdated Bool
--- Update
update : Flags -> TimeZone -> Msg -> Model -> ( Model, Cmd Msg )
update flags tz msg model =
case msg of
UserListResp (Ok list) ->
let
um =
Comp.Dropdown.makeSingleList
{ options = list.items
, selected = Nothing
}
in
( { model | userDropdown = um, userList = list.items }, Cmd.none )
UserListResp (Err err) ->
( model, Cmd.none )
AddonListResp (Ok list) ->
let
am =
Comp.Dropdown.makeSingleList
{ options = list.items
, selected = Nothing
}
in
( { model | existingAddonDropdown = am, existingAddons = list.items }, Cmd.none )
AddonListResp (Err err) ->
( model, Cmd.none )
UserDropdownMsg lm ->
let
( um, cmd ) =
Comp.Dropdown.update lm model.userDropdown
sel =
Comp.Dropdown.getSelected um |> List.head
in
( { model | userDropdown = um, userId = Maybe.map .id sel }, Cmd.map UserDropdownMsg cmd )
TriggerDropdownMsg lm ->
let
( tm, tc ) =
Comp.Dropdown.update lm model.triggerDropdown
( nm, nc ) =
initScheduleIfNeeded flags { model | triggerDropdown = tm } tz
in
( nm, Cmd.batch [ Cmd.map TriggerDropdownMsg tc, nc ] )
ScheduleMsg lm ->
case model.scheduleModel of
Just m ->
let
( cm, cc, ce ) =
Comp.CalEventInput.update flags tz model.schedule lm m
in
( { model | scheduleModel = Just cm, schedule = ce }, Cmd.map ScheduleMsg cc )
Nothing ->
( model, Cmd.none )
ToggleEnabled ->
( { model | enabled = not model.enabled }, Cmd.none )
AddonDropdownMsg lm ->
let
( am, ac ) =
Comp.Dropdown.update lm model.existingAddonDropdown
in
( { model | existingAddonDropdown = am }, Cmd.map AddonDropdownMsg ac )
Configure index ref ->
let
cfg =
{ ref = ref
, position = index + 1
, args = ref.args
, readMore = False
}
in
( { model | selectedAddon = Just cfg }, Cmd.none )
ConfigCancel ->
( { model | selectedAddon = Nothing }, Cmd.none )
ConfigToggleReadMore ->
case model.selectedAddon of
Just cfg ->
( { model | selectedAddon = Just { cfg | readMore = not cfg.readMore } }, Cmd.none )
Nothing ->
( model, Cmd.none )
ConfigArgsUpdated flag ->
( { model | configApplied = flag }, Cmd.none )
ConfigSetArgs str ->
case model.selectedAddon of
Just cfg ->
( { model | selectedAddon = Just { cfg | args = str } }
, Cmd.none
)
Nothing ->
( model, Cmd.none )
ConfigApply ->
case model.selectedAddon of
Just cfg ->
let
na =
getRef cfg
addons =
Util.List.replaceByIndex (cfg.position - 1) na model.addons
in
( { model | addons = addons, configApplied = True }
, Process.sleep 1200 |> Task.perform (\_ -> ConfigArgsUpdated False)
)
Nothing ->
( model, Cmd.none )
AddSelectedAddon ->
let
sel =
Comp.Dropdown.getSelected model.existingAddonDropdown |> List.head
( dm, _ ) =
Comp.Dropdown.update (Comp.Dropdown.SetSelection []) model.existingAddonDropdown
addon =
Maybe.map
(\a ->
{ addonId = a.id
, name = a.name
, version = a.version
, description = a.description
, args = ""
}
)
sel
newAddons =
Maybe.map (\e -> e :: model.addons) addon
|> Maybe.withDefault model.addons
in
( { model | addons = newAddons, existingAddonDropdown = dm, selectedAddon = Nothing }, Cmd.none )
Up curIndex ->
let
newAddons =
Util.List.changePosition curIndex (curIndex - 1) model.addons
in
( { model | addons = newAddons, selectedAddon = Nothing }, Cmd.none )
Down curIndex ->
let
newAddons =
Util.List.changePosition (curIndex + 1) curIndex model.addons
in
( { model | addons = newAddons, selectedAddon = Nothing }, Cmd.none )
SetName str ->
( { model | name = str }, Cmd.none )
Remove index ->
( { model | addons = Util.List.removeByIndex index model.addons, selectedAddon = Nothing }, Cmd.none )
initScheduleIfNeeded : Flags -> Model -> TimeZone -> ( Model, Cmd Msg )
initScheduleIfNeeded flags model tz =
let
hasTrigger =
Comp.Dropdown.getSelected model.triggerDropdown
|> List.any ((==) Data.AddonTrigger.Scheduled)
noModel =
model.scheduleModel == Nothing
hasModel =
not noModel
ce =
Data.CalEvent.everyMonthTz tz
( cm, cc ) =
Comp.CalEventInput.init flags ce
in
if hasTrigger && noModel then
( { model | scheduleModel = Just cm, schedule = Just ce }, Cmd.map ScheduleMsg cc )
else if not hasTrigger && hasModel then
( { model | scheduleModel = Nothing, schedule = Nothing }, Cmd.none )
else
( model, Cmd.none )
--- View
view : Texts -> UiSettings -> Model -> Html Msg
view texts settings model =
let
userDs =
{ makeOption = \user -> { text = user.login, additional = "" }
, placeholder = texts.basics.selectPlaceholder
, labelColor = \_ -> \_ -> ""
, style = DS.mainStyle
}
triggerDs =
{ makeOption = \trigger -> { text = Data.AddonTrigger.asString trigger, additional = "" }
, placeholder = texts.basics.selectPlaceholder
, labelColor = \_ -> \_ -> ""
, style = DS.mainStyle
}
in
div
[ class "flex flex-col" ]
[ div [ class "mb-4" ]
[ div [ class "mb-4" ]
[ label
[ class S.inputLabel
]
[ text texts.basics.name
, B.inputRequired
]
, input
[ type_ "text"
, placeholder texts.chooseName
, value model.name
, onInput SetName
, class S.textInput
, classList [ ( S.inputErrorBorder, model.name == "" ) ]
]
[]
]
, div [ class "mb-4" ]
[ MB.viewItem <|
MB.Checkbox
{ tagger = \_ -> ToggleEnabled
, label = texts.enableDisable
, value = model.enabled
, id = "addon-run-config-enabled"
}
]
, div [ class "mb-4" ]
[ label
[ class S.inputLabel
]
[ text texts.impersonateUser
]
, Html.map UserDropdownMsg
(Comp.Dropdown.view2 userDs settings model.userDropdown)
]
, div [ class "mb-4" ]
[ label
[ class S.inputLabel
]
[ text texts.triggerRun
, B.inputRequired
]
, Html.map TriggerDropdownMsg
(Comp.Dropdown.view2 triggerDs settings model.triggerDropdown)
]
, case model.scheduleModel of
Nothing ->
span [ class "hidden" ] []
Just m ->
div [ class "mb-4" ]
[ label
[ class S.inputLabel ]
[ text texts.schedule
]
, Html.map ScheduleMsg (Comp.CalEventInput.view2 texts.calEventInput "" model.schedule m)
]
]
, div [ class "mb-4" ]
[ h2 [ class S.header2 ]
[ text texts.addons ]
, addonRef texts model
, div [ class "mb-4" ]
[ label [ class S.inputLabel ]
[ text texts.includedAddons
, B.inputRequired
]
, newAddon texts settings model
, div [ class "mb-4" ]
[ div [ class "flex flex-col mb-4" ]
(List.indexedMap (addonLine texts model) model.addons)
]
]
]
]
newAddon : Texts -> UiSettings -> Model -> Html Msg
newAddon texts uiSettings model =
let
addonDs =
{ makeOption = \addon -> { text = addon.name ++ " / " ++ addon.version, additional = "" }
, placeholder = texts.basics.selectPlaceholder
, labelColor = \_ -> \_ -> ""
, style = DS.mainStyle
}
in
div [ class "mb-4" ]
[ div [ class "flex flex-row" ]
[ div [ class "flex-grow mr-2" ]
[ Html.map AddonDropdownMsg
(Comp.Dropdown.view2 addonDs uiSettings model.existingAddonDropdown)
]
, B.primaryBasicButton
{ label = texts.add
, icon = "fa fa-plus"
, disabled = List.isEmpty (Comp.Dropdown.getSelected model.existingAddonDropdown)
, handler = onClick AddSelectedAddon
, attrs = [ href "#" ]
}
]
]
addonRef : Texts -> Model -> Html Msg
addonRef texts model =
let
maybeRef =
Maybe.map .ref model.selectedAddon
refInfo =
case model.selectedAddon of
Nothing ->
div [ class "mb-4" ]
[ text "[ -- ]"
]
Just cfg ->
let
( descr, requireFolding ) =
case cfg.ref.description of
Just d ->
let
part =
Util.String.firstSentenceOrMax 120 d
text =
if cfg.readMore then
d
else
Maybe.withDefault d part
in
( Markdown.toHtml [ class "markdown-preview" ] text, part /= Nothing )
Nothing ->
( span [ class "italic" ] [ text "No description." ], False )
in
div [ class "flex flex-col mb-4" ]
[ div [ class "mt-2" ]
[ label [ class " font-semibold py-0.5 " ]
[ text cfg.ref.name
, text " "
, text cfg.ref.version
, text " (pos. "
, text <| String.fromInt cfg.position
, text ")"
, span
[ classList [ ( "hidden", not requireFolding ) ]
, class "ml-2"
]
[ a
[ class "px-4"
, class S.link
, href "#"
, onClick ConfigToggleReadMore
]
[ if cfg.readMore then
text texts.readLess
else
text texts.readMore
]
]
]
, div [ class "px-3 py-1 border-l dark:border-slate-600" ]
[ descr
]
]
]
in
div
[ class "flex flex-col mb-3"
, classList [ ( "disabled", maybeRef == Nothing ) ]
]
[ refInfo
, div [ class "mb-2" ]
[ label [ class S.inputLabel ] [ text texts.arguments ]
, textarea
[ Maybe.map .args model.selectedAddon |> Maybe.withDefault "" |> value
, class S.textAreaInput
, class "font-mono"
, rows 8
, onInput ConfigSetArgs
]
[]
]
, MB.view
{ start =
[ MB.PrimaryButton
{ tagger = ConfigApply
, title = ""
, icon = Just "fa fa-save"
, label = texts.update
}
, MB.SecondaryButton
{ tagger = ConfigCancel
, title = texts.basics.cancel
, icon = Just "fa fa-times"
, label = texts.basics.cancel
}
, MB.CustomElement <|
div
[ classList [ ( "hidden", not model.configApplied ) ]
, class S.successText
, class "inline-block min-w-fit font-semibold text-normal min-w-fit"
]
[ text texts.argumentsUpdated
, i [ class "fa fa-thumbs-up ml-2" ] []
]
]
, end = []
, rootClasses = "mb-4 text-sm"
, sticky = False
}
]
addonLine : Texts -> Model -> Int -> AddonRef -> Html Msg
addonLine texts model index ref =
let
isSelected =
case model.selectedAddon of
Just cfg ->
cfg.position - 1 == index
Nothing ->
False
in
div
[ class "flex flex-row items-center px-4 py-4 rounded shadow dark:border dark:border-slate-600 mb-2"
, classList [ ( "ring-2", isSelected ) ]
]
[ div [ class "px-2 hidden sm:block" ]
[ span [ class "label rounded-full opacity-75" ]
[ text <| String.fromInt (index + 1)
]
]
, div [ class "px-4 font-semibold" ]
[ text ref.name
, text " v"
, text ref.version
]
, div [ class "flex-grow" ]
[]
, div [ class "px-2" ]
[ MB.view
{ start = []
, end =
[ MB.PrimaryButton
{ tagger = Configure index ref
, title = texts.configureTitle
, icon = Just "fa fa-cog"
, label = texts.configureLabel
}
, MB.CustomElement <|
B.secondaryButton
{ handler = onClick (Up index)
, attrs = [ title "Move up", href "#" ]
, icon = "fa fa-arrow-up"
, label = ""
, disabled = index == 0
}
, MB.CustomElement <|
B.secondaryButton
{ handler = onClick (Down index)
, attrs = [ title "Move down", href "#" ]
, icon = "fa fa-arrow-down"
, label = ""
, disabled = index + 1 == List.length model.addons
}
, MB.CustomElement <|
B.deleteButton
{ label = ""
, icon = "fa fa-trash"
, disabled = False
, handler = onClick (Remove index)
, attrs = [ href "#" ]
}
]
, rootClasses = "text-sm"
, sticky = False
}
]
]

View File

@ -0,0 +1,364 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Comp.AddonRunConfigManage exposing (Model, Msg, init, loadConfigs, update, view)
import Api
import Api.Model.AddonRunConfig exposing (AddonRunConfig)
import Api.Model.AddonRunConfigList exposing (AddonRunConfigList)
import Api.Model.BasicResult exposing (BasicResult)
import Comp.AddonRunConfigForm
import Comp.AddonRunConfigTable
import Comp.Basic as B
import Comp.ItemDetail.Model exposing (Msg(..))
import Comp.MenuBar as MB
import Data.Flags exposing (Flags)
import Data.TimeZone exposing (TimeZone)
import Data.UiSettings exposing (UiSettings)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
import Http
import Messages.Comp.AddonRunConfigManage exposing (Texts)
import Page exposing (Page(..))
import Styles as S
type FormError
= FormErrorNone
| FormErrorHttp Http.Error
| FormErrorInvalid
| FormErrorSubmit String
type ViewMode
= Table
| Form
type DeleteConfirm
= DeleteConfirmOff
| DeleteConfirmOn
type alias Model =
{ viewMode : ViewMode
, runConfigs : List AddonRunConfig
, formModel : Comp.AddonRunConfigForm.Model
, loading : Bool
, formError : FormError
, deleteConfirm : DeleteConfirm
}
init : Flags -> ( Model, Cmd Msg )
init flags =
let
( fm, fc ) =
Comp.AddonRunConfigForm.init flags
in
( { viewMode = Table
, runConfigs = []
, formModel = fm
, loading = False
, formError = FormErrorNone
, deleteConfirm = DeleteConfirmOff
}
, Cmd.batch
[ Cmd.map FormMsg fc
, Api.addonRunConfigGet flags LoadConfigsResp
]
)
type Msg
= LoadRunConfigs
| TableMsg Comp.AddonRunConfigTable.Msg
| FormMsg Comp.AddonRunConfigForm.Msg
| InitNewConfig
| SetViewMode ViewMode
| Submit
| RequestDelete
| CancelDelete
| DeleteConfigNow String
| LoadConfigsResp (Result Http.Error AddonRunConfigList)
| AddConfigResp (Result Http.Error BasicResult)
| DeleteConfigResp (Result Http.Error BasicResult)
loadConfigs : Msg
loadConfigs =
LoadRunConfigs
--- update
update : Flags -> TimeZone -> Msg -> Model -> ( Model, Cmd Msg, Sub Msg )
update flags tz msg model =
case msg of
InitNewConfig ->
let
( bm, bc ) =
Comp.AddonRunConfigForm.init flags
nm =
{ model
| viewMode = Form
, formError = FormErrorNone
, formModel = bm
}
in
( nm, Cmd.map FormMsg bc, Sub.none )
SetViewMode vm ->
( { model | viewMode = vm, formError = FormErrorNone }
, if vm == Table then
Api.addonRunConfigGet flags LoadConfigsResp
else
Cmd.none
, Sub.none
)
FormMsg lm ->
let
( fm, fc ) =
Comp.AddonRunConfigForm.update flags tz lm model.formModel
in
( { model | formModel = fm, formError = FormErrorNone }
, Cmd.map FormMsg fc
, Sub.none
)
TableMsg lm ->
let
action =
Comp.AddonRunConfigTable.update lm
in
case action of
Comp.AddonRunConfigTable.Selected addon ->
let
( bm, bc ) =
Comp.AddonRunConfigForm.initWith flags addon
in
( { model
| viewMode = Form
, formError = FormErrorNone
, formModel = bm
}
, Cmd.map FormMsg bc
, Sub.none
)
RequestDelete ->
( { model | deleteConfirm = DeleteConfirmOn }, Cmd.none, Sub.none )
CancelDelete ->
( { model | deleteConfirm = DeleteConfirmOff }, Cmd.none, Sub.none )
DeleteConfigNow id ->
( { model | deleteConfirm = DeleteConfirmOff, loading = True }
, Api.addonRunConfigDelete flags id DeleteConfigResp
, Sub.none
)
LoadRunConfigs ->
( { model | loading = True }
, Api.addonRunConfigGet flags LoadConfigsResp
, Sub.none
)
LoadConfigsResp (Ok list) ->
( { model | loading = False, runConfigs = list.items, formError = FormErrorNone }
, Cmd.none
, Sub.none
)
LoadConfigsResp (Err err) ->
( { model | loading = False, formError = FormErrorHttp err }, Cmd.none, Sub.none )
Submit ->
case Comp.AddonRunConfigForm.get model.formModel of
Just data ->
( { model | loading = True }, Api.addonRunConfigSet flags data AddConfigResp, Sub.none )
Nothing ->
( { model | formError = FormErrorInvalid }, Cmd.none, Sub.none )
AddConfigResp (Ok res) ->
if res.success then
( { model | loading = False }, Cmd.none, Sub.none )
else
( { model | loading = False, formError = FormErrorSubmit res.message }, Cmd.none, Sub.none )
AddConfigResp (Err err) ->
( { model | loading = False, formError = FormErrorHttp err }, Cmd.none, Sub.none )
DeleteConfigResp (Ok res) ->
if res.success then
update flags tz (SetViewMode Table) { model | loading = False }
else
( { model | formError = FormErrorSubmit res.message, loading = False }, Cmd.none, Sub.none )
DeleteConfigResp (Err err) ->
( { model | formError = FormErrorHttp err, loading = False }, Cmd.none, Sub.none )
--- view
view : Texts -> UiSettings -> Flags -> Model -> Html Msg
view texts settings flags model =
if model.viewMode == Table then
viewTable texts model
else
viewForm texts settings flags model
viewTable : Texts -> Model -> Html Msg
viewTable texts model =
div [ class "flex flex-col" ]
[ MB.view
{ start =
[]
, end =
[ MB.PrimaryButton
{ tagger = InitNewConfig
, title = texts.createNewAddonRunConfig
, icon = Just "fa fa-plus"
, label = texts.newAddonRunConfig
}
]
, rootClasses = "mb-4"
, sticky = True
}
, div
[ class "flex flex-col"
]
[ Html.map TableMsg
(Comp.AddonRunConfigTable.view texts.addonArchiveTable model.runConfigs)
]
, B.loadingDimmer
{ label = ""
, active = model.loading
}
]
viewForm : Texts -> UiSettings -> Flags -> Model -> Html Msg
viewForm texts uiSettings _ model =
let
newConfig =
model.formModel.runConfig.id == ""
isValid =
Comp.AddonRunConfigForm.get model.formModel /= Nothing
in
div []
[ Html.form []
[ if newConfig then
h1 [ class S.header2 ]
[ text texts.createNewAddonRunConfig
]
else
h1 [ class S.header2 ]
[ text (Comp.AddonRunConfigForm.get model.formModel |> Maybe.map .name |> Maybe.withDefault "Update")
]
, MB.view
{ start =
[ MB.CustomElement <|
B.primaryButton
{ handler = onClick Submit
, title = texts.basics.submitThisForm
, icon = "fa fa-save"
, label = texts.basics.submit
, disabled = not isValid
, attrs = [ href "#" ]
}
, MB.SecondaryButton
{ tagger = SetViewMode Table
, title = texts.basics.backToList
, icon = Just "fa fa-arrow-left"
, label = texts.basics.back
}
]
, end =
if not newConfig then
[ MB.DeleteButton
{ tagger = RequestDelete
, title = texts.deleteThisAddonRunConfig
, icon = Just "fa fa-trash"
, label = texts.basics.delete
}
]
else
[]
, rootClasses = "mb-4"
, sticky = True
}
, div
[ classList
[ ( "hidden", model.formError == FormErrorNone )
]
, class "my-2"
, class S.errorMessage
]
[ case model.formError of
FormErrorNone ->
text ""
FormErrorHttp err ->
text (texts.httpError err)
FormErrorInvalid ->
text texts.correctFormErrors
FormErrorSubmit m ->
text m
]
, div []
[ Html.map FormMsg (Comp.AddonRunConfigForm.view texts.addonArchiveForm uiSettings model.formModel)
]
, B.loadingDimmer
{ active = model.loading
, label = texts.basics.loading
}
, B.contentDimmer
(model.deleteConfirm == DeleteConfirmOn)
(div [ class "flex flex-col" ]
[ div [ class "text-lg" ]
[ i [ class "fa fa-info-circle mr-2" ] []
, text texts.reallyDeleteAddonRunConfig
]
, div [ class "mt-4 flex flex-row items-center" ]
[ B.deleteButton
{ label = texts.basics.yes
, icon = "fa fa-check"
, disabled = False
, handler = onClick (DeleteConfigNow model.formModel.runConfig.id)
, attrs = [ href "#" ]
}
, B.secondaryButton
{ label = texts.basics.no
, icon = "fa fa-times"
, disabled = False
, handler = onClick CancelDelete
, attrs = [ href "#", class "ml-2" ]
}
]
]
)
]
]

View File

@ -0,0 +1,79 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Comp.AddonRunConfigTable exposing (..)
import Api.Model.AddonRunConfig exposing (AddonRunConfig)
import Comp.Basic as B
import Html exposing (Html, div, table, tbody, td, text, th, thead, tr)
import Html.Attributes exposing (class)
import Messages.Comp.AddonRunConfigTable exposing (Texts)
import Styles as S
import Util.Html
type Msg
= SelectRunConfig AddonRunConfig
type TableAction
= Selected AddonRunConfig
--- Update
update : Msg -> TableAction
update msg =
case msg of
SelectRunConfig cfg ->
Selected cfg
--- View
view : Texts -> List AddonRunConfig -> Html Msg
view texts addons =
table [ class S.tableMain ]
[ thead []
[ tr []
[ th [ class "" ] []
, th [ class "text-left" ]
[ text texts.basics.name
]
, th [ class "px-2 text-center" ] [ text texts.enabled ]
, th [ class "px-2 text-left" ] [ text texts.trigger ]
, th [ class "px-2 text-center" ] [ text "# Addons" ]
]
]
, tbody []
(List.map (renderRunConfigLine texts) addons)
]
renderRunConfigLine : Texts -> AddonRunConfig -> Html Msg
renderRunConfigLine texts cfg =
tr
[ class S.tableRow
]
[ B.editLinkTableCell texts.basics.edit (SelectRunConfig cfg)
, td [ class "text-left py-4 md:py-2" ]
[ text cfg.name
]
, td [ class "w-px whitespace-nowrap px-2 text-center" ]
[ Util.Html.checkbox2 cfg.enabled
]
, td [ class "px-2 text-left" ]
[ text (String.join ", " cfg.trigger)
]
, td [ class "px-2 text-center" ]
[ text (String.fromInt <| List.length cfg.addons)
]
]

View File

@ -28,6 +28,8 @@ module Comp.ItemDetail.Model exposing
, resultModelCmdSub
)
import Api.Model.AddonRunConfig exposing (AddonRunConfig)
import Api.Model.AddonRunConfigList exposing (AddonRunConfigList)
import Api.Model.BasicResult exposing (BasicResult)
import Api.Model.CustomField exposing (CustomField)
import Api.Model.EquipmentList exposing (EquipmentList)
@ -72,6 +74,7 @@ import Set exposing (Set)
type alias Model =
{ item : ItemDetail
, runConfigs : List AddonRunConfig
, visibleAttach : Int
, attachMenuOpen : Bool
, menuOpen : Bool
@ -123,6 +126,9 @@ type alias Model =
, viewMode : ViewMode
, showQrModel : ShowQrModel
, itemLinkModel : Comp.ItemLinkForm.Model
, showRunAddon : Bool
, addonRunConfigDropdown : Comp.Dropdown.Model AddonRunConfig
, addonRunSubmitted : Bool
}
@ -204,6 +210,7 @@ isEditNotes field =
emptyModel : Model
emptyModel =
{ item = Api.Model.ItemDetail.empty
, runConfigs = []
, visibleAttach = 0
, attachMenuOpen = False
, menuOpen = False
@ -259,6 +266,9 @@ emptyModel =
, viewMode = SimpleView
, showQrModel = initShowQrModel
, itemLinkModel = Comp.ItemLinkForm.emptyModel
, showRunAddon = False
, addonRunConfigDropdown = Comp.Dropdown.makeSingle
, addonRunSubmitted = False
}
@ -373,6 +383,12 @@ type Msg
| SetNameMsg Comp.SimpleTextInput.Msg
| ToggleSelectItem
| ItemLinkFormMsg Comp.ItemLinkForm.Msg
| ToggleShowRunAddon
| LoadRunConfigResp (Result Http.Error AddonRunConfigList)
| RunAddonMsg (Comp.Dropdown.Msg AddonRunConfig)
| RunSelectedAddon
| RunAddonResp (Result Http.Error BasicResult)
| SetAddonRunSubmitted Bool
type SaveNameState

View File

@ -0,0 +1,78 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Comp.ItemDetail.RunAddonForm exposing (..)
import Comp.Basic as B
import Comp.Dropdown
import Comp.ItemDetail.Model exposing (..)
import Comp.MenuBar as MB
import Data.DropdownStyle as DS
import Data.UiSettings exposing (UiSettings)
import Html exposing (Html, div, h3, label, text)
import Html.Attributes exposing (class, classList, title)
import Html.Events exposing (onClick)
import Messages.Comp.ItemDetail.RunAddonForm exposing (Texts)
import Styles as S
view : Texts -> UiSettings -> Model -> Html Msg
view texts uiSettings model =
let
viewSettings =
{ makeOption = \cfg -> { text = cfg.name, additional = "" }
, placeholder = texts.basics.selectPlaceholder
, labelColor = \_ -> \_ -> ""
, style = DS.mainStyle
}
runDisabled =
Comp.Dropdown.getSelected model.addonRunConfigDropdown
|> List.isEmpty
in
div
[ classList [ ( "hidden", not model.showRunAddon ) ]
, class "mb-4"
]
[ h3 [ class S.header3 ] [ text texts.runAddon ]
, div [ class "my-2" ]
[ label [ class S.inputLabel ]
[ text texts.addonRunConfig
]
, Html.map RunAddonMsg (Comp.Dropdown.view2 viewSettings uiSettings model.addonRunConfigDropdown)
]
, div [ class "my-2" ]
[ MB.view
{ start =
[ MB.CustomElement <|
B.primaryButton
{ label = "Run"
, icon =
if model.addonRunSubmitted then
"fa fa-check"
else
"fa fa-play"
, disabled = runDisabled
, handler = onClick RunSelectedAddon
, attrs =
[ title texts.runAddonTitle
]
}
, MB.SecondaryButton
{ label = texts.basics.cancel
, icon = Just "fa fa-times"
, tagger = ToggleShowRunAddon
, title = ""
}
]
, end = []
, rootClasses = "text-sm mt-1"
, sticky = False
}
]
]

View File

@ -56,6 +56,7 @@ import Comp.PersonForm
import Comp.SentMails
import Comp.SimpleTextInput
import Comp.TagDropdown
import Data.AddonTrigger
import Data.CustomFieldChange exposing (CustomFieldChange(..))
import Data.Direction
import Data.Environment as Env
@ -75,7 +76,9 @@ import Html5.DragDrop as DD
import Http
import Page exposing (Page(..))
import Ports
import Process
import Set exposing (Set)
import Task
import Util.File exposing (makeFileId)
import Util.List
import Util.Maybe
@ -121,9 +124,77 @@ update inav env msg model =
, Cmd.map ItemMailMsg ic
, Cmd.map CustomFieldMsg cc
, Api.getSentMails env.flags model.item.id SentMailsResp
, Api.addonRunConfigGet env.flags LoadRunConfigResp
]
)
LoadRunConfigResp (Ok list) ->
let
existingItem cfg =
cfg.enabled
&& (Data.AddonTrigger.fromList cfg.trigger
|> List.any ((==) Data.AddonTrigger.ExistingItem)
)
configs =
List.filter existingItem list.items
dropdown =
Comp.Dropdown.makeSingleList { options = configs, selected = Nothing }
in
resultModel { model | runConfigs = configs, addonRunConfigDropdown = dropdown }
RunAddonMsg lm ->
let
( dd, dc ) =
Comp.Dropdown.update lm model.addonRunConfigDropdown
in
resultModelCmd ( { model | addonRunConfigDropdown = dd }, Cmd.map RunAddonMsg dc )
RunSelectedAddon ->
let
configs =
Comp.Dropdown.getSelected model.addonRunConfigDropdown
|> List.map .id
payload =
{ itemId = model.item.id
, additionalItems = []
, addonRunConfigIds = configs
}
( dd, _ ) =
Comp.Dropdown.update (Comp.Dropdown.SetSelection []) model.addonRunConfigDropdown
in
case configs of
[] ->
resultModel model
_ ->
resultModelCmd
( { model | addonRunConfigDropdown = dd }
, Api.addonRunExistingItem env.flags payload RunAddonResp
)
LoadRunConfigResp (Err _) ->
resultModel model
RunAddonResp (Ok res) ->
if res.success then
resultModelCmd
( { model | addonRunSubmitted = True }
, Process.sleep 1200 |> Task.perform (\_ -> SetAddonRunSubmitted False)
)
else
resultModel model
RunAddonResp (Err _) ->
resultModel model
SetAddonRunSubmitted flag ->
resultModel { model | addonRunSubmitted = flag }
SetItem item ->
let
res1 =
@ -1638,6 +1709,9 @@ update inav env msg model =
, Sub.map ItemLinkFormMsg ils
)
ToggleShowRunAddon ->
resultModel { model | showRunAddon = not model.showRunAddon, mobileItemMenuOpen = False }
--- Helper

View File

@ -22,6 +22,7 @@ import Comp.ItemDetail.Model
, isShowQrItem
)
import Comp.ItemDetail.Notes
import Comp.ItemDetail.RunAddonForm
import Comp.ItemDetail.ShowQrCode
import Comp.ItemDetail.SingleAttachment
import Comp.ItemLinkForm
@ -177,6 +178,24 @@ menuBar texts inav env model =
]
[ Icons.addFilesIcon2 ""
]
, MB.CustomElement <|
a
[ classList
[ ( "bg-gray-200 dark:bg-slate-600", model.showRunAddon )
, ( "hidden", not env.flags.config.addonsEnabled || List.isEmpty model.runConfigs )
, ( "hidden md:block", env.flags.config.addonsEnabled && not (List.isEmpty model.runConfigs) )
]
, if model.showRunAddon then
title texts.close
else
title texts.runAddonTitle
, onClick ToggleShowRunAddon
, class S.secondaryBasicButton
, href "#"
]
[ Icons.addonIcon ""
]
, MB.CustomElement <|
a
[ classList
@ -248,6 +267,15 @@ menuBar texts inav env model =
, onClick AddFilesToggle
]
}
, { icon = Icons.addonIcon ""
, label = texts.runAddonLabel
, disabled = False
, attrs =
[ href "#"
, onClick ToggleShowRunAddon
, classList [ ( "hidden", not env.flags.config.addonsEnabled ) ]
]
}
, { icon = Icons.showQrIcon ""
, label = texts.showQrCode
, disabled = False
@ -402,6 +430,11 @@ itemActions texts flags settings model classes =
(S.border ++ " mb-4")
model
(Comp.ItemDetail.ShowQrCode.Item model.item.id)
, if flags.config.addonsEnabled then
Comp.ItemDetail.RunAddonForm.view texts.runAddonForm settings model
else
span [ class "hidden" ] []
]

View File

@ -0,0 +1,59 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Data.AddonTrigger exposing (..)
-- A copy of docspell.addons.AddonTrigger.scala
type AddonTrigger
= FinalProcessItem
| FinalReprocessItem
| Scheduled
| ExistingItem
all : List AddonTrigger
all =
[ FinalProcessItem
, FinalReprocessItem
, Scheduled
, ExistingItem
]
asString : AddonTrigger -> String
asString t =
case t of
FinalProcessItem ->
"final-process-item"
FinalReprocessItem ->
"final-reprocess-item"
Scheduled ->
"scheduled"
ExistingItem ->
"existing-item"
fromString : String -> Maybe AddonTrigger
fromString s =
let
name =
String.toLower s
x =
List.filter (\e -> asString e == name) all
in
List.head x
fromList : List String -> List AddonTrigger
fromList list =
List.filterMap fromString list

View File

@ -8,6 +8,7 @@
module Data.CalEvent exposing
( CalEvent
, everyMonth
, everyMonthTz
, fromEvent
, makeEvent
)
@ -29,7 +30,12 @@ type alias CalEvent =
everyMonth : CalEvent
everyMonth =
CalEvent Nothing "*" "*" "01" "00" "00" Data.TimeZone.utc
everyMonthTz Data.TimeZone.utc
everyMonthTz : TimeZone -> CalEvent
everyMonthTz tz =
CalEvent Nothing "*" "*" "01" "00" "00" tz
makeEvent : CalEvent -> String

View File

@ -38,6 +38,7 @@ type alias Config =
, downloadAllMaxFiles : Int
, downloadAllMaxSize : Int
, openIdAuth : List OpenIdAuth
, addonsEnabled : Bool
}

View File

@ -8,6 +8,8 @@
module Data.Icons exposing
( addFiles2
, addFilesIcon2
, addonIcon
, addonRunConfigIcon
, concerned
, concerned2
, concernedIcon
@ -76,7 +78,7 @@ module Data.Icons exposing
)
import Data.CustomFieldType exposing (CustomFieldType)
import Html exposing (Html, i, img)
import Html exposing (Html, div, i, img, span)
import Html.Attributes exposing (class, src)
import Svg
import Svg.Attributes as SA
@ -265,6 +267,24 @@ customFieldIcon2 classes =
i [ class (customField2 ++ " " ++ classes) ] []
addon : String
addon =
"fa fa-puzzle-piece"
addonIcon : String -> Html msg
addonIcon classes =
i [ class (addon ++ " " ++ classes) ] []
addonRunConfigIcon : String -> Html msg
addonRunConfigIcon classes =
div [ class (classes ++ " inline-block relative margin-auto leading-8") ]
[ i [ class "fa fa-puzzle-piece" ] []
, i [ class "fa fa-play font-bold absolute text-xs -right-2 top-0" ] []
]
search : String
search =
"fa fa-search"

View File

@ -5,15 +5,34 @@
-}
module Data.ServerEvent exposing (ServerEvent(..), decode)
module Data.ServerEvent exposing (AddonInfo, ServerEvent(..), decode)
import Json.Decode as D
import Json.Decode.Pipeline as P
type ServerEvent
= JobSubmitted String
| JobDone String
| JobsWaiting Int
| AddonInstalled AddonInfo
type alias AddonInfo =
{ success : Bool
, addonId : Maybe String
, addonUrl : Maybe String
, message : String
}
addonInfoDecoder : D.Decoder AddonInfo
addonInfoDecoder =
D.succeed AddonInfo
|> P.required "success" D.bool
|> P.optional "addonId" (D.maybe D.string) Nothing
|> P.optional "addonUrl" (D.maybe D.string) Nothing
|> P.required "message" D.string
decoder : D.Decoder ServerEvent
@ -43,5 +62,9 @@ decodeTag tag =
D.field "content" D.int
|> D.map JobsWaiting
"addon-installed" ->
D.field "content" addonInfoDecoder
|> D.map AddonInstalled
_ ->
D.fail ("Unknown tag: " ++ tag)

View File

@ -0,0 +1,54 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.AddonArchiveForm exposing
( Texts
, de
, fr
, gb
)
import Messages.Basics
type alias Texts =
{ basics : Messages.Basics.Texts
, addonUrl : String
, addonUrlPlaceholder : String
, installInfoText : String
}
gb : Texts
gb =
{ basics = Messages.Basics.gb
, addonUrl = "Addon URL"
, addonUrlPlaceholder = "e.g. https://github.com/some-user/project/refs/tags/1.0.zip"
, installInfoText = "Only urls to remote addon zip files are supported."
}
de : Texts
de =
{ basics = Messages.Basics.de
, addonUrl = "Addon URL"
, addonUrlPlaceholder = "z.B. https://github.com/some-user/project/refs/tags/1.0.zip"
, installInfoText = "Nur URLs to externen zip Dateien werden unterstützt."
}
-- TODO: translate-fr
fr : Texts
fr =
{ basics = Messages.Basics.fr
, addonUrl = "Addon URL"
, addonUrlPlaceholder = "p.e. https://github.com/some-user/project/refs/tags/1.0.zip"
, installInfoText = "Only urls to remote addon zip files are supported."
}

View File

@ -0,0 +1,86 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.AddonArchiveManage exposing
( Texts
, de
, fr
, gb
)
import Http
import Messages.Basics
import Messages.Comp.AddonArchiveForm
import Messages.Comp.AddonArchiveTable
import Messages.Comp.HttpError
type alias Texts =
{ basics : Messages.Basics.Texts
, addonArchiveTable : Messages.Comp.AddonArchiveTable.Texts
, addonArchiveForm : Messages.Comp.AddonArchiveForm.Texts
, httpError : Http.Error -> String
, newAddonArchive : String
, reallyDeleteAddonArchive : String
, createNewAddonArchive : String
, deleteThisAddonArchive : String
, correctFormErrors : String
, installNow : String
, updateNow : String
, description : String
}
gb : Texts
gb =
{ basics = Messages.Basics.gb
, addonArchiveTable = Messages.Comp.AddonArchiveTable.gb
, addonArchiveForm = Messages.Comp.AddonArchiveForm.gb
, httpError = Messages.Comp.HttpError.gb
, newAddonArchive = "New Addon"
, reallyDeleteAddonArchive = "Really delete this Addon?"
, createNewAddonArchive = "Install new Addon"
, deleteThisAddonArchive = "Delete this Addon"
, correctFormErrors = "Please correct the errors in the form."
, installNow = "Install Addon"
, updateNow = "Update Addon"
, description = "Description"
}
de : Texts
de =
{ basics = Messages.Basics.de
, addonArchiveTable = Messages.Comp.AddonArchiveTable.de
, addonArchiveForm = Messages.Comp.AddonArchiveForm.de
, httpError = Messages.Comp.HttpError.de
, newAddonArchive = "Neues Addon"
, reallyDeleteAddonArchive = "Dieses Addon wirklich entfernen?"
, createNewAddonArchive = "Neues Addon installieren"
, deleteThisAddonArchive = "Addon löschen"
, correctFormErrors = "Bitte korrigiere die Fehler im Formular."
, installNow = "Addon Installieren"
, updateNow = "Addon aktualisieren"
, description = "Beschreibung"
}
fr : Texts
fr =
{ basics = Messages.Basics.fr
, addonArchiveTable = Messages.Comp.AddonArchiveTable.fr
, addonArchiveForm = Messages.Comp.AddonArchiveForm.fr
, httpError = Messages.Comp.HttpError.fr
, newAddonArchive = "Nouveau favori"
, reallyDeleteAddonArchive = "Confirmer la suppression de ce favori ?"
, createNewAddonArchive = "Créer un nouveau favori"
, deleteThisAddonArchive = "Supprimer ce favori"
, correctFormErrors = "Veuillez corriger les erreurs du formulaire"
, installNow = "Installation de l'addon"
, updateNow = "Actualiser l'addon"
, description = "Description"
}

View File

@ -0,0 +1,42 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.AddonArchiveTable exposing
( Texts
, de
, fr
, gb
)
import Messages.Basics
type alias Texts =
{ basics : Messages.Basics.Texts
, version : String
}
gb : Texts
gb =
{ basics = Messages.Basics.gb
, version = "Version"
}
de : Texts
de =
{ basics = Messages.Basics.de
, version = "Version"
}
fr : Texts
fr =
{ basics = Messages.Basics.fr
, version = "Version"
}

View File

@ -0,0 +1,108 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.AddonRunConfigForm exposing
( Texts
, de
, fr
, gb
)
import Data.TimeZone exposing (TimeZone)
import Messages.Basics
import Messages.Comp.CalEventInput
type alias Texts =
{ basics : Messages.Basics.Texts
, calEventInput : Messages.Comp.CalEventInput.Texts
, enableDisable : String
, chooseName : String
, impersonateUser : String
, triggerRun : String
, schedule : String
, addons : String
, includedAddons : String
, add : String
, readMore : String
, readLess : String
, arguments : String
, update : String
, argumentsUpdated : String
, configureTitle : String
, configureLabel : String
}
gb : TimeZone -> Texts
gb tz =
{ basics = Messages.Basics.gb
, calEventInput = Messages.Comp.CalEventInput.gb tz
, enableDisable = "Enable or disable this run configuration."
, chooseName = "Choose a name"
, impersonateUser = "Run on behalf of user"
, triggerRun = "Trigger Run"
, schedule = "Schedule"
, addons = "Addons"
, includedAddons = "Included addons"
, add = "Add"
, readMore = "Read more"
, readLess = "Read less"
, arguments = "Arguments"
, update = "Update"
, argumentsUpdated = "Arguments updated"
, configureTitle = "Configure this addon"
, configureLabel = "Configure"
}
de : TimeZone -> Texts
de tz =
{ basics = Messages.Basics.de
, calEventInput = Messages.Comp.CalEventInput.de tz
, enableDisable = "Konfiguration aktivieren oder deaktivieren"
, chooseName = "Name der Konfiguration"
, impersonateUser = "Als Benutzer ausführen"
, triggerRun = "Auslöser"
, schedule = "Zeitplan"
, addons = "Addons"
, includedAddons = "Gewählte Addons"
, add = "Hinzufügen"
, readMore = "Mehr"
, readLess = "Weniger"
, arguments = "Argumente"
, update = "Aktualisieren"
, argumentsUpdated = "Argumente aktualisiert"
, configureTitle = "Konfiguriere dieses Addon"
, configureLabel = "Konfigurieren"
}
-- TODO: translate-fr
fr : TimeZone -> Texts
fr tz =
{ basics = Messages.Basics.fr
, calEventInput = Messages.Comp.CalEventInput.fr tz
, enableDisable = "Activer ou désactiver cette tâche."
, chooseName = "Choose a name"
, impersonateUser = "Impersonate user"
, triggerRun = "Trigger Run"
, schedule = "Programmation"
, addons = "Addons"
, includedAddons = "Included addons"
, add = "Ajouter"
, readMore = "Read more"
, readLess = "Read less"
, arguments = "Arguments"
, update = "Update"
, argumentsUpdated = "Arguments updated"
, configureTitle = "Configure this addon"
, configureLabel = "Configure"
}

View File

@ -0,0 +1,79 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.AddonRunConfigManage exposing
( Texts
, de
, fr
, gb
)
import Data.TimeZone exposing (TimeZone)
import Http
import Messages.Basics
import Messages.Comp.AddonRunConfigForm
import Messages.Comp.AddonRunConfigTable
import Messages.Comp.HttpError
type alias Texts =
{ basics : Messages.Basics.Texts
, addonArchiveTable : Messages.Comp.AddonRunConfigTable.Texts
, addonArchiveForm : Messages.Comp.AddonRunConfigForm.Texts
, httpError : Http.Error -> String
, newAddonRunConfig : String
, reallyDeleteAddonRunConfig : String
, createNewAddonRunConfig : String
, deleteThisAddonRunConfig : String
, correctFormErrors : String
}
gb : TimeZone -> Texts
gb tz =
{ basics = Messages.Basics.gb
, addonArchiveTable = Messages.Comp.AddonRunConfigTable.gb
, addonArchiveForm = Messages.Comp.AddonRunConfigForm.gb tz
, httpError = Messages.Comp.HttpError.gb
, newAddonRunConfig = "New"
, reallyDeleteAddonRunConfig = "Really delete this run config?"
, createNewAddonRunConfig = "Create a new run configuration"
, deleteThisAddonRunConfig = "Delete this run configuration"
, correctFormErrors = "Please correct the errors in the form."
}
de : TimeZone -> Texts
de tz =
{ basics = Messages.Basics.de
, addonArchiveTable = Messages.Comp.AddonRunConfigTable.de
, addonArchiveForm = Messages.Comp.AddonRunConfigForm.de tz
, httpError = Messages.Comp.HttpError.de
, newAddonRunConfig = "Neu"
, reallyDeleteAddonRunConfig = "Dieses Konfiguration wirklich entfernen?"
, createNewAddonRunConfig = "Neue Run-Konfiguration erstellen"
, deleteThisAddonRunConfig = "Run-Konfiguration löschen"
, correctFormErrors = "Bitte korrigiere die Fehler im Formular."
}
--- TODO translate-fr
fr : TimeZone -> Texts
fr tz =
{ basics = Messages.Basics.fr
, addonArchiveTable = Messages.Comp.AddonRunConfigTable.fr
, addonArchiveForm = Messages.Comp.AddonRunConfigForm.fr tz
, httpError = Messages.Comp.HttpError.fr
, newAddonRunConfig = "Nouveau favori"
, reallyDeleteAddonRunConfig = "Confirmer la suppression de ce favori ?"
, createNewAddonRunConfig = "Créer un nouveau favori"
, deleteThisAddonRunConfig = "Supprimer ce favori"
, correctFormErrors = "Veuillez corriger les erreurs du formulaire"
}

View File

@ -0,0 +1,50 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.AddonRunConfigTable exposing
( Texts
, de
, fr
, gb
)
import Messages.Basics
type alias Texts =
{ basics : Messages.Basics.Texts
, enabled : String
, trigger : String
}
gb : Texts
gb =
{ basics = Messages.Basics.gb
, enabled = "Enabled"
, trigger = "Triggered"
}
de : Texts
de =
{ basics = Messages.Basics.de
, enabled = "Aktive"
, trigger = "Auslöser"
}
-- TODO translate-fr
fr : Texts
fr =
{ basics = Messages.Basics.fr
, enabled = "Enabled"
, trigger = "Triggered"
}

View File

@ -20,6 +20,7 @@ import Messages.Comp.ItemDetail.AddFilesForm
import Messages.Comp.ItemDetail.ConfirmModal
import Messages.Comp.ItemDetail.ItemInfoHeader
import Messages.Comp.ItemDetail.Notes
import Messages.Comp.ItemDetail.RunAddonForm
import Messages.Comp.ItemDetail.SingleAttachment
import Messages.Comp.ItemLinkForm
import Messages.Comp.ItemMail
@ -38,6 +39,7 @@ type alias Texts =
, detailEdit : Messages.Comp.DetailEdit.Texts
, confirmModal : Messages.Comp.ItemDetail.ConfirmModal.Texts
, itemLinkForm : Messages.Comp.ItemLinkForm.Texts
, runAddonForm : Messages.Comp.ItemDetail.RunAddonForm.Texts
, httpError : Http.Error -> String
, key : String
, backToSearchResults : String
@ -64,6 +66,8 @@ type alias Texts =
, selectItem : String
, deselectItem : String
, relatedItems : String
, runAddonLabel : String
, runAddonTitle : String
}
@ -78,6 +82,7 @@ gb tz =
, detailEdit = Messages.Comp.DetailEdit.gb
, confirmModal = Messages.Comp.ItemDetail.ConfirmModal.gb
, itemLinkForm = Messages.Comp.ItemLinkForm.gb tz
, runAddonForm = Messages.Comp.ItemDetail.RunAddonForm.gb
, httpError = Messages.Comp.HttpError.gb
, key = "Key"
, backToSearchResults = "Back to search results"
@ -104,6 +109,8 @@ gb tz =
, selectItem = "Select this item"
, deselectItem = "Deselect this item"
, relatedItems = "Linked items"
, runAddonLabel = "Run addon"
, runAddonTitle = "Run an addon on this item"
}
@ -118,6 +125,7 @@ de tz =
, detailEdit = Messages.Comp.DetailEdit.de
, confirmModal = Messages.Comp.ItemDetail.ConfirmModal.de
, itemLinkForm = Messages.Comp.ItemLinkForm.de tz
, runAddonForm = Messages.Comp.ItemDetail.RunAddonForm.de
, httpError = Messages.Comp.HttpError.de
, key = "Taste"
, backToSearchResults = "Zurück zur Suche"
@ -144,6 +152,8 @@ de tz =
, selectItem = "Zur Auswahl hinzufügen"
, deselectItem = "Aus Auswahl entfernen"
, relatedItems = "Verknüpfte Dokumente"
, runAddonLabel = "Addon ausführen"
, runAddonTitle = "Addons für dieses Dokument ausführen"
}
@ -158,6 +168,7 @@ fr tz =
, detailEdit = Messages.Comp.DetailEdit.fr
, confirmModal = Messages.Comp.ItemDetail.ConfirmModal.fr
, itemLinkForm = Messages.Comp.ItemLinkForm.fr tz
, runAddonForm = Messages.Comp.ItemDetail.RunAddonForm.fr
, httpError = Messages.Comp.HttpError.fr
, key = "Clé"
, backToSearchResults = "Retour aux résultat de recherche"
@ -184,4 +195,10 @@ fr tz =
, selectItem = "Sélectionner ce document"
, deselectItem = "Désélectionner ce document"
, relatedItems = "Documents associés"
, runAddonLabel = "Run addon"
, runAddonTitle = "Run an addon on this item"
}
-- TODO translate-fr

View File

@ -0,0 +1,49 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.ItemDetail.RunAddonForm exposing (Texts, de, fr, gb)
import Messages.Basics
type alias Texts =
{ basics : Messages.Basics.Texts
, runAddon : String
, addonRunConfig : String
, runAddonTitle : String
}
gb : Texts
gb =
{ basics = Messages.Basics.gb
, runAddon = "Run an addon"
, addonRunConfig = "Addon run configuration"
, runAddonTitle = "Run the selected addon on this item."
}
de : Texts
de =
{ basics = Messages.Basics.de
, runAddon = "Addon ausführen"
, addonRunConfig = "Addon Konfiguration"
, runAddonTitle = "Run the selected addon on this item."
}
-- TODO: translate-fr
fr : Texts
fr =
{ basics = Messages.Basics.fr
, runAddon = "Run an addon"
, addonRunConfig = "Addon run configuration"
, runAddonTitle = "Run the selected addon on this item."
}

View File

@ -14,6 +14,8 @@ module Messages.Page.ManageData exposing
import Data.TimeZone exposing (TimeZone)
import Messages.Basics
import Messages.Comp.AddonArchiveManage
import Messages.Comp.AddonRunConfigManage
import Messages.Comp.BookmarkManage
import Messages.Comp.CustomFieldManage
import Messages.Comp.EquipmentManage
@ -32,8 +34,12 @@ type alias Texts =
, folderManage : Messages.Comp.FolderManage.Texts
, customFieldManage : Messages.Comp.CustomFieldManage.Texts
, bookmarkManage : Messages.Comp.BookmarkManage.Texts
, addonArchiveManage : Messages.Comp.AddonArchiveManage.Texts
, addonRunConfigManage : Messages.Comp.AddonRunConfigManage.Texts
, manageData : String
, bookmarks : String
, addonArchives : String
, addonRunConfigs : String
}
@ -47,8 +53,12 @@ gb tz =
, folderManage = Messages.Comp.FolderManage.gb tz
, customFieldManage = Messages.Comp.CustomFieldManage.gb tz
, bookmarkManage = Messages.Comp.BookmarkManage.gb
, addonArchiveManage = Messages.Comp.AddonArchiveManage.gb
, addonRunConfigManage = Messages.Comp.AddonRunConfigManage.gb tz
, manageData = "Manage Data"
, bookmarks = "Bookmarks"
, addonArchives = "Addons"
, addonRunConfigs = "Addon Run Configurations"
}
@ -62,8 +72,12 @@ de tz =
, folderManage = Messages.Comp.FolderManage.de tz
, customFieldManage = Messages.Comp.CustomFieldManage.de tz
, bookmarkManage = Messages.Comp.BookmarkManage.de
, addonArchiveManage = Messages.Comp.AddonArchiveManage.de
, addonRunConfigManage = Messages.Comp.AddonRunConfigManage.de tz
, manageData = "Daten verwalten"
, bookmarks = "Bookmarks"
, addonArchives = "Addons"
, addonRunConfigs = "Addon Run Configurations"
}
@ -77,6 +91,10 @@ fr tz =
, folderManage = Messages.Comp.FolderManage.fr tz
, customFieldManage = Messages.Comp.CustomFieldManage.fr tz
, bookmarkManage = Messages.Comp.BookmarkManage.fr
, addonArchiveManage = Messages.Comp.AddonArchiveManage.fr
, addonRunConfigManage = Messages.Comp.AddonRunConfigManage.fr tz
, manageData = "Gestion des métadonnées"
, bookmarks = "Favoris"
, addonArchives = "Addons"
, addonRunConfigs = "Addon Run Configurations"
}

View File

@ -12,6 +12,8 @@ module Page.ManageData.Data exposing
, init
)
import Comp.AddonArchiveManage
import Comp.AddonRunConfigManage
import Comp.BookmarkManage
import Comp.CustomFieldManage
import Comp.EquipmentManage
@ -31,6 +33,8 @@ type alias Model =
, folderManageModel : Comp.FolderManage.Model
, fieldManageModel : Comp.CustomFieldManage.Model
, bookmarkModel : Comp.BookmarkManage.Model
, addonArchiveModel : Comp.AddonArchiveManage.Model
, addonRunConfigModel : Comp.AddonRunConfigManage.Model
}
@ -42,6 +46,12 @@ init flags =
( bm, bc ) =
Comp.BookmarkManage.init flags
( aam, aac ) =
Comp.AddonArchiveManage.init flags
( arm, arc ) =
Comp.AddonRunConfigManage.init flags
in
( { currentTab = Just TagTab
, tagManageModel = m2
@ -51,10 +61,14 @@ init flags =
, folderManageModel = Comp.FolderManage.empty
, fieldManageModel = Comp.CustomFieldManage.empty
, bookmarkModel = bm
, addonArchiveModel = aam
, addonRunConfigModel = arm
}
, Cmd.batch
[ Cmd.map TagManageMsg c2
, Cmd.map BookmarkMsg bc
, Cmd.map AddonArchiveMsg aac
, Cmd.map AddonRunConfigMsg arc
]
)
@ -67,6 +81,8 @@ type Tab
| FolderTab
| CustomFieldTab
| BookmarkTab
| AddonArchiveTab
| AddonRunConfigTab
type Msg
@ -78,3 +94,5 @@ type Msg
| FolderMsg Comp.FolderManage.Msg
| CustomFieldMsg Comp.CustomFieldManage.Msg
| BookmarkMsg Comp.BookmarkManage.Msg
| AddonArchiveMsg Comp.AddonArchiveManage.Msg
| AddonRunConfigMsg Comp.AddonRunConfigManage.Msg

View File

@ -7,6 +7,8 @@
module Page.ManageData.Update exposing (update)
import Comp.AddonArchiveManage
import Comp.AddonRunConfigManage
import Comp.BookmarkManage
import Comp.CustomFieldManage
import Comp.EquipmentManage
@ -15,11 +17,12 @@ import Comp.OrgManage
import Comp.PersonManage
import Comp.TagManage
import Data.Flags exposing (Flags)
import Data.UiSettings exposing (UiSettings)
import Page.ManageData.Data exposing (..)
update : Flags -> Msg -> Model -> ( Model, Cmd Msg, Sub Msg )
update flags msg model =
update : Flags -> UiSettings -> Msg -> Model -> ( Model, Cmd Msg, Sub Msg )
update flags uiSettings msg model =
case msg of
SetTab t ->
let
@ -28,16 +31,16 @@ update flags msg model =
in
case t of
TagTab ->
update flags (TagManageMsg Comp.TagManage.LoadTags) m
update flags uiSettings (TagManageMsg Comp.TagManage.LoadTags) m
EquipTab ->
update flags (EquipManageMsg Comp.EquipmentManage.LoadEquipments) m
update flags uiSettings (EquipManageMsg Comp.EquipmentManage.LoadEquipments) m
OrgTab ->
update flags (OrgManageMsg Comp.OrgManage.LoadOrgs) m
update flags uiSettings (OrgManageMsg Comp.OrgManage.LoadOrgs) m
PersonTab ->
update flags (PersonManageMsg Comp.PersonManage.LoadPersons) m
update flags uiSettings (PersonManageMsg Comp.PersonManage.LoadPersons) m
FolderTab ->
let
@ -60,6 +63,20 @@ update flags msg model =
in
( { m | bookmarkModel = bm }, Cmd.map BookmarkMsg bc, Sub.none )
AddonArchiveTab ->
let
( aam, aac ) =
Comp.AddonArchiveManage.init flags
in
( { m | addonArchiveModel = aam }, Cmd.map AddonArchiveMsg aac, Sub.none )
AddonRunConfigTab ->
let
( arm, arc ) =
Comp.AddonRunConfigManage.init flags
in
( { m | addonRunConfigModel = arm }, Cmd.map AddonRunConfigMsg arc, Sub.none )
TagManageMsg m ->
let
( m2, c2 ) =
@ -117,3 +134,23 @@ update flags msg model =
, Cmd.map BookmarkMsg c2
, Sub.map BookmarkMsg s2
)
AddonArchiveMsg lm ->
let
( aam, aac, aas ) =
Comp.AddonArchiveManage.update flags lm model.addonArchiveModel
in
( { model | addonArchiveModel = aam }
, Cmd.map AddonArchiveMsg aac
, Sub.map AddonArchiveMsg aas
)
AddonRunConfigMsg lm ->
let
( arm, arc, ars ) =
Comp.AddonRunConfigManage.update flags uiSettings.timeZone lm model.addonRunConfigModel
in
( { model | addonRunConfigModel = arm }
, Cmd.map AddonRunConfigMsg arc
, Sub.map AddonRunConfigMsg ars
)

View File

@ -7,6 +7,8 @@
module Page.ManageData.View2 exposing (viewContent, viewSidebar)
import Comp.AddonArchiveManage
import Comp.AddonRunConfigManage
import Comp.BookmarkManage
import Comp.CustomFieldManage
import Comp.EquipmentManage
@ -27,7 +29,7 @@ import Styles as S
viewSidebar : Texts -> Bool -> Flags -> UiSettings -> Model -> Html Msg
viewSidebar texts visible _ settings model =
viewSidebar texts visible flags settings model =
div
[ id "sidebar"
, class S.sidebar
@ -134,6 +136,32 @@ viewSidebar texts visible _ settings model =
[ text texts.bookmarks
]
]
, a
[ href "#"
, onClick (SetTab AddonArchiveTab)
, menuEntryActive model AddonArchiveTab
, class S.sidebarLink
, classList [ ( "hidden", not flags.config.addonsEnabled ) ]
]
[ Icons.addonIcon ""
, span
[ class "ml-3" ]
[ text texts.addonArchives
]
]
, a
[ href "#"
, onClick (SetTab AddonRunConfigTab)
, menuEntryActive model AddonRunConfigTab
, class S.sidebarLink
, classList [ ( "hidden", not flags.config.addonsEnabled ) ]
]
[ Icons.addonRunConfigIcon ""
, span
[ class "ml-3" ]
[ text texts.addonRunConfigs
]
]
]
]
@ -166,6 +194,20 @@ viewContent texts flags settings model =
Just BookmarkTab ->
viewBookmarks texts flags settings model
Just AddonArchiveTab ->
if flags.config.addonsEnabled then
viewAddonArchives texts flags settings model
else
[]
Just AddonRunConfigTab ->
if flags.config.addonsEnabled then
viewAddonRunConfigs texts flags settings model
else
[]
Nothing ->
[]
)
@ -306,3 +348,33 @@ viewBookmarks texts flags settings model =
]
, Html.map BookmarkMsg (Comp.BookmarkManage.view texts.bookmarkManage settings flags model.bookmarkModel)
]
viewAddonArchives : Texts -> Flags -> UiSettings -> Model -> List (Html Msg)
viewAddonArchives texts flags settings model =
[ h2
[ class S.header1
, class "inline-flex items-center"
]
[ Icons.addonIcon ""
, div [ class "ml-2" ]
[ text texts.addonArchives
]
]
, Html.map AddonArchiveMsg (Comp.AddonArchiveManage.view texts.addonArchiveManage settings flags model.addonArchiveModel)
]
viewAddonRunConfigs : Texts -> Flags -> UiSettings -> Model -> List (Html Msg)
viewAddonRunConfigs texts flags settings model =
[ h2
[ class S.header1
, class "inline-flex items-center"
]
[ Icons.addonRunConfigIcon "mr-4"
, div [ class "ml-2" ]
[ text texts.addonRunConfigs
]
]
, Html.map AddonRunConfigMsg (Comp.AddonRunConfigManage.view texts.addonRunConfigManage settings flags model.addonRunConfigModel)
]

View File

@ -35,6 +35,11 @@ sidebarLink =
" mb-2 px-4 py-3 flex flex-row hover:bg-blue-100 dark:hover:bg-slate-600 hover:font-bold rounded rounded-lg items-center "
successText : String
successText =
" text-green-600 dark:text-lime-500 "
successMessage : String
successMessage =
" border border-green-600 bg-green-50 text-green-600 dark:border-lime-800 dark:bg-lime-300 dark:text-lime-800 px-4 py-2 rounded "
@ -281,7 +286,7 @@ link =
inputErrorBorder : String
inputErrorBorder =
" border-red-600 dark:border-orange-600 "
" ring dark:ring-0 ring-red-600 dark:border-orange-600 "
inputLabel : String

View File

@ -14,12 +14,42 @@ module Util.List exposing
, findNext
, findPrev
, get
, removeByIndex
, replaceByIndex
, sliding
)
import Html.Attributes exposing (list)
removeByIndex : Int -> List a -> List a
removeByIndex index list =
List.indexedMap
(\idx ->
\e ->
if idx == index then
Nothing
else
Just e
)
list
|> List.filterMap identity
replaceByIndex : Int -> a -> List a -> List a
replaceByIndex index element list =
let
repl idx e =
if idx == index then
element
else
e
in
List.indexedMap repl list
changePosition : Int -> Int -> List a -> List a
changePosition source target list =
let

View File

@ -9,6 +9,7 @@ module Util.String exposing
( appendIfAbsent
, crazyEncode
, ellipsis
, firstSentenceOrMax
, isBlank
, isNothingOrBlank
, underscoreToSpace
@ -16,7 +17,6 @@ module Util.String exposing
)
import Base64
import Html exposing (strong)
crazyEncode : String -> String
@ -45,6 +45,26 @@ ellipsis len str =
String.left (len - 1) str ++ ""
firstSentenceOrMax : Int -> String -> Maybe String
firstSentenceOrMax maxLen str =
let
idx =
String.indexes "." str
|> List.head
|> Maybe.map ((+) 2)
|> Maybe.map (min maxLen)
|> Maybe.withDefault maxLen
len =
String.length str
in
if len <= maxLen then
Nothing
else
Just <| String.left (idx - 1) str ++ ""
withDefault : String -> String -> String
withDefault default str =
if str == "" then

View File

@ -96,4 +96,8 @@
.markdown-preview a {
@apply text-blue-400 hover:text-blue-500 dark:text-sky-200 dark:hover:text-sky-100 cursor-pointer;
}
.markdown-preview pre {
@apply font-mono px-2 py-2 text-sm border dark:border-slate-600 rounded my-2;
}
}