Fork me on GitHub
#hyperfiddle
<
2023-10-05
>
pez12:10:49

Here’s a way to detect key presses, and stop detecting them when the app loses focus. It emits a key state if the currently pressed keys, but if some other tab or window becomes active or something like find-in-page happens, the key state will be cleared. Another thing handled is that you can tell the flow to preventDefault on some particular keys. NB: It doesn’t handle the case that you might want to register a key that is pressed when the app gains focus. Thoughts?

❤️ 4
sergey.shvets18:10:24

Hi, first time trying Electric, and I got stuck on building a recursive tree in UI. Is there a way to do a forward declaration for e/defn? Similar to declare in Clojure?

sergey.shvets18:10:40

Oh, nevermind e/def + binding work, I just forgot to call it with new.

Dustin Getz18:10:27

best workaround is probably letfn

Dustin Getz18:10:49

yeah, binding a lambda into dynamic scope also works

sergey.shvets21:10:37

Do I have to use e/fn in dom handlers? I'm trying to handle drop event and it has a very noticeable delay (about 0.5 seconds) before firing? My assumption is that the delay is due to some propagation of reactive events through graph? Ideally, I'd like to skip the whole electric graph for this handler, as it is a pure UI handler.

Dustin Getz22:10:09

dom/on! is idiomatic, not sure what’s up with the delay, that is unexpected would need to see more context

Dustin Getz22:10:34

dom/on! takes a clojure fn

sergey.shvets23:10:45

with all the drag events as dom/on! things work as expected. I think I pinned down the problem. "dragover" handler with e/fn probably fired too many events and it was processing it. As soon as I switched that to e/on! delay disappeared. dragover fires non-stop (at least every animation frame) while dragging, so it makes sense that it may create a long queue of events to propagate through graph.

👀 1
sergey.shvets23:10:10

But nice to know about e/on! I'll use it when I don't need reactivity

sergey.shvets23:10:22

Thanks for the quick response!

Dustin Getz00:10:05

hmm maybe, we relieve backpressure in these event apis so that shouldn’t be the issue, however the event apis that take e/fn have FSMs inside them for dealing with possibility of latency they are all super complicated so i expect it’s an interaction with that. This is all getting paved but not until next spring at this point

sergey.shvets00:10:04

Makes sense. Glad there is a workaround for now!

Fabim03:10:24

I’m experiencing the same delay for on drop. the drag over dom/on "drop" areas seems to go in slow motion, because I use dom/on "dragenter"and e/on-unmount in "drop" to update the dragging states in atoms. on! did not fix it. The UI feels laggy while dragging

sergey.shvets04:10:07

Probably try to debounce some of the events?

Fabim04:10:27

@U4EFBUCUE you mean using debounce of missionary?

sergey.shvets05:10:52

If I understand correct what happening, some events spams a bunch of events and then delay is happening while those events propagate through dag over and over again. If you reduce number of those events it should help reduce delay.

Fabim05:10:50

how would I reduce the events? I need to update the drag state so that ui and logic can adapt to the change

sergey.shvets05:10:42

Is it possible for you to put logic into only dragstart/drop? Those fire once. I have some logic in dragenter/dragleave but that only setting some classes on DOM so I can move it easily into dom/on!

Fabim05:10:14

(dom/on "dragover" (e/fn [e] (.preventDefault e)))
                       (dom/on! "dragenter" (fn [_] (reset! !stage-dragged-to id)))
                       (dom/on "drop" (e/fn [_]
                                        (when (= stage-dragged-to id)
                                          (e/server
                                            (e/discard
                                              (d/transact! !conn
                                                           [{:db/id lead-dragging
                                                             :lead/stage id}]))))
                                        (e/on-unmount (fn []
                                                        (reset! !lead-dragging nil)
                                                        (reset! !stage-dragged-to nil)))))

Fabim05:10:06

the state is used so switch css classes as well, I could add those using js but then its less declarative.

sergey.shvets05:10:29

I suggest you get target where it dragged within the drop event. I have "droppable" class on allowable target and then do the following in drop:

(dom/on! "drop" (fn [evt]
                       (.stopPropagation evt)
                       (js/console.log "Handle Drop" evt "tree-state" tree-state)
                       (let [target     (.closest (gobj/get evt "target") ".droppable")
                             block-id   (.getAttribute target "data-block-id")
                             block-type (:dragged tree-state)]
                         #?(:cljs (do (trigger-tree-command target
                                                            {:block-id   block-id
                                                             :command    :add-item
                                                             :block-type block-type})
                                      (trigger-tree-command target
                                                            {:command :set-dragged
                                                             :target  nil}
                                                            ))))
                       false))

sergey.shvets05:10:56

look at the first let binding.

