diff --git a/resources/public/editor.html b/resources/public/editor.html
new file mode 100644
index 00000000..f1ff7899
--- /dev/null
+++ b/resources/public/editor.html
@@ -0,0 +1,52 @@
+
+
+
+
+ Hyperfiddle
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Compile editor with shadow-cljs compile :editor
+
+
+
+
+
+
+
+
+
diff --git a/shadow-cljs.edn b/shadow-cljs.edn
index 9a4ca903..3e40241a 100644
--- a/shadow-cljs.edn
+++ b/shadow-cljs.edn
@@ -8,6 +8,13 @@
:output-dir "resources/public/js"
:asset-path "/js"
:modules {:main {:entries [user]}}}
+ :editor {:target :browser
+ :devtools {:watch-dir "resources/public" ; live reload CSS
+ :hud #{:errors :progress}
+ :ignore-warnings true} ; warnings don't prevent hot-reload
+ :output-dir "resources/public/js/editor"
+ :asset-path "/js"
+ :modules {:main {:entries [user]}}}
:test {:target :node-test
:output-to "out/node-tests.js"
:ns-regexp "^hyperfiddle.(photon-[^dom]|core-async).*$"
diff --git a/src-dev/user.cljs b/src-dev/user.cljs
index f4cc7e48..3adf1a2a 100644
--- a/src-dev/user.cljs
+++ b/src-dev/user.cljs
@@ -16,6 +16,7 @@
wip.demo-logical-clock
wip.example-router
wip.hfql-links
+ wip.editor
))
(defn runtime-resolve [exported-qualified-sym]
@@ -27,9 +28,12 @@
(defonce user-photon-main `user.demo-healthcheck/main) ; lazy resolve
(defonce reactor nil) ; save for debugging
-(defn ^:dev/after-load ^:export start! []
- (when user-photon-main
- (set! reactor ((runtime-resolve user-photon-main) ; Photon main recompiles every reload, must re-resolve it
+(defn set-main [s]
+ (set! user-photon-main (symbol s)))
+
+(defn ^:dev/after-load ^:export start! [main]
+ (when (or user-photon-main main)
+ (set! reactor ((runtime-resolve (or main user-photon-main)) ; Photon main recompiles every reload, must re-resolve it
#(js/console.log "Reactor success:" %)
#(js/console.error "Reactor failure:" %)))))
@@ -39,4 +43,4 @@
(defn browser-main! [photon-main-sym]
;(println ::received-reload-command photon-main-sym (type photon-main-sym))
- (set! user-photon-main photon-main-sym) (stop!) (start!))
+ (set! user-photon-main photon-main-sym) (stop!) (start! nil))
diff --git a/src-docs/wip/editor.cljc b/src-docs/wip/editor.cljc
new file mode 100644
index 00000000..157b71fc
--- /dev/null
+++ b/src-docs/wip/editor.cljc
@@ -0,0 +1,80 @@
+(ns wip.editor
+ (:require [hyperfiddle.photon :as p]
+ [hyperfiddle.photon-dom3 :as dom]
+ [missionary.core :as m]
+ #?(:clj [clojure.java.io :as io]))
+ (:import (hyperfiddle.photon Pending)
+ (missionary Cancelled)
+ #?(:clj (java.nio.file WatchService Paths FileSystems))))
+
+#?(:clj (def ENTRY_MODIFY java.nio.file.StandardWatchEventKinds/ENTRY_MODIFY))
+
+#?(:clj (defn register! [dir watcher]
+ (let [;; File change notification might take several seconds on macos.
+ ;; Set watch event priority to high to be notfied ASAP.
+ modifiers (when-let [modifier (try
+ (let [c (Class/forName "com.sun.nio.file.SensitivityWatchEventModifier")
+ f (.getField c "HIGH")]
+ (.get f c))
+ (catch Exception _ nil))]
+ (doto (make-array java.nio.file.WatchEvent$Modifier 1)
+ (aset 0 modifier)))
+ types (into-array [ENTRY_MODIFY])]
+ (if modifiers
+ (.register dir watcher (into-array types) modifiers)
+ (.register dir watcher (into-array types))))))
+
+#?(:clj (defn watch-dir! [dir-path callback]
+ (let [dir (Paths/get dir-path (make-array String 0))
+ watcher (.. FileSystems getDefault newWatchService)]
+ (register! dir watcher)
+ (letfn [(watch [watcher keys]
+ (let [key (.take watcher)]
+ (doseq [event (.pollEvents key)]
+ (let [name (->> event .context (.resolve dir) str)]
+ (callback name)
+ (.reset key)))
+ (recur watcher keys)))]
+ (future (watch watcher keys))
+ #(.close watcher)))))
+
+(defn file-watcher [file-path]
+ #?(:clj
+ (let [dir-path (.getParent (io/file file-path))]
+ (->> (m/observe (fn [!]
+ (let [stop (watch-dir! dir-path
+ (fn [filename]
+ (prn dir-path filename)
+ (when (= filename file-path)
+ (! (slurp filename)))))]
+ #(stop))))
+ (m/eduction (dedupe))
+ (m/reductions {} (slurp file-path))
+ (m/relieve {})))))
+
+(defn write! [dir-path text]
+ #?(:clj (spit dir-path text)))
+
+(defn debounce [delay flow]
+ (m/relieve {} (m/reductions {} nil (m/ap (let [x (m/?< flow)]
+ (try (m/? (m/sleep delay x))
+ (catch Cancelled _ (m/amb))))))))
+
+(def main #?(:cljs (p/client
+ (p/main
+ (try (binding [dom/parent (dom/by-id "root")]
+ ~@(let [dir-path "src-docs/user/demo_healthcheck.cljc"
+ content (new (file-watcher dir-path))
+ edited ~@(dom/div {:style {:width "100vw"}}
+ (dom/textarea {:rows 25
+ :style {:width "100%"}}
+ (dom/text content)
+ (new (->> (dom/events "input" (map dom/target-value))
+ (debounce 2000)))))]
+ (when (and edited (not= edited content))
+ (write! dir-path edited))))
+ (catch Pending _))))))
+
+(comment
+ (user/browser-main! `main)
+ )