commit 8a3f9fee9a26e0e0239faabd5d8e13995c3ee3c4 Author: ajet Date: Fri Jul 18 08:04:26 2025 -0900 init commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4889e40 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.nrepl-port +.cpcache diff --git a/README.md b/README.md new file mode 100644 index 0000000..74f85c7 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# Hello world example + +## Running the example + +- repl: + +``` +clj -A:repl +``` + +- main: + +``` +clojure -M -m example.main +``` diff --git a/deps.edn b/deps.edn new file mode 100644 index 0000000..4e96647 --- /dev/null +++ b/deps.edn @@ -0,0 +1,22 @@ +{:paths ["src/main" "resources"] + + :deps { + starfederation.datastar/sdk {:git/url "https://github.com/starfederation/datastar/" + :git/sha "376c4e2411706b942ea0ab937e4c6218d24fb30f" + :deps/root "sdk/clojure/sdk"} + starfederation.datastar/ring {:git/url "https://github.com/starfederation/datastar/" + :git/sha "376c4e2411706b942ea0ab937e4c6218d24fb30f" + :deps/root "sdk/clojure/adapter-ring"} + ring/ring-jetty-adapter {:mvn/version "1.13.0"} + metosin/reitit {:mvn/version "0.7.2"} + dev.onionpancakes/chassis {:mvn/version "1.0.365"} + com.cnuernber/charred {:mvn/version "1.034"} + org.clojure/data.json {:mvn/version "2.5.1"}} + + :aliases + {:repl {:extra-paths ["src/dev"] + :extra-deps {org.clojure/clojure {:mvn/version "1.12.1"} + nrepl/nrepl {:mvn/version "1.3.0"} + cider/cider-nrepl {:mvn/version "0.50.2"} + io.github.tonsky/clj-reload {:mvn/version "0.7.1"}} + :main-opts ["-m" "nrepl.cmdline" "--middleware" "[cider.nrepl/cider-middleware]"]}}} diff --git a/resources/public/index.html b/resources/public/index.html new file mode 100644 index 0000000..3689315 --- /dev/null +++ b/resources/public/index.html @@ -0,0 +1,14 @@ + + + + + + hello world d* + + + + + + %%content%% + + diff --git a/src/dev/user.clj b/src/dev/user.clj new file mode 100644 index 0000000..1c0e887 --- /dev/null +++ b/src/dev/user.clj @@ -0,0 +1,21 @@ +(ns user + (:require + [clj-reload.core :as reload])) + + +(alter-var-root #'*warn-on-reflection* (constantly true)) + + +(reload/init + {:no-reload ['user]}) + + +(defn reload! [] + (reload/reload)) + + +(comment + (reload!) + *e) + + diff --git a/src/main/example/core.clj b/src/main/example/core.clj new file mode 100644 index 0000000..83bbaa8 --- /dev/null +++ b/src/main/example/core.clj @@ -0,0 +1,144 @@ +(ns example.core + (:require + [clojure.java.io :as io] + [clojure.string :as string] + [dev.onionpancakes.chassis.compiler :as hc] + [dev.onionpancakes.chassis.core :as h] + [example.utils :as u] + [reitit.ring.middleware.parameters :as rmparams] + [reitit.ring :as rr] + [ring.util.response :as ruresp] + [starfederation.datastar.clojure.api :as d*] + [starfederation.datastar.clojure.adapter.ring :refer [->sse-response on-open on-close] :as dr] + [clojure.data.json :as json])) + +(declare conns) ;; for broadcasts + +;; html utils +(defn html->str [hiccup-forms] + (h/html + (hc/compile + hiccup-forms))) + +(defn page-template [content] + (-> (io/resource "public/index.html") + slurp + (string/split-lines) + (->> (drop 3) + (apply str)) + (string/replace "%%content%%" content))) + + +;; home page +(def home-frag + (html->str + [:main {:id "main" + :data-on-load "@get('/connect')"} + [:input {:data-bind "msg"}] + [:div {:data-text "$msg"}] + [:button {:data-on-click "@get('/hello-world')"} "click for text animation"]])) + +(def home-page + (page-template home-frag)) + +(defn home [request respond _] + (respond + (-> home-page + ruresp/response + (ruresp/content-type "text/html")))) + + +;; d* api utils +(defn patch-signals-edn! [sse edn] + (d*/patch-signals! sse (json/write-str edn))) + +(defn add-elements! [sse elems] + (d*/patch-elements! sse elems #:d*.elements{:patch-mode "append" + :selector "body"})) +(defn try! + [d*-f! sse & args] + (try (apply d*-f! sse args) + (catch Exception _ + (println "exception occured. dropping connection") + (d*/close-sse! sse) + (swap! conns disj sse)))) + + +;; broadcast utils +(def conns (atom #{})) + +(defn broadcast! + ([f] + (doseq [conn @conns] + (try! f conn))) + ([f arg & args] + (doseq [conn @conns] + (apply try! f conn arg args)))) + +(defn broadcast-signals! [data] + (broadcast! patch-signals-edn! data)) + +(defn broadcast-js! [js-src] + (broadcast! add-elements! (html->str [:script js-src]))) + +(defn broadcast-reload! [] + (broadcast-js! "location.reload()")) + +(defn kill-broadcast! [] + (doseq [conn @conns] + (try! d*/close-sse! conn) + (swap! conns disj conn))) + +(defn connect [request respond _raise] + (respond + (->sse-response request + {on-open + (fn [sse] (swap! conns conj sse) + (println "adding connection")) + on-close + (fn [sse] + (swap! conns disj sse) + (println "dropping connection"))}))) + + +;; hello world animation +(def message "Hello, world!") + +(def msg-count (count message)) + +(defn hello-world [request respond _raise] + (respond + (->sse-response request + {on-open + (fn [sse] + (d*/with-open-sse sse + (dotimes [i msg-count] + (patch-signals-edn! sse {:msg (subs message 0 (inc i))}) + (Thread/sleep 500))))}))) + + +;; http stuffs +(def routes + [["/" {:handler home}] + ["/connect" {:handler connect}] + ["/hello-world" {:handler hello-world}]]) + +(def router (rr/router routes)) + +(def handler (rr/ring-handler router)) + + +;; repl it up ;P +(comment + (broadcast-signals! {:msg "hi franz"}) + (broadcast-signals! {:msg "hi ty"}) + (broadcast! d*/console-log! "hi franz") + (broadcast-js! "console.log('test')") + (broadcast-reload!) + + (kill-broadcast!) + + + (clojure.repl/dir d*) + (clojure.repl/doc d*/patch-signals!)) + diff --git a/src/main/example/main.clj b/src/main/example/main.clj new file mode 100644 index 0000000..66a9687 --- /dev/null +++ b/src/main/example/main.clj @@ -0,0 +1,12 @@ +(ns example.main + (:require + [example.core :as c] + [example.server :as server])) + + +(defn -main [& _] + (let [server (server/start! c/handler)] + (.addShutdownHook (Runtime/getRuntime) + (Thread. (fn [] + (server/stop! server) + (shutdown-agents)))))) diff --git a/src/main/example/server.clj b/src/main/example/server.clj new file mode 100644 index 0000000..a91e755 --- /dev/null +++ b/src/main/example/server.clj @@ -0,0 +1,33 @@ +(ns example.server + (:require + [example.core :as c] + [ring.adapter.jetty :as jetty]) + (:import + org.eclipse.jetty.server.Server)) + + +(defonce !jetty-server (atom nil)) + + +(defn start! [handler & {:as opts}] + (let [opts (merge {:port 80 :join? false} + opts)] + (println "Starting server on port:" (:port opts)) + (jetty/run-jetty handler opts))) + + +(defn stop! [server] + (println "Stopping server") + (println server) + (.stop ^Server server)) + + +(defn reboot-jetty-server! [handler & {:as opts}] + (swap! !jetty-server + (fn [server] + (when server + (stop! server)) + (start! handler opts)))) + +(comment + (reboot-jetty-server! #'c/handler {:async? true})) diff --git a/src/main/example/utils.clj b/src/main/example/utils.clj new file mode 100644 index 0000000..09bd802 --- /dev/null +++ b/src/main/example/utils.clj @@ -0,0 +1,13 @@ +(ns example.utils + (:require + [charred.api :as charred] + [starfederation.datastar.clojure.api :as d*])) + + +(def ^:private bufSize 1024) +(def read-json (charred/parse-json-fn {:async? false :bufsize bufSize})) + +(defn get-signals [req] + (-> req d*/get-signals read-json)) + +