Fork me on GitHub
#clerk
<
2024-05-05
>
dobladez23:05:12

Hi there 👋 ... Playing with Clerk here: I'm writing a notebook to explain (and develop, actually) a model for chess-like games, for pedagogical purposes. I have a working viewer for game boards, that's good. Now I found that I almost always want to show both the Clojure data structure in addition to the visual board representation. I started simply repeating the code, with metadata to use the standard viewer, and then my own viewer (hiding the code)... but it gets tedious. Later I wrote a couple of macros... also problematic. So now I'm trying to implement something like the literal-viewer described on "the Book", but cannot get it to work... no surprise here, since I'm not sure what I'm doing :-) Here's what I have:

(def board-toggle-viewer
  {:transform-fn (comp clerk/mark-preserve-keys
                       (clerk/update-val (fn [v]
                                           {"Clojure" (clerk/with-viewer clerk-viewer/map-viewer v)
                                            "Board" (clerk/with-viewer board-viewer v) })))
   :render-fn '(fn [label->val]
                 (reagent.core/with-let [!selected-label (reagent.core/atom (ffirst label->val))]
                 ...
The Clojure version renders fine... But when I select by "Board", I get what seems to be a React error that reads: Doesn't support name: ["Board" 0]. Any ideas before I give up? :-\ Thanks!!

dobladez23:05:41

It'd be awesome to have a generic "multi-viewer"... by default showing a "tab" for each matching viewer (truthy pred ). Would this be feasible? Any pointers to guide me in the right direction?

Sam Ritchie14:05:47

(def tabbed-viewer
  "Clerk viewer for showing values in a tabbed interface. Use this viewer with

  - A map of label => value
  - A sequence of pairs of the form `[label, value]`

  Use the second form if you care about the order of your tabs."
  {:name `tabbed-viewer
   :render-fn
   '(fn [pairs opts]
      (reagent.core/with-let
        [ks (mapv
             (fn [{[k] :nextjournal/value}]
               (:nextjournal/value k))
             pairs)
         m  (into {} (map
                      (fn [{[k v] :nextjournal/value}]
                        [(:nextjournal/value k) v]))
                  pairs)
         !k (reagent.core/atom (first ks))]
        [:<> (into
              [:div.flex.items-center.font-sans.text-xs.mb-3
               [:span.text-slate-500.mr-2 "View as:"]]
              (map (fn [k]
                     [:button.px-3.py-1.font-medium.hover:bg-indigo-50.rounded-full.hover:text-indigo-600.transition
                      {:class
                       (if (= @!k k)
                         "bg-indigo-100 text-indigo-600"
                         "text-slate-500")
                       :on-click #(reset! !k k)}
                      k]))
              ks)
         [nextjournal.clerk.viewer/inspect-presented
          (get m @!k)]]))})

(defn multi
  "Given either

  - A map of label => value
  - A sequence of pairs of the form `[label, value]`

  returns a form that will render in Clerk as a tabbed interface, where clicking
  the tab assigned to a label will replace the space below with the
  corresponding value.

  Use the second form if you care about the order of your tabs."
  [xs]
  (viewer/with-viewer tabbed-viewer xs))

dobladez17:05:21

Thanks @U017QJZ9M7W!... I'll give that a try later today. While not quite what I'm trying to achieve, it's definitely in the right direction

dobladez17:05:05

Got what I wanted!

dobladez17:05:45

Usage example:

(def your-tabbed-viewer (new-tabbed-viewer {"Map" v/map-viewer
                                            "Code" v/code-viewer}))

dobladez18:05:43

Thanks again! I'll fiddle with the styling and create a similar side-by-side viewer

❤️ 1