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

@ -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" ] []
]