membrane

zimablue 2022-11-29T12:00:37.116279Z

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

phronmophobic 2022-11-29T18:33:11.088189Z

> 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).

phronmophobic 2022-11-29T18:33:56.295509Z

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

phronmophobic 2022-11-29T18:46:44.715569Z

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

zimablue 2022-11-29T18:58:54.272189Z

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

phronmophobic 2022-11-29T19:03:35.331489Z

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.

phronmophobic 2022-11-29T19:04:27.705349Z

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

zimablue 2022-11-29T19:05:05.050789Z

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

zimablue 2022-11-29T19:05:50.935729Z

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

zimablue 2022-11-29T19:06:24.375639Z

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

phronmophobic 2022-11-29T19:10:20.016269Z

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

phronmophobic 2022-11-29T19:13:18.656409Z

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

phronmophobic 2022-11-29T19:20:12.215429Z

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})))

phronmophobic 2022-11-29T19:21:54.956069Z

Here's a more "real-world" example of calling a dynamic component, https://github.com/phronmophobic/spreadsheet/blob/main/src/com/phronemophobic/membrane/spreadsheet.clj#L668

zimablue 2022-11-29T19:22:19.880119Z

Ooh interesting thanks I'll take a look

zimablue 2022-11-29T22:51:01.064979Z

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?

phronmophobic 2022-11-29T22:52:52.304919Z

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.

phronmophobic 2022-11-29T22:54:05.388939Z

the extra and context symbols exist inside of defui implicitly

phronmophobic 2022-11-29T22:56:50.561079Z

maybe the https://github.com/phronmophobic/membrane/blob/master/src/membrane/example/kitchen_sink.clj#L247 example is more approachable

phronmophobic 2022-11-29T22:59:26.673149Z

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.

zimablue 2022-11-29T23:00:06.669479Z

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

zimablue 2022-11-29T23:01:36.036439Z

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

phronmophobic 2022-11-29T23:04:01.697049Z

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.

zimablue 2022-11-29T23:05:10.884199Z

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

phronmophobic 2022-11-29T23:05:37.130239Z

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.

zimablue 2022-11-29T23:06:07.791929Z

yeah I'm with you so far

phronmophobic 2022-11-29T23:06:42.470939Z

> 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

zimablue 2022-11-29T23:08:03.196689Z

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

phronmophobic 2022-11-29T23:09:04.861469Z

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

zimablue 2022-11-29T23:09:28.993979Z

wow this is clojure syntax I haven't seen lol

zimablue 2022-11-29T23:09:38.921519Z

it that extended metadata syntax

zimablue 2022-11-29T23:10:03.216969Z

*is that

phronmophobic 2022-11-29T23:10:19.387279Z

I guess it's less common, but yea

phronmophobic 2022-11-29T23:11:29.882879Z

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.

zimablue 2022-11-29T23:12:10.206689Z

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

phronmophobic 2022-11-29T23:12:30.144129Z

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.

phronmophobic 2022-11-29T23:14:09.074389Z

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

zimablue 2022-11-29T23:14:09.583669Z

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)

zimablue 2022-11-29T23:15:47.893679Z

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
phronmophobic 2022-11-29T23:18:52.626649Z

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.

phronmophobic 2022-11-29T23:19:20.151889Z

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

zimablue 2022-11-29T23:20:15.509999Z

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
phronmophobic 2022-11-29T23:21:44.361729Z

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.

zimablue 2022-11-29T23:22:02.218589Z

which improvement?

phronmophobic 2022-11-29T23:22:39.538179Z

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

🦾 1
zimablue 2022-11-29T23:23:21.735559Z

got you

phronmophobic 2022-11-29T23:25:11.970659Z

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)

zimablue 2022-11-29T23:34:22.656919Z

: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

phronmophobic 2022-11-29T23:35:08.395529Z

It seems to work in my repl

phronmophobic 2022-11-29T23:36:21.954339Z

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

