From df415f8181479b5a5377e0eceb0cde68f3a44c72 Mon Sep 17 00:00:00 2001 From: "Robert C. Martin" Date: Wed, 27 Apr 2022 12:07:24 -0500 Subject: [PATCH] Swing begins. --- spec/more_speech/nostr/events_spec.clj | 82 ++++++++++++------------ src/more_speech/core.clj | 43 +++++++------ src/more_speech/nostr/events.clj | 59 +++++++++-------- src/more_speech/nostr/protocol.clj | 18 +++--- src/more_speech/ui/swing/main_window.clj | 79 +++++++++++++++++++++++ 5 files changed, 186 insertions(+), 95 deletions(-) create mode 100644 src/more_speech/ui/swing/main_window.clj diff --git a/spec/more_speech/nostr/events_spec.clj b/spec/more_speech/nostr/events_spec.clj index cf363d2..1df9014 100644 --- a/spec/more_speech/nostr/events_spec.clj +++ b/spec/more_speech/nostr/events_spec.clj @@ -14,15 +14,15 @@ ["e" "0002" "anotherurl"]] "content" "the content" "sig" "dddddd"}) - (with state {:application - {:text-event-map {} - :chronological-text-events (make-chronological-text-events) - } - }) + (with state + {:text-event-map {} + :chronological-text-events (make-chronological-text-events) + } + ) (it "creates the map of text events by id" (let [state (process-text-event @state @event) - event-map (get-in state [:application :text-event-map]) - text-events (get-in state [:application :chronological-text-events]) + event-map (get-in state [:text-event-map]) + text-events (get-in state [:chronological-text-events]) event (get event-map 0xdeadbeef :not-in-map)] (should= 1 (count event-map)) (should= 1 (count text-events)) @@ -38,10 +38,10 @@ (it "adds references to tagged articles." (let [state (assoc-in @state - [:application :text-event-map 2] + [:text-event-map 2] {:id 2}) state (process-references state (translate-text-event @event)) - text-event-map (get-in state [:application :text-event-map]) + text-event-map (get-in state [:text-event-map]) article (get text-event-map 2)] (should= [0xdeadbeef] (:references article))) ) @@ -49,31 +49,31 @@ (context "sorted set for handling events" (it "adds one element" (let [state (add-event @state {:id 10 :created-at 0})] - (should= #{[10 0]} (get-in state [:application :chronological-text-events])) - (should= {10 {:id 10 :created-at 0}} (get-in state [:application :text-event-map])))) + (should= #{[10 0]} (get-in state [:chronological-text-events])) + (should= {10 {:id 10 :created-at 0}} (get-in state [:text-event-map])))) (it "adds two elements in chronological order, should be reversed" (let [state (add-event @state {:id 10 :created-at 0}) state (add-event state {:id 20 :created-at 1}) ] - (should= [[20 1] [10 0]] (seq (get-in state [:application :chronological-text-events]))) + (should= [[20 1] [10 0]] (seq (get-in state [:chronological-text-events]))) (should= {10 {:id 10 :created-at 0} - 20 {:id 20 :created-at 1}} (get-in state [:application :text-event-map]))) + 20 {:id 20 :created-at 1}} (get-in state [:text-event-map]))) ) (it "adds two elements in reverse chronological order, should remain." (let [state (add-event @state {:id 10 :created-at 1}) state (add-event state {:id 20 :created-at 0}) ] - (should= [[10 1] [20 0]] (seq (get-in state [:application :chronological-text-events]))) + (should= [[10 1] [20 0]] (seq (get-in state [:chronological-text-events]))) (should= {10 {:id 10 :created-at 1} - 20 {:id 20 :created-at 0}} (get-in state [:application :text-event-map]))) + 20 {:id 20 :created-at 0}} (get-in state [:text-event-map]))) ) (it "adds two elements with equal ids" (let [state (add-event @state {:id 10 :created-at 1}) state (add-event state {:id 10 :created-at 0}) - event-map (get-in state [:application :text-event-map])] - (should= [[10 1]] (seq (get-in state [:application :chronological-text-events]))) + event-map (get-in state [:text-event-map])] + (should= [[10 1]] (seq (get-in state [:chronological-text-events]))) (should= 1 (count event-map)) ) ) @@ -90,13 +90,13 @@ now (quot (System/currentTimeMillis) 1000)] (should= "EVENT" (first event)) (should= (ecc/bytes->hex-string public-key) pubkey) - (should (<= 0 (- now created_at) 1 )) ;within one second. + (should (<= 0 (- now created_at) 1)) ;within one second. (should= 1 kind) (should= [] tags) (should= text content) (should (ecc/do-verify (ecc/hex-string->bytes id) - public-key - (ecc/hex-string->bytes sig))))) + public-key + (ecc/hex-string->bytes sig))))) (it "composes a reply." (let [private-key (ecc/num->bytes 64 42) @@ -108,31 +108,31 @@ now (quot (System/currentTimeMillis) 1000)] (should= "EVENT" (first event)) (should= (ecc/bytes->hex-string public-key) pubkey) - (should (<= 0 (- now created_at) 1)) ;within one second. + (should (<= 0 (- now created_at) 1)) ;within one second. (should= 1 kind) (should= [[:e (ecc/bytes->hex-string reply-to)]] tags) - (should= text content) - (should (ecc/do-verify (ecc/hex-string->bytes id) - public-key - (ecc/hex-string->bytes sig))))) + (should= text content) + (should (ecc/do-verify (ecc/hex-string->bytes id) + public-key + (ecc/hex-string->bytes sig))))) (it "composes a message with a slash." - (let [private-key (ecc/num->bytes 64 42) - public-key (ecc/get-pub-key private-key) - reply-to nil - text "message/text" - event (compose-text-event private-key text reply-to) - {:keys [pubkey created_at kind tags content id sig]} (second event) - now (quot (System/currentTimeMillis) 1000)] - (should= "EVENT" (first event)) - (should= (ecc/bytes->hex-string public-key) pubkey) - (should (<= 0 (- now created_at) 1)) ;within one second. - (should= 1 kind) - (should= [] tags) - (should= text content) - (should (ecc/do-verify (ecc/hex-string->bytes id) - public-key - (ecc/hex-string->bytes sig))))) + (let [private-key (ecc/num->bytes 64 42) + public-key (ecc/get-pub-key private-key) + reply-to nil + text "message/text" + event (compose-text-event private-key text reply-to) + {:keys [pubkey created_at kind tags content id sig]} (second event) + now (quot (System/currentTimeMillis) 1000)] + (should= "EVENT" (first event)) + (should= (ecc/bytes->hex-string public-key) pubkey) + (should (<= 0 (- now created_at) 1)) ;within one second. + (should= 1 kind) + (should= [] tags) + (should= text content) + (should (ecc/do-verify (ecc/hex-string->bytes id) + public-key + (ecc/hex-string->bytes sig))))) ) (describe "json" diff --git a/src/more_speech/core.clj b/src/more_speech/core.clj index 2e3ae71..f1b3323 100644 --- a/src/more_speech/core.clj +++ b/src/more_speech/core.clj @@ -15,7 +15,6 @@ (ns more-speech.core (:require [quil.core :as q] - [quil.middleware :as m] [clojure.core.async :as async] [more-speech.nostr.events :as nostr] [more-speech.ui.widget :refer [draw-widget @@ -26,8 +25,10 @@ [more-speech.ui.application :refer [map->application]] [more-speech.ui.graphics :as g] [more-speech.ui.widget :as w] - [more-speech.ui.config :as config] - [more-speech.nostr.protocol :as protocol])) + [more-speech.nostr.protocol :as protocol] + [more-speech.ui.swing.main-window :as swing] + [more-speech.nostr.events :as events]) + ) (def events (atom [])) (def send-chan (async/chan)) @@ -80,25 +81,29 @@ (draw-widget application state) ) -(declare more-speech) +(declare more-speech setup-jframe) (defn ^:export -main [& args] - (q/defsketch more-speech - :title "More Speech" - :size [(q/screen-width) (- (q/screen-height) config/window-margin)] - :setup setup - :update update-state - :draw draw-state - :mouse-wheel w/mouse-wheel - :mouse-pressed w/mouse-pressed - :mouse-released w/mouse-released - :mouse-moved w/mouse-moved - :mouse-dragged w/mouse-dragged - :key-pressed w/key-pressed - :middleware [m/fun-mode] - :on-close protocol/close-connection) + ;(q/defsketch more-speech + ; :title "More Speech" + ; :size [(q/screen-width) (- (q/screen-height) config/window-margin)] + ; :setup setup + ; :update update-state + ; :draw draw-state + ; :mouse-wheel w/mouse-wheel + ; :mouse-pressed w/mouse-pressed + ; :mouse-released w/mouse-released + ; :mouse-moved w/mouse-moved + ; :mouse-dragged w/mouse-dragged + ; :key-pressed w/key-pressed + ; :middleware [m/fun-mode] + ; :on-close protocol/close-connection) + ;(reset! events (read-string (slurp "nostr-messages"))) - (protocol/get-events events send-chan) + (let [event-agent (events/make-event-agent)] + (swing/setup-jframe event-agent send-chan) + (protocol/get-events event-agent send-chan)) args ) + diff --git a/src/more_speech/nostr/events.clj b/src/more_speech/nostr/events.clj index 2842730..411187a 100644 --- a/src/more_speech/nostr/events.clj +++ b/src/more_speech/nostr/events.clj @@ -2,7 +2,6 @@ (:require [clojure.spec.alpha :as s] [clojure.data.json :as json] [more-speech.nostr.util :refer [hex-string->num]] - [more-speech.ui.widget :as w] [more-speech.nostr.elliptic-signature :as ecc]) (:import (java.nio.charset StandardCharsets))) (s/def ::id number?) @@ -23,34 +22,42 @@ (declare process-text-event process-name-event) +(defn make-event-agent [] + (agent {:chronological-text-events [] + :nicknames {} + :text-event-map {} + :update false})) + +(defn updated [event-state] + (assoc event-state :update false)) + (defn to-json [o] (json/write-str o :escape-slash false :escape-unicode false)) -(defn process-event [{:keys [application] :as state} event] - (let [{:keys [_articles nicknames]} application - _name-of (fn [pubkey] (get nicknames pubkey pubkey)) +(defn process-event [{:keys [nicknames] :as event-state} event] + (let [_name-of (fn [pubkey] (get nicknames pubkey pubkey)) [_name _subscription-id inner-event :as _decoded-msg] event {:strs [_id _pubkey _created_at kind _tags _content _sig]} inner-event] (condp = kind - 0 (process-name-event state inner-event) + 0 (process-name-event event-state inner-event) 3 (do ;(printf "%s: %s %s %s\n" kind (f/format-time created_at) (name-of pubkey) content) - state) + event-state) 1 (do ;(printf "%s: %s %s %s\n" kind (f/format-time created_at) (name-of pubkey) (subs content 0 (min 50 (count content)))) - (process-text-event state inner-event)) + (process-text-event event-state inner-event)) 4 (do ;(printf "%s: %s %s %s\n" kind (f/format-time created_at) (name-of pubkey) content) - state) + event-state) (do (prn "unknown event: " event) - state)))) + event-state)))) -(defn process-name-event [state {:strs [_id pubkey _created_at _kind _tags content _sig]}] +(defn process-name-event [event-state {:strs [_id pubkey _created_at _kind _tags content _sig]}] (let [pubkey (hex-string->num pubkey) - name (get (json/read-str content) "name" "tilt") - state (w/redraw-widget state [:application :author-window])] - (update-in - state [:application :nicknames] assoc pubkey name))) + name (get (json/read-str content) "name" "tilt")] + (-> event-state + (update-in [:nicknames] assoc pubkey name) + (assoc :update true)))) (defn process-tag [[type arg1 arg2]] [(keyword type) arg1 arg2]) @@ -67,7 +74,7 @@ state state] (if (empty? refs) state - (let [referent-path [:application :text-event-map (first refs)]] + (let [referent-path [:text-event-map (first refs)]] (if (nil? (get-in state referent-path)) (recur (rest refs) state) (recur (rest refs) @@ -87,18 +94,20 @@ :sig sig :tags (process-tags (get event "tags"))})) -(defn add-event [state event] +(defn add-event [event-state event] (let [id (:id event) - time (:created-at event) - state (assoc-in state [:application :text-event-map id] event) - state (update-in state [:application :chronological-text-events] conj [id time])] - state)) + time (:created-at event)] + (-> event-state + (assoc-in [:text-event-map id] event) + (update-in [:chronological-text-events] conj [id time])))) -(defn process-text-event [state event] - (let [event (translate-text-event event) - state (add-event state event) - state (w/redraw-widget state [:application :header-window])] - (process-references state event))) +(defn process-text-event [event-state event] + (let [event (translate-text-event event)] + (-> event-state + (add-event event) + (process-references event) + (assoc :update true) + ))) (defn chronological-event-comparator [[i1 t1] [i2 t2]] (if (= i1 i2) diff --git a/src/more_speech/nostr/protocol.clj b/src/more_speech/nostr/protocol.clj index bc35ace..82534e8 100644 --- a/src/more_speech/nostr/protocol.clj +++ b/src/more_speech/nostr/protocol.clj @@ -21,7 +21,6 @@ "ws://nostr-pub.wellorder.net:7000" ]) - (defn send-to [^WebSocket conn msg] (let [msg (events/to-json msg)] (println "sending:" msg) @@ -57,14 +56,14 @@ (defn unsubscribe [^WebSocket conn id] (send-to conn ["CLOSE" id])) -(defrecord listener [buffer events] +(defrecord listener [buffer event-agent] WebSocket$Listener (onOpen [_this _webSocket] (prn 'open)) (onText [_this webSocket data last] (.append buffer (.toString data)) (when last - (swap! events conj (json/read-str (.toString buffer))) + (send event-agent events/process-event (json/read-str (.toString buffer))) (.delete buffer 0 (.length buffer))) (.request webSocket 1) ) @@ -85,10 +84,10 @@ ) ) -(defn connect-to-relay ^WebSocket [url events] +(defn connect-to-relay ^WebSocket [url event-agent] (let [client (HttpClient/newHttpClient) cl (.newWebSocketBuilder client) - cws (.buildAsync cl (URI/create url) (->listener (StringBuffer.) events)) + cws (.buildAsync cl (URI/create url) (->listener (StringBuffer.) event-agent)) ws (.get cws) ] ws) @@ -96,15 +95,14 @@ (def private-key (ecc/sha-256 (.getBytes "I am Bob."))) -(defn get-events [events send-chan] - (let [conn (connect-to-relay (get relays 0) events) +(defn get-events [event-agent send-chan] + (let [conn (connect-to-relay (get relays 0) event-agent) id "more-speech" - date (make-date "04/01/2022") + date (make-date "04/25/2022") ] (prn date (format-time date)) (unsubscribe conn id) (subscribe conn id date) - ;(send-to conn ["EVENT" {:pubkey "2ef93f01cd2493e04235a6b87b10d3c4a74e2a7eb7c3caf168268f6af73314b5", :created_at 1649969311, :kind 1, :tags [], :content "Hello from more-speech.", :id "b05141aecb7d975ae7df861a13082d98ad76e9f46accc046ba221533877b69c6", :sig "e80411eee168aa620b035ce16622eda24d059859562276305a1c8900559b3bc5ae0505754e5d0f352891717eb34f4495771bcf11c80d01cdd19025a51e99979d"}]) (loop [msg (async/








")) + scroll-pane (JScrollPane. text-area) + content (.getContentPane frame) + timer (Timer. 100 nil) + screen-size (.getScreenSize (Toolkit/getDefaultToolkit)) + font (Font. "Courier New" Font/PLAIN, 14)] + + (.addWindowListener frame (proxy [WindowListener] [] + (windowActivated [_e]) + (windowOpened [_e]) + (windowClosed [_e]) + (windowDeactivated [_e]) + (windowClosing [_e] + (.stop timer) + (async/>!! output-channel [:closed]) + (.dispose frame)))) + + (with-action timer action-event + (draw-events text-area event-agent) + ) + (.setSize frame (.getWidth screen-size) (.getHeight screen-size)) + (.setSize text-area (.getWidth screen-size) (.getHeight screen-size)) + (.setFont text-area font) + (.add content scroll-pane BorderLayout/NORTH) + (.setVisible frame true) + (.start timer))) + +(declare format-events append-event format-event) + +(defn draw-events [text-area event-agent] + (prn "tick") + (when (:update @event-agent) + (let [event-state @event-agent + formatted-events (format-events event-state)] + (.setText text-area formatted-events) + (send event-agent events/updated)))) + +(defn format-events [{:keys [chronological-text-events nicknames text-event-map]}] + (let [header-ids (map first chronological-text-events) + headers (map #(get text-event-map %) header-ids)] + (reduce #(append-event nicknames %1 %2) "" headers))) + +(defn append-event [nicknames formatted-events event] + (str formatted-events (format-event nicknames event))) + +(defn format-event [nicknames {:keys [pubkey created-at content]}] + (str "

" + (formatters/abbreviate + (get nicknames pubkey (util/num->hex-string pubkey)) + 20) + " " + (formatters/format-time created-at) + " " + (formatters/abbreviate content 50) + "<


")) \ No newline at end of file