mirror of
https://github.com/edufeed-org/edufeed-web.git
synced 2025-12-10 00:34:34 +00:00
some stuff kind of works now
This commit is contained in:
commit
36dad21f43
10 changed files with 545 additions and 0 deletions
17
package.json
Normal file
17
package.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "ied",
|
||||
"scripts": {
|
||||
"ancient": "clojure -Sdeps '{:deps {com.github.liquidz/antq {:mvn/version \"RELEASE\"}}}' -m antq.core",
|
||||
"watch": "npx shadow-cljs watch app browser-test karma-test",
|
||||
"release": "npx shadow-cljs release app",
|
||||
"build-report": "npx shadow-cljs run shadow.cljs.build-report app target/build-report.html"
|
||||
},
|
||||
"dependencies": {
|
||||
"@noble/secp256k1": "^2.1.0",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"shadow-cljs": "2.26.2"
|
||||
}
|
||||
}
|
||||
39
shadow-cljs.edn
Normal file
39
shadow-cljs.edn
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
{:nrepl {:port 8777}
|
||||
|
||||
:source-paths ["src" "test"]
|
||||
|
||||
:dependencies
|
||||
[[reagent "1.1.1"]
|
||||
[re-frame "1.4.2"]
|
||||
[day8.re-frame/tracing "0.6.2"]
|
||||
[bidi "2.1.6"]
|
||||
[clj-commons/pushy "0.3.10"]
|
||||
[binaryage/devtools "1.0.6"]
|
||||
[day8.re-frame/re-frame-10x "1.9.3"]
|
||||
|
||||
[funcool/promesa "11.0.678"]
|
||||
[nilenso/wscljs "0.2.0"]
|
||||
]
|
||||
|
||||
:dev-http
|
||||
{8280 "resources/public"
|
||||
8290 "target/browser-test"}
|
||||
|
||||
:builds
|
||||
{:app
|
||||
{:target :browser
|
||||
:output-dir "resources/public/js/compiled"
|
||||
:asset-path "/js/compiled"
|
||||
:modules
|
||||
{:app {:init-fn ied.core/init}}
|
||||
:devtools
|
||||
{:preloads [day8.re-frame-10x.preload]}
|
||||
:dev
|
||||
{:compiler-options
|
||||
{:closure-defines
|
||||
{re-frame.trace.trace-enabled? true
|
||||
day8.re-frame.tracing.trace-enabled? true}}}
|
||||
:release
|
||||
{:build-options
|
||||
{:ns-aliases
|
||||
{day8.re-frame.tracing day8.re-frame.tracing-stubs}}}}}}
|
||||
4
src/ied/config.cljs
Normal file
4
src/ied/config.cljs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
(ns ied.config)
|
||||
|
||||
(def debug?
|
||||
^boolean goog.DEBUG)
|
||||
24
src/ied/core.cljs
Normal file
24
src/ied/core.cljs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
(ns ied.core
|
||||
(:require
|
||||
[reagent.dom :as rdom]
|
||||
[re-frame.core :as re-frame]
|
||||
[ied.events :as events]
|
||||
[ied.routes :as routes]
|
||||
[ied.views :as views]
|
||||
[ied.config :as config]))
|
||||
|
||||
(defn dev-setup []
|
||||
(when config/debug?
|
||||
(println "dev mode")))
|
||||
|
||||
(defn ^:dev/after-load mount-root []
|
||||
(re-frame/clear-subscription-cache!)
|
||||
(let [root-el (.getElementById js/document "app")]
|
||||
(rdom/unmount-component-at-node root-el)
|
||||
(rdom/render [views/main-panel] root-el)))
|
||||
|
||||
(defn init []
|
||||
(routes/start!)
|
||||
(re-frame/dispatch-sync [::events/initialize-db])
|
||||
(dev-setup)
|
||||
(mount-root))
|
||||
8
src/ied/db.cljs
Normal file
8
src/ied/db.cljs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
(ns ied.db)
|
||||
|
||||
(def default-db
|
||||
{:name "re-frame"
|
||||
:show-add-event false
|
||||
:events nil
|
||||
:pk nil
|
||||
:sockets []})
|
||||
164
src/ied/events.cljs
Normal file
164
src/ied/events.cljs
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
(ns ied.events
|
||||
(:require
|
||||
[re-frame.core :as re-frame]
|
||||
[ied.db :as db]
|
||||
[day8.re-frame.tracing :refer-macros [fn-traced]]
|
||||
[ied.subs :as subs]
|
||||
[ied.nostr :as nostr]
|
||||
|
||||
[promesa.core :as p]
|
||||
[wscljs.client :as ws]
|
||||
[wscljs.format :as fmt]))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::initialize-db
|
||||
(fn-traced [_ _]
|
||||
db/default-db))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::navigate
|
||||
(fn-traced [_ [_ handler]]
|
||||
{:navigate handler}))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::set-active-panel
|
||||
(fn-traced [{:keys [db]} [_ active-panel]]
|
||||
{:db (assoc db :active-panel active-panel)}))
|
||||
|
||||
;; Database Event?
|
||||
(re-frame/reg-event-fx
|
||||
::save-event
|
||||
;; TODO if EOSE retrieved end connection identified by uri
|
||||
;; TODO make events a set (?)
|
||||
(fn-traced [{:keys [db]} [_ [uri event]]]
|
||||
(println uri event)
|
||||
(when (= (first event) "EVENT")
|
||||
{:db (update db :events conj event)})))
|
||||
|
||||
(defn handlers
|
||||
[ws-uri]
|
||||
{:on-message (fn [e] (re-frame/dispatch [::save-event [ws-uri (-> (.-data e)
|
||||
js/JSON.parse
|
||||
(js->clj :keywordize-keys true))]]))
|
||||
:on-open #(prn "Opening a new connection")
|
||||
:on-close #(prn "Closing a connection")})
|
||||
|
||||
(defn create-socket
|
||||
[uri]
|
||||
(ws/create uri (handlers uri)))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::create-websocket
|
||||
(fn-traced [{:keys [db]} [_ ws]]
|
||||
{:db (update db :sockets conj (merge ws {:socket (create-socket (:uri ws))}))}))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::connect-to-websocket
|
||||
(fn-traced [{:keys [db]} [_ _]]
|
||||
{::connect-to-websocket-fx _})) ;; TODO identify the socket to connect to
|
||||
|
||||
(re-frame/reg-fx
|
||||
::connect-to-websocket-fx
|
||||
(fn [_]
|
||||
(let [sockets (re-frame/subscribe [::subs/sockets])]
|
||||
(println "Sockets: " @sockets)
|
||||
(ws/send (:socket (first @sockets)) ["REQ" "4242" {:kinds [1 30142]
|
||||
:limit 10}] fmt/json)
|
||||
; (ws/close (:socket (first @sockets))) ;; should be handled otherwise (?)
|
||||
)))
|
||||
|
||||
;; TODO use id to close socket
|
||||
;; add connection status to socket
|
||||
;; render connect / disconnect button based on status
|
||||
(re-frame/reg-event-fx
|
||||
::close-connection-to-websocket
|
||||
(fn-traced [{:keys [db]} [_ _]]
|
||||
{::close-connection-to-websocket-fx _}))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::close-connection-to-websocket-fx
|
||||
(fn [_]
|
||||
(let [sockets (re-frame/subscribe [::subs/sockets])]
|
||||
(ws/close (:socket (first @sockets))))))
|
||||
|
||||
;; TODO
|
||||
(re-frame/reg-event-fx
|
||||
::send-to-relays
|
||||
(fn-traced [cofx [_ signedEvent]]
|
||||
{::send-to-relays-fx signedEvent}))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::send-to-relays-fx
|
||||
(fn [signedEvent]
|
||||
(let [sockets (re-frame/subscribe [::subs/sockets])]
|
||||
(.log js/console (clj->js ["EVENT" signedEvent]))
|
||||
(ws/send (:socket (first @sockets)) ["EVENT" signedEvent] fmt/json))))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::update-websockets
|
||||
(fn [db [_ sockets]]
|
||||
(assoc db :sockets sockets)))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::remove-websocket
|
||||
(fn-traced [{:keys [db]} [_ socket]]
|
||||
{::remove-websocket-fx (:id socket)}))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::remove-websocket-fx
|
||||
(fn [id]
|
||||
(let [sockets (re-frame/subscribe [::subs/sockets])
|
||||
filtered (filter #(not= id (:id %)) @sockets)] ;; TODO maybe this can also be done using the URI
|
||||
(re-frame/dispatch [::update-websockets filtered]))))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::toggle-show-add-event
|
||||
(fn [db _]
|
||||
(assoc db :show-add-event (not (:show-add-event db)))))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::publish-resource
|
||||
[(re-frame/inject-cofx :now)]
|
||||
(fn-traced [cofx [_ resource]]
|
||||
(let [event {:kind 30142
|
||||
:created_at (:now cofx)
|
||||
:content "hello world"
|
||||
::tags []
|
||||
;;:tags [["author" "" (:author resource)]]
|
||||
}]
|
||||
{::publish-resource-fx event})))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::publish-resource-fx
|
||||
(fn [resource]
|
||||
(p/let [signedEvent (.nostr.signEvent js/window (clj->js resource))]
|
||||
(re-frame/dispatch [::send-to-relays signedEvent]))))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::login-with-extension
|
||||
(fn-traced [cofx [_ _]]
|
||||
{::login-with-extension-fx _}))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::login-with-extension-fx
|
||||
(fn [db _]
|
||||
(p/let [pk (.nostr.getPublicKey js/window)]
|
||||
(re-frame/dispatch [::save-pk pk]))))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::save-pk
|
||||
(fn-traced [db [_ pk]]
|
||||
(assoc db :pk pk)))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::logout
|
||||
(fn-traced [db _]
|
||||
(assoc db :pk nil)))
|
||||
|
||||
(re-frame/reg-cofx
|
||||
:now
|
||||
(fn [cofx _data] ;; _data unused
|
||||
(assoc cofx :now (quot (.now js/Date) 1000))))
|
||||
|
||||
(comment
|
||||
(quot (.now js/Date) 1000))
|
||||
52
src/ied/nostr.cljs
Normal file
52
src/ied/nostr.cljs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
(ns ied.nostr
|
||||
(:require
|
||||
[promesa.core :as p]
|
||||
["@noble/secp256k1" :as secp]))
|
||||
|
||||
(defn event-to-serialized-json [m]
|
||||
(let [event [0
|
||||
(:pubkey m)
|
||||
(:created_at m)
|
||||
(:kind m)
|
||||
(:tags m)
|
||||
(:content m)]]
|
||||
|
||||
(js/JSON.stringify (clj->js event))))
|
||||
|
||||
(defn pad-start [s len pad]
|
||||
(let [padding (apply str (repeat (- len (count s)) pad))]
|
||||
(str padding s)))
|
||||
|
||||
(defn to-hex [b]
|
||||
(pad-start (.toString b 16) 2 "0"))
|
||||
|
||||
(defn byte-array-to-hex [byte-array]
|
||||
(apply str (map to-hex byte-array)))
|
||||
|
||||
(defn sha256 [text]
|
||||
(p/let [encoder (new js/TextEncoder)
|
||||
data (.encode encoder text)
|
||||
hash (.crypto.subtle.digest js/window "SHA-256" data)
|
||||
hashArray (.from js/Array (new js/Uint8Array hash))
|
||||
byteArray (js/Uint8Array. hashArray)
|
||||
hexArray (byte-array-to-hex byteArray)]
|
||||
hexArray))
|
||||
|
||||
(defn signEvent [hashedEvent sk]
|
||||
(.sign secp hashedEvent sk))
|
||||
|
||||
(comment
|
||||
(.getPublicKey secp (.utils.randomPrivateKey secp)))
|
||||
|
||||
(comment
|
||||
(p/let [event {:content "hello world"
|
||||
:created_at 1722606842
|
||||
:kind 1
|
||||
:pubkey "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
|
||||
:tags []}
|
||||
|
||||
serialized-event (event-to-serialized-json event)
|
||||
id (sha256 serialized-event)]
|
||||
(println serialized-event)
|
||||
(println id)))
|
||||
|
||||
44
src/ied/routes.cljs
Normal file
44
src/ied/routes.cljs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
(ns ied.routes
|
||||
(:require
|
||||
[bidi.bidi :as bidi]
|
||||
[pushy.core :as pushy]
|
||||
[re-frame.core :as re-frame]
|
||||
[ied.events :as events]))
|
||||
|
||||
(defmulti panels identity)
|
||||
(defmethod panels :default [] [:div "No panel found for this route."])
|
||||
|
||||
(def routes
|
||||
(atom
|
||||
["/" {"" :home
|
||||
"about" :about
|
||||
"settings" :settings}]))
|
||||
|
||||
(defn parse
|
||||
[url]
|
||||
(bidi/match-route @routes url))
|
||||
|
||||
(defn url-for
|
||||
[& args]
|
||||
(apply bidi/path-for (into [@routes] args)))
|
||||
|
||||
(defn dispatch
|
||||
[route]
|
||||
(let [panel (keyword (str (name (:handler route)) "-panel"))]
|
||||
(re-frame/dispatch [::events/set-active-panel panel])))
|
||||
|
||||
(defonce history
|
||||
(pushy/pushy dispatch parse))
|
||||
|
||||
(defn navigate!
|
||||
[handler]
|
||||
(pushy/set-token! history (url-for handler)))
|
||||
|
||||
(defn start!
|
||||
[]
|
||||
(pushy/start! history))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:navigate
|
||||
(fn [handler]
|
||||
(navigate! handler)))
|
||||
33
src/ied/subs.cljs
Normal file
33
src/ied/subs.cljs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
(ns ied.subs
|
||||
(:require
|
||||
[re-frame.core :as re-frame]))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::name
|
||||
(fn [db]
|
||||
(:name db)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::active-panel
|
||||
(fn [db _]
|
||||
(:active-panel db)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::sockets
|
||||
(fn [db _]
|
||||
(:sockets db)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::events
|
||||
(fn [db _]
|
||||
(:events db)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::show-add-event
|
||||
(fn [db _]
|
||||
(:show-add-event db)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::pk
|
||||
(fn [db _]
|
||||
(:pk db)))
|
||||
160
src/ied/views.cljs
Normal file
160
src/ied/views.cljs
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
(ns ied.views
|
||||
(:require
|
||||
[re-frame.core :as re-frame]
|
||||
[ied.events :as events]
|
||||
[ied.routes :as routes]
|
||||
[ied.subs :as subs]
|
||||
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
;; add resource form
|
||||
(defn add-resource-form
|
||||
[name uri author]
|
||||
(let [s (reagent/atom {:name name
|
||||
:uri uri
|
||||
:author author})]
|
||||
(fn []
|
||||
[:form {:on-submit (fn [e]
|
||||
(.preventDefault e)
|
||||
;; do something with the state @s
|
||||
)}
|
||||
[:label {:for name} "Name: "]
|
||||
[:input {:type :text :name :name
|
||||
:value (:name @s)
|
||||
:on-change (fn [e]
|
||||
(swap! s assoc :name (-> e .-target .-value)))}]
|
||||
[:label {:for uri} "Uri: "]
|
||||
[:input {:type :text :name :uri
|
||||
:value (:uri @s)
|
||||
:on-change (fn [e]
|
||||
(swap! s assoc :uri (-> e .-target .-value)))}]
|
||||
[:label {:for uri} "Author: "]
|
||||
[:input {:type :text :name :author
|
||||
:value (:author @s)
|
||||
:on-change (fn [e]
|
||||
(swap! s assoc :author (-> e .-target .-value)))}]
|
||||
[:button {:on-click #(re-frame/dispatch [::events/publish-resource {:name (:name @s)
|
||||
:uri (:uri @s)
|
||||
:author (:author @s)}])}
|
||||
"Publish Resource"]])))
|
||||
|
||||
;; events
|
||||
(defn events-panel []
|
||||
(let [events (re-frame/subscribe [::subs/events])
|
||||
show-add-event (re-frame/subscribe [::subs/show-add-event])]
|
||||
[:div
|
||||
[:p {:on-click #(re-frame/dispatch [::events/toggle-show-add-event])}
|
||||
(if @show-add-event "X" "Add Resource!")]
|
||||
(when @show-add-event
|
||||
[add-resource-form])
|
||||
[:p (str "Num of events: " (count @events))]
|
||||
(if (> (count @events) 0)
|
||||
(doall
|
||||
(for [event @events]
|
||||
; [:li (:content (nth event 2 {:content "hello"}))]
|
||||
[:li {:key (:id event)} (get (nth event 2) :content "")]
|
||||
))
|
||||
[:p "no events there"])]))
|
||||
|
||||
;; relays
|
||||
(defn add-relay-form
|
||||
[name uri]
|
||||
(let [s (reagent/atom {:name name
|
||||
:uri uri})]
|
||||
(fn []
|
||||
[:form {:on-submit (fn [e]
|
||||
(.preventDefault e)
|
||||
;; do something with the state @s
|
||||
)}
|
||||
[:label {:for name} "Name: "]
|
||||
[:input {:type :text :name :name
|
||||
:value (:name @s)
|
||||
:on-change (fn [e]
|
||||
(swap! s assoc :name (-> e .-target .-value)))}]
|
||||
[:label {:for uri} "Uri: "]
|
||||
[:input {:type :text :name :uri
|
||||
:value (:uri @s)
|
||||
:on-change (fn [e]
|
||||
(swap! s assoc :uri (-> e .-target .-value)))}]
|
||||
[:button {:on-click #(re-frame/dispatch [::events/create-websocket {:name (:name @s)
|
||||
:id (random-uuid)
|
||||
:uri (:uri @s)}])}
|
||||
|
||||
"Add Relay"]])))
|
||||
|
||||
(defn relays-panel
|
||||
[]
|
||||
(let [sockets (re-frame/subscribe [::subs/sockets])]
|
||||
[:div
|
||||
[add-relay-form]
|
||||
|
||||
(when (> (count @sockets) 0)
|
||||
[:ul
|
||||
(doall
|
||||
(for [socket @sockets]
|
||||
[:li {:key (:id socket)}
|
||||
[:span (:name socket)]
|
||||
[:button {:on-click #(re-frame/dispatch [::events/connect-to-websocket])} "Load events"]
|
||||
[:button {:on-click #(re-frame/dispatch [::events/close-connection-to-websocket])} "Disconnect"]
|
||||
[:button {:on-click #(re-frame/dispatch [::events/remove-websocket socket])} "Remove relay"]]))])]))
|
||||
|
||||
;; Header
|
||||
|
||||
(defn header []
|
||||
(let [pk (re-frame/subscribe [::subs/pk])]
|
||||
[:div
|
||||
[:a {:on-click #(re-frame/dispatch [::events/navigate :home])}
|
||||
"home"]
|
||||
"|"
|
||||
[:a {:on-click #(re-frame/dispatch [::events/navigate :about])}
|
||||
"about"]
|
||||
"|"
|
||||
[:a {:on-click #(re-frame/dispatch [::events/navigate :settings])}
|
||||
"settings"]
|
||||
"|"
|
||||
(if @pk
|
||||
[:a {:on-click #(re-frame/dispatch [::events/logout])} "logout"]
|
||||
[:a {:on-click #(re-frame/dispatch [::events/login-with-extension])} "login"])]))
|
||||
|
||||
;; Settings
|
||||
(defn settings-panel []
|
||||
[:div
|
||||
[:h1 "Settings"]
|
||||
[relays-panel]])
|
||||
|
||||
(defmethod routes/panels :settings-panel [] [settings-panel])
|
||||
|
||||
;; Home
|
||||
(defn home-panel []
|
||||
(let [name (re-frame/subscribe [::subs/name])
|
||||
events (re-frame/subscribe [::subs/events])]
|
||||
[:div
|
||||
[:h1
|
||||
(str "Hello from " @name ". This is the Home Page.")]
|
||||
|
||||
[events-panel]
|
||||
[:p (count @events)]]))
|
||||
|
||||
(defmethod routes/panels :home-panel [] [home-panel])
|
||||
|
||||
;; about
|
||||
(defn about-panel []
|
||||
[:div
|
||||
[:h1 "This is the About Page."]
|
||||
|
||||
[:div
|
||||
[:a {:on-click #(re-frame/dispatch [::events/navigate :home])}
|
||||
"go to Home Page"]]])
|
||||
|
||||
(defmethod routes/panels :about-panel [] [about-panel])
|
||||
|
||||
;; main
|
||||
(defn main-panel []
|
||||
(let [active-panel (re-frame/subscribe [::subs/active-panel])]
|
||||
[:div
|
||||
[header]
|
||||
(routes/panels @active-panel)]))
|
||||
|
||||
(comment
|
||||
|
||||
(.log js/console "Hello World"))
|
||||
Loading…
Add table
Add a link
Reference in a new issue