zimablue 2022-11-29T23:36:52.835819Z

you think you know a language lol

😆 1
zimablue 2022-11-29T23:37:04.643339Z

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

phronmophobic 2022-11-29T23:37:20.054449Z

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

zimablue 2022-11-29T23:37:23.768079Z

even kondo doesn't understand

phronmophobic 2022-11-29T23:37:43.297889Z

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

zimablue 2022-11-29T23:37:58.979019Z

😂

zimablue 2022-11-30T00:41:02.779479Z

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

zimablue 2022-11-30T00:41:07.260419Z

(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)))

zimablue 2022-11-30T00:41:43.361179Z

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

zimablue 2022-11-30T00:42:27.238529Z

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

zimablue 2022-11-30T00:42:54.316849Z

(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)))

phronmophobic 2022-11-30T00:43:00.659009Z

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

zimablue 2022-11-30T00:43:19.467559Z

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

👍 1
zimablue 2022-11-30T00:43:34.639819Z

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

zimablue 2022-11-30T00:44:08.216899Z

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

phronmophobic 2022-11-30T00:44:52.777559Z

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

zimablue 2022-11-30T00:46:11.294019Z

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

phronmophobic 2022-11-30T00:46:38.958939Z

let me try it locally.

zimablue 2022-11-30T00:48:11.393829Z

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

👍 1
phronmophobic 2022-11-30T00:54:50.306379Z

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))}))

phronmophobic 2022-11-30T00:55:53.187389Z

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

phronmophobic 2022-11-30T00:56:11.049919Z

Which version of membrane are you using?

zimablue 2022-11-30T07:56:20.111339Z

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

zimablue 2022-11-30T07:56:39.812029Z

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

zimablue 2022-11-30T07:58:07.390629Z

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

zimablue 2022-11-30T14:53:38.342529Z

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

zimablue 2022-11-30T14:55:50.413609Z

;; 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})]

phronmophobic 2022-11-30T18:31:25.748039Z

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.

phronmophobic 2022-11-30T18:32:42.489469Z

Do you have the error?

zimablue 2022-11-30T18:33:32.106499Z

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

zimablue 2022-11-30T18:33:53.166279Z

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

phronmophobic 2022-11-30T18:34:15.608519Z

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

zimablue 2022-11-30T18:34:47.800379Z

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

zimablue 2022-11-30T18:35:09.098319Z

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

zimablue 2022-11-30T18:35:10.293529Z

And see

phronmophobic 2022-11-30T18:35:11.890719Z

oh ok. hmmm

zimablue 2022-11-30T18:36:04.344669Z

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

zimablue 2022-11-30T18:36:15.351949Z

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

phronmophobic 2022-11-30T18:36:23.652089Z

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}))))

phronmophobic 2022-11-30T18:36:41.054479Z

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

zimablue 2022-11-30T18:38:07.011439Z

Is that an identity with a smuggled side effect

zimablue 2022-11-30T18:38:13.511909Z

Nice I should make a note of that

zimablue 2022-11-30T18:39:03.061029Z

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

phronmophobic 2022-11-30T18:39:08.866709Z

haha. I use println debugging often 😬

zimablue 2022-11-30T18:39:30.694799Z

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

zimablue 2022-11-30T18:40:07.257829Z

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

phronmophobic 2022-11-30T18:40:38.154989Z

I'll sometimes use https://github.com/phronmophobic/spreadsheet

phronmophobic 2022-11-30T18:41:13.326569Z

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

zimablue 2022-11-30T18:41:47.656629Z

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

zimablue 2022-11-30T18:41:55.980269Z

I saw you had something like that already

phronmophobic 2022-11-30T18:42:05.946849Z

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

phronmophobic 2022-11-30T18:42:44.291789Z

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.

zimablue 2022-11-30T18:44:03.752419Z

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

zimablue 2022-11-30T18:44:09.038139Z

I say we, borkdude mostly

zimablue 2022-11-29T15:10:49.271519Z

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