Fork me on GitHub
#portal
<
2023-06-01
>
Sam Ritchie13:06:03

@djblue question — where does the code live that makes the magic possible, for SCI to go find NPM code or cljs code like mafs?

djblue15:06:59

Are you running into issues with the code?

Sam Ritchie15:06:00

I was interested for @U5H74UNSF and Clerk

Sam Ritchie15:06:18

Since this is such a nice way to extend the system with no custom build

djblue15:06:21

Gotcha, btw there is also portal.api/repl which will convert your nrepl repl eval to use portal.api/eval-str when evaluating code 👌

djblue15:06:07

Then eval :cljs/quit to drop the repl back into clj mode

mkvlr16:06:42

@djblue very cool 👏

🙏 2
Sam Ritchie19:06:39

@djblue one more for you. is there some way that I can have portal perform some transform server-side before I pass the object to the browser? For example, if I have (+ 'x 'y) in Emmy, that evaluates to an Expression that wraps the list '(+ x y). If I want to make a LaTeX viewer, I have to first call emmy.env/->TeX to get a string on the server side, and then my viewer can handle rendering the string using katex or mathjax or something.

Sam Ritchie19:06:55

or for mafs, one trick I’ve been using in Clerk is the following: this form

(mafs/of-x sin)
evaluates to
'[mafs.plot/OfX {:y Math/sin}]
but that will NOT actually work as a reagent component. So I first pass to a function which returns
'[mafs.core/Mafs {}
   [mafs.coordinates/Cartesian {}]
   [mafs.plot/OfX {:y Math/sin}]]
and THEN passes that along to the reagent / eval renderer

djblue19:06:38

