edufeed-web/src/ied/views.cljs
2024-12-02 09:37:20 +01:00

392 lines
15 KiB
Clojure

(ns ied.views
(:require
[re-frame.core :as re-frame]
[cljs.pprint :refer [pprint]]
[ied.events :as events]
[ied.routes :as routes]
[ied.subs :as subs]
[ied.nostr :as nostr]
[ied.components.icons :as icons]
[ied.opencard.views :as opencard]
[ied.views.search :as search]
[ied.views.resource :as resource]
[ied.components.modals :as modals]
[ied.views.add :as add]
[ied.components.resource :as resource-component]
[reagent.core :as reagent]))
;; event data modal
(defn event-data-modal []
(let [visible? @(re-frame/subscribe [::subs/show-event-data-modal])
selected-event @(re-frame/subscribe [::subs/selected-event])]
(when visible?
[:dialog {:open visible? :class "modal"}
[:div {:class "modal-box relative flex flex-col"}
[:h3 {:class "text-lg font-bold"} (nostr/get-name-from-metadata-event selected-event)]
[:pre (with-out-str (pprint selected-event))]
[:p {:class "py-4"} "Press ESC key or click outside to close"]]
[:form {:on-click #(re-frame/dispatch [::events/toggle-show-event-data-modal])
:method "dialog" :class "modal-backdrop"}
[:button "close"]]])))
;; metadata event component
;; TODO add author profile name and image
(defn metadata-event-component [event]
(let [selected-events @(re-frame/subscribe [::subs/selected-events])]
[:div {:class "flex flex-row gap-2"}
[:div {:class "w-3/4 p-2 min-h-full flex flex-col mt-2 justify-between"}
[:div {:class ""}
[:div {:class "flex flex-row gap-2 "}
[:div {:class "w-6 h-6 rounded-full bg-white"}
(cond
(= 30004 (:kind event)) [icons/layer-icon]
(= 30142 (:kind event)) [icons/file-icon])]
[:a {:href (nostr/get-d-id-from-event event)
:class "font-bold hover:underline text-ellipsis overflow-hidden"}
(nostr/get-name-from-metadata-event event)]]
[:div {:class "flex flex-wrap"}
(resource-component/about-tags [event])]
[:p {:class "text-wrap"}
(nostr/get-description-from-metadata-event event)]]
[:div {:class "flex flex-row justify-between items-center"}
[:button {:class "btn"
:on-click #(re-frame/dispatch [::events/toggle-show-event-data-modal event])} "Show Event Data"]
[resource-component/add-to-list event]]]
[:div {:class "w-1/4"}
[:figure
[:img
{:class "h-48 object-cover mx-auto m-2"
:loading "lazy"
:src
(nostr/get-image-from-metadata-event event)
:alt ""}]]]]))
(defn activity-feed []
(let [following @(re-frame/subscribe [::subs/following])
npub @(re-frame/subscribe [::subs/npub])
_ (when (nil? following)
(when npub (re-frame/dispatch [::events/get-follow-set-for-npub npub])))
following-events @(re-frame/subscribe [::subs/posts-from-pks-actor-follows])
_ (.log js/console (count following-events) (< 5 (count following-events)))
_ (when (> 5 (count following-events))
(re-frame/dispatch [::events/events-from-pks-actor-follows following]))]
[:div
(doall
(for [event following-events]
[:p {:key (:id event)} (:content event)]))]))
;; events
(defn events-panel []
(fn []
(let [events @(re-frame/subscribe [::subs/feed-events])
selected-events @(re-frame/subscribe [::subs/selected-events])]
[:div {:class "flex flex-col mx-auto gap-4 "}
[activity-feed]
(if (> (count events) 0)
[:div {:class "divide-y divide-slate-500"}
(doall
(for [event events]
[:div {:key (:id event)
:class ""}
[metadata-event-component event]]))]
[:p "no events there"])
[:button {:class "btn"
:disabled (not (boolean (seq selected-events)))
:on-click #(re-frame/dispatch [::events/add-metadata-event-to-list [{:d "unique-id-1"
:name "Test List SC"}
selected-events]])}
"Add To Lists"]])))
;; event feed component
(defn event-feed-component [event]
(let [_ () #_(re-frame/dispatch [::events/add-confetti])]
[:div
{:class "animate-flyIn card bg-base-100 w-64 h-64 shadow-xl border border-white border-w "}
(cond
(= 30004 (:kind event)) [:p {:class "text-2xl float-right"} "📖"]
(= 30142 (:kind event)) [:p {:class "text-2xl float-right"} "📚"])
[:figure
[:img
{:class "h-48 object-contain"
:src
(nostr/get-image-from-metadata-event event)
:alt ""}]]
[:div
{:class "card-body"}
[:button {:on-click #(re-frame/dispatch [::events/toggle-show-event-data-modal event])} "Show Event Data"]]]))
(defn event-feed-panel []
(let [events @(re-frame/subscribe [::subs/feed-events])]
[:div {:class ""}
[:h1 "Event Feed"]
[:p (str "Num of events: " (count events))]
(if (> (count events) 0)
[:div {:class "flex flex-col"}
(doall
(for [event events]
[:div {:key (:id event)}
[event-feed-component event]]))]
[:p "no events there"])]))
(defmethod routes/panels :event-feed-panel [] [event-feed-panel])
;; 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]
(if (> (count @sockets) 0)
[:ul
(doall
(for [socket @sockets]
[:li {:key (:id socket)}
[:span (:name socket)]
[:span (:status socket)]
[:button {:class "btn"
:disabled (not= "connected" (:status socket))
:on-click #(re-frame/dispatch [::events/load-events (:uri socket)])} "Load events"]
(if (not= (:status socket) "connected")
[:button {:class "btn"
:on-click #(re-frame/dispatch [::events/connect-to-websocket (:uri socket)])} "Connect"]
[:button {:class "btn"
:on-click #(re-frame/dispatch [::events/close-connection-to-websocket (:uri socket)])} "Disconnect"])
[:button {:class "btn btn-error"
:on-click #(re-frame/dispatch [::events/remove-websocket socket])} "Remove relay"]]))]
[:p "No relays found"]
;(re-frame/dispatch [::events/connect-to-default-relays])
)]))
;; shopping cart
(defn shopping-cart []
(let [selected-events @(re-frame/subscribe [::subs/selected-events])]
[:div {:class "dropdown dropdown-end"}
[:div
{:tabIndex "0", :role "button", :class "btn btn-ghost btn-circle"}
[:div
{:class "indicator"}
[icons/shopping-cart]
[:span {:class "badge badge-sm indicator-item"} (count selected-events)]]]
[:div
{:tabIndex "0",
:class
"card card-compact dropdown-content bg-base-100 z-[1] mt-3 w-52 shadow"}
[:div
{:class "card-body"}
[:span {:class "text-lg font-bold"}
(str (count selected-events)
" Items")]
[:div {:class "card-actions"}
[:button {:class "btn"
:on-click #((modals/open-add-to-list-modal)
; re-frame/dispatch [::events/toggle-show-lists-modal]
)}
"Add To Lists"]]]]]))
(defn user-menu []
(let [pk @(re-frame/subscribe [::subs/pk])
user-profile (re-frame/subscribe [::subs/profile pk])]
(if pk
[:div
{:class "dropdown dropdown-end"}
[:div
{:tabIndex "0",
:role "button",
:class "btn btn-ghost btn-circle avatar"}
[:div
{:class "w-10 rounded-full"}
[:img
{:alt "user image",
:src
(nostr/profile-picture @user-profile pk)}]]]
[:ul
{:tabIndex "0",
:class
"menu menu-sm dropdown-content bg-base-100 rounded-box z-[1] mt-3 w-52 p-2 shadow"}
[:li
[:a
{:on-click #(re-frame/dispatch [::events/navigate [:npub-view :npub (nostr/get-npub-from-pk pk)]])}
"Profile"]]
[:li [:a {:on-click #(re-frame/dispatch [::events/navigate [:settings]])} "Settings"]]
[:li [:a {:on-click #(re-frame/dispatch [::events/navigate [:keys]])} "Keys"]]
[:li [:a {:on-click #(re-frame/dispatch [::events/logout])} "Logout"]]]]
[:div
{:class "dropdown dropdown-end"}
[:div {:tabIndex "0", :role "button", :class "btn m-1"} "Login"]
[:ul
{:tabIndex "0",
:class
"dropdown-content menu bg-base-100 rounded-box z-[1] w-52 p-2 shadow"}
[:li [:a {:on-click #(re-frame/dispatch [::events/create-sk])}
"... Anonymously"]]
[:li [:a {:on-click #(re-frame/dispatch [::events/login-with-extension])}
"... with Extension"]]]])))
;; Header
(defn header []
(let [pk @(re-frame/subscribe [::subs/pk])]
[:div
{:class "navbar sticky z-50 top-0 left-0 bg-base-100"}
[:div
{:class "flex-1"}
[:a {:class "cursor-pointer text-xl font-bold"
:on-click #(re-frame/dispatch [::events/navigate [:home]])}
"edufeed"]
#_[:a {:class "btn btn-circle"
:on-click #(re-frame/dispatch [::events/navigate [:event-feed]])} "Event-Feed"]]
[:div
{:class "flex-none"}
[:a {:disabled (nil? pk)
:class "btn btn-circle btn-primary text-xl"
:on-click #(re-frame/dispatch [::events/navigate [:add-resource]])} "+"]
(when (not (nil? pk))
[shopping-cart])
[user-menu]]]))
;; Keys Panel
(defn keys-panel []
(let [npub @(re-frame/subscribe [::subs/npub])
pk (nostr/get-pk-from-npub npub)
nsec @(re-frame/subscribe [::subs/nsec])] [:div
[:h1 "Keys"]
[:p (str "Your Npub: " npub)]
[:p (str "Your PK: " pk)]
[:p (str "Your Nsec: " nsec)]]))
(defmethod routes/panels :keys-panel [] [keys-panel])
;; Settings
(defn settings-panel []
[:div
[:h1 "Settings"]
[relays-panel]])
(defmethod routes/panels :settings-panel [] [settings-panel])
(defn sidepanel []
(let [pk (re-frame/subscribe [::subs/pk])]
(fn []
[:div {:class "static flex flex-col m-2 "}
[:ul {:class "menu rounded-box"}
[:li
[:a {:on-click #(re-frame/dispatch [::events/navigate [:home]])}
"Feed"]]
[:li [:a {:on-click #(re-frame/dispatch [::events/navigate [:search-view]])}
"Search"]]
[:li [:a "My Network"]]
[:li [:a {:on-click #(re-frame/dispatch [::events/navigate [:npub-view :npub (nostr/get-npub-from-pk @pk)]])}
"Bookmarks"]]
[:li [:a {:on-click #(re-frame/dispatch [::events/navigate [:opencard-view]])} "Opencard"]]]])))
;; Home
(defn home-panel []
(let [events (re-frame/subscribe [::subs/events])]
[:div {:class "basis-5/6"}
[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])
;; Lists
(defn list-component [list]
(let [pk @(re-frame/subscribe [::subs/pk])
ids-in-list (nostr/get-event-ids-from-list list)
events-in-list @(re-frame/subscribe [::subs/events-in-list ids-in-list]) ;; TODO should be sorted after appeareande in tags
]
[:div {:key (:id list)
:class "p-2"}
[:details
{:class "collapse bg-base-200"}
[:summary
{:class "collapse-title text-xl font-medium"}
(nostr/get-list-name list)]
[:div {:class "collapse-content"}
(doall
(for [event events-in-list]
[:div {:key (:id event)}
[metadata-event-component event]
(when pk
[:button {:class "btn btn-warning"
:on-click #(re-frame/dispatch [::events/delete-event-from-list [event list]])}
"Delete Resource from List"])]))
(when pk [:button {:class "btn btn-error"
:on-click #(re-frame/dispatch [::events/delete-list list])} "Delete List"])]]]))
;; npub / Profile
(defn npub-view-panel []
(let [sockets @(re-frame/subscribe [::subs/sockets])
route-params @(re-frame/subscribe [::subs/route-params])
lists @(re-frame/subscribe [::subs/lists-for-npub])
_ (when (not (seq lists)) (re-frame/dispatch [::events/get-lists-for-npub (:npub route-params)]))
missing-events-from-lists @(re-frame/subscribe [::subs/missing-events-from-lists])
_ (when (seq missing-events-from-lists) (re-frame/dispatch [::events/query-for-event-ids [sockets missing-events-from-lists]]))]
[:div
[:h1 (str "Hello Npub: " (:npub route-params))]
(if-not (seq lists)
[:p "No lists there...yet"]
(doall
(for [l lists]
^{:key (:id l)} [list-component l])))
[:button {:class "btn ml-auto mr-0"
:on-click #((modals/open-create-list-modal))}
"Create New List"]]))
(defmethod routes/panels :npub-view-panel [] [npub-view-panel])
;; main
(defn main-panel []
(let [active-panel (re-frame/subscribe [::subs/active-panel])]
[:div {:class "relative"}
[header]
[:div {:class "flex flex-row"}
[:div {:class "w-1/6"}
[sidepanel]]
[:div {:class "w-5/6 mt-1"}
[:div {:class ""}
(routes/panels @active-panel)]]
[modals/add-to-lists-modal]
[modals/create-list-modal]
[event-data-modal]]]))