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) + )