So, since not all runtimes support eval in an easy way, I've opted to expose most functionality via rpc. The good news is that it's equivalent to (apply f & args). In the runtime you can register functions via (portal.api/register! #'my.ns/f) and then in the ui runtime, you can invoke the function via (portal.ui.rpc/call 'my.ns/f & args)

djblue20:06:14

This will return a promise and deal with serialization both ways

djblue20:06:14

If you dont want to deal with the rpc stuff, adding the pregenerated latex string as metadata on the list might be easier

Sam Ritchie20:06:36

got it - so in that case, I’d need to find a new approach that doesn’t involve adding a function value as metadata. here’s a better example: I want

(mafs/of-x sin)
to wrap itself up in [mafs [cartesian] …] like above. but if you nest it, like
(mafs/mafs (mafs/of-x sin) (mafs/of-x cos))
then I don’t want any wrapping. I’ve been implementing this like
(mafs/of-x sin)
;;=> (with-meta '[mafs.plot/OfX {:y Math/sin}] {:transform #(mafs/mafs (mafs/cartesian) %)})

Sam Ritchie20:06:12

I stick a transform on as metadata and then apply it before serializing the value out to Clerk if that metadata entry exists

Sam Ritchie20:06:39

but then if you nest that value I ignore the metadata (actually I go explicitly strip it out so it doesn’t mess with serialization)

djblue20:06:02

I don't do this normally, but that function should be callable via rpc as well since it gets stuffed into the portal cache during serialization

djblue20:06:48

(portal.ui.rpc/call (:transform (meta value)) value) might work :thinking_face:

Sam Ritchie20:06:10

nice, even if it’s a function value under :transform? I wouldn’t expect that to make it across the wire, but you’re saying something gets across that I can use to make an RPC call/

djblue20:06:07

The value you get in the ui is like a box, which when given back to the host runtime will get the original captured value 👌

djblue20:06:49

So when the host runtime goes to call apply, the fn should show back up for the party 🎉

Sam Ritchie20:06:36

wild if that does the trick

djblue20:06:15

Also, just as a cool note, when said box gets gc'd in the ui, it will automatically be evicted from the portal cache in the host runtime

Sam Ritchie20:06:23

okay very cool. I probably need to just go try this now!

Sam Ritchie20:06:51

I’m trying to tease apart my clerk-specific viewer code from the actual reagent fragment assembly so I can enable portal and clerk separately with most of the same stuff

💯 2
Sam Ritchie03:06:33

@djblue okay, I am getting closer… Given something like this (simplified so it doesn’t involve any mafs)

(tap> ^{:transform
        (fn [v]
          ^{:portal.viewer/default :portal-present.viewer/reagent}
          [:div "The value is " v])}
      [:pre "cake"])
the goal is to see

Sam Ritchie03:06:03

I have it working with

(defn submit [value]
  (if-let [xform (:transform (meta value))]
    (submit (xform (vary-meta value dissoc :transform)))
    (p/submit value)))
but when I try to do an RPC call from the viewer (snippet and result coming):

Sam Ritchie03:06:50

(p/register-viewer!
 {:name ::reagent
  :predicate (constantly true)
  :component (fn [form]
               (eval (list 'fn []
                       (if-let [xform (:transform (meta form))]
                         (if (fn? xform)
                           (portal.ui.rpc/call xform form)))))
                         form)})

Sam Ritchie03:06:41

I’m not seeing anything… I am sure I’m making some simple mistake

Sam Ritchie03:06:59

tapping my submit vs p/submit is not a good solution of course

djblue03:06:00

The rpc call is going to yield a promise which is probably making reagent/react unhappy

djblue03:06:49

I think you might need to use a react/useEffect to make the rpc call and .then the promise

Sam Ritchie04:06:49

interesting, and then eval the result of that call, and presumably I can pass that out as a component…

👍 2
Sam Ritchie04:06:11

for v1 of this I can also just tell the user that they can’t use this transform feature, so (mafs/of-x sin) wouldn’t work standalone

Sam Ritchie04:06:46

I’ll give that a try in the AM, thank you!

👍 2
djblue04:06:27

If you have a branch, I'd also be happy to help 👌

Sam Ritchie04:06:39

I’ll get a repro set up with some simple test cases for us in the AM 🙂 I’ve massaged the clerk support so that I don’t need to ship a clerk dependency anymore in https://github.com/mentat-collective/emmy-viewers; the user can use either emmy.clerk or emmy.portal to activate support, and the rest of the library is now ONLY about building these quoted forms that can be ingested by clerk or portal (or other viewer libraries, if those exist now/later)

Sam Ritchie04:06:22

I would just say “use this branch I’m on” but I’ve got a local/root dep that I need to back out so this can run easily for you. THANK YOU for the offer, I’ll push something in the AM!

👍 2
Sam Ritchie16:06:57

@djblue okay, I’ve got a repro up! The code is in https://github.com/mentat-collective/emmy-viewers, under the sritchie/mafs branch. You should be able to start a REPL with either the :portal or the :nextjournal/clerk profile enabled, and then go to dev/examples/portal.clj for my repro notes: https://github.com/mentat-collective/emmy-viewers/blob/b77b9a98bf695c1085178af342ec4f6490f09ca2/dev/examples/portal.clj Things feel pretty good for the code that does NOT need expansion. For the code that does need expansion, I am hitting

;; main.js:3403 Uncaught (in promise) Error: Doesn't support
       ;; namespace: [object Object] at uh (main.js:3403:404) at
       ;; main.js:4704:202
Here is my attempt: https://github.com/mentat-collective/emmy-viewers/blob/b77b9a98bf695c1085178af342ec4f6490f09ca2/src/emmy/portal/viewer.cljs#L38-L56

djblue17:06:00

I see, https://github.com/djblue/portal/blob/master/src/portal/ui/state.cljs#L232 is probably the issue, should be an easy fix in portal to support this

💯 2
djblue18:06:27

Until I get this fixed, you could try the following to get unstuck:

(portal.api/register! #'clojure.core/apply)
(portal.ui.rpc/call 'clojure.core/apply xform [form])

Sam Ritchie18:06:59

I could also I guess write an “expand” function and rpc call that, basically that’s what you’re suggesting but implementing it outside the viewer code might make things more reusable

Sam Ritchie19:06:23

Amazing!!! I will try this shortly!