Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ vsl/*
/resources/midi/soundfonts/
/src/noon/doc.clj
/cljs-test-runner-out/
/docs/
/notebooks/
1 change: 0 additions & 1 deletion client/noon/client/core.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,4 @@

(defn ^:export init []
(stylefy/init {:dom (gdom/init)})
#_ (println guide/guide)
(render))
16 changes: 6 additions & 10 deletions client/noon/client/ui/code_editor.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
["react-spinners/BeatLoader" :default spinner]
["@uiw/codemirror-themes-all" :as cm-themes]
[noon.client.ui.misc :as ui.misc]
[noon.client.state :refer [<<]]
["react-highlight" :default Highlight]))

(def EDITOR_EXTENSIONS
Expand Down Expand Up @@ -97,10 +96,10 @@
:hover (when (not= state :visible) {:color "#64748b"})}
:on-click on-click}
(c :span {:style {:display :inline-block
:width "7px" :height "7px"
:border-radius "50%"
:background color
:opacity (case state :visible 1, :dimmed 0.4, 0.15)}})
:width "7px" :height "7px"
:border-radius "50%"
:background color
:opacity (case state :visible 1, :dimmed 0.4, 0.15)}})
(str "ch " ch)))

;; ── Piano roll view ──────────────────────────────────────────────
Expand Down Expand Up @@ -159,7 +158,6 @@
(seq dimmed-chs) (assoc :dimmed-channels (set dimmed-chs))
channel-order (assoc :channel-order channel-order))))}})))))


(defui code-editor [{:keys [source options]}]
(let [input-editor-ref (uix/use-ref)
[source set-source] (uix/use-state source)
Expand All @@ -168,9 +166,8 @@
[editing set-editing] (uix/use-state false)
[evaluating set-evaluating] (uix/use-state false)
[playing set-playing] (uix/use-state false)
[local-piano-roll set-local-piano-roll] (uix/use-state nil) ;; nil = follow global, true/false = override
global-piano-rolls (<< [:piano-rolls.get])
show-piano-roll? (if (some? local-piano-roll) local-piano-roll global-piano-rolls)
[local-piano-roll set-local-piano-roll] (uix/use-state nil) ;; nil = follow option, true/false = override
show-piano-roll? (if (some? local-piano-roll) local-piano-roll (:show-piano-roll? options))
color :light-skyblue
error? (:error return)]

Expand Down Expand Up @@ -346,7 +343,6 @@
:on-click (fn [_] (set-local-piano-roll true))}
(c TbPiano {:size 18})))


(if editing

(c CodeMirror
Expand Down
6 changes: 5 additions & 1 deletion client/noon/client/ui/doc.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
[noon.client.ui.breadcrumbs :as ui.breadcrumbs]
[noon.client.constants :as constants]))

(defui connected-code-editor [props]
(let [show-piano-roll? (<< [:piano-rolls.get])]
(uix/$ ui.code-editor/code-editor (assoc-in props [:options :show-piano-roll?] show-piano-roll?))))

(defn render-doc-node [node]
(case (:type node)
:section (uix/$ ui.section/section
Expand All @@ -21,7 +25,7 @@
(concat (:content node)
(sort-by :idx (vals (:children node))))))
:raw (uix/$ ui.misc/raw node)
:code (uix/$ ui.code-editor/code-editor node)))
:code (uix/$ connected-code-editor node)))

(def doc-content
(render-doc-node doc/doc-data))
Expand Down
45 changes: 45 additions & 0 deletions client/noon/widget.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
(ns noon.widget
"Mounting layer for embedding noon code-editor widgets in external pages.
Scans the DOM for [data-noon-widget] elements and mounts the
noon.client.ui.code-editor component on them.

Called explicitly via noon.widget.init() from a <script> tag."
(:require [noon.client.ui.code-editor :as ui.code-editor]
[uix.core :refer [$]]
[uix.dom]
[stylefy.core :as stylefy]
[stylefy.generic-dom :as gdom]))

(defonce ^:private initialized? (atom false))

(defn- ensure-stylefy!
"Initialize stylefy for external pages that don't have the
required <style> elements in their HTML."
[]
(when-not @initialized?
(reset! initialized? true)
(doseq [id ["_stylefy-constant-styles_" "_stylefy-styles_"]]
(when-not (.getElementById js/document id)
(let [style (.createElement js/document "style")]
(.setAttribute style "id" id)
(.appendChild js/document.head style))))
(stylefy/init {:dom (gdom/init)})))

(defn- mount-widgets! []
(doseq [el (array-seq (.querySelectorAll js/document "[data-noon-widget]"))]
(when-not (.getAttribute el "data-noon-mounted")
(.setAttribute el "data-noon-mounted" "true")
(let [source-el (.querySelector el ".noon-source")
source (when source-el (.-textContent source-el))]
(when source
(when source-el (.removeChild el source-el))
(let [root (uix.dom/create-root el)]
(uix.dom/render-root
($ ui.code-editor/code-editor {:source source})
root)))))))

(defn ^:export init []
(ensure-stylefy!)
(if (= "loading" (.-readyState js/document))
(.addEventListener js/document "DOMContentLoaded" (fn [_] (mount-widgets!)))
(mount-widgets!)))
1 change: 1 addition & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@
<body>
<div id="app"></div>
<script src="js/main.js"></script>
<script>noon.client.core.init();</script>
</body>
</html>
2 changes: 1 addition & 1 deletion shadow-cljs.edn
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

:builds {:client
{:target :browser
:modules {:main {:init-fn noon.client.core/init
:modules {:main {:entries [noon.client.core noon.widget]
:reload-fn noon.client.core/reload}}
:devtools {:http-root "public"
:http-port 8999
Expand Down
34 changes: 34 additions & 0 deletions src/noon/viz/clay.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
(ns noon.viz.clay
"Clay/Kindly integration for embedding interactive noon editor widgets.
Produces `kind/hiccup` output that mounts the noon code-editor component.

Usage in a Clay notebook:
(require '[noon.viz.clay :as nclay])
(nclay/editor \"(play (tup s0 s1 s2))\")

The widget JS is part of the main noon client build (shadow-cljs).")

(def ^:dynamic *widget-js-path*
"Path to the noon client JS bundle.
Default points to the local shadow-cljs dev server.
Override with `binding` for production or custom setups."
"http://localhost:8999/js/main.js")

(defn editor
"Return a `kind/hiccup` form that renders an interactive noon editor widget.

The widget includes:
- A CodeMirror editor with the given source code
- An Eval & Play button (runs via SCI in the browser)
- A piano roll visualization
- Audio playback via Web Audio API"
[source]
^{:kindly/kind :kind/hiccup
:kindly/hide-code true}
[:div
[:div {:data-noon-widget ""}
[:pre {:class "noon-source"
:style {:display "none"}}
source]]
[:script {:src *widget-js-path*}]
[:script "noon.widget.init();"]])
Loading