I managed to get individual notebooks served with live-updates via sockets! :tada: :yay:
I did some digging/reading around the code base, and modeled it on the `clerk/webserver` implementation - so, not using transit at all for now. I'd love to move to something more robust/reactive (and I'm sure there's a benefit to adding transit?), but this is enough for me to get hacking on some notebooks and toggle them independently (though I'm not yet filtering the clients that I'm broadcasting updates).
This is likely not the intended way to embed clerk - I'm not sure if these functions/namespaces are intended as an external api or not - but, I'm very happy to have it working!
Dropping the code in a thread :thread: for reference.
;; https://github.com/russmatney/clawe/blob/1dafd7d4815b26dac705e417a984f4b1a54380a7/src/doctor/server.clj#L70-L100
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; clerk server helpers
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn eval-notebook
"Evaluates the notebook identified by its `ns-sym`"
[ns-sym]
(try
(-> ns-sym clerk-analyzer/ns->path (str ".clj") io/resource clerk-eval/eval-file)
(catch Throwable e
(println "error evaling notebook", ns-sym)
(println e))))
(comment
(eval-notebook 'notebooks.wallpapers)
(eval-notebook 'notebooks.clawe)
(eval-notebook 'notebooks.dice))
(defn ns-sym->html [ns-sym]
(some-> (eval-notebook ns-sym) (clerk-view/doc->html nil)))
(defn ns-sym->viewer [ns-sym]
(some-> (eval-notebook ns-sym) (clerk-view/doc->viewer)))
(defonce !clients (atom #{}))
(comment
(reset! !clients #{}))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; broadcast an updated notebook
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn broadcast! [notebook-sym]
(when-let [doc (ns-sym->viewer notebook-sym)]
(println "broadcasting notebook-sym" notebook-sym)
(doseq [ch @!clients]
;; TODO only send to channels viewing this doc
(undertow.ws/send (clerk-viewer/->edn {:doc doc}) ch))))
(comment
;; called from the notebook itself, via a keybinding, or a db listen event
(broadcast! 'notebooks.wallpapers))
;; https://github.com/russmatney/clawe/blob/1dafd7d4815b26dac705e417a984f4b1a54380a7/src/doctor/server.clj#L167-L187
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; create the server
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; set to some atom or however you maintain systems/components
;; this uses ring-undertow: https://github.com/luminus-framework/ring-undertow-adapter
(undertow/run-undertow
(fn [{:keys [uri] :as req}]
(cond
(:websocket? req)
{:undertow/websocket
{:on-open (fn [msg] (swap! !clients conj (:channel msg)))
:on-close-message (fn [msg] (swap! !clients disj (:channel msg)))
:on-message
(fn [msg]
(let [data (:data msg)]
;; TODO make sure this ns is loaded
;; note that the form from the FE can't be aliased
(println "evaling: " data)
(eval (read-string data))))}}
(string/starts-with? uri "/notebooks/")
(let [notebook-sym ;; convert "/notebooks/clawe" -> 'notebooks.clawe
(-> uri (string/replace-first "/" "") (string/replace-first "/" ".") symbol)]
(log/info "loading notebook" notebook-sym)
{:status 200
:headers {"Content-Type" "text/html"}
:body (or (ns-sym->html notebook-sym)
(str "No notebook (or failed to load nb) at uri: " uri))})))
{:port 3334
:session-manager? false
:websocket? true})
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; https://github.com/russmatney/clawe/blob/1dafd7d4815b26dac705e417a984f4b1a54380a7/src/notebooks/wallpapers.clj#L64
;; then, in a notebook (or wherever)
(comment
(server/broadcast! 'notebooks.wallpapers))