portal

Sam Ritchie 2023-05-23T15:55:58.649479Z

Portal Q! I get that I can build a custom viewer by creating a reagent component. But let’s say I want to use some cljs library like Mafs.cljs when building the components — is this possible with portal?

Sam Ritchie 2023-05-30T22:21:26.416219Z

@djblue

(do (portal.api/eval-str
     "(ns portal-present.viewer
        (:require [portal.ui.api :as p]
            [\"mafs\" :as m]    
            [portal.ui.inspector :as ins]
            [reagent.core :as r]))

 (defn view-scene [_]
  [:> m/Mafs {}
     [:> (.-Cartesian m/Coordinates) {}]])

(portal.ui.api/register-viewer!
 {:name ::mafs
  :predicate (constantly true)
  :component view-scene})")
    (tap>
     ^{:portal.viewer/default :portal-present.viewer/mafs}
     []))

Sam Ritchie 2023-05-30T22:22:13.101559Z

this is a “hello world”; I am fairly certain I’m going to need to add any new library I want to use to the SCI context

Sam Ritchie 2023-05-30T22:23:57.171719Z

yeah, I think I’ll need to extend

djblue 2023-05-30T22:25:34.516039Z

The npm support in portal looks like it can import that module

djblue 2023-05-30T22:25:49.830089Z

Are you getting a specific error?

Sam Ritchie 2023-05-30T22:26:01.218569Z

Show: Project-Only All 
  Hide: Clojure Java REPL Tooling Duplicates  (14 frames hidden)

1. Unhandled clojure.lang.ExceptionInfo
   Could not resolve symbol: m/Mafs
   {:error
    #error {:message "Could not resolve symbol: m/Mafs", :data {:type :sci/error, :line 8, :column 2, :file nil, :phase "analysis"}},
    :message "Could not resolve symbol: m/Mafs",
    :op :portal.rpc/response,
    :portal.rpc/id 39}
              launcher.clj:  117  portal.runtime.jvm.launcher/eval-str
              launcher.clj:  110  portal.runtime.jvm.launcher/eval-str
                  api.cljc:  138  portal.api$eval_str/invokeStatic
                  api.cljc:  122  portal.api$eval_str/invoke
                  api.cljc:  136  portal.api$eval_str/invokeStatic
                  api.cljc:  122  portal.api$eval_str/invoke
                  api.cljc:  134  portal.api$eval_str/invokeStatic
                  api.cljc:  122  portal.api$eval_str/invoke
                      REPL:  743  emmy.mafs.core/eval67631
                      REPL:  743  emmy.mafs.core/eval67631
             Compiler.java: 7194  clojure.lang.Compiler/eval
             Compiler.java: 7183  clojure.lang.Compiler/eval
             Compiler.java: 7149  clojure.lang.Compiler/eval
                  core.clj: 3215  clojure.core/eval
                  core.clj: 3211  clojure.core/eval
    interruptible_eval.clj:   87  nrepl.middleware.interruptible-eval/evaluate/fn/fn
                  AFn.java:  152  clojure.lang.AFn/applyToHelper
                  AFn.java:  144  clojure.lang.AFn/applyTo
                  core.clj:  667  clojure.core/apply
                  core.clj: 1990  clojure.core/with-bindings*
                  core.clj: 1990  clojure.core/with-bindings*
               RestFn.java:  425  clojure.lang.RestFn/invoke
    interruptible_eval.clj:   87  nrepl.middleware.interruptible-eval/evaluate/fn
                  main.clj:  437  clojure.main/repl/read-eval-print/fn
                  main.clj:  437  clojure.main/repl/read-eval-print
                  main.clj:  458  clojure.main/repl/fn
                  main.clj:  458  clojure.main/repl
                  main.clj:  368  clojure.main/repl
               RestFn.java: 1523  clojure.lang.RestFn/invoke
    interruptible_eval.clj:   84  nrepl.middleware.interruptible-eval/evaluate
    interruptible_eval.clj:   56  nrepl.middleware.interruptible-eval/evaluate
    interruptible_eval.clj:  152  nrepl.middleware.interruptible-eval/interruptible-eval/fn/fn
                  AFn.java:   22  clojure.lang.AFn/run
               session.clj:  218  nrepl.middleware.session/session-exec/main-loop/fn
               session.clj:  217  nrepl.middleware.session/session-exec/main-loop
                  AFn.java:   22  clojure.lang.AFn/run
               Thread.java:  833  java.lang.Thread/run

Sam Ritchie 2023-05-30T22:28:15.799869Z

I first tried to require a cljs file that was on the classpath. If i do this:

(do (portal.api/eval-str
     "(ns portal-present.viewer
        (:require [portal.ui.api :as p]
            [mafs.core :as m]
            [mafs.coordinate :as c]
            [reagent.core :as r]))

 (defn view-scene [_]
  [m/Mafs [m/Cartesian]])

(p/register-viewer!
 {:name ::mafs
  :predicate (constantly true)
  :component view-scene})")
    (tap>
     ^{:portal.viewer/default :portal-present.viewer/mafs}
     []))
I get
1. Unhandled clojure.lang.ExceptionInfo
   Could not resolve symbol: reagent/with-let
   {:error
    #error {:message "Could not resolve symbol: reagent/with-let", :data {:type :sci/error, :line 190, :column 1, :file "file:/Users/sritchie/code/clj/mafs.cljs/src/mafs/core.cljs", :phase "analysis"}},
    :message "Could not resolve symbol: reagent/with-let",
    :op :portal.rpc/response,
    :portal.rpc/id 40}

djblue 2023-05-30T22:29:05.907889Z

What version of Portal are you using?

djblue 2023-05-30T22:30:06.391699Z

Here is what I get when I use that viewer

djblue 2023-05-30T22:30:45.055089Z

Is there some css that needs to be side loaded?

Sam Ritchie 2023-05-30T22:31:25.385659Z

Ah, there is!

Sam Ritchie 2023-05-30T22:31:40.139099Z

this is with 0.40.0

Sam Ritchie 2023-05-30T22:31:57.707429Z

[""
          ""
          ""]

Sam Ritchie 2023-05-30T22:32:01.484739Z

these are the 3 css files

Sam Ritchie 2023-05-30T22:32:19.418889Z

I am puzzled that this is working, but if we can get it going this will be an awesome viewer, I’m really excited

1
Sam Ritchie 2023-05-30T22:32:26.014979Z

to get this going without the ceremony of a Clerk notebook

Sam Ritchie 2023-05-30T22:32:44.803749Z

I figured out today how to do a decent job of getting values together that describe interactive scenes

💯 1
Sam Ritchie 2023-05-30T22:34:49.983439Z

to get set up, I followed this: https://github.com/djblue/portal#api after adding this dep:

djblue/portal {:mvn/version "0.40.0"}

djblue 2023-05-30T22:41:13.411909Z

After I get the css loaded, I get this so this does look promising 👌

Sam Ritchie 2023-05-30T22:42:30.711569Z

the actual viewer I will end up writing is something like an eval-viewer… where I am going to pass the reagent component in as my value, something like this built server-side:

[mafs.core/Mafs
 [mafs.coordinates/Cartesian {}]
 [mafs.core/Point {:x 1, :y 2}]]
and then the component will… I guess eval it directly

Sam Ritchie 2023-05-30T22:42:42.711249Z

@djblue so what was the secret??

Sam Ritchie 2023-05-30T22:43:13.642589Z

I may have to run shortly to take my daughter to the pool, but I am very motivated here

👍 1
djblue 2023-05-30T22:43:23.234669Z

Did you npm install mafs to the root of the repl process? That might be the main difference here if we are using the same version but your Portal is unable to resolve the dependency 🤔

Sam Ritchie 2023-05-30T22:45:23.644399Z

I started my repl via CIDER so I wonder if it has a different root

Sam Ritchie 2023-05-30T22:45:49.719129Z

It’s npm installed for sure

djblue 2023-05-30T22:47:03.237359Z

https://github.com/djblue/portal/blob/master/src/portal/runtime/npm.cljc#L53C44-L61 is the npm resolution algo Portal uses 🤔

Sam Ritchie 2023-05-30T22:47:07.173789Z

Clearly it was able to find the mafs.cljs dependency and start loading it, so once I get this solved I bet the rest will work

djblue 2023-05-30T22:47:56.215949Z

I think for the cljs code to load, I will need to update sci to include with-let 👍

djblue 2023-05-30T22:51:36.864379Z

Probably should just use this in Portal https://github.com/babashka/sci.configs/blob/main/src/sci/configs/reagent/reagent.cljs

👍 1
djblue 2023-05-30T23:07:20.738679Z

In trying to load mafs.macros, I did run into "No matching clause: require-macros" which I think is a limitation in sci 🤔

Sam Ritchie 2023-05-31T00:32:47.472779Z

I have a new version without the macros, I’ll send the branch tonight and more info

👍 1
djblue 2023-05-31T00:33:13.256419Z

(ns mafs.macros
  #?(:cljs
     (:require [reagent.core])))

