This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-04-21
Channels
- # announcements (12)
- # atom-editor (2)
- # babashka (44)
- # beginners (4)
- # calva (3)
- # clj-kondo (10)
- # clj-yaml (1)
- # cljsrn (1)
- # clojars (5)
- # clojure (68)
- # clojure-europe (40)
- # clojure-nl (1)
- # clojure-norway (75)
- # clojure-uk (2)
- # clojurescript (23)
- # conjure (1)
- # datomic (2)
- # defnpodcast (7)
- # emacs (23)
- # etaoin (7)
- # fulcro (7)
- # funcool (2)
- # honeysql (3)
- # hyperfiddle (40)
- # interop (4)
- # jobs (3)
- # lsp (1)
- # malli (16)
- # off-topic (16)
- # parinfer (2)
- # podcasts-discuss (1)
- # portal (61)
- # reitit (6)
- # releases (1)
- # remote-jobs (6)
- # scittle (4)
- # shadow-cljs (18)
- # spacemacs (4)
- # vim (22)
- # xtdb (12)
Is there a way to detect if Portal is already running and only open a new portal window if one is not already running?
In my workflow, I use a dev/user.clj
file to automatically start portal when developing a Clojure project.
;; Open Portal window in browser with dark theme
(inspect/open {:portal.colors/theme :portal.colors/gruvbox})
;; Add portal as a tap> target and listen to all evaluation results (via nREPL)
(add-tap #'portal.api/submit)
I use reload / restart functions (clojure.tools.namespace or IntegrantREPL) when there are significant changes and this is causing my dev/user.clj to be reloaded and a new Portal is opened
If there isnt a way to detect if portal is already connected, then I could
• exclude dev
from the reload via clojure.tools.namespace.repl/set-refresh-dirs - (set-refresh-dirs "src" "resources")
although sometime its handy to include dev
code, but that can be re-evaluated easily enough.
• manually close portal (I'll probably forget 😿 )
• add code to the restart to close portal (possibly leading to a bit of coupling I'd rather not have)
Any other options you can think of?
Thanks.I think you can pass the return value of open back into open to avoid opening multiple windows.
I am afraid I dont seem to know how to do what was suggested, as the following opens two portal windows and when I refresh I get two more
(def portal-instance (inspect/open {:portal.colors/theme :portal.colors/gruvbox}))
(inspect/open portal-instance)
Ohh, just remembered refresh will remove all the vars in the ns. You can call portal.api/sessions to get all currently connected sessions.
(def portal-instance
(or (first (inspect/sessions))
(inspect/open {:portal.colors/theme :portal.colors/gruvbox})))
Should be enough I think :thinking_face:Pretty close, although now it taps everything twice to portal 😆 so if I bring the add tap into the or
form then it should work okay I believe..
Hmm, I tried this but its still showing duplicate results in portal, each time refresh is used then another duplicate tap is added... curious..
(def portal-instance
(or (first (inspect/sessions))
(do
(inspect/open {:portal.colors/theme :portal.colors/gruvbox})
(add-tap #'portal.api/submit))))
Curious. If I (remove-tap #'portal.api/submit)
then portal no longer received the data (expected)
Although a new (add-tap #'portal.api/submit)
will send duplicates to portal (based on how many times restart was used)
So for now I could wrap portal in a function in the dev/user.clj
and it can be called by the developer. This should avoid reloading effecting portal.
Or using (set-refresh-dirs "src" "resources")
also worked well and is very simple, so I could use that approach instead. I'll think about the need to reload dev code (I dont think its that important) and I can always update with set-refresh-dirs
before doing a refresh.
Thanks for the advice.
I can ensure that there is only one instance of Portal window
(def portal-instance
(or (seq (inspect/sessions))
(inspect/open {:portal.colors/theme :portal.colors/gruvbox})))
But is there something I can check to see if Portal has already been added as a tap source?
If so, then that would allow automatic loading in the dev/user.clj
file without duplication.
If that would be interesting, I can raise an issue. Otherwise I will stick with the set-refresh-dirs
Thanks.After some more testing, I think its the mulog tap publisher and/or mulog trace middleware I'm using that may be causing the duplication, rather than add-tap over nrepl. The duplications are when I am testing an API via the swagger interface and I assume the mulog-middleware Without taping all the mulog logs, I dont seem to see any duplications... its been a useful exercise to learn more about tap and porta.
I can access the value in the private var clojure.core/tapset
which is an atom that contains a set. Although setting a condidtion does not make a difference, which further suggests its the mulog publisher / middleware
(when (empty? (deref (deref #'clojure.core/tapset)))
(add-tap #'portal.api/submit))
I remember having duplication with mulog previously, when I forgot to close the publisher, so it seems I need to shut down the publisher before a restart (or ignore the dev directory)
There is a journal of my experiments in the following issue https://github.com/practicalli/project-templates/issues/7
In summary, I'll exclude the code in dev
, although I have code that does allow for automatic startup and reloading together now
Thanks again for the help and advice
Is there a way to filter the tap list in the UI?
I just tapped a whole bunch of values with the same exact shape and would like to narrow down the scope of what I'm looking at by e.g. (filter #(= (:kind %) "x") the-tap-list)
. With a possibility to further narrow it done, as if I was chaining multiple filters.
Currently, you can select any value and filter based on string matching. https://github.com/djblue/portal/blob/master/src/portal/ui/filter.cljc#L35
You might have more success pulling the value back into your repl by derefing the portal atom returned by portal.api/open or getting your portal atom from portal.api/sessions
I think things could be improved, just haven't found something I liked yet :thinking_face:
Ah, this might work. I have trouble being effective with the current UI. Apart from not being able to use plain Clojure code for filtering or maybe even transforming values, it keeps losing selection and current scrolling position when new values arrive. But I just got started with using it for something more or less serious so maybe I haven't figured out how to set it up correctly yet.
I have been considering adding the ability to pause updates which might help with that :thinking_face:
> haven't found something I liked yet
How about plain code? :) It doesn't have to look pretty or have all the affordances of a fully fledged editor. But having it there would be quite useful.
And if you want a neat list, it can be represented as items in the xform list of into
.
Just like you can have
(into (empty tap-list)
(comp
(filter #(= (:kind %) :item))
(keep #(when (even? (:value %)) (update % :position inc)))
(remove #(odd? (:position %))))
tap-list)
the list in the UI could only have three items:
(filter #(= (:kind %) :item))
(keep #(when (even? (:value %)) (update % :position inc)))
(remove #(odd? (:position %))))
@U2FRKM4TW I have ctrl+alt+space q
bound in VS Code to a fragment that lets me input Clojure code and evaluate it -- and *v
is shorthand for "the most recent tap>
'd value"
Another thing that I would personally find immensely useful - marking values and having a separate panel where I can view just those items and nothing else.
It should already be registered as a command in the ui, it will even keep the options used for the current session
I think my other issue with code is what runtime would you expect it to be running in? the browser or your app runtime?
FWIW, I try to do everything via the app runtime, including driving the Portal UI where possible.
@U04V70XH6 That would work, although in my case it's "all tapped values", and if I understand correctly that is also available, so I'll try it. @U1G869VNV Regarding inspect - that's not quite it. It does open a new window, every single time. And you can't easily distinguish those windows. Of course, I could select a value in Portal, get it back in my REPL, store it somewhere, assign some meaningful label to it, repeat 10 more times, constantly switching back and forth between Portal and REPL, and finally tap the resulting map or whatever I'll end up with. Quite tedious. Compare it to how you'd try to make sense of e.g. a printed out scientific paper on 15 pages. Some pages are completely irrelevant to your inquiry - you put them aside with an always present option to get a second look at them. Some pages have clearly interesting parts - you circle and highlight them. Some disparate pages have parts that belong together - you put those papers close to each other and label the circled/highlighted items in the same way or use the same highlight color. And so on an so forth, all while not leaving the paper medium. I might be "Bret Victor"ing too much right now, but something like that would be perfect. :)
@U2FRKM4TW https://github.com/seancorfield/vscode-calva-setup/blob/develop/calva/config.edn -- happy to answer any Qs about any of this code (via DM if you want, else here). In particular, the "start Portal" code is in that file too, and uses two Portal windows...
... I also wire up clojure.tools.logging
to Portal and use the nREPL middleware: see https://github.com/seancorfield/dot-clojure/blob/develop/src/org/corfield/dev/repl.clj
For that use case, I would do something like:
(require '[portal.api :as p])
(def bookmarks
(atom ^{:portal.viewer/default :portal.viewer/inspector} []))
(defn bookmark-add [v]
(swap! bookmarks conj v))
(defn bookmark-remove [v]
(swap! bookmarks #(into [] (remove #{v}) %)))
(defn open-bookmarks []
(p/open {:window-title "bookmarks" :value bookmarks}))
(p/register! #'bookmark-add)
(p/register! #'bookmark-remove)
You have complete control to do any transformations / annotations that you might find useful as well
For me, learning that you can have a "Portal instance" attached to any atom
containing values was quite a revelation...
@U04V70XH6 Sean, that's 450 lines dense with domain-specific things... And I believe that it's all immensely useful lines! But wouldn't you agree that having a few UI buttons would be a better affordance? :) And it's alright if I end up having to dig through them to make it all work. I probably will, at some point, once I'm more familiar with Portal and perhaps after I will have migrated to Calva ("any minute now"). It's just that I have a somewhat pathologic tendency to think of how other people would do the same thing. We wouldn't get too far if we were still stuck with having to deal with spreadsheets in TUI. @U1G869VNV Thanks! That's definitely useful. I still have that itch though, nagging at me that it all should be a part of the default UI - after all, we have bookmarks pretty much everywhere. Even IDEA has "Favorites" with different sections. And it all feels like it's just asking for a deeper editor integration where possible. Would love to do some experiments - shame there's just 24 hours in a day.
I don't like buttons 🙂 I prefer "command-line" and "raw code" approaches. Portal's great for visualizing stuff but I pretty much never interact with the UI itself -- and even when I do, it's all via the keyboard...
To each their own, of course. But with complex enough data, I just don't see how keyboard interaction can scale. Especially when that data is heavily skewed to be represented graphically.
I think custom shortcuts for custom commands might make things a little more ergonomic
A completely separate thing that just made me think I was losing my mind.
Why does the pr-str
viewer display double 1.0
as 1
? Or any other viewer, at that.
Clojure makes it a very big deal, and (= 1.0 1)
return false
.
I was going nuts trying to figure out why some pure functions worked on statically provided data but failed on some data provided from some other places - data that has doubles without the fractional part.
Clojure vs ClojureScript.
Or rather JavaScript 🙂
If you're in UI land, you're in JS land, right?
Yeah, although I though I made sure to encode types for number during serialization. Not sure what's up here, but it is a bug 😢
Blimey. I failed to internalize that the UI part is JS. Not sure how I would've fixed it then, apart from creating a representation on the CLJ side. No clue how feasible that is. You would get the same issue anywhere, be it EDN or Transit or msgpack. In JS 1.0 is 1. Unless you assign each numeric value a tag or create representations on the CLJ side, I don't think there's a way around it.
If you put the number in a container [1.0]
I think it will preserve the type since the vector in the runtime is exactly the same one you sent to portal
There's another potential alternative.
To send the types along with the values, but nested under a separate structure.
So you'd have your {:a 1 :b [1 2 3]}
but also {:a Long :b [Double Double Long]}
or something like that that would be reasonably useful and flexible.
Well, the vector will be a vector, true.
But longs and doubles will all become js/Number
, right? So it will still be [1]
.
My numbers were in a map - that didn't help them. :)
I mean if you pull the value back into the repl, your code should get the correct types
I could see a world where you could opt into :preserve-doubles per portal session and use the tagged approach you mentioned above
https://github.com/djblue/portal/blob/master/src/portal/runtime/cson.cljc#L706 already has something special for the CLR