diff --git a/README.md b/README.md index 6ec3b23..09d746e 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,15 @@ the correct context. I only write to get thoughts of my head, and connect with people who find this worth engaging :) discourse graphs: https://network-goods.notion.site/The-Discourse-Graph-starter-pack-312374c813b24ec6b4d53a054371ee5a + ologs: https://arxiv.org/pdf/1102.1889.pdf + collective intelligence:https://topos.site/work/collective-intelligence/ +applied-category-theory:https://arxiv.org/pdf/1803.05316.pdf + + + --- We have Google earth to explore any part of the world, but we don't have anything like that for knowledge. @@ -44,6 +50,9 @@ which I think is the first layer of abstraction over "here are cursors of all th software are considered to be bad, yes they are by current definition at the current level of abstraction. If the abstraction layer is higher conflicts are good for progress. +My very strong intuition as of april 14 is that category-theory is THE missing piece I have been looking since the starting +of this project as a mere thought. I think so because would provide me with the tool that lets me think about the structure and coherence. + --- diff --git a/deps.edn b/deps.edn index 2985276..f4d18f4 100644 --- a/deps.edn +++ b/deps.edn @@ -1,59 +1,43 @@ -{:paths ["src" "resources"] +{:paths ["src" "resources"] :mvn/repos {"nexus-releases" {:url "https://nexus.redplanetlabs.com/repository/maven-public-releases"}} - :deps {com.hyperfiddle/electric {:mvn/version "v2-alpha-536-g0c582f78"} - org.clojure/data.xml {:mvn/version "0.2.0-alpha8"} - com.hyperfiddle/rcf {:mvn/version "20220926-202227"} - info.sunng/ring-jetty9-adapter - {:mvn/version "0.14.3" ; (Jetty 9) is Java 8 compatible; - :exclusions [org.slf4j/slf4j-api info.sunng/ring-jetty9-adapter-http3]} ; no need + :deps {com.hyperfiddle/electric {:git/url "https://github.com/hyperfiddle/electric" :git/sha "b32ac98df7d7ec87f225d47354671be172ffa87e"} + com.hyperfiddle/rcf {:mvn/version "20220926-202227"} + ring/ring {:mvn/version "1.11.0"} ; comes with Jetty - ;; Electric IC version requires 1.12 but rama is fixed on 1.11.1 so I have included the newer changes - ;; by importing data.xml above according to this comment - ;; https://clojurians.slack.com/archives/C7Q9GSHFV/p1704475238085959?thread_ts=1704210542.998019&cid=C7Q9GSHFV - - ;org.clojure/clojure {:mvn/version "1.12.0-alpha4"} - org.clojure/clojure {:mvn/version "1.11.1"} - org.clojure/clojurescript {:mvn/version "1.11.60"} - org.clojure/tools.logging {:mvn/version "1.2.4"} - ch.qos.logback/logback-classic {:mvn/version "1.2.11"} - datascript/datascript {:mvn/version "1.5.2"} - com.rpl/rama {:mvn/version "0.11.9"} - com.rpl/rama-helpers {:mvn/version "0.9.3"} - com.rpl/specter {:mvn/version "1.1.4"} - io.github.nextjournal/clojure-mode {:git/tag "v0.3.0" :git/sha "694abc7"} - ring-basic-authentication/ring-basic-authentication {:mvn/version "1.1.1"}} - :aliases {:dev - {:extra-deps - {binaryage/devtools {:mvn/version "1.0.6"} - net.clojars.wkok/openai-clojure {:mvn/version "0.14.0"} + ;; Electric IC version requires 1.12 but rama is fixed on 1.11.1 so I have included the newer changes + ;; by importing data.xml above according to this comment + ;; https://clojurians.slack.com/archives/C7Q9GSHFV/p1704475238085959?thread_ts=1704210542.998019&cid=C7Q9GSHFV + com.google.guava/guava {:mvn/version "33.1.0-jre"} + org.clojure/data.xml {:mvn/version "0.2.0-alpha8"} + org.clojure/clojure {:mvn/version "1.11.1"} + org.clojure/clojurescript {:mvn/version "1.11.60"} + org.clojure/tools.logging {:mvn/version "1.2.4"} + ch.qos.logback/logback-classic {:mvn/version "1.2.11"} + datascript/datascript {:mvn/version "1.5.2"} + com.rpl/rama {:mvn/version "0.12.1"} + com.rpl/rama-helpers {:mvn/version "0.9.3"} + com.rpl/specter {:mvn/version "1.1.4"} + net.clojars.wkok/openai-clojure {:mvn/version "0.14.0"} io.github.nextjournal/clojure-mode {:git/tag "v0.3.0" :git/sha "694abc7"} - reagent/reagent {:mvn/version "1.2.0"} - com.rpl/rama {:mvn/version "0.10.0"} - com.rpl/rama-helpers {:mvn/version "0.9.1"} - thheller/shadow-cljs {:mvn/version "2.25.2"}} - :jvm-opts - ["-Xss2m" ; https://github.com/hyperfiddle/photon/issues/11 - "-XX:-OmitStackTraceInFastThrow"] ;; RCF + org.apache.logging.log4j/log4j-slf4j2-impl {:mvn/version "2.23.1"}} + :aliases {:dev {:extra-paths ["src-dev"] + :extra-deps {io.github.clojure/tools.build {:mvn/version "0.9.6" + :exclusions [com.google.guava/guava ; Guava version conflict between tools.build and clojurescript. + org.slf4j/slf4j-nop]} ; clashes with app logger} + thheller/shadow-cljs {:mvn/version "2.25.2"}}} - :exec-fn user/main - :exec-args {}} + :nrepl {:extra-deps + {cider/cider-nrepl {:mvn/version "0.29.0"}} + :main-opts + ["-m" "nrepl.cmdline" + "--port" "9001"]} - ;; nrepl - :nrepl - {:extra-deps - {cider/cider-nrepl {:mvn/version "0.29.0"}} - :main-opts - ["-m" "nrepl.cmdline" - "--port" "9001"]} + :prod {:extra-paths ["src-prod"]} - :build - {:extra-paths ["src-build"] - :ns-default build - :extra-deps {io.github.clojure/tools.build {:mvn/version "0.9.5" :exclusions [com.google.guava/guava]} - io.github.seancorfield/build-clj {:git/tag "v0.8.0" :git/sha "9bd8b8a"} - com.rpl/rama {:mvn/version "0.10.0"} - com.rpl/rama-helpers {:mvn/version "0.9.1"} - net.clojars.wkok/openai-clojure {:mvn/version "0.14.0"} - io.github.nextjournal/clojure-mode {:git/sha "694abc7"} - thheller/shadow-cljs {:mvn/version "2.25.2"}} - :jvm-opts ["-Xss2m"]}}} + ; use `clj -X:build build-client`, NOT -T! build/app classpath contamination cannot be prevented + :build {:extra-paths ["src-build"] + :ns-default build + :extra-deps {io.github.clojure/tools.build {:mvn/version "0.9.6" + :exclusions [com.google.guava/guava ; Guava version conflict between tools.build and clojurescript. + org.slf4j/slf4j-nop]} ; clashes with app logger} + thheller/shadow-cljs {:mvn/version "2.25.2"}}}}} diff --git a/resources/public/index.html b/resources/public/index.html index ddf20be..04e720b 100644 --- a/resources/public/index.html +++ b/resources/public/index.html @@ -3,8 +3,7 @@ - - Electric Starter App + Softland diff --git a/shadow-cljs.edn b/shadow-cljs.edn index eb8afb8..316b1e7 100644 --- a/shadow-cljs.edn +++ b/shadow-cljs.edn @@ -1,21 +1,15 @@ -{:deps {:aliases [:dev]} - :nrepl {:port 9002} +{:nrepl {:port 9002} :builds - {:dev - {: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" - :asset-path "/js" - :modules {:main {:entries [user] - :init-fn user/start!}} - :build-hooks [(hyperfiddle.electric.shadow-cljs.hooks/reload-clj) - (shadow.cljs.build-report/hook {:output-to "target/build_report.html"}) - (user/rcf-shadow-hook)]} - :prod - {:target :browser - :output-dir "resources/public/js" - :asset-path "/js" - :module-hash-names true - :modules {:main {:entries [user] :init-fn user/start!}}}}} + {:dev {:target :browser + :devtools {:watch-dir "resources/public" + :loader-mode :default} + :output-dir "resources/public/js" + :asset-path "/js" + :modules {:main {:entries [dev] + :init-fn dev/start!}} + :build-hooks [(hyperfiddle.electric.shadow-cljs.hooks/reload-clj)]} + :prod {:target :browser + :output-dir "resources/public/js" + :asset-path "/js" + :module-hash-names true + :modules {:main {:entries [prod] :init-fn prod/start!}}}}} diff --git a/src-build/build.clj b/src-build/build.clj index c7b858e..fa04b5d 100644 --- a/src-build/build.clj +++ b/src-build/build.clj @@ -1,62 +1,65 @@ (ns build - "build electric.jar library artifact and demos" - (:require [clojure.tools.build.api :as b] - [org.corfield.build :as bb] - [shadow.cljs.devtools.api :as shadow-api] ; so as not to shell out to NPM for shadow - [shadow.cljs.devtools.server :as shadow-server])) + (:require + [clojure.tools.build.api :as b] + [clojure.tools.logging :as log] + [shadow.cljs.devtools.api :as shadow-api] + [shadow.cljs.devtools.server :as shadow-server])) +(def electric-user-version (b/git-process {:git-args "describe --tags --long --always --dirty"})) -(def lib 'com.hyperfiddle/electric) -(def version (b/git-process {:git-args "describe --tags --long --always --dirty"})) -(def basis (b/create-basis {:project "deps.edn"})) +(defn build-client ; invoke with `clj -X ...` + "build Electric app client, invoke with -X + e.g. `clojure -X:build:prod build-client :debug false :verbose false :optimize true` + Note: Electric shadow compilation requires application classpath to be available, so do not use `clj -T`" + [argmap] + (let [{:keys [optimize debug verbose] + :or {optimize true, debug false, verbose false} + :as config} + (assoc argmap :hyperfiddle.electric/user-version electric-user-version)] + (b/delete {:path "resources/public/js"}) + (b/delete {:path "resources/electric-manifest.edn"}) -(defn clean [opts] - (bb/clean opts)) + ; bake user-version into artifact, cljs and clj + (b/write-file {:path "resources/electric-manifest.edn" :content config}) + + ; "java.lang.NoClassDefFoundError: com/google/common/collect/Streams" is fixed by + ; adding com.google.guava/guava {:mvn/version "31.1-jre"} to deps, + ; see https://hf-inc.slack.com/archives/C04TBSDFAM6/p1692636958361199 + (shadow-server/start!) + (as-> + (shadow-api/release :prod + {:debug debug, + :verbose verbose, + :config-merge + [{:compiler-options {:optimizations (if optimize :advanced :simple)} + :closure-defines {'hyperfiddle.electric-client/ELECTRIC_USER_VERSION electric-user-version}}]}) + shadow-status (assert (= shadow-status :done) "shadow-api/release error")) ; fail build on error + (shadow-server/stop!) + (log/info "Client build successful. Version:" electric-user-version))) (def class-dir "target/classes") -(defn default-jar-name [{:keys [version] :or {version version}}] - (format "target/%s-%s-standalone.jar" (name lib) version)) - -(defn clean-cljs [_] - (b/delete {:path "resources/public/js"})) - -(defn build-client - "Prod optimized ClojureScript client build. (Note: in dev, the client is built -on startup)" - [{:keys [optimize debug verbose version] - :or {optimize true, debug false, verbose false, version version}}] - (println "Building client. Version:" version) - (shadow-server/start!) - (shadow-api/release :prod {:debug debug, - :verbose verbose, - :config-merge [{:compiler-options {:optimizations (if optimize :advanced :simple)} - :closure-defines {'hyperfiddle.electric-client/ELECTRIC_USER_VERSION version}}]}) - (shadow-server/stop!)) - -(defn uberjar [{:keys [jar-name version optimize debug verbose] - :or {version version, optimize true, debug false, verbose false}}] - (println "Cleaning up before build") - (clean nil) - - (println "Cleaning cljs compiler output") - (clean-cljs nil) - - (build-client {:optimize optimize, :debug debug, :verbose verbose, :version version}) - - (println "Bundling sources") - (b/copy-dir {:src-dirs ["src" "resources"] - :target-dir class-dir}) - - (println "Compiling server. Version:" version) - (b/compile-clj {:basis basis - :src-dirs ["src"] - :ns-compile '[prod] - :class-dir class-dir}) - - (println "Building uberjar") - (b/uber {:class-dir class-dir - :uber-file (str (or jar-name (default-jar-name {:version version}))) - :basis basis - :main 'prod})) - -(defn noop [_]) ; run to preload mvn deps \ No newline at end of file + +(defn uberjar + [{:keys [optimize debug verbose ::jar-name, ::skip-client] + :or {optimize true, debug false, verbose false, skip-client false} + :as args}] + ; careful, shell quote escaping combines poorly with clj -X arg parsing, strings read as symbols + (log/info `uberjar (pr-str args)) + (b/delete {:path "target"}) + + (when-not skip-client + (build-client {:optimize optimize, :debug debug, :verbose verbose})) + + (b/copy-dir {:target-dir class-dir :src-dirs ["src" "src-prod" "resources"]}) + (let [jar-name (or (some-> jar-name str) ; override for Dockerfile builds to avoid needing to reconstruct the name + (format "target/electricfiddle-%s.jar" electric-user-version)) + aliases [:prod]] + (log/info `uberjar "included aliases:" aliases) + (b/uber {:class-dir class-dir + :uber-file jar-name + :basis (b/create-basis {:project "deps.edn" :aliases aliases})}) + (log/info jar-name))) + +;; clj -X:build:prod build-client +;; clj -X:build:prod uberjar :build/jar-name "app.jar" +;; java -cp app.jar clojure.main -m prod \ No newline at end of file diff --git a/src-dev/dev.cljc b/src-dev/dev.cljc new file mode 100644 index 0000000..a04a3fa --- /dev/null +++ b/src-dev/dev.cljc @@ -0,0 +1,49 @@ +(ns dev + (:require + app.electric-flow + [hyperfiddle.electric :as e] + #?(:clj [app.server-jetty :as jetty]) + #?(:clj [shadow.cljs.devtools.api :as shadow]) + #?(:clj [shadow.cljs.devtools.server :as shadow-server]) + #?(:clj [clojure.tools.logging :as log]))) + +(comment (-main)) ; repl entrypoint + +#?(:clj ;; Server Entrypoint + (do + (def config + {:host "0.0.0.0" + :port 8080 + :resources-path "public/" + :manifest-path ; contains Electric compiled program's version so client and server stays in sync + "public/js/manifest.edn"}) + + (defn -main [& args] + (log/info "Starting Electric compiler and server...") + + (shadow-server/start!) + (shadow/watch :dev) + (comment (shadow-server/stop!)) + + (def server (jetty/start-server! + (fn [ring-request] + (e/boot-server {} app.electric-flow/main ring-request)) + config)) + + (comment (.stop server))))) + + +#?(:cljs ;; Client Entrypoint + (do + (def electric-entrypoint (e/boot-client {} app.electric-flow/main nil)) + + (defonce reactor nil) + + (defn ^:dev/after-load ^:export start! [] + (set! reactor (electric-entrypoint + #(js/console.log "Reactor success:" %) + #(js/console.error "Reactor failure:" %)))) + + (defn ^:dev/before-load stop! [] + (when reactor (reactor)) ; stop the reactor + (set! reactor nil)))) \ No newline at end of file diff --git a/src-dev/logback.xml b/src-dev/logback.xml new file mode 100644 index 0000000..291dada --- /dev/null +++ b/src-dev/logback.xml @@ -0,0 +1,16 @@ + + + + + %highlight(%-5level) %logger: %msg%n + + + + + + + + + + + \ No newline at end of file diff --git a/src-dev/user.clj b/src-dev/user.clj new file mode 100644 index 0000000..d5e55b7 --- /dev/null +++ b/src-dev/user.clj @@ -0,0 +1 @@ +(ns user (:require [dev])) \ No newline at end of file diff --git a/src-prod/logback.xml b/src-prod/logback.xml new file mode 100644 index 0000000..8c87b2c --- /dev/null +++ b/src-prod/logback.xml @@ -0,0 +1,12 @@ + + + + + %highlight(%-5level) %logger: %msg%n + + + + + + + diff --git a/src-prod/prod.cljc b/src-prod/prod.cljc new file mode 100644 index 0000000..5b19008 --- /dev/null +++ b/src-prod/prod.cljc @@ -0,0 +1,45 @@ +(ns prod + (:require + #?(:clj [clojure.edn :as edn]) + #?(:clj [clojure.java.io :as io]) + #?(:clj [clojure.tools.logging :as log]) + [contrib.assert :refer [check]] + app.electric-flow + #?(:clj [app.server-jetty :as jetty]) + [hyperfiddle.electric :as e]) + #?(:cljs (:require-macros [prod :refer [compile-time-resource]]))) + +(defmacro compile-time-resource [filename] (some-> filename io/resource slurp edn/read-string)) + +(def config + (merge + ;; Client program's version and server program's versions must match in prod (dev is not concerned) + ;; `src-build/build.clj` will compute the common version and store it in `resources/electric-manifest.edn` + ;; On prod boot, `electric-manifest.edn`'s content is injected here. + ;; Server is therefore aware of the program version. + ;; The client's version is injected in the compiled .js file. + (doto (compile-time-resource "electric-manifest.edn") prn) + {:host "0.0.0.0", :port 8080, + :resources-path "public/" + ;; shadow build manifest path, to get the fingerprinted main.sha1.js file to ensure cache invalidation + :manifest-path "public/js/manifest.edn"})) + +;;; Prod server entrypoint + +#?(:clj + (defn -main [& {:strs [] :as args}] ; clojure.main entrypoint, args are strings + (log/info (pr-str config)) + (check string? (::e/user-version config)) + (jetty/start-server! + (fn [ring-req] (e/boot-server {} app.electric-flow/main ring-req)) + config))) + +;;; Prod client entrypoint + +#?(:cljs + (do + (def electric-entrypoint (e/boot-client {} app.electric-flow/main nil)) + (defn ^:export start! [] + (electric-entrypoint + #(js/console.log "Reactor success:" %) + #(js/console.error "Reactor failure:" %))))) \ No newline at end of file diff --git a/src/app/client/shapes/circle.cljc b/src/app/client/shapes/circle.cljc index f0a9e33..d6bca42 100644 --- a/src/app/client/shapes/circle.cljc +++ b/src/app/client/shapes/circle.cljc @@ -17,7 +17,8 @@ -(e/defn circle [[k {:keys [id x y r color dragging?]}]] +(e/defn circle [id] + (println "cirtle id" id) (let [x-p [ id :x] y-p [ id :y] r-p [ id :type-specific-data :r] @@ -26,35 +27,36 @@ (e/client (svg/circle (dom/props {:id id - :cx (subscribe. x-p) - :cy (subscribe. y-p) - :r (subscribe. r-p) - :fill (subscribe. color-p)}) - (dom/on "mousemove" (e/fn [e] - (.preventDefault e) - (when dragging? - (println "dragging element" - (let [el (.getElementById js/document (name id)) - [x y] (fc/element-new-coordinates1 e el)] - (e/server (swap! !nodes assoc-in [k :x] x)) - (e/server (swap! !nodes assoc-in [k :y] y))))))) - (dom/on "mousedown" (e/fn [e] - (.preventDefault e) - (.stopPropagation e) - (println "pointerdown element") - (e/server (swap! !nodes assoc-in [k :dragging?] true)))) - (dom/on "mouseup" (e/fn [e] - (.preventDefault e) - (.stopPropagation e) - (println "pointerup element") - (e/server (swap! !nodes assoc-in [k :dragging?] false)))) - (dom/on "mouseleave" (e/fn [e] + :cx (subscribe. x-p) ;(+ 100 (rand-int 1000)) ; + :cy (subscribe. y-p) ;(+ 400 (rand-int 300)); + :r (+ 2 (rand-int 30)) ;(subscribe. r-p) + :fill "brown"}) ;(subscribe. color-p)}) + #_(dom/on "mousemove" (e/fn [e] + (.preventDefault e) + (when dragging? + (println "dragging element" + (let [el (.getElementById js/document (name id)) + [x y] (fc/element-new-coordinates1 e el)] + (e/server (swap! !nodes assoc-in [k :x] x)) + (e/server (swap! !nodes assoc-in [k :y] y))))))) + #_(dom/on "mousedown" (e/fn [e] + (.preventDefault e) + (.stopPropagation e) + (println "pointerdown element") + (e/server (swap! !nodes assoc-in [k :dragging?] true)))) + #_(dom/on "mouseup" (e/fn [e] + (.preventDefault e) + (.stopPropagation e) + (println "pointerup element") + (e/server (swap! !nodes assoc-in [k :dragging?] false)))) + #_(dom/on "mouseleave" (e/fn [e] + (.preventDefault e) + (.stopPropagation e) + (println "mouseleave element") + (e/server (swap! !nodes assoc-in [k :dragging?] false)))) + #_(dom/on "mouseout" (e/fn [e] (.preventDefault e) (.stopPropagation e) - (println "mouseleave element") - (e/server (swap! !nodes assoc-in [k :dragging?] false)))) - (dom/on "mouseout" (e/fn [e] - (.preventDefault e) - (.stopPropagation e) - (println "mouseout element") - (e/server (swap! !nodes assoc-in [k :dragging?] false)))))))) + (println "mouseout element") + (e/server (swap! !nodes assoc-in [k :dragging?] false)))))))) + diff --git a/src/app/client/shapes/line.cljc b/src/app/client/shapes/line.cljc index 3ed615d..5c6b3d8 100644 --- a/src/app/client/shapes/line.cljc +++ b/src/app/client/shapes/line.cljc @@ -1,33 +1,57 @@ (ns app.client.shapes.line (:require [hyperfiddle.electric-svg :as svg] [hyperfiddle.electric :as e] - [hyperfiddle.electric-dom2 :as dom])) + [app.client.flow-calc :as fc] + [hyperfiddle.electric-dom2 :as dom] + [app.client.utils :refer [ ui-mode edges nodes + is-dragging? zoom-level last-position subscribe + viewbox context-menu? reset-global-vals]])) +(defn attributes [x y height width a b] + (let [xmin x + ymin y + xmax (+ x width) + ymax (+ y height) + rx (atom nil) + ry (atom nil)] + (println "xmin" xmin "xmax" xmax "ymin" ymin "ymax" ymax "a " a "b" b) + (cond + (<= a xmin) (reset! rx xmin) + (>= a xmax) (reset! rx xmax) + :else (reset! rx a)) + (cond + (<= b ymin) (reset! ry ymin) + (>= b ymax) (reset! ry ymax) + :else (reset! ry b)) + [@rx @ry])) +(comment + (attributes 0 0 100 100 -51 51) + (attributes 0 0 100 100 51 51) + (attributes 0 0 100 100 -51 -51) + (attributes 0 0 100 100 51 -51) + (attributes 0 0 100 100 51 -21)) (e/defn line [[k {:keys [id color to from]}]] + (println "--->" k id color to from) + (println "--"[to :y]) (e/client - (let [tw (subscribe. [ to :width]) - th (subscribe. [ to :height]) - fw (subscribe. [ from :width]) - fh (subscribe. [ from :height]) - tx (subscribe. [ to :x]) - ty (subscribe. [ to :y]) - fx (subscribe. [ from :x]) - fy (subscribe. [ from :y])] + (let [tw (int (subscribe. [ to :type-specific-data :width])) + th (int (subscribe. [ to :type-specific-data :height])) + tx (int (subscribe. [to :x])) + ty (int (subscribe. [to :y])) + fx (int (subscribe. [from :x])) + fy (int (subscribe. [from :y])) + [xx yy] (attributes tx ty th tw fx fy)] (svg/line (dom/props {:style {:z-index -1} :id id - :x1 (if tw - (+ tx) (/ tw 2) - tx) - :y1 (if th - (+ ty (/ th 2)) - ty) - :x2 (if fw - (+ fx (/ fw 2)) - fx) - :y2 (if fh - (+ fy (/ fh 2)) - fy) + :x1 xx #_(if tw + (+ tx) (/ tw 2) + tx) + :y1 yy #_(if th + (+ ty (/ th 2)) + ty) + :x2 fx + :y2 fy :stroke color :stroke-width 4}))))) diff --git a/src/app/client/shapes/rect.cljc b/src/app/client/shapes/rect.cljc index 0eddff2..3c76c36 100644 --- a/src/app/client/shapes/rect.cljc +++ b/src/app/client/shapes/rect.cljc @@ -5,6 +5,8 @@ [app.client.electric-codemirror :as cm] [hyperfiddle.electric :as e] [hyperfiddle.electric-dom2 :as dom] + [hyperfiddle.electric-ui5 :as ui5] + [hyperfiddle.electric-ui4 :as ui4] [app.client.flow-calc :as fc] [clojure.edn :as edn] [clojure.pprint :as pprint] @@ -21,7 +23,7 @@ :clj [[com.rpl.rama.path :as path :refer [subselect ALL FIRST keypath select]] [app.client.utils :refer [!ui-mode !edges !nodes]] - [app.server.rama :as rama :refer [!subscribe nodes-pstate get-event-id add-new-node]]]))) + [app.server.rama :as rama :refer [!subscribe nodes-pstate get-event-id add-new-node update-node]]]))) (e/defn card-topbar [id] @@ -93,30 +95,30 @@ (outlined-button. "7"))))) -(e/defn rect [id] - (e/server - (let [pstate nodes-pstate] - (e/client - (println "id" id "pstate" pstate) - (let [!cm-text (atom nil) - cm-text (e/watch !cm-text) - read (fn [edn-str] +(e/defn rect [[id {:keys [y x type id type-specific-data]}]] + (e/client + (let [#_#_!cm-text (atom nil) + #_#_cm-text (e/watch !cm-text) + #_#_read (fn [edn-str] (println "Read string:" (edn/read-string edn-str)) (reset! !cm-text (str edn-str)) (try (edn/read-string edn-str) (catch #?(:clj Throwable :cljs :default) t #?(:clj (clojure.tools.logging/error t) :cljs (js/console.warn t)) nil))) - write (fn [edn] (with-out-str (pprint/pprint edn))) - dom-id (str "dom-id-" (str id)) - x-p [ id :x] - y-p [ id :y] - text-p [ id :type-specific-data :text] - width-p [ id :type-specific-data :width] - height-p [ id :type-specific-data :height] - fill-p [ id :fill]] - (svg/g - (svg/rect + write (fn [edn] (with-out-str (pprint/pprint edn))) + dom-id (str "dom-id-" (str id)) + x-p [ id :x] + y-p [ id :y] + text-p [ id :type-specific-data :text] + width-p [ id :type-specific-data :width] + height-p [ id :type-specific-data :height] + dragging? (atom false) + h (:height type-specific-data) + w (:width type-specific-data)] + + #_(svg/g + #_(svg/rect (dom/props {:id dom-id :x (subscribe. x-p) :y (subscribe. y-p) @@ -126,87 +128,158 @@ #_#_:fill (:editor-border (theme. ui-mode))}) (dom/on "click" (e/fn [e] (println "clicked the rect."))) - (dom/on "mousedown" (e/fn [e] - (println "mousedown the rect.") - (let [el (.getElementById js/document (name dom-id)) - [x y] (fc/element-new-coordinates1 e el)] + #_(dom/on "mousedown" (e/fn [e] + (println "mousedown the rect.") + (let [el (.getElementById js/document (name dom-id)) + [x y] (fc/element-new-coordinates1 e el)] + (e/server + (swap! !edges assoc :raw {:id :raw + :type "raw" + :x1 x + :y1 y + :x2 nil + :y2 nil + :stroke (:edge-color (theme. ui-mode)) + :stroke-width 4}))) + (reset! !border-drag? true))) + #_(dom/on "mouseup" (e/fn [e] + (println "mouseup the rect.") + (reset! !border-drag? false))))) + (when (and (some? x)(some? y)(some? h)(some? w)) + (svg/foreignObject + (dom/props {:x x;(subscribe. x-p) ;(+ (subscribe. x-p) 5) + :y y ;(subscribe. y-p) ;(+ (subscribe. y-p) 5) + :height h ;(subscribe. height-p);(- (subscribe. height-p) 10) + :width w ; (subscribe. width-p) ;(- (subscribe. width-p) 10) + :fill "white" + :id id + :style {:display "flex" + :flex-direction "column" + :border "1px solid black" + :border-radius "10px" + :background-color "white" + :overflow "scroll"}}) + #_(dom/div + (dom/props {:style {:background-color "white" + :display "flex" + :overflow "scroll" + :color "black" + :flex-direction "column" + :font-size "23px" + :padding "20px" + :border-radius "10px"}}) + + (dom/div (dom/text (:text type-specific-data)))) + (dom/on "mousemove" (e/fn [e] + (e/client + (.preventDefault e) + (when @dragging? + (println "MOUSE MOVE element") + ;(println "////////" "&&&&&7" (fc/browser-to-svg-coords e viewbox (.getElementById js/document (name id)))) + ;(js/console.log "--**" (.getElementById js/document (name id))) + (let [[cx cy] (fc/element-new-coordinates1 e (.getElementById js/document (name id))) + new-x (- cx (/ w 2)) + new-y (- cy (/ h 2))] + ;(println "new x " new-x "ox" x "new y " new-y "oy" y) + (when (and (some? new-x) + (some? new-y)) + ;(println "NOT NIL new x " new-x "new y " new-y) (e/server - (swap! !edges assoc :raw {:id :raw - :type "raw" - :x1 x - :y1 y - :x2 nil - :y2 nil - :stroke (:edge-color (theme. ui-mode)) - :stroke-width 4}))) - (reset! !border-drag? true))) - (dom/on "mouseup" (e/fn [e] - (println "mouseup the rect.") - (reset! !border-drag? false)))) - (svg/foreignObject - (dom/props {:x (subscribe. x-p) ;(+ (subscribe. x-p) 5) - :y (subscribe. y-p) ;(+ (subscribe. y-p) 5) - :height 398 ;(subscribe. height-p);(- (subscribe. height-p) 10) - :width (subscribe. width-p) ;(- (subscribe. width-p) 10) - ;:fill "black" - :style {:display "flex" - :flex-direction "column" - :border "1px solid black" - :border-radius "10px" - :overflow "scroll"}}) - (dom/div - (dom/props {:style {:background-color "white" - :height "100%" - :width "100%" - :display "flex" - :overflow "scroll" - :flex-direction "column" - :border-radius "10px"}}) - (card-topbar. id) + (update-node + [text-p "HELLO WORLD"] + {:graph-name :main + :event-id (get-event-id) + :create-time (System/currentTimeMillis)} + true + false) + (update-node + [x-p new-x] + {:graph-name :main + :event-id (get-event-id) + :create-time (System/currentTimeMillis)} + true + false) + (update-node + [y-p new-y] + {:graph-name :main + :event-id (get-event-id) + :create-time (System/currentTimeMillis)} + true + true)))))))) + + (dom/on "mousedown" (e/fn [e] + (.preventDefault e) + (.stopPropagation e) + (println "MOUSEDOWN element") + (reset! dragging? true))) + (dom/on "mouseup" (e/fn [e] + (.preventDefault e) + (.stopPropagation e) + (println "pointerup element") + (reset! dragging? false))) + #_(dom/on "mouseleave" (e/fn [e] + (.preventDefault e + (.stopPropagation e) + (println "mouseleave element") + (reset! dragging? false)))) + #_(dom/on "mouseout" (e/fn [e] + (.preventDefault e) + (.stopPropagation e) + (println "mouseout element") + (reset! dragging? false)) + + #_(card-topbar. id) + + #_(dom/div + (dom/props {:class "middle-earth" + :style {:padding "5px" + :background "whi}te" + :display "flex" + :flex-direction "column" + :height "100%"}}) (dom/div - (dom/props {:class "middle-earth" - :style {:padding "5px" - :background "whi}te" - :display "flex" - :flex-direction "column" - :height "100%"}}) - (dom/div - (dom/props {:id (str "cm-" dom-id) - :style {:height "100%" - :overflow "scroll" - :background "white" - :box-shadow "black 0px 0px 2px 1px" - ;:border "1px solid black" - ;:border-radius "10px" - :margin-bottom "10px"}}) - #_(canvas. dom-id) - (new cm/CodeMirror + (dom/props {:id (str "cm-" dom-id) + :style {:height "100%" + :overflow "scroll" + :background "white" + :box-shadow "black 0px 0px 2px 1px" + ;:border "1px solid black" + ;:border-radius "10px" + :margin-bottom "10px"}}) + #_(canvas. dom-id) + (dom/textarea (dom/text (subscribe. text-p))) + #_(new cm/CodeMirror {:parent dom/node} read identity (subscribe. text-p))) - (button-bar.)) - - - #_(dom/div - (dom/button - (dom/props {:style {:background-color "white" - :border "none" - :padding "5px" - :border-width "5px" - :font-size "18px" - :color (:button-text (theme. ui-mode)) - :height "50px" - :width "100%"}}) - (dom/text - "Save") - - #_(dom/on "click" (e/fn [e] - (let [child-uid (new-uuid) - x (e/server (rama/get-path-data x-p pstate)) - y (e/server (rama/get-path-data y-p pstate))] - (when (some? cm-text) - (println "cm-text -->" cm-text) - (create-new-child-node. id child-uid (+ x 600) y cm-text))))))))))))))) + #_(button-bar.)) + + + #_(dom/div + (dom/button + (dom/props {:style {:background-color "white" + :border "none" + :padding "5px" + :border-width "5px" + :font-size "18px" + :color (:button-text (theme. ui-mode)) + :height "50px" + :width "100%"}}) + (dom/text + "Save") + + #_(dom/on "click" (e/fn [e] + (let [child-uid (new-uuid) + x (e/server (rama/get-path-data x-p pstate)) + y (e/server (rama/get-path-data y-p pstate))] + (when (some? cm-text) + (println "cm-text -->" cm-text) + (create-new-child-node. id child-uid (+ x 600) y cm-text))))))))))))) +#_(update-node + [[:08fe4616-4a43-4b5c-9d77-87fc7dc462c5 :x] 2000] + {:graph-name :main + :event-id (get-event-id) + :create-time (System/currentTimeMillis)}) \ No newline at end of file diff --git a/src/app/client/utils.cljc b/src/app/client/utils.cljc index 16abfae..1616f1f 100644 --- a/src/app/client/utils.cljc +++ b/src/app/client/utils.cljc @@ -1,27 +1,96 @@ (ns app.client.utils (:require [hyperfiddle.electric :as e] #?@(:clj - [[app.server.rama :as rama :refer [!subscribe nodes-pstate get-event-id add-new-node]] - [com.rpl.rama.path :as path :refer [subselect ALL FIRST keypath select]]]))) + [ + [app.server.rama :as rama :refer [!subscribe nodes-pstate get-event-id add-new-node]] + [com.rpl.rama.path :as path :refer [subselect ALL FIRST keypath select]]] + :cljs [[cljs.reader :as reader]]))) +(defn new-uuid [] + (keyword (str (random-uuid)))) #_(defn log [message & args] (js/console.log message args)) +#?(:clj (defn read-local-file [file-path] + (slurp file-path))) + +#?(:clj (def get-dg-nodes-from-local + (let + [file-path "/Users/sid597/Downloads/dg11.edn" + file-content (read-local-file file-path)] + (clojure.edn/read-string (first (clojure.edn/read-string file-content)))))) + +#?(:clj (def unique-dg-nodes + (let [unique (atom #{})] + (doseq [node get-dg-nodes-from-local] + (let [src (first node) + des (last node)] + (when (not (contains? @unique (:title des))) + (swap! unique conj src)) + (when (not (contains? @unique (:title src))) + (swap! unique conj des)))) + (vec @unique)))) + + +(e/defn add-dg-new-nodes [] + (e/client + (let [!x (atom 3200.01) + !y (atom 600.01) + idx (atom 1)] + (e/for-by + identity + [node (e/server unique-dg-nodes)] + (e/client + (do + (println "idx--" @idx "node --" node "--x" @!x "--y " @!y) + (let [id (new-uuid) + _ (println "1. ==>" @!x @!y (mod @idx 6) @idx) + node-data {id {:id id + :x @!x + :y @!y + :type-specific-data {:text (:title node) + :uid (:uid node) + :width 400 + :height 800} + :type "rect" + :fill "lightblue"}}] + (do + (if (= 0 (mod @idx 6)) + (do + (reset! !x 3200.01) + (swap! !y + 450) + (println "==>" @!x @!y (mod @idx 6) @idx)) + (do + (swap! !x + 450) + (println "==>" @!x @!y (mod @idx 6) @idx))) + (swap! idx + 1) + (println "-->" node-data) + + (e/server + (let [event-data {:graph-name :main + :event-id (get-event-id) + :create-time (System/currentTimeMillis)}] + (add-new-node node-data + event-data + true + true))))))))))) + + +(= 0 (mod 2 5)) #?(:clj (def !ui-mode (atom :dark))) (e/def ui-mode (e/server (e/watch !ui-mode))) -(defn new-uuid [] - (keyword (str (random-uuid)))) -#?(:clj (def !edges (atom {#_#_:sv-line {:id :sv-line - :type "line" - :to :sv-circle - :from :sv-circle1 - :color "black"}}))) + +#?(:clj (def !edges (atom {:sv-line {:id :sv-line + :type "line" + :to :83696284-4eb1-481e-bbb7-b8d555495b76 + :from :08fe4616-4a43-4b5c-9d77-87fc7dc462c5 + :color "black"}}))) (e/def edges (e/server (e/watch !edges))) @@ -57,17 +126,19 @@ #?(:cljs (def !zoom-level (atom 1.5))) #?(:cljs (def !last-position (atom {:x 0 :y 0}))) #?(:cljs (def !viewbox (atom [0 0 2000 2000]))) +#?(:cljs (def !added-new-nodes? (atom false))) (e/def viewbox (e/client (e/watch !viewbox))) (e/def last-position (e/client (e/watch !last-position))) (e/def zoom-level (e/client (e/watch !zoom-level))) (e/def is-dragging? (e/client (e/watch !is-dragging?))) (e/def new-line (e/client (e/watch !new-line))) - +(e/def added-new-nodes? (e/client (e/watch !added-new-nodes?))) (e/defn subscribe [path] + (println "SUBSCRIBE PATH: " path) (e/server (new (!subscribe (concat [(keypath :main)] path) nodes-pstate)))) diff --git a/src/app/electric_flow.cljc b/src/app/electric_flow.cljc index dce6b76..4296249 100644 --- a/src/app/electric_flow.cljc +++ b/src/app/electric_flow.cljc @@ -1,8 +1,7 @@ (ns app.electric-flow (:import [hyperfiddle.electric Failure Pending]) - (:require contrib.str + (:require [contrib.str :refer [pprint-str]] [clojure.edn :as edn] - [clojure.pprint :as pprint] [hyperfiddle.electric :as e] [hyperfiddle.electric-svg :as svg] [hyperfiddle.electric-dom2 :as dom] @@ -10,31 +9,35 @@ [app.client.flow-calc :as fc] [app.client.electric-codemirror :as cm] [app.client.shapes.circle :refer [circle]] + [app.client.shapes.line :refer [line]] [app.client.shapes.rect :refer [rect]] [app.client.mode :refer [theme]] [app.client.shapes.util :as sutil :refer [new-line-el]] [app.client.playground.actions :refer [context-menu theme-toggle]] - [app.client.utils :refer [ ui-mode edges nodes + [app.client.utils :refer [ ui-mode edges nodes added-new-nodes? add-dg-new-nodes is-dragging? zoom-level last-position subscribe viewbox context-menu? reset-global-vals]] #?@(:cljs [[clojure.string :as str] - [app.client.utils :refer [!border-drag? !is-dragging? !zoom-level !last-position !viewbox !context-menu?]] + [app.client.utils :refer [!added-new-nodes? !border-drag? !is-dragging? !zoom-level !last-position !viewbox !context-menu?]] [missionary.core :as m]] :clj [[missionary.core :as m] [com.rpl.rama :as r] [app.server.llm :as llm :refer [chat-complete]] [app.client.utils :refer [!ui-mode !edges !nodes]] - [com.rpl.rama.path :as path :refer [subselect ALL FIRST keypath select]] + [com.rpl.rama.path :as path :refer [subselect ALL FIRST STAY MAP-KEYS keypath select]] [app.server.rama :as rama :refer [!subscribe nodes-pstate get-event-id add-new-node]] [clojure.core.async :as a :refer [! go]]]))) - (e/defn view [] + (pprint-str "IN THE VIEW") (let [current-selection (atom nil)] (e/client + #_(when-not @!added-new-nodes? + (add-dg-new-nodes.) + (reset! !added-new-nodes? true)) (theme-toggle.) (when (some? context-menu?) (println "context menu is " @!context-menu? (some? @!context-menu?)) @@ -59,16 +62,16 @@ (dom/on "mousemove" (e/fn [e] (when (= 0 (.-button e)) - (when @!border-drag? - (let [el (.getElementById js/document "sv") - [x y] (fc/element-new-coordinates1 e el)] - (println "border draging" x y) - (e/server - (swap! !edges assoc-in [:raw :x2] x) - (swap! !edges assoc-in [:raw :y2] y)))) + #_(when @!border-drag? + (let [el (.getElementById js/document "sv") + [x y] (fc/element-new-coordinates1 e el)] + (println "border draging" x y) + (e/server + (swap! !edges assoc-in [:raw :x2] x) + (swap! !edges assoc-in [:raw :y2] y)))) (cond - (and is-dragging? + (and @!is-dragging? (= "background" (:selection @current-selection)) @@ -82,7 +85,7 @@ (swap! !viewbox assoc 1 ny) (reset! !last-position {:x ex :y ey})) - #_#_@!border-drag? (println "border draging"))))) + @!border-drag? (println "border draging"))))) (dom/on "mousedown" (e/fn [e] (.preventDefault e) (when (= 0 @@ -91,6 +94,7 @@ ey (e/client (.-clientY e))] (reset! current-selection {:selection (.-id (.-target e)) :movable? true}) + (js/console.log "target" (.-target e)) (println "pointerdown svg" @current-selection) (println "pointerdown svg" (fc/browser-to-svg-coords e viewbox (.getElementById js/document "sv"))) (reset! !last-position {:x ex @@ -125,23 +129,28 @@ (bg/dot-background. (:svg-dots (theme. ui-mode)) viewbox) (e/server - (e/for-by identity [node-id (new (!subscribe (keypath :main) nodes-pstate))] - (let [type (subscribe. [:main (first node-id) :type])] - (e/client - (println "type" type (= "circle" type)) - (rect. (first node-id)) - #_(cond - (= "circle" type) (circle. node-id) - (= "rect" type) (rect. node-id nodes-pstate))))) - #_(e/for-by identity [edge edges] + (e/for-by identity [node-id (new (!subscribe [:main ] nodes-pstate))] + (e/client + (println "NODE ID" node-id) + (let [type (-> node-id second :type)] + (println "type" type (= "circle" type)) + (println "node" node-id) + #_(circle. (first node-id)) + (when (= "rect" type) + (rect. node-id)) ;(first node-id) (second node-id)) + #_(cond + (= "circle" type) (circle. node-id) + (= "rect" type) (rect. node-id nodes-pstate))))) + (e/for-by identity [edge edges] (let [[_ {:keys [type x2 y2]}] edge] (e/client (println "edge type" type edge) - (cond - (and - (= "raw" type) - (not-every? nil? [x2 y2])) (new-line-el. edge) - (= "line" type) (line. edge)))))))))) + (line. edge) + #_(cond + #_#_(and + (= "raw" type) + (not-every? nil? [x2 y2])) (new-line-el. edge) + (= "line" type) (line. edge)))))))))) (e/defn main [ring-request] (e/client diff --git a/src/app/electric_server_java11_jetty10.clj b/src/app/electric_server_java11_jetty10.clj deleted file mode 100644 index 8336010..0000000 --- a/src/app/electric_server_java11_jetty10.clj +++ /dev/null @@ -1,147 +0,0 @@ -(ns app.electric-server-java11-jetty10 - "preferred entrypoint (cleaner middleware for integration) but no java 8 compat" - (:require [clojure.java.io :as io] - [hyperfiddle.electric-jetty-adapter :as adapter] - [clojure.tools.logging :as log] - [ring.adapter.jetty9 :as ring] - [ring.middleware.basic-authentication :as auth] - [ring.middleware.content-type :refer [wrap-content-type]] - [ring.middleware.cookies :as cookies] - [ring.middleware.params :refer [wrap-params]] - [ring.middleware.resource :refer [wrap-resource]] - [ring.util.response :as res] - [clojure.string :as str] - [clojure.edn :as edn]) - (:import [java.io IOException] - [java.net BindException] - [org.eclipse.jetty.server.handler.gzip GzipHandler])) - -(defn authenticate [username password] username) ; demo (accept-all) authentication - -(defn wrap-demo-authentication "A Basic Auth example. Accepts any username/password and store the username in a cookie." - [next-handler] - (-> (fn [ring-req] - (let [res (next-handler ring-req)] - (if-let [username (:basic-authentication ring-req)] - (res/set-cookie res "username" username {:http-only true}) - res))) - (cookies/wrap-cookies) - (auth/wrap-basic-authentication authenticate))) - -(defn wrap-demo-router "A basic path-based routing middleware" - [next-handler] - (fn [ring-req] - (case (:uri ring-req) - "/auth" (let [response ((wrap-demo-authentication next-handler) ring-req)] - (if (= 401 (:status response)) ; authenticated? - response ; send response to trigger auth prompt - (-> (res/status response 302) ; redirect - (res/header "Location" (get-in ring-req [:headers "referer"]))))) ; redirect to where the auth originated - ;; For any other route, delegate to next middleware - (next-handler ring-req)))) - -(defn template "Takes a `string` and a map of key-values `kvs`. Replace all instances of `$key$` by value in `string`" - [string kvs] - (reduce-kv (fn [r k v] (str/replace r (str "$" k "$") v)) string kvs)) - -(defn get-modules [manifest-path] - (when-let [manifest (io/resource manifest-path)] - (let [manifest-folder (when-let [folder-name (second (rseq (str/split manifest-path #"\/")))] - (str "/" folder-name "/"))] - (->> (slurp manifest) - (edn/read-string) - (reduce (fn [r module] (assoc r (keyword "hyperfiddle.client.module" (name (:name module))) (str manifest-folder (:output-name module)))) {}))))) - -(defn wrap-index-page - "Server the `index.html` file with injected javascript modules from `manifest.edn`. `manifest.edn` is generated by the client build and contains javascript modules information." - [next-handler resources-path manifest-path] - (fn [ring-req] - (if-let [response (res/resource-response (str resources-path "/index.html"))] - (if-let [modules (get-modules manifest-path)] - (-> (res/response (template (slurp (:body response)) modules)) ; TODO cache in prod mode - (res/content-type "text/html") ; ensure `index.html` is not cached - (res/header "Cache-Control" "no-store") - (res/header "Last-Modified" (get-in response [:headers "Last-Modified"]))) - ;; No manifest found, can't inject js modules - (-> (res/not-found "Missing client program manifest") - (res/content-type "text/plain"))) - ;; index.html file not found on classpath - (next-handler ring-req)))) - -(def ^:const VERSION (not-empty (System/getProperty "ELECTRIC_USER_VERSION"))) ; see Dockerfile - -(defn wrap-reject-stale-client - "Intercept websocket UPGRADE request and check if client and server versions matches. - An electric client is allowed to connect if its version matches the server's version, or if the server doesn't have a version set (dev mode). - Otherwise, the client connection is rejected gracefully." - [next-handler] - (fn [ring-req] - (if (ring/ws-upgrade-request? ring-req) - (let [client-version (get-in ring-req [:query-params "ELECTRIC_USER_VERSION"])] - (cond - (nil? VERSION) (next-handler ring-req) - (= client-version VERSION) (next-handler ring-req) - :else (adapter/reject-websocket-handler 1008 "stale client"))) ; https://www.rfc-editor.org/rfc/rfc6455#section-7.4.1 - - (next-handler ring-req)))) - -(defn wrap-electric-websocket [next-handler entrypoint] - (fn [ring-request] - (if (ring/ws-upgrade-request? ring-request) - (let [authenticated-request (auth/basic-authentication-request ring-request authenticate) ; optional - electric-message-handler (partial adapter/electric-ws-message-handler authenticated-request entrypoint)] ; takes the ring request as first arg - makes it available to electric program - (ring/ws-upgrade-response (adapter/electric-ws-adapter electric-message-handler))) - (next-handler ring-request)))) - -(defn electric-websocket-middleware [next-handler entrypoint] - (-> (wrap-electric-websocket next-handler entrypoint) ; 4. connect electric client - (cookies/wrap-cookies) ; 3. makes cookies available to Electric app - (wrap-reject-stale-client) ; 2. reject stale electric client - (wrap-params))) ; 1. parse query params - - -(defn not-found-handler [_ring-request] - (-> (res/not-found "Not found") - (res/content-type "text/plain"))) - -(defn http-middleware [resources-path manifest-path entrypoint] - ;; these compose as functions, so are applied bottom up - (-> not-found-handler - (wrap-index-page resources-path manifest-path) ; 5. otherwise fallback to default page file - (wrap-resource resources-path) ; 4. serve static file from classpath - (wrap-content-type) ; 3. detect content (e.g. for index.html) - (wrap-demo-router) ; 2. route - (electric-websocket-middleware entrypoint))) ; 1. intercept electric websocket - - -(defn- add-gzip-handler - "Makes Jetty server compress responses. Optional but recommended." - [server] - (.setHandler server - (doto (GzipHandler.) - #_(.setIncludedMimeTypes (into-array ["text/css" "text/plain" "text/javascript" "application/javascript" "application/json" "image/svg+xml"])) ; only compress these - (.setMinGzipSize 1024) - (.setHandler (.getHandler server))))) - -(defn start-server! [entrypoint - {:keys [port resources-path manifest-path] - :or {port 8081 - resources-path "public" - manifest-path "public/js/manifest.edn"} - :as config}] - (try - (let [server (ring/run-jetty (http-middleware resources-path manifest-path entrypoint) - (merge {:port port - :join? false - :configurator add-gzip-handler} - config)) - final-port (-> server (.getConnectors) first (.getPort))] - (println "\nšŸ‘‰ App server available at" (str "http://" (:host config) ":" final-port "\n")) - server) - - (catch IOException err - (if (instance? BindException (ex-cause err)) ; port is already taken, retry with another one - (do (log/warn "Port" port "was not available, retrying with" (inc port)) - (start-server! entrypoint (update config :port inc))) - (throw err))))) - diff --git a/src/app/electric_server_java8_jetty9.clj b/src/app/electric_server_java8_jetty9.clj deleted file mode 100644 index 47e3a48..0000000 --- a/src/app/electric_server_java8_jetty9.clj +++ /dev/null @@ -1,142 +0,0 @@ -(ns app.electric-server-java8-jetty9 - "for java 8 compat, which is macos system java" - (:require [clojure.java.io :as io] - [hyperfiddle.electric-jetty-adapter :as adapter] - [clojure.tools.logging :as log] - [ring.adapter.jetty9 :as ring] - [ring.middleware.basic-authentication :as auth] - [ring.middleware.content-type :refer [wrap-content-type]] - [ring.middleware.cookies :as cookies] - [ring.middleware.params :refer [wrap-params]] - [ring.middleware.resource :refer [wrap-resource]] - [ring.util.response :as res] - [clojure.string :as str] - [clojure.edn :as edn]) - (:import [java.io IOException] - [java.net BindException] - [org.eclipse.jetty.server.handler.gzip GzipHandler])) - -(defn authenticate [username password] username) ; demo (accept-all) authentication - -(defn wrap-demo-authentication "A Basic Auth example. Accepts any username/password and store the username in a cookie." - [next-handler] - (-> (fn [ring-req] - (let [res (next-handler ring-req)] - (if-let [username (:basic-authentication ring-req)] - (res/set-cookie res "username" username {:http-only true}) - res))) - (cookies/wrap-cookies) - (auth/wrap-basic-authentication authenticate))) - -(defn wrap-demo-router "A basic path-based routing middleware" - [next-handler] - (fn [ring-req] - (case (:uri ring-req) - "/auth" (let [response ((wrap-demo-authentication next-handler) ring-req)] - (if (= 401 (:status response)) ; authenticated? - response ; send response to trigger auth prompt - (-> (res/status response 302) ; redirect - (res/header "Location" (get-in ring-req [:headers "referer"]))))) ; redirect to where the auth originated - ;; For any other route, delegate to next middleware - (next-handler ring-req)))) - -(defn template "Takes a `string` and a map of key-values `kvs`. Replace all instances of `$key$` by value in `string`" - [string kvs] - (reduce-kv (fn [r k v] (str/replace r (str "$" k "$") v)) string kvs)) - -(defn get-modules [manifest-path] - (when-let [manifest (io/resource manifest-path)] - (let [manifest-folder (when-let [folder-name (second (rseq (str/split manifest-path #"\/")))] - (str "/" folder-name "/"))] - (->> (slurp manifest) - (edn/read-string) - (reduce (fn [r module] (assoc r (keyword "hyperfiddle.client.module" (name (:name module))) (str manifest-folder (:output-name module)))) {}))))) - -(defn wrap-index-page - "Server the `index.html` file with injected javascript modules from `manifest.edn`. `manifest.edn` is generated by the client build and contains javascript modules information." - [next-handler resources-path manifest-path] - (fn [ring-req] - (if-let [response (res/resource-response (str resources-path "/index.html"))] - (if-let [modules (get-modules manifest-path)] - (-> (res/response (template (slurp (:body response)) modules)) ; TODO cache in prod mode - (res/content-type "text/html") ; ensure `index.html` is not cached - (res/header "Cache-Control" "no-store") - (res/header "Last-Modified" (get-in response [:headers "Last-Modified"]))) - ;; No manifest found, can't inject js modules - (-> (res/not-found "Missing client program manifest") - (res/content-type "text/plain"))) - ;; index.html file not found on classpath - (next-handler ring-req)))) - -(def VERSION (not-empty (System/getProperty "ELECTRIC_USER_VERSION"))) ; see Dockerfile - -(defn wrap-reject-stale-client - "Intercept websocket UPGRADE request and check if client and server versions matches. - An electric client is allowed to connect if its version matches the server's version, or if the server doesn't have a version set (dev mode). - Otherwise, the client connection is rejected gracefully." - [next-handler] - (fn [ring-req] - (let [client-version (get-in ring-req [:query-params "ELECTRIC_USER_VERSION"])] - (cond - (nil? VERSION) (next-handler ring-req) - (= client-version VERSION) (next-handler ring-req) - :else (adapter/reject-websocket-handler 1008 "stale client"))))) ; https://www.rfc-editor.org/rfc/rfc6455#section-7.4.1 - - -(def websocket-middleware - (fn [next-handler] - (-> (cookies/wrap-cookies next-handler) ; makes cookies available to Electric app - (wrap-reject-stale-client) - (wrap-params)))) - -(defn not-found-handler [_ring-request] - (-> (res/not-found "Not found") - (res/content-type "text/plain"))) - -(defn http-middleware [resources-path manifest-path] - ;; these compose as functions, so are applied bottom up - (-> not-found-handler - (wrap-index-page resources-path manifest-path) ; 4. otherwise fallback to default page file - (wrap-resource resources-path) ; 3. serve static file from classpath - (wrap-content-type) ; 2. detect content (e.g. for index.html) - (wrap-demo-router))) ; 1. route - - -(defn- add-gzip-handler - "Makes Jetty server compress responses. Optional but recommended." - [server] - (.setHandler server - (doto (GzipHandler.) - #_(.setIncludedMimeTypes (into-array ["text/css" "text/plain" "text/javascript" "application/javascript" "application/json" "image/svg+xml"])) ; only compress these - (.setMinGzipSize 1024) - (.setHandler (.getHandler server))))) - -(defn start-server! [entrypoint {:keys [port resources-path manifest-path] - :or {port 8081 - resources-path "public" - manifest-path "public/js/manifest.edn"} - :as config}] - (try - (let [server (ring/run-jetty (http-middleware resources-path manifest-path) - (merge {:port port - :join? false - :configurator add-gzip-handler - ;; Jetty 9 forces us to declare WS paths out of a ring handler. - ;; For Jetty 10 (NOT Java 8 compatible), drop the following and use `wrap-electric-websocket` as above - :websockets {"/" (websocket-middleware - (fn [ring-req] - (adapter/electric-ws-adapter - (partial adapter/electric-ws-message-handler - (auth/basic-authentication-request ring-req authenticate) - entrypoint))))}} - config)) - final-port (-> server (.getConnectors) first (.getPort))] - (println "\nšŸ‘‰ App server available at" (str "http://" (:host config) ":" final-port "\n")) - server) - - (catch IOException err - (if (instance? BindException (ex-cause err)) ; port is already taken, retry with another one - (do (log/warn "Port" port "was not available, retrying with" (inc port)) - (start-server! entrypoint (update config :port inc))) - (throw err))))) - diff --git a/src/app/server/file.clj b/src/app/server/file.clj index cf90b87..da1a585 100644 --- a/src/app/server/file.clj +++ b/src/app/server/file.clj @@ -3,7 +3,7 @@ [clojure.java.io :as io])) -(defonce file-location "/Users/bharatyadav/Documents/Softland-files/softland.edn") +(defonce file-location "/Users/sid597/Documents/Softland-files/softland.edn") ;; --------- Serialize and save event to file ------------ (defn save-event [function-name args] diff --git a/src/app/server/rama.clj b/src/app/server/rama.clj index d166cc1..53b357a 100644 --- a/src/app/server/rama.clj +++ b/src/app/server/rama.clj @@ -81,7 +81,7 @@ (source> *node-events-depot :> {:keys [*action-type *node-data *event-data]}) (local-select> (keypath :graph-name) *event-data :> *graph-name) (|hash *graph-name) - (println "R: PROCESSING EVENT" (= :new-node *action-type)) + (println "R: PROCESSING EVENT" *action-type) (< (= :update-node *action-type)) + (println "----------------------------------------------------") + (println "R: UPDATING NODE" *node-data) (local-transform> - [(first *node-data) (termval (second *node-data))] + [*graph-name (first *node-data) (termval (second *node-data))] $$nodes-pstate) + (println "R: NODE UPDATED" (local-select> ALL $$nodes-pstate)) + (println "----------------------------------------------------") ;; update event id (case> (= :update-event-id *action-type)) @@ -226,6 +230,21 @@ (some? (:event-id event-data))) (update-event-id))))) +(defn update-node + ([node-map event-data] + (update-node node-map event-data false false)) + ([node-map event-data save? update?] + (do + (foreign-append! event-depot (->node-events + :update-node + node-map + event-data) + :append-ack) + (when save? + (save-event "update-node" [node-map event-data])) + (when (or update? + (some? (:event-id event-data))) + (update-event-id))))) (defn get-path-data [path pstate] (foreign-select path pstate {:pkey :rect})) diff --git a/src/app/server_jetty.clj b/src/app/server_jetty.clj new file mode 100644 index 0000000..3b762a6 --- /dev/null +++ b/src/app/server_jetty.clj @@ -0,0 +1,120 @@ +(ns app.server-jetty + "Electric integrated into a sample ring + jetty app." + (:require + [clojure.edn :as edn] + [clojure.java.io :as io] + [clojure.string :as str] + [clojure.tools.logging :as log] + [contrib.assert :refer [check]] + [hyperfiddle.electric-ring-adapter :as electric-ring] + [ring.adapter.jetty :as ring] + [ring.middleware.content-type :refer [wrap-content-type]] + [ring.middleware.cookies :as cookies] + [ring.middleware.params :refer [wrap-params]] + [ring.middleware.resource :refer [wrap-resource]] + [ring.util.response :as res]) + (:import + (org.eclipse.jetty.server.handler.gzip GzipHandler) + (org.eclipse.jetty.websocket.server.config JettyWebSocketServletContainerInitializer JettyWebSocketServletContainerInitializer$Configurator))) + +;;; Electric integration + +(defn electric-websocket-middleware + "Open a websocket and boot an Electric server program defined by `entrypoint`. + Takes: + - a ring handler `next-handler` to call if the request is not a websocket upgrade (e.g. the next middleware in the chain), + - a `config` map eventually containing {:hyperfiddle.electric/user-version } to ensure client and server share the same version, + - see `hyperfiddle.electric-ring-adapter/wrap-reject-stale-client` + - an Electric `entrypoint`: a function (fn [ring-request] (e/boot-server {} my-ns/My-e-defn ring-request)) + " + [next-handler config entrypoint] + ;; Applied bottom-up + (-> (electric-ring/wrap-electric-websocket next-handler entrypoint) ; 5. connect electric client + ; 4. this is where you would add authentication middleware (after cookie parsing, before Electric starts) + (cookies/wrap-cookies) ; 3. makes cookies available to Electric app + (electric-ring/wrap-reject-stale-client config) ; 2. reject stale electric client + (wrap-params))) ; 1. parse query params + +(defn get-modules [manifest-path] + (when-let [manifest (io/resource manifest-path)] + (let [manifest-folder (when-let [folder-name (second (rseq (str/split manifest-path #"\/")))] + (str "/" folder-name "/"))] + (->> (slurp manifest) + (edn/read-string) + (reduce (fn [r module] (assoc r (keyword "hyperfiddle.client.module" (name (:name module))) + (str manifest-folder (:output-name module)))) {}))))) + +(defn template + "In string template `
$:foo/bar$
`, replace all instances of $key$ +with target specified by map `m`. Target values are coerced to string with `str`. + E.g. (template \"
$:foo$
\" {:foo 1}) => \"
1
\" - 1 is coerced to string." + [t m] (reduce-kv (fn [acc k v] (str/replace acc (str "$" k "$") (str v))) t m)) + +;;; Template and serve index.html + +(defn wrap-index-page + "Server the `index.html` file with injected javascript modules from `manifest.edn`. +`manifest.edn` is generated by the client build and contains javascript modules +information." + [next-handler config] + (fn [ring-req] + (if-let [response (res/resource-response (str (check string? (:resources-path config)) "/index.html"))] + (if-let [bag (merge config (get-modules (check string? (:manifest-path config))))] + (-> (res/response (template (slurp (:body response)) bag)) ; TODO cache in prod mode + (res/content-type "text/html") ; ensure `index.html` is not cached + (res/header "Cache-Control" "no-store") + (res/header "Last-Modified" (get-in response [:headers "Last-Modified"]))) + (-> (res/not-found (pr-str ::missing-shadow-build-manifest)) ; can't inject js modules + (res/content-type "text/plain"))) + ;; index.html file not found on classpath + (next-handler ring-req)))) + +(defn not-found-handler [_ring-request] + (-> (res/not-found "Not found") + (res/content-type "text/plain"))) + +(defn http-middleware [config] + ;; these compose as functions, so are applied bottom up + (-> not-found-handler + (wrap-index-page config) ; 3. otherwise fallback to default page file + (wrap-resource (:resources-path config)) ; 2. serve static file from classpath + (wrap-content-type) ; 1. detect content (e.g. for index.html) + )) + +(defn middleware [config entrypoint] + (-> (http-middleware config) ; 2. otherwise, serve regular http content + (electric-websocket-middleware config entrypoint))) ; 1. intercept websocket upgrades and maybe start Electric + +(defn- add-gzip-handler! + "Makes Jetty server compress responses. Optional but recommended." + [server] + (.setHandler server + (doto (GzipHandler.) + #_(.setIncludedMimeTypes (into-array ["text/css" "text/plain" "text/javascript" "application/javascript" "application/json" "image/svg+xml"])) ; only compress these + (.setMinGzipSize 1024) + (.setHandler (.getHandler server))))) + +(defn- configure-websocket! + "Tune Jetty Websocket config for Electric compat." [server] + (JettyWebSocketServletContainerInitializer/configure + (.getHandler server) + (reify JettyWebSocketServletContainerInitializer$Configurator + (accept [_this _servletContext wsContainer] + (.setIdleTimeout wsContainer (java.time.Duration/ofSeconds 60)) + (.setMaxBinaryMessageSize wsContainer (* 100 1024 1024)) ; 100M - temporary + (.setMaxTextMessageSize wsContainer (* 100 1024 1024)) ; 100M - temporary + )))) + +(defn start-server! [entrypoint + {:keys [port host] + :or {port 8080, host "0.0.0.0"} + :as config}] + (let [server (ring/run-jetty (middleware config entrypoint) + (merge {:port port + :join? false + :configurator (fn [server] + (configure-websocket! server) + (add-gzip-handler! server))} + config))] + (log/info "šŸ‘‰" (str "http://" host ":" (-> server (.getConnectors) first (.getPort)))) + server)) \ No newline at end of file diff --git a/src/boot.cljc b/src/boot.cljc deleted file mode 100644 index 9e7a695..0000000 --- a/src/boot.cljc +++ /dev/null @@ -1,11 +0,0 @@ -(ns boot - (:require - app.electric-flow - [hyperfiddle.electric :as e] - [hyperfiddle.rcf])) - - -(hyperfiddle.rcf/enable!) - -#?(:clj (defn with-ring-request [ring-request] (e/boot-server {} app.electric-flow/main ring-request))) -#?(:cljs (def client (e/boot-client {} app.electric-flow/main nil))) diff --git a/src/logback.xml b/src/logback.xml deleted file mode 100644 index cc9cc6d..0000000 --- a/src/logback.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - %highlight(%-5level) %logger: %msg%n - - - - - - - - - - diff --git a/src/prod.clj b/src/prod.clj deleted file mode 100644 index 24959ef..0000000 --- a/src/prod.clj +++ /dev/null @@ -1,16 +0,0 @@ -(ns prod - (:gen-class) - (:require - clojure.string - app.electric-server-java8-jetty9 - boot)) - -(def electric-server-config - {:host "0.0.0.0", :port 8080, :resources-path "public"}) - -(defn -main [& args] ; run with `clj -M -m prod` - (when (clojure.string/blank? (System/getProperty "ELECTRIC_USER_VERSION")) - (throw (ex-info "ELECTRIC_USER_VERSION jvm property must be set in prod" {})) - (app.electric-server-java8-jetty9/start-server! boot/with-ring-request electric-server-config))) - -; On CLJS side we reuse src/user.cljs for prod entrypoint \ No newline at end of file diff --git a/src/user.clj b/src/user.clj deleted file mode 100644 index 67e1555..0000000 --- a/src/user.clj +++ /dev/null @@ -1,29 +0,0 @@ -(ns user) ; Must be ".clj" file, Clojure doesn't auto-load user.cljc - -; lazy load dev stuff - for faster REPL startup and cleaner dev classpath -(def start-electric-server! (delay (partial @(requiring-resolve 'app.electric-server-java8-jetty9/start-server!) - @(requiring-resolve 'boot/with-ring-request)))) -(def shadow-start! (delay @(requiring-resolve 'shadow.cljs.devtools.server/start!))) -(def shadow-watch (delay @(requiring-resolve 'shadow.cljs.devtools.api/watch))) - -(def electric-server-config - {:host "0.0.0.0", :port 8080, :resources-path "public"}) - -(defn main [& args] - (println "Starting Electric compiler and server...") - (@shadow-start!) ; serves index.html as well - (@shadow-watch :dev) ; depends on shadow server - ; Shadow loads app.tod-list here, such that it shares memory with server. - (def server (@start-electric-server! electric-server-config)) - (comment (.stop server))) - -; Server-side Electric userland code is lazy loaded by the shadow build. -; WARNING: make sure your REPL and shadow-cljs are sharing the same JVM! - -(comment - (main) ; Electric Clojure(JVM) REPL entrypoint - (hyperfiddle.rcf/enable!) ; turn on RCF after all transitive deps have loaded - (shadow.cljs.devtools.api/repl :dev) ; shadow server hosts the cljs repl - ; connect a second REPL instance to it - ; (DO NOT REUSE JVM REPL it will fail weirdly) - (type 1)) \ No newline at end of file diff --git a/src/user.cljs b/src/user.cljs deleted file mode 100644 index f1b0062..0000000 --- a/src/user.cljs +++ /dev/null @@ -1,15 +0,0 @@ -(ns ^:dev/always user ; Electric currently needs to rebuild everything when any file changes. Will fix - (:require boot)) - - -(defonce reactor nil) - -(defn ^:dev/after-load ^:export start! [] - (assert (nil? reactor) "reactor already running") - (set! reactor (boot/client - #(js/console.log "Reactor success:" %) - #(js/console.error "Reactor failure:" %)))) - -(defn ^:dev/before-load stop! [] - (when reactor (reactor)) ; teardown - (set! reactor nil)) \ No newline at end of file