(defmacro defcomponent
  ([sym component]
   `(defcomponent ~sym "" {} ~component))
  ([sym docstring component]
   `(defcomponent ~sym ~docstring {} ~component))
  ([sym docstring attr component]
   {:pre [(string? docstring)
          (map? attr)]}
   (let [m (-> (meta sym)
               (merge (assoc attr :doc docstring)))]
     `(def ~(with-meta sym m)
        (reagent.core/adapt-react-class ~component)))))
worked for me locally

djblue 2023-05-31T00:33:36.029679Z

(do (portal.api/eval-str
       "(ns portal-present.viewer
        (:require [portal.ui.api :as p]
            [mafs.core :as m]
            [mafs.coordinates :as c]
            [reagent.core :as r]))

(defn inject [href]
  (let [link (.createElement js/document \"link\")]
    (set! (.-rel link) \"stylesheet\")
    (set! (.-href link) href)
    (js/document.head.appendChild link)))

(inject \"\")
(inject \"\")
(inject \"\")

 (defn view-scene [_]
  [m/Mafs [c/Cartesian]])

(p/register-viewer!
 {:name ::mafs
  :predicate (constantly true)
  :component view-scene})")
      (tap>
       ^{:portal.viewer/default :portal-present.viewer/mafs}
       []))
Works for me with https://github.com/djblue/portal/commit/26188aec167e6e617c5869cbecbcbdba6e9e01a6

djblue 2023-05-31T00:34:44.492869Z

Also, if you want to load the code from a file you can

(portal.api/eval-str "(require 'portal-present.viewer :reload)")

djblue 2023-05-31T00:36:55.632459Z

Do you have other hello world examples? I would like to try some stuff out before I cut a release 👌

djblue 2023-05-31T00:39:48.823829Z

Also, I think it might be nice to have some helpers to get resources from node_modules that aren't js files. Would that be something useful to you? That way the viewer could work entirely offline.

Sam Ritchie 2023-05-31T00:45:20.400899Z

Yes I’ll send some this evening, I’m in family maelstrom! I know you’re not pressuring I’m just excited so checking in

👌 1
Sam Ritchie 2023-05-31T00:46:32.904819Z

I think it would be helpful to get the CSS out, as I think you’re anticipating. Assuming we’d be able to load those in portal and wouldn’t see an error from loading a file://

Sam Ritchie 2023-05-31T00:47:48.669589Z

so anything in https://mafs.mentat.org/ inside of a show-sci form should work. The only change to make is expanding the mafs alias into mafs.core and reagent into reagent.core

🙏 1
Sam Ritchie 2023-05-31T00:48:28.525009Z

this branch removes all of the macro code, so pulling this in via git dep should solve that issue

Sam Ritchie 2023-05-31T00:54:53.449039Z

https://github.com/mentat-collective/Mafs.cljs/pull/17

Sam Ritchie 2023-05-31T02:13:44.269789Z

I’m running into this error when using portal as a git dep:

(base) [sritchie@wintermute ~/code/clj/emmy-viewers (sritchie/mafs)]$ clj -X:deps prep :aliases '[:nextjournal/clerk :dev]'
Prepping io.github.djblue/portal in /Users/sritchie/.gitlibs/libs/io.github.djblue/portal/26188aec167e6e617c5869cbecbcbdba6e9e01a6
Execution error (FileNotFoundException) at tasks.docs/eval461$loading (docs.clj:1).
Could not locate examples/data__init.class, examples/data.clj or examples/data.cljc on classpath.

Full report at:
/var/folders/xw/0lq56zhn4hb4lknppw_k086c0000gn/T/clojure-4188203586225886789.edn
Execution error (ExceptionInfo) at clojure.tools.deps/prep-libs!$fn (deps.clj:711).
Prep function could not be resolved: tasks.build/prep
I’m going to apply your patch to the clj file inside the 0.40.0 jar 🙂

Sam Ritchie 2023-05-31T02:30:20.400269Z

I am trying a local build but running into trouble where I can’t get the prep step to run. I will debug more or maybe wait for a release here, but looks like this is all close to working well

djblue 2023-05-31T02:44:07.953319Z

If you have the repo locally, bb dev should get you going

Sam Ritchie 2023-05-31T02:44:48.789469Z

ah, very nice, I use those too and should have checked

djblue 2023-05-31T02:45:31.405859Z

Looks like I broke prep recently 😭

Sam Ritchie 2023-05-31T02:47:18.053149Z

we have liftoff

Sam Ritchie 2023-05-31T02:47:20.073089Z

2
🚀 2
djblue 2023-05-31T02:49:36.066089Z

If you find the need to tweak any of the portal code, try (portal.api/open {:mode :dev})

Sam Ritchie 2023-05-31T03:01:58.416919Z

I can get an interactive thing up, but it doesn’t look like reagent atoms are doing their thing inside of portal - probably a mistake on my part

Sam Ritchie 2023-05-31T03:02:04.244999Z

let me know if you think this should work:

Sam Ritchie 2023-05-31T03:02:30.608089Z

first, this:

(portal.api/eval-str
     "(ns portal-present.viewer
        (:require [portal.ui.api :as p]
            [mafs.debug]
            [mafs.line]
            [mafs.core]
            [mafs.coordinates]
            [mafs.plot]
            [reagent.core :as r]))

(defn inject [href]
  (let [link (.createElement js/document \"link\")]
    (set! (.-rel link) \"stylesheet\")
    (set! (.-href link) href)
    (js/document.head.appendChild link)))

(inject \"\")
(inject \"\")
(inject \"\")


(p/register-viewer!
 {:name ::mafs
  :predicate (constantly true)
  :component eval})")

Sam Ritchie 2023-05-31T03:02:41.462479Z

notice that I have :component eval

Sam Ritchie 2023-05-31T03:03:40.619919Z

then this works:

(-> '[mafs.core/Mafs [mafs.coordinates/Cartesian]]
     (with-meta {:portal.viewer/default :portal-present.viewer/mafs})
     (tap>))

Sam Ritchie 2023-05-31T03:04:15.511499Z

Sam Ritchie 2023-05-31T03:04:40.459679Z

a more complex one also looks great! but the atom state doesn’t seem to update, which means that elements can’t respond reactively. this is obviously some sort of bug on my side, since you have the slideshow example where state does work

Sam Ritchie 2023-05-31T03:05:30.935429Z

(-> '(reagent.core/with-let
       [G__68160 (reagent.core/atom [0 0])]
       [:<>
        [:pre (clojure.core/pr-str @G__68160)]
        [mafs.core/Mafs
         [mafs.coordinates/Cartesian {}]
         (reagent.core/with-let
           [G__68161
            (js/Function.
             "[y0001]"
             "[p0002]"
             "  const _0006 = - p0002;\n  const _0008 = _0006 + y0001;\n  const _0013 = Math.sinh(_0008);\n  const _0014 = Math.cosh(_0008);\n  const _0015 = Math.pow(_0014, 4.0);\n  return (- 6.0 * Math.pow(_0013, 4.0) + 8.0 * Math.pow(_0013, 2.0) * Math.pow(_0014, 2.0) - 2.0 * _0015) / _0015;")]
           [mafs.plot/OfX
            {:y
             (let [G__68162 (mapv @G__68160 [0])] (fn [x] (G__68161 [x] G__68162)))}])
         (reagent.core/with-let
           [G__68163
            (js/Function.
             "[y0001]"
             "[p0002]"
             "  const _0006 = - p0002;\n  const _0008 = _0006 + y0001;\n  const _0013 = Math.sinh(_0008);\n  const _0014 = Math.cosh(_0008);\n  const _0015 = Math.pow(_0014, 4.0);\n  return (- 6.0 * Math.pow(_0013, 4.0) + 8.0 * Math.pow(_0013, 2.0) * Math.pow(_0014, 2.0) - 2.0 * _0015) / _0015;")
            G__68165
            (js/Function. "y0001" "  return Math.cos(y0001);")]
           [mafs.plot/Inequality
            {:y
             {:<=
              (let [G__68164 (mapv @G__68160 [0])] (fn [x] (G__68163 [x] G__68164))),
              :> G__68165},
             :color :blue}])
         [mafs.core/MovablePoint {:atom G__68160, :constrain "horizontal"}]]])
    (with-meta {:portal.viewer/default :portal-present.viewer/mafs})
    (tap>))

Sam Ritchie 2023-05-31T03:05:35.219839Z

(there’s the code for that example, generated of course, not handwritten)

Sam Ritchie 2023-05-31T03:07:19.832969Z

this simpler example seems to fail to update a reagent atom inside of with-let . So maybe with-let is the problem?

(-> '(reagent.core/with-let [!click (reagent.core/atom 0)]
       [:div
        "The atom " [:code "!click"] " has value: "
        @!click ". "
        [:input {:type :button :value "Click me!" :on-click #(swap! !click inc)}]])
    (with-meta {:portal.viewer/default :portal-present.viewer/mafs})
    (tap>))

Sam Ritchie 2023-05-31T03:08:47.515499Z

I have to duck out for now, but my next debugging steps are 1. see if this works when I don’t use eval 2. try it without with-let, maybe that has some error curious if you have thoughts too. But this is now looking like it will be trivial for me to ship support for Clerk AND portal with my “emmy-viewers” project, so folks can do 2d stuff like above and also 3d:

💯 1
🎉 1
djblue 2023-05-31T03:12:29.994119Z

(-> '(fn []
       (reagent.core/with-let [!click (reagent.core/atom 0)]
         [:div
          "The atom " [:code "!click"] " has value: "
          @!click ". "
          [:input {:type :button
                   :value "Click me!"
                   :on-click
                   (fn [e]
                     (js/console.log e)
                     (.stopPropagation e)
                     (swap! !click inc))}]]))
    (with-meta {:portal.viewer/default :portal-present.viewer/mafs})
    (tap>))
Works for me

djblue 2023-05-31T03:12:57.878399Z

I think this is related to it not being a "react" component so wrapping it in a fn produces a functional component 👌

djblue 2023-05-31T03:13:47.272489Z

BTW using eval as your viewer is so good chef_kiss

djblue 2023-05-31T03:15:13.181629Z

Also, love the js function definitions as strings

djblue 2023-05-31T03:33:00.069219Z

I would also be down to ship this type of viewer in portal, something like portal.viewer/reagent 🤔

djblue 2023-05-31T03:38:37.235449Z

Then the only thing Emmy needs is the default viewer metadata which could be included unconditionally

Sam Ritchie 2023-05-31T04:15:16.461749Z

that sounds great

Sam Ritchie 2023-05-31T04:15:33.669309Z

yeah, the eval-style viewer cracked this project open for me

Sam Ritchie 2023-05-31T04:18:12.886129Z

the reason it is important is that Emmy can compile these fast JS function definitions down from high level code after running a powerful simplifier on it… I actually defined that function body as

(fn [shift]
  (((cube D) tanh) (- identity shift)))
where the return value is using • function algebra! (- identity shift) is actually a function, (fn [x] (- x shift) , and the whole form is actually (fn [x] (((cube D) tanh) (- x shift))) • automatic differentiation… ((cube D) tanh) is the 3rd derivative of tanh

djblue 2023-05-31T04:19:40.397389Z

It makes a lot of sense, you have a specialized optimizer / compiler targeting js 👌

djblue 2023-05-31T04:20:37.891139Z

I feel like I can fully appreciate your conj talk now, it's all coming together for me 🙏

Sam Ritchie 2023-05-31T04:20:54.665139Z

I come bearing reagent fragments!!

😆 1
Sam Ritchie 2023-05-31T04:21:46.117519Z

thanks for all of your help here, I remain happily surprised at how easy it is to fuse together clojure projects that weren’t explicitly designed to fuse

💯 1
2
❤️ 1
djblue 2023-05-31T05:50:10.413739Z

Also, got git deps working again, I guess for a prep builds you don't automatically get your top level :paths/:deps by default 🤔

djblue 2023-05-23T16:43:43.130049Z

The sci implementation in portal should be able to load cljs code from the classpath so in theory this should be possible 👍

Sam Ritchie 2023-05-23T16:44:29.577869Z

Oh got it! I might have to make a custom build that adds all the code I need into the sci context in that case

Sam Ritchie 2023-05-23T16:44:43.434789Z

But that’s very comfortable at this point :)

djblue 2023-05-23T16:45:47.285999Z

What type of dependencies does your viewer have?

Sam Ritchie 2023-05-23T16:46:22.847839Z

Mafs.cljs builds on the mafs npm package

djblue 2023-05-23T16:47:17.239219Z

So you can pull js directly from node_modules, https://github.com/djblue/portal/tree/master/examples/plotly-viewer is an example of dynamically loading plotly

Sam Ritchie 2023-05-23T16:48:17.925659Z

Oh very cool

djblue 2023-05-23T16:48:23.967549Z

I don't think people have used this dependency loading code in portal so it might have issues, but I'm down to fix any issues you run into 👍

Sam Ritchie 2023-05-23T16:48:46.167769Z

Okay I’ll take a look!! That’s awesome, I forgot it might be simpler because we’re staying local vs deploying a bundle

👍 1
djblue 2023-05-23T16:50:28.067099Z

https://cljdoc.org/d/djblue/portal/0.40.0/doc/guides/custom-viewer is some docs on setting the viewer within the Portal ui runtime

djblue 2023-05-23T16:51:13.430509Z

https://github.com/djblue/portal/blob/0.40.0/examples/portal-present/src/portal_present/viewer.cljs is a more complete example

👍 1
djblue 2023-05-23T16:51:24.398299Z

Please let me know if you run into any issues 🙏

Sam Ritchie 2023-05-23T16:51:36.170909Z

Will do, thanks for your support!

💯 1
Sam Ritchie 2023-05-23T15:57:05.785779Z

I have an example here in Clerk where (plot/of-x f) returns a reagent fragment, [mafs.plot/OfX {:y <my-compiled-function-body>}], which Clerk then evaluates into an actual reagent component using SCI

👀 1
Sam Ritchie 2023-05-23T15:58:46.030419Z

so it feels like this is something I should be able to do in Portal, to let these plot objects render correctly.