summaryrefslogtreecommitdiff
path: root/elm
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2019-10-30 12:39:07 +0100
committerYorhel <git@yorhel.nl>2019-11-10 12:44:55 +0100
commit683c2298dcdbda96d44e23d5f4db3f3c959d1161 (patch)
treeae3064f7723e21c1e5f153a56d10b02cdcbffcfa /elm
parentdc243fb2d89c69611e3c2a154749c14643a69db0 (diff)
ulist: Inline assigning labels to VNs
I'm really unhappy with the workarounds to deal with the global onClick subscription doing the right thing, but I wasn't able to find a good alternative.
Diffstat (limited to 'elm')
-rw-r--r--elm/Lib/Html.elm4
-rw-r--r--elm/ULists/LabelEdit.elm117
-rw-r--r--elm/ULists/ManageLabels.elm2
-rw-r--r--elm/ULists/ManageLabels.js4
4 files changed, 124 insertions, 3 deletions
diff --git a/elm/Lib/Html.elm b/elm/Lib/Html.elm
index d9c0594a..770e9142 100644
--- a/elm/Lib/Html.elm
+++ b/elm/Lib/Html.elm
@@ -12,6 +12,10 @@ import Lib.Api as Api
onClickN : m -> Attribute m
onClickN action = custom "click" (JD.succeed { message = action, stopPropagation = True, preventDefault = True})
+-- onClick with preventDefault
+onClickD : m -> Attribute m
+onClickD action = custom "click" (JD.succeed { message = action, stopPropagation = False, preventDefault = True})
+
-- onInput that also tells us whether the input is valid
onInputValidation : (String -> Bool -> msg) -> Attribute msg
onInputValidation msg = custom "input" <|
diff --git a/elm/ULists/LabelEdit.elm b/elm/ULists/LabelEdit.elm
new file mode 100644
index 00000000..eb59c823
--- /dev/null
+++ b/elm/ULists/LabelEdit.elm
@@ -0,0 +1,117 @@
+module ULists.LabelEdit exposing (main)
+
+import Html exposing (..)
+import Html.Attributes exposing (..)
+import Html.Events exposing (..)
+import Task
+import Process
+import Browser
+import Browser.Events as E
+import Json.Decode as JD
+import Set exposing (Set)
+import Dict exposing (Dict)
+import Lib.Html exposing (..)
+import Lib.Api as Api
+import Gen.Api as GApi
+import Gen.LabelEdit as GLE
+
+
+main : Program GLE.Recv Model Msg
+main = Browser.element
+ { init = \f -> (init f, Cmd.none)
+ , subscriptions = \model -> if model.opened then E.onClick (JD.succeed (Open False)) else Sub.none
+ , view = view
+ , update = update
+ }
+
+type alias Model =
+ { uid : Int
+ , vid : Int
+ , labels : List GLE.RecvLabels
+ , sel : Set Int -- Set of label IDs applied on the server
+ , tsel : Set Int -- Set of label IDs applied on the client
+ , state : Dict Int Api.State -- Only for labels that are being changed
+ , opened : Bool
+ }
+
+init : GLE.Recv -> Model
+init f =
+ { uid = f.uid
+ , vid = f.vid
+ , labels = f.labels
+ , sel = Set.fromList f.selected
+ , tsel = Set.fromList f.selected
+ , state = Dict.empty
+ , opened = False
+ }
+
+type Msg
+ = Redo Msg
+ | Open Bool
+ | Toggle Int Bool
+ | Saved Int Bool GApi.Response
+
+
+update : Msg -> Model -> (Model, Cmd Msg)
+update msg model =
+ case msg of
+ -- 'Redo' will process the same message again after a very short timeout,
+ -- this is used to overrule an 'Open False' triggered by the onClick
+ -- subscription.
+ Redo m -> (model, Cmd.batch [ Task.perform (\_ -> m) (Task.succeed ()), Task.perform (\_ -> m) (Process.sleep 0) ])
+ Open b -> ({ model | opened = b }, Cmd.none)
+
+ -- The 'opened = True' counters the onClick subscription that would have
+ -- closed the dropdown, this works because that subscription triggers
+ -- before the Toggle (I just hope this is well-defined, otherwise we need
+ -- to use Redo for this one as well).
+ Toggle l b ->
+ ( { model
+ | opened = True
+ , tsel = if b then Set.insert l model.tsel else Set.remove l model.tsel
+ , state = Dict.insert l Api.Loading model.state
+ }
+ , Api.post "/u/ulist/setlabel.json" (GLE.encode { uid = model.uid, vid = model.vid, label = l, applied = b }) (Saved l b)
+ )
+
+ Saved l b (GApi.Success) -> ({ model | sel = if b then Set.insert l model.sel else Set.remove l model.sel, state = Dict.remove l model.state }, Cmd.none)
+ Saved l b e -> ({ model | state = Dict.insert l (Api.Error e) model.state }, Cmd.none)
+
+
+view : Model -> Html Msg
+view model =
+ let
+ str = String.join ", " <| List.filterMap (\l -> if Set.member l.id model.sel then Just l.label else Nothing) model.labels
+
+ item l =
+ let lid = "label_edit_" ++ String.fromInt model.vid ++ "_" ++ String.fromInt l.id
+ in
+ li [ class "linkradio" ]
+ [ inputCheck lid (Set.member l.id model.tsel) (Toggle l.id)
+ , label [ for lid ]
+ [ text l.label
+ , text " "
+ , span [ class "spinner", classList [("invisible", Dict.get l.id model.state /= Just Api.Loading)] ] []
+ , case Dict.get l.id model.state of
+ Just (Api.Error _) -> b [ class "standout" ] [ text "error" ] -- Need something better
+ _ -> text ""
+ ]
+ ]
+
+ loading = List.any (\s -> s == Api.Loading) <| Dict.values model.state
+
+ in
+ div [ class "labeledit" ]
+ [ a [ href "#", onClickD (Redo (Open (not model.opened))) ]
+ [ text <| if str == "" then "-" else str
+ , span []
+ [ if loading && not model.opened
+ then span [ class "spinner" ] []
+ else i [] [ text "▾" ]
+ ]
+ ]
+ , div []
+ [ ul [ classList [("hidden", not model.opened)] ]
+ <| List.map item model.labels
+ ]
+ ]
diff --git a/elm/ULists/ManageLabels.elm b/elm/ULists/ManageLabels.elm
index f6e1d5e5..1e953b0a 100644
--- a/elm/ULists/ManageLabels.elm
+++ b/elm/ULists/ManageLabels.elm
@@ -86,7 +86,7 @@ view model =
]
]
in
- Html.form [ onSubmit Submit, class "labeledit hidden" ]
+ Html.form [ onSubmit Submit, class "managelabels hidden" ]
[ div [ ]
[ b [] [ text "How to use labels" ]
, ul []
diff --git a/elm/ULists/ManageLabels.js b/elm/ULists/ManageLabels.js
index 85312d21..76ee960c 100644
--- a/elm/ULists/ManageLabels.js
+++ b/elm/ULists/ManageLabels.js
@@ -1,6 +1,6 @@
-document.querySelectorAll('#labeledit').forEach(function(b) {
+document.querySelectorAll('#managelabels').forEach(function(b) {
b.onclick = function() {
- document.querySelectorAll('.labeledit').forEach(function(e) { e.classList.toggle('hidden') })
+ document.querySelectorAll('.managelabels').forEach(function(e) { e.classList.toggle('hidden') })
};
return false;
})