sergey.shvets05:10:11

just find a closest droppable to where it dropped.

sergey.shvets05:10:17

This way you can drop dragenter logic and I hope remove the delay

Fabim05:10:22

if I remove the dragenter the UI can’t update while the user is dragging

Fabim05:10:50

how does your trigger-tree-command look like?

sergey.shvets05:10:51

Try to make dragover a dom/on! command and return false from it after preventing default. Trigger-tree-command is just firing a custom js event.

#?(:cljs (defn trigger-tree-command
           [el data]
           (let [command (js/CustomEvent. "treeCommand" #js {"detail" data "bubbles" true})]
             (.dispatchEvent el command))))

sergey.shvets05:10:48

I capture it within the root div to localize all state updates.

(dom/on "treeCommand" (e/fn [evt]
                                    (let [data (gobj/get evt "detail")]
                                      (case (:command data)
                                        :toggle-opened (swap! !state update :opened (fn [opened]
                                                                                      (if (contains? opened (:block-id data))
                                                                                        (disj opened (:block-id data))
                                                                                        (conj opened (:block-id data)))))

                                        :set-dragged   (swap! !state assoc :dragged (:target data))
                                        
                                        :add-item      (e/server 
                                                        (page-model/block-command (merge {:builder/page-id page-id} e/*http-request*)
                                                                                  {:command   :add-item
                                                                                   :block-id  (:block-id data)
                                                                                   :item-type (:block-type data)}))))))

Fabim05:10:29

interesting. rebuilding reframe haha if you have one !state atom wouldn’t e/watch on !state signal everytime anything in the state changes? how do you listen for only parts of the !state on the client

sergey.shvets05:10:50

!state is a client-side only atom and I guess it's updates every time something changes, but it's not that often. My whole Electric implementation is for one very interactive form, not for the whole application.

sergey.shvets05:10:41

I also believe that even when it signals it won't update parts of the graph that haven't changed, but I'm not 100% sure about this.

sergey.shvets05:10:31

It's my first day with Electric, so take everything I say with a grain of salt :))

Dustin Getz10:10:26

send us a working gist or repo that is slow and we will make it fast for you

Fabim10:10:14

Thanks. DMed

xificurC10:10:57

> if you have one !state atom wouldn’t e/watch on !state signal everytime anything in the state changes? yes, the !state watch will trigger. But it might be less costly than expected. E.g. in (expensive (:bar state)) • there's an (swap! !state update :foo inc) somewhere in the app • the state watch fires • state changed, so (:bar state) re-runs • (:bar state) returns the same value • (expensive (:bar state)) doesn't re-run because the arguments didn't change

❤️ 1
xificurC10:10:03

how do you listen for only parts of the !state on the client1 atom should have 1 e/watch to prevent FRP glitches. Passing part of the state means selecting a part of the watched value

(let [!state (atom {}), state (e/watch !state)]
  (Foo (:foo state)))

Fabim10:10:58

okay will unify the atoms into one !state atom then to avoid multiple rerenders.

Fabim10:10:46

the (dom/on! "dragenter" (fn [_] (reset! !stage-dragged-to id))) still causes the multiple changes causing the ui to lag behind if I drag over multiple elements. would need a way do discard reset! if a new one is triggered

xificurC11:10:26

we don't advertise any state management solution. Single atom, many atoms, datascript db, rama... All work and have their tradeoffs. I'm just pointing out a global atom is a viable option in many scenarios so if it is one's preference one is fine to try it out

Fabim11:10:01

maybe there is an easy way to use missionary to queue up client state updates and only signal updates to the e/watch when no other state update is queued

Dustin Getz11:10:50

i still suspect something wrong in userland, dom/on! relieves backpressure, it is not slow

sergey.shvets15:10:54

@U010L3S1XHS can you try wrap your state reset! in some simple debouncer? You can write one in pure js with setTimeout/clearTimeout. While it won't solve the delay UI problem, it will at least help figure out if that's related to Electric or something else. With debouncer all of your handlers should fire immediately once you timeout interval passes, if the problem was too many events scheduled by dragenter/dragover. Put something like 16ms. Or even simpler comment those handlers and see if drop fires immediately and then return them one by one to see where the problem is.

Fabim06:10:31

@U4EFBUCUE thanks for the suggestion. even when removing on! dragenter the on drop still is delayed for a second. not sure why because there are no state changes before.

xificurC06:10:47

we haven't seen a case where electric would lag behind one second. If there's a clear repro we can take a quick look

👍 1
Fabim10:10:46

the (dom/on! "dragenter" (fn [_] (reset! !stage-dragged-to id))) still causes the multiple changes causing the ui to lag behind if I drag over multiple elements. would need a way do discard reset! if a new one is triggered