Fork me on GitHub
#membrane
<
2022-11-29
>
zimablue12:11:37

I still nesting components to be tough, as in this thread: https://clojurians.slack.com/archives/CVB8K7V50/p1643730530089629 I understand I think the three approaches, more or less: 1. rewrite your code so that membrane's macro can interpret it, 2. manually recreate the reference rewriting, 3. intercept all the events (which is sort of starting from scratch) 1. and 2. are both pretty tough/opaque, as you either have to know perfectly which code will and won't be understood by the macro, or have to understand the macro to a level where you can rewrite all the refs by hand. 3. is kind of doing the work you're using a component to avoid. My specific use case is an arbitrary number of textboxes at the root of a quite complex layout pipeline, they're complicated enough that I don't want to write from scratch, but also take 5 explicit parameters (?) and (4? :extra :$extra :context :$context) implicit ones

phronmophobic18:11:11

> 1. and 2. are both pretty tough/opaque, as you either have to know perfectly which code will and won't be understood by the macro, or have to understand the macro to a level where you can rewrite all the refs by hand. 3. is kind of doing the work you're using a component to avoid. Yea, I think that's a fair point. I've improved on this a bit, but I think there's still more room for improvement. The most recent version of membrane now supports destructuring (inside both let and for ). There's also a list of supported forms in this https://phronmophobic.github.io/membrane/membrane-topics.html#Defui-Limitations of the docs. Ideally, membrane components should "just work". Are there any forms that are missing that you think should be included? There's an issue for adding better support for type of use case you're describing (https://github.com/phronmophobic/membrane/issues/61). It probably makes sense to add an example somewhere that explicitly wires components (maybe https://phronmophobic.github.io/membrane/membrane-topics.html#Explicitly-Providing-References).

phronmophobic18:11:56

> (4? :extra :$extra :context :$context) implicit ones Yes, that's right.

phronmophobic18:11:44

Do you have some code/psuedo code for how you would want to express your UI? Maybe we can have membrane support that.

zimablue18:11:54

I think I can work with the new "for" support, that plus the document are a great addition for me

phronmophobic19:11:35

cool. It's always hard to know what's missing or unclear. If you run into anything that's clunky or confusing, don't hesitate to ask.

phronmophobic19:11:27

There should also probably be a way to get feedback if you do accidentally use an "unsupported form".

zimablue19:11:05

So I was trying to reverse engineer an explicit binding to work out how to be able to pass explicitly to an arbitrary intermediate fn, maybe the biggest question was what happens with the four implicit parameters, the other complicating factor was how to pass the ^contextual focus pair of params as presumably the rules are different

zimablue19:11:50

I mean what you've built is a small compiler inside a cond statement right, I think that's kind of unavoidable and must have some implications

zimablue19:11:24

As in similarly to core.async, as described in the "deep walking macros" YouTube video

phronmophobic19:11:20

Yea, it probably makes sense to write it to use the tools.analyzer lib at some point.

phronmophobic19:11:18

Regarding the 4 implicit parameters, it's really 2 parameters and their references. extra is for the component's incidental state and context is used for the component's contextual state

phronmophobic19:11:12

One trick to avoid dealing with default and contextual parameters is to write a small wrapper component that takes care of that for you:

(defui my-wrapped-component [{:keys [text]}]
  (basic/textarea {:text text}))


(defui component-with-defui-call [{:keys [my-text my-component]}]
  (let [my-component-extra (get extra [:my-component-id])]
    (my-component {:text my-text
                   :$text $my-text
                   :extra my-component-extra
                   :$extra $my-component-extra
                   :context context
                   :$context $context})))

zimablue19:11:19

Ooh interesting thanks I'll take a look

zimablue22:11:01

I don't understand your code snippet, is there a typo where the my-component inside the let should be my-wrapped component, and do the extra and context symbols exist inside the macroexpansion in a kind of anaphoric way?

phronmophobic22:11:52

my-component isn't a typo. since components are regular functions, my-component is being passed as a parameter. However, the defui macro doesn't know that it's a component, so you have to manually wire the implicit extra and context parameters.

phronmophobic22:11:05

the extra and context symbols exist inside of defui implicitly

phronmophobic22:11:26

the API for dynamic components like this could use some work because I haven't quite figured out the best way to expose it, but it's really useful in certain scenarios.

zimablue23:11:06

re your previous snippet: I see that is an interesting example of a pattern. I was just trying to think of a similar thing, can I write a fn that returns the state and component as a tuple then apply the first to the second in a defui so that it wires up. Your pattern shows how to get around the component being opaque but what if the params (in the map) are also opaque, ideally something like (apply c (merge m-with-params {:extra extra :context context :$extra...})) would work, but it won't because defui won't understand the merge call right

zimablue23:11:36

sorry my code is missing a square bracket should be [(merge...

phronmophobic23:11:01

Let's say you have:

(defui a-component ...)

(defui my-parent-component [{:keys []]
  (a-component (merge ...)))
With the latest version of membrane (see https://github.com/phronmophobic/membrane/issues/54) the call to a-component will now get any missing incidental state since a-component resolves to a defui component.

zimablue23:11:10

it would almost be nice to be able to tag defui components with metadata, just tell the outer defui macro that this parameter is a component

phronmophobic23:11:37

if you had this instead:

(defui my-parent-component [{:keys [dynamic-component]]
  (dynamic-component (merge ...)))
The call to dynamic-component won't automatically get incidental state.

zimablue23:11:07

yeah I'm with you so far

phronmophobic23:11:42

> it would almost be nice to be able to tag defui components with metadata, just tell the outer defui macro that this parameter is a component I think that actually works, but it's undocumented since I'm not totally sure what the API should look like for this

zimablue23:11:03

so something like (defui [{:keys [^some-component-tag component state]}] (apply component state))

phronmophobic23:11:04

(defui check-dynamic-call-with-meta [{:keys [my-component]}]
  (^{:arglists ([{:keys [text extra context]}]),
     :membrane.component/special? true,}
   my-component {}))

zimablue23:11:28

wow this is clojure syntax I haven't seen lol

zimablue23:11:38

it that extended metadata syntax

phronmophobic23:11:19

I guess it's less common, but yea

phronmophobic23:11:29

This is a bit of implementation detail, so I can't guarantee it won't break at some point, but just trying to explain some of the concepts.

zimablue23:11:10

this is very helpful, knowing that extra and context are what I'd call anaphoric is very useful and seeing this metadata to trigger treatment as a component

phronmophobic23:11:30

Thanks for sharing your thoughts. It really does help me think about some of the use cases so that I can work on a more permanent solution.

phronmophobic23:11:09

Adding a link to this discussion on the github issue before I forget , https://github.com/phronmophobic/membrane/issues/61

zimablue23:11:09

in your example, the text key is necessary? that means that the defui needs to know what the component is even if it's effectively wiring a pointer to a component to a pointer to an opaque map (which is knows the "location/path" of)

zimablue23:11:47

thanks so much for your help, fwiw I'm trying to use membrane in anger and imo the "edge/advanced" cases might actually where membrane has the most value as there's a venn diagram intersection of "want arbitrary control/flexiblity" "not using the DOM/react/a fixed java framework"

👍 1
phronmophobic23:11:52

The text key is necessary for the current implementation. Right now, the caller does a lot of work passing incidental state along. It's probably possible to delegate that work to the defui implementation. As an example, if you call (textarea {:text my-text}), currently the caller will add the missing incidental state like the cursor position. However, as long as textarea receives the extra and context parameters, then the textarea implementation could probably look for missing state in extra automatically.

phronmophobic23:11:20

Some of the recent changes simplified the defui implementation a bit which would make that easier.

zimablue23:11:15

I think what you've given me is enough for my problem: wiring textboxes that come out of a long and opaque pipeline back into a defui component that connects them, I can use your metadata method, I and manually list the parameters

👍 1
phronmophobic23:11:44

I think that should be a relatively small change to implement that improvement. Even though membrane is marked as beta*, I still try to avoid breaking changes. So even though it's a small change, it is a big enough change that I would want to check to make sure it's backwards compatible.

zimablue23:11:02

which improvement?

phronmophobic23:11:39

making it so that the text key wouldn't be required in the metadata

🦾 1
phronmophobic23:11:11

Just realizing if I remove the need to have the text key, then dynamic calls could be supported just by using a single metadata key:

(^::component/component my-dynamic-component arg)

zimablue23:11:22

:arglists ([{:keys [text extra context]}])
is this part in your snippet right? inside the metadata it's ok to have that list with vector as first element? or is it missing a quote

phronmophobic23:11:08

It seems to work in my repl

phronmophobic23:11:21

I think since this is a reader macro, it's correct without the quote

zimablue23:11:52

you think you know a language lol

😆 1
zimablue23:11:04

not you as in "one thinks", apparently I have much to learn

phronmophobic23:11:20

> Note that metadata reader macros are applied at read-time, not at evaluation-time

zimablue23:11:23

even kondo doesn't understand

phronmophobic23:11:43

oh, I didnt mean to get you in trouble with kondo 😛

zimablue00:11:02

so the first part "producing" and second "consuming" works great:

zimablue00:11:07

(mapv (fn [_]
           [textarea {:text "text 3"}])
         (range 1))
   (vec (for [[c s] c+ss]
          (^{:arglists ([{:keys [text border? font focus textarea-state extra context]}])
             :membrane.component/special? true}
           c
           s)))

zimablue00:11:43

but when there's an intermediate defui it seems not to be working

zimablue00:11:27

(defui wrapped-textarea [{:keys [x y
                                 text]
                           :or {x 0 y 0}}]
  (translate x y (textarea {:text text})))

zimablue00:11:54

(mapv (fn [_]
           [wrapped-textarea {:x 0 :y 0 :text "text 4"}])
         (range 1))
   (vec (for [[c s] c+ss]
          (^{:arglists ([{:keys [x y
                                 text
                                 extra
                                 context]}])
             :membrane.component/special? true}
           c
           s)))

phronmophobic00:11:00

so the first example works, but the second doesn't?

zimablue00:11:19

if that makes sense, the mapv and vec fragments are in different parts of the code but they're corresponding consumers and producers

👍 1
zimablue00:11:34

I've tried it passing through more or less parameters explicitly to the wrapped textarea

zimablue00:11:08

also as an aside not sure if textarea or buffer is more appropriate, buffer is backed by liquid and textarea more component-coded from what I can see but I don't know enough about liquid to know what will be the key difference

phronmophobic00:11:52

the buffer/liquid stuff is meant for clojure code whereas textarea is meant for text editing

zimablue00:11:11

I realize that a workaround would be to apply my translations etc explicitly in the "binding" defui which might be manageable but will get verbose pretty quickly, another workaround would be to tree-traverse the defui and expand a macro for doing that but a bit fiddly

phronmophobic00:11:38

let me try it locally.

zimablue00:11:11

I'm heading in, I'll check back tomorrow if you've gotten to it, thanks again!

👍 1
phronmophobic00:11:50

This seems to work for me:

(defui wrapped-textarea [{:keys [x y
                                 text]
                           :or {x 0 y 0}
                          :as m}]
  (ui/translate x y (basic/textarea {:text text})))

(defui parent-wrapper [{:keys [c+ss]}]
  (vec
   (for [[c s] c+ss]
     (^{:arglists ([{:keys [x y
                            text
                            extra
                            context]}])
        :membrane.component/special? true}
      c
      s))))

(backend/run (component/make-app #'parent-wrapper
                                 {:c+ss
                                  (mapv (fn [i]
                                          [wrapped-textarea {:x (* 5 i) :y (* i 30) :text (str "text " i)}])
                                        (range 5))}))

phronmophobic00:11:53

The mistake I make a lot is using defn instead of defui when defining a component.

phronmophobic00:11:11

Which version of membrane are you using?

zimablue07:11:20

After a night's sleep the same code is working, sorry I must have gotten to the delusional stage

zimablue07:11:39

Or more likely wiping the shadow-cljs dir eliminated some compilation problem

zimablue07:11:07

I'm using the latest master on your GitHub but patched so that it loads on nodes main process whilst silently not loading audio or fonts or the main webgl driver but components etc work, also added rotation for webgl but haven't touched the core component code

zimablue14:11:38

Also for me this is sort of partly working, did you try in webgl, clicking the bottom textarea and entering text? I get this weird error something like the translation offset doesn't propagate inside the wrapped component so unless it's sitting on 0 0 or close I can't click it

zimablue14:11:50

;; works mode.translate-textarea7 [(rectangle 0 1000) (translate 0 300 (textarea {:text translate-textarea3}))] ;; fails :mode.translate-textarea8 [(rectangle 0 1000) (wrapped-textarea {:text translate-textarea3 :x 0 :y 300})] ;; works :mode.translate-textarea9 [(rectangle 0 1000) (wrapped-textarea {:text translate-textarea3 :x 0 :y 0})]

phronmophobic18:11:25

I was able to run my version with webgl:

(defui wrapped-textarea [{:keys [x y
                                 text]
                           :or {x 0 y 0}
                          :as m}]
  (ui/translate x y
                (basic/textarea {:text text})))

(defui parent-wrapper [{:keys [c+ss]}]
  (vec
   (for [[c s] c+ss]
     (^{:arglists ([{:keys [x y
                            text
                            extra
                            context]}])
        :membrane.component/special? true}
      c
      s))))

(defn -main []
  (defonce canvas-info (webgl/run
                         (membrane.component/make-app #'parent-wrapper
                                                      {:c+ss
                                                       (mapv (fn [i]
                                                               [wrapped-textarea {:x (* 5 i) :y (* i 30) :text (str "text " i)}])
                                                             (range 5))})
                         {:container canvas})))
I did have to clear the shadow cljs cache before it would work though.

phronmophobic18:11:42

Do you have the error?

zimablue18:11:32

I'm running a sightly more involved method but the key parts look identical, I'm clearing the cache each time now and ran enough tests to be certain that with my setup, nested translation is somehow stopping the ability to gain focus, I'll make a blank solution and do a truly minimal test

zimablue18:11:53

As in I'm using a cond statement etc it shouldn't be different in any other key ways

phronmophobic18:11:15

My suspicion is that it's not related to the position, but that either the :$extra, :$context, or another reference key isn't a path to an editable location

zimablue18:11:47

But my tests only modified position and made the difference between working and not working

zimablue18:11:09

Several times with a cleared cache, but I'll do the difference and make a totally blank project

zimablue18:11:04

Maybe there's a difference buried in the fluff around this but I sure hope not, I'll also check membrane versions but I had bumped it

zimablue18:11:15

Also I'm using electron I also pray it's not that

phronmophobic18:11:23

another debugging trick is to use wrap-on to print out the intents and events that are going to the text area:

(defui wrapped-textarea [{:keys [x y
                                 text]
                           :or {x 0 y 0}
                          :as m}]
  (ui/translate x y
                (ui/wrap-on
                 (fn [handler pos]
                   (let [intents (handler pos)]
                     (prn pos intents)
                     intents))
                 (basic/textarea {:text text}))))

phronmophobic18:11:41

I don't think electron should affect it (hopefully 🙏 )

zimablue18:11:07

Is that an identity with a smuggled side effect

zimablue18:11:13

Nice I should make a note of that

zimablue18:11:03

I'll reproduce so you don't think I'm going mad although I gave up and moved the translation explicitly to the wrapper if that makes sense

phronmophobic18:11:08

haha. I use println debugging often 😬

zimablue18:11:30

And then it worked , as in my first example but pulling the numbers from the state

zimablue18:11:07

I use a central atom that I log things to by topic and sometime serialize but everyone loves portal and tap

phronmophobic18:11:13

I'll probably make my own version of portal/clerk/reveal at some point

zimablue18:11:47

I have the same ambition let me know if you do lol although I'm busy failing to make and sell a product

zimablue18:11:55

I saw you had something like that already

phronmophobic18:11:05

The thing I don't get about portal is it seems like the data representation is pretty sparse.

phronmophobic18:11:44

I use https://github.com/phronmophobic/viscous all the time. I feel like it should be the default printer everywhere, but I seem to be the only one.

zimablue18:11:03

I heretically think that Emacs kind of holds the community back and we're slowly reinventing a more useful light table with calva/joyride/portal

zimablue18:11:09

I say we, borkdude mostly

zimablue15:11:49

this documentation is new since I last looked and helpful though, thanks for that https://phronmophobic.github.io/membrane/membrane-topics.html#Defui-Components