refactor
This commit is contained in:
parent
905640b0e1
commit
c190f20b14
@ -1,151 +1,41 @@
|
|||||||
(ns ajet.todo.core
|
(ns ajet.todo.core
|
||||||
(:require
|
(:require
|
||||||
[ajet.todo.db :as db]
|
[ajet.todo.db :as db]
|
||||||
[clojure.data.json :as json]
|
[ajet.todo.handler :as handler]
|
||||||
[clojure.pprint :as pprint]
|
[ajet.todo.util :refer [fn-sse]]
|
||||||
|
[ajet.todo.view :as view]
|
||||||
[compojure.core :refer [context defroutes DELETE GET let-routes PATCH POST]]
|
[compojure.core :refer [context defroutes DELETE GET let-routes PATCH POST]]
|
||||||
[compojure.route :as route]
|
[compojure.route :as route]
|
||||||
[hiccup2.core :as h]
|
[hiccup2.core :as h]
|
||||||
[ring.middleware.json :refer [wrap-json-body]]
|
[ring.middleware.json :refer [wrap-json-body]]))
|
||||||
[starfederation.datastar.clojure.adapter.common :refer [on-open]]
|
|
||||||
[starfederation.datastar.clojure.adapter.ring :refer [->sse-response]]
|
|
||||||
[starfederation.datastar.clojure.api :as d*]
|
|
||||||
[starfederation.datastar.clojure.expressions :refer [->expr]]))
|
|
||||||
|
|
||||||
;; utils
|
|
||||||
(defmacro fn-sse [[req sse :as _bindings]
|
|
||||||
& body]
|
|
||||||
`(fn [~req respond# _#]
|
|
||||||
(respond#
|
|
||||||
(->sse-response
|
|
||||||
~req
|
|
||||||
{on-open (fn [~sse]
|
|
||||||
(d*/with-open-sse ~sse
|
|
||||||
~@body))}))))
|
|
||||||
|
|
||||||
(defmacro defn-sse [var-name
|
|
||||||
[req sse :as _bindings]
|
|
||||||
& body]
|
|
||||||
`(defn ~var-name [~req respond# _#]
|
|
||||||
(respond#
|
|
||||||
(->sse-response
|
|
||||||
~req
|
|
||||||
{on-open (fn [~sse]
|
|
||||||
(d*/with-open-sse ~sse
|
|
||||||
~@body))}))))
|
|
||||||
(comment
|
|
||||||
(do
|
|
||||||
(defonce printer (bound-fn* pprint/pprint))
|
|
||||||
(add-tap printer))
|
|
||||||
|
|
||||||
(remove-tap printer))
|
|
||||||
|
|
||||||
;; app
|
|
||||||
(defn render-page [content]
|
|
||||||
(str
|
|
||||||
(h/html
|
|
||||||
(h/raw "<!DOCTYPE html>")
|
|
||||||
[:html
|
|
||||||
[:head
|
|
||||||
[:script {:src "https://cdn.jsdelivr.net/gh/starfederation/datastar@1.0.0-RC.5/bundles/datastar.js"
|
|
||||||
:type "module"
|
|
||||||
:defer true}]
|
|
||||||
[:link {:rel "icon"
|
|
||||||
:type "imaage/x-icon"
|
|
||||||
:href "https://data-star.dev/cdn-cgi/image/format=auto,width=24/static/images/rocket-48x48-4c739bfaffe86a6ffcc3a6d77e3c5547730f03d74c11aa460209596d1811f7a3.png"}]]
|
|
||||||
[:body content]])))
|
|
||||||
|
|
||||||
(defn todos-fragment []
|
|
||||||
(let [todos (db/get-all-todos)] ;; todo: make functional. sloppy sloppy
|
|
||||||
(h/html
|
|
||||||
[:div {:id "todos"}
|
|
||||||
[:div {:data-computed-show-toggle-display "($showComplete ? 'hide' : 'show') + ' done'"}]
|
|
||||||
[:ul
|
|
||||||
(for [{id :id
|
|
||||||
done :todo/done?
|
|
||||||
title :todo/title} todos]
|
|
||||||
[:li {:data-show (->expr (or (not ~done)
|
|
||||||
$showComplete))}
|
|
||||||
[:span {:style (str "text-decoration: " (if done "line-through" "none"))}
|
|
||||||
title]
|
|
||||||
[:input {:type "checkbox"
|
|
||||||
:checked done
|
|
||||||
:data-on-click (str "@patch('/sse/todo/" id "/" (when done "un") "set')")
|
|
||||||
:style "margin-right: 0.25rem;"}]
|
|
||||||
(when done
|
|
||||||
[:button {:data-on-click (->expr (@delete ~(str "/sse/todo/" id)))}
|
|
||||||
"X"])])]
|
|
||||||
[:div
|
|
||||||
[:p "settings"]
|
|
||||||
[:button {:data-on-click "$showComplete = !$showComplete"
|
|
||||||
:data-text "$showToggleDisplay"
|
|
||||||
:style "margin-right: 0.25rem;"}]
|
|
||||||
[:button {:data-on-click "$debugMode = !$debugMode"}
|
|
||||||
"toggle debug"]]])))
|
|
||||||
|
|
||||||
(defn create-todo-fragment []
|
|
||||||
(h/html
|
|
||||||
[:div {:id "create-box"
|
|
||||||
:style "margin-top: 2rem;"}
|
|
||||||
[:label "Make a todo: "]
|
|
||||||
[:input {:name "title"
|
|
||||||
:data-bind "title"
|
|
||||||
:style "margin-right: 0.25rem;"}]
|
|
||||||
[:button {:data-on-click (->expr (@post "/sse/todo/create-todo"))}
|
|
||||||
"submit"]]))
|
|
||||||
|
|
||||||
(defn app-fragment []
|
|
||||||
(h/html
|
|
||||||
[:div {:data-signals #json{:showComplete true
|
|
||||||
:debugMode false
|
|
||||||
:title ""}}]
|
|
||||||
[:pre {:data-json-signals "true"
|
|
||||||
:data-show "$debugMode"}]
|
|
||||||
[:div {:id "app"}
|
|
||||||
(todos-fragment)
|
|
||||||
(create-todo-fragment)]))
|
|
||||||
|
|
||||||
(defn refresh-todos! [sse]
|
|
||||||
(d*/patch-elements! sse
|
|
||||||
(str (todos-fragment))
|
|
||||||
#:d*.elements{:selector "#todos"
|
|
||||||
:patch-mode "replace"}))
|
|
||||||
|
|
||||||
(defn-sse create-todo-handler [req sse]
|
|
||||||
(let [title (-> req :body :title)]
|
|
||||||
(when title
|
|
||||||
(db/add-todo! title))
|
|
||||||
(d*/with-open-sse sse
|
|
||||||
(refresh-todos! sse)
|
|
||||||
(d*/patch-signals! sse #json{:title ""}))))
|
|
||||||
|
|
||||||
(defroutes approutes
|
(defroutes approutes
|
||||||
(GET "/" []
|
(GET "/" []
|
||||||
(fn [_ respond _]
|
(fn [_ respond _]
|
||||||
(-> (app-fragment)
|
(-> (view/app-fragment)
|
||||||
render-page
|
view/render-page
|
||||||
respond)))
|
respond)))
|
||||||
|
|
||||||
(-> (context "/sse/todo" []
|
(-> (context "/sse/todo" []
|
||||||
(GET "/" []
|
(GET "/" []
|
||||||
(fn-sse [_req sse]
|
(fn-sse [_req sse]
|
||||||
(refresh-todos! sse)))
|
(handler/refresh-todos! sse)))
|
||||||
(POST "/create-todo" [] create-todo-handler)
|
(POST "/create-todo" [] handler/create-todo-handler)
|
||||||
|
|
||||||
(context ["/:id", :id #"[0-9]+"] [id]
|
(context ["/:id", :id #"[0-9]+"] [id]
|
||||||
(let-routes [id (read-string id)]
|
(let-routes [id (read-string id)]
|
||||||
(PATCH "/set" []
|
(PATCH "/set" []
|
||||||
(fn-sse [_req sse]
|
(fn-sse [_req sse]
|
||||||
(db/set-todo-done! id true)
|
(db/set-todo-done! id true)
|
||||||
(refresh-todos! sse)))
|
(handler/refresh-todos! sse)))
|
||||||
(PATCH "/unset" []
|
(PATCH "/unset" []
|
||||||
(fn-sse [_req sse]
|
(fn-sse [_req sse]
|
||||||
(db/set-todo-done! id false)
|
(db/set-todo-done! id false)
|
||||||
(refresh-todos! sse)))
|
(handler/refresh-todos! sse)))
|
||||||
(DELETE "/" []
|
(DELETE "/" []
|
||||||
(fn-sse [_req sse]
|
(fn-sse [_req sse]
|
||||||
(db/delete-todo! id)
|
(db/delete-todo! id)
|
||||||
(refresh-todos! sse))))))
|
(handler/refresh-todos! sse))))))
|
||||||
(wrap-json-body {:keywords? true}))
|
(wrap-json-body {:keywords? true}))
|
||||||
(route/not-found (str (h/html [:h2 "Not Found"]))))
|
(route/not-found (str (h/html [:h2 "Not Found"]))))
|
||||||
|
|
||||||
|
|||||||
@ -58,16 +58,8 @@
|
|||||||
(sort-by first)
|
(sort-by first)
|
||||||
(map normalize-todo))))
|
(map normalize-todo))))
|
||||||
|
|
||||||
(defn get-todo [id]
|
(defn get-todo [db id]
|
||||||
(let [db (d/db conn)]
|
(d/pull db '[*] id))
|
||||||
(some->> (d/q '[:find ?e ?title ?done
|
|
||||||
:in $ ?e
|
|
||||||
:where [?e :todo/title ?title]
|
|
||||||
[?e :todo/done? ?done]]
|
|
||||||
db
|
|
||||||
id)
|
|
||||||
first
|
|
||||||
normalize-todo)))
|
|
||||||
|
|
||||||
(defn get-todo-by-title [title]
|
(defn get-todo-by-title [title]
|
||||||
(let [db (d/db conn)]
|
(let [db (d/db conn)]
|
||||||
|
|||||||
20
src/ajet/todo/handler.clj
Normal file
20
src/ajet/todo/handler.clj
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
(ns ajet.todo.handler
|
||||||
|
(:require
|
||||||
|
[ajet.todo.db :as db]
|
||||||
|
[ajet.todo.util :refer [defn-sse]]
|
||||||
|
[ajet.todo.view :as view]
|
||||||
|
[starfederation.datastar.clojure.api :as d*]))
|
||||||
|
|
||||||
|
(defn refresh-todos! [sse]
|
||||||
|
(d*/patch-elements! sse
|
||||||
|
(str (view/todos-fragment))
|
||||||
|
#:d*.elements{:selector "#todos"
|
||||||
|
:patch-mode "replace"}))
|
||||||
|
|
||||||
|
(defn-sse create-todo-handler [req sse]
|
||||||
|
(let [title (-> req :body :title)]
|
||||||
|
(when title
|
||||||
|
(db/add-todo! title))
|
||||||
|
(d*/with-open-sse sse
|
||||||
|
(refresh-todos! sse)
|
||||||
|
(d*/patch-signals! sse #json{:title ""}))))
|
||||||
@ -3,7 +3,7 @@
|
|||||||
[ajet.todo.core :as core]
|
[ajet.todo.core :as core]
|
||||||
[ring.adapter.jetty :refer [run-jetty]]))
|
[ring.adapter.jetty :refer [run-jetty]]))
|
||||||
|
|
||||||
(def port 1738)
|
(def port 80)
|
||||||
|
|
||||||
(defn make-server [opts]
|
(defn make-server [opts]
|
||||||
(run-jetty #'core/app (merge {:join? false,
|
(run-jetty #'core/app (merge {:join? false,
|
||||||
|
|||||||
27
src/ajet/todo/util.clj
Normal file
27
src/ajet/todo/util.clj
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
(ns ajet.todo.util
|
||||||
|
(:require
|
||||||
|
[starfederation.datastar.clojure.adapter.common :refer [on-open]]
|
||||||
|
[starfederation.datastar.clojure.adapter.ring :refer [->sse-response]]
|
||||||
|
[starfederation.datastar.clojure.api :as d*]))
|
||||||
|
|
||||||
|
;; utils
|
||||||
|
(defmacro fn-sse [[req sse :as _bindings]
|
||||||
|
& body]
|
||||||
|
`(fn [~req respond# _#]
|
||||||
|
(respond#
|
||||||
|
(->sse-response
|
||||||
|
~req
|
||||||
|
{on-open (fn [~sse]
|
||||||
|
(d*/with-open-sse ~sse
|
||||||
|
~@body))}))))
|
||||||
|
|
||||||
|
(defmacro defn-sse [var-name
|
||||||
|
[req sse :as _bindings]
|
||||||
|
& body]
|
||||||
|
`(defn ~var-name [~req respond# _#]
|
||||||
|
(respond#
|
||||||
|
(->sse-response
|
||||||
|
~req
|
||||||
|
{on-open (fn [~sse]
|
||||||
|
(d*/with-open-sse ~sse
|
||||||
|
~@body))}))))
|
||||||
76
src/ajet/todo/view.clj
Normal file
76
src/ajet/todo/view.clj
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
(ns ajet.todo.view
|
||||||
|
(:require
|
||||||
|
[ajet.todo.db :as db]
|
||||||
|
[clojure.data.json]
|
||||||
|
[hiccup2.core :as h]
|
||||||
|
[hiccup.util :as hu]
|
||||||
|
[starfederation.datastar.clojure.expressions :refer [->expr]]))
|
||||||
|
|
||||||
|
(defn render-page [content]
|
||||||
|
(str
|
||||||
|
(h/html
|
||||||
|
(h/raw "<!DOCTYPE html>")
|
||||||
|
[:html
|
||||||
|
[:head
|
||||||
|
[:script {:src "https://cdn.jsdelivr.net/gh/starfederation/datastar@1.0.0-RC.5/bundles/datastar.js"
|
||||||
|
:type "module"
|
||||||
|
:defer true}]
|
||||||
|
[:script {:src "https://cdn.jsdelivr.net/npm/eruda"
|
||||||
|
:id "erudaLoaderScript"
|
||||||
|
:type "module"
|
||||||
|
:defer true}]
|
||||||
|
(hu/raw-string "<script>document.getElementById('erudaLoaderScript').addEventListener('load', function () { eruda.init () });</script/>")
|
||||||
|
[:link {:rel "icon"
|
||||||
|
:type "imaage/x-icon"
|
||||||
|
:href "https://data-star.dev/cdn-cgi/image/format=auto,width=24/static/images/rocket-48x48-4c739bfaffe86a6ffcc3a6d77e3c5547730f03d74c11aa460209596d1811f7a3.png"}]]
|
||||||
|
[:body content]])))
|
||||||
|
|
||||||
|
(defn todos-fragment []
|
||||||
|
(let [todos (db/get-all-todos)] ;; todo: make functional. sloppy sloppy
|
||||||
|
(h/html
|
||||||
|
[:div {:id "todos"}
|
||||||
|
[:div {:data-computed-show-toggle-display "($showComplete ? 'hide' : 'show') + ' done'"}]
|
||||||
|
[:ul
|
||||||
|
(for [{id :id
|
||||||
|
done :todo/done?
|
||||||
|
title :todo/title} todos]
|
||||||
|
[:li {:data-show (->expr (or (not ~done)
|
||||||
|
$showComplete))}
|
||||||
|
[:span {:style (str "text-decoration: " (if done "line-through" "none"))}
|
||||||
|
title]
|
||||||
|
[:input {:type "checkbox"
|
||||||
|
:checked done
|
||||||
|
:data-on-click (str "@patch('/sse/todo/" id "/" (when done "un") "set')")
|
||||||
|
:style "margin-right: 0.25rem;"}]
|
||||||
|
(when done
|
||||||
|
[:button {:data-on-click (->expr (@delete ~(str "/sse/todo/" id)))}
|
||||||
|
"X"])])]
|
||||||
|
[:div
|
||||||
|
[:p "settings"]
|
||||||
|
[:button {:data-on-click "$showComplete = !$showComplete"
|
||||||
|
:data-text "$showToggleDisplay"
|
||||||
|
:style "margin-right: 0.25rem;"}]
|
||||||
|
[:button {:data-on-click "$debugMode = !$debugMode"}
|
||||||
|
"toggle debug"]]])))
|
||||||
|
|
||||||
|
(defn create-todo-fragment []
|
||||||
|
(h/html
|
||||||
|
[:div {:id "create-box"
|
||||||
|
:style "margin-top: 2rem;"}
|
||||||
|
[:label "Make a todo: "]
|
||||||
|
[:input {:name "title"
|
||||||
|
:data-bind "title"
|
||||||
|
:style "margin-right: 0.25rem;"}]
|
||||||
|
[:button {:data-on-click (->expr (@post "/sse/todo/create-todo"))}
|
||||||
|
"submit"]]))
|
||||||
|
|
||||||
|
(defn app-fragment []
|
||||||
|
(h/html
|
||||||
|
[:div {:data-signals #json{:showComplete true
|
||||||
|
:debugMode false
|
||||||
|
:title ""}}]
|
||||||
|
[:pre {:data-json-signals "true"
|
||||||
|
:data-show "$debugMode"}]
|
||||||
|
[:div {:id "app"}
|
||||||
|
(todos-fragment)
|
||||||
|
(create-todo-fragment)]))
|
||||||
@ -1,11 +1,21 @@
|
|||||||
(ns user
|
(ns user
|
||||||
(:require [ajet.todo.server :as server]))
|
(:require
|
||||||
|
[ajet.todo.server :as server]
|
||||||
|
[clojure.pprint :as pprint]))
|
||||||
|
|
||||||
(defonce server (server/make-server {}))
|
(defonce server (server/make-server {}))
|
||||||
|
|
||||||
(comment
|
(comment
|
||||||
|
;; server controls
|
||||||
server
|
server
|
||||||
(. server stop)
|
(. server stop)
|
||||||
(. server start))
|
(. server start)
|
||||||
|
|
||||||
|
;; tap debugging
|
||||||
|
(do
|
||||||
|
(defonce printer (bound-fn* pprint/pprint))
|
||||||
|
(add-tap printer))
|
||||||
|
|
||||||
|
(remove-tap printer))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user