Skip to content

Commit

Permalink
more schema-based views, fields
Browse files Browse the repository at this point in the history
  • Loading branch information
mhuebert committed Dec 27, 2023
1 parent 7dc370a commit 977a8eb
Show file tree
Hide file tree
Showing 22 changed files with 363 additions and 325 deletions.
2 changes: 1 addition & 1 deletion bb.edn
Expand Up @@ -40,7 +40,7 @@

migrate:fetch (yarn shadow-cljs clj-run sb.migration.core/tx!)
migrate:tx (do (shell "rm -rf .db/datalevin")
(shell "yarn shadow-cljs clj-run sb.migration.core/tx!"))
(shell "yarn shadow-cljs -A:dev:nio:local clj-run sb.migration.core/tx!"))

;; this step is run manually after verifying a staging build.
staging:promote (let [{:strs [Registry Repository Tag]} (-> (shell {:out :string}
Expand Down
2 changes: 1 addition & 1 deletion deps.edn
Expand Up @@ -5,7 +5,7 @@

;; v2
datalevin/datalevin {:mvn/version "0.8.21"}
io.github.mhuebert/re-db {:git/sha "479a57401635f3a11ab128226976c6388b5f7f2c"}
io.github.mhuebert/re-db {:git/sha "db43109983156bf12adfdac38134b91f782179cd"}
io.github.mhuebert/yawn {:git/sha "68285f6c132f26a2ff3cc2f7dffc3fe68c9856d9"}
io.github.mhuebert/inside-out {:git/sha "6f8a4f4413f344d59da2ed55ddb1a0e9090c30b7"}

Expand Down
41 changes: 16 additions & 25 deletions src/sb/app.cljc
Expand Up @@ -22,42 +22,33 @@
[sb.app.social-feed.ui]
[sb.app.vote.ui]
[sb.i18n :refer [t]]
[sb.transit :as t]
[sb.util :as u]))
[sb.transit :as t]))

#?(:cljs
(def client-endpoints (t/read (shadow.resource/inline "public/js/sparkboard.views.transit.json"))))

(defn fields-editor-field []
(io/field :many (u/prune {:field/id ?id
:field/type ?type
:field/label ?label
:field/hint ?hint
:field/options (?options :many {:field-option/label ?label
:field-option/value ?value
:field-option/color ?color})
:field/required? ?required?
:field/show-as-filter? ?show-as-filter?
:field/show-at-registration? ?show-at-registration?
:field/show-on-card? ?show-on-card?})))

(def global-field-meta
{:account/email {:view field.ui/text-field
:props {:type "email"
{:string {:view field.ui/text-field}
:http/url {:view field.ui/text-field}
:boolean {:view field.ui/checkbox-field}
:prose/as-map {:view field.ui/prose-field
:make-field (fn [init _props]
(io/form {:prose/format prose/?format
:prose/string prose/?string}
:init init))}
:account/email {:props {:type "email"
:placeholder (t :tr/email)}
:validators [form.ui/email-validator]}
:account/password {:view field.ui/text-field
:props {:type "password"
:placeholder (t :tr/password)}
:validators [(io/min-length 8)]}
:entity/title {:validators [(io/min-length 3)]}
:board/project-fields {:view field.admin-ui/fields-editor
:make-field fields-editor-field}
:board/member-fields {:view field.admin-ui/fields-editor
:make-field fields-editor-field}
:field/label {:view field.ui/text-field}
:field/hint {:view field.ui/text-field}
:field/options {:view field.admin-ui/options-editor}
:entity/domain-name {:view domain.ui/domain-field
:validators (domain.ui/validators)}
:image/avatar {:view field.ui/image-field}})
:make-field domain.ui/make-domain-field}
:entity/fields {:view field.admin-ui/fields-editor
:make-field field.admin-ui/make-field:fields}
:entity/field-entries {:view field.ui/entries-field
:make-field field.ui/make-field:entries}
:asset/as-map {:view field.ui/image-field}})
5 changes: 3 additions & 2 deletions src/sb/app/asset/ui.cljc
@@ -1,13 +1,14 @@
(ns sb.app.asset.ui
(:require [sb.query-params :as query-params]
[sb.app.asset.data]))
[sb.app.asset.data]
[sb.schema :as sch]))

(def variants {:avatar {:op "bound" :width 200 :height 200}
:card {:op "bound" :width 600}
:page {:op "bound" :width 1200}})

(defn asset-src [asset variant]
(when-let [id (:entity/id asset)]
(when-let [id (sch/unwrap-id asset)]
(str "/assets/" id
(some-> (variants variant) query-params/query-string))))

Expand Down
36 changes: 19 additions & 17 deletions src/sb/app/board/admin_ui.cljc
Expand Up @@ -21,30 +21,32 @@

[:<>
(header/entity board (list (entity.ui/settings-button board)))

[radix/accordion {:class "max-w-[600px] mx-auto my-6 flex-v gap-6"
:multiple true}
:multiple true}

[:div.field-label (t :tr/basic-settings)]
[:div.flex-v.gap-4
[:div.field-label (t :tr/basic-settings)]
[:div.flex-v.gap-4

(use-persisted-attr board :entity/title)
(use-persisted-attr board :entity/description)
(use-persisted-attr board :entity/domain-name)
(use-persisted-attr board :image/avatar {:label (t :tr/image.logo)})]
(use-persisted-attr board :entity/title)
(use-persisted-attr board :entity/description)
(use-persisted-attr board :entity/domain-name)
(use-persisted-attr board :image/avatar {:field/label (t :tr/image.logo)})]


[:div.field-label (t :tr/projects-and-members)]
[:div.flex-v.gap-4
(use-persisted-attr board :board/member-fields)
(use-persisted-attr board :board/project-fields)]
[:div.field-label (t :tr/projects-and-members)]
[:div.flex-v.gap-4
(use-persisted-attr board :board/member-fields)
(use-persisted-attr board :board/project-fields)]


[:div.field-label (t :tr/registration)]
[:div.flex-v.gap-4
(use-persisted-attr board :board/registration-open?)
(use-persisted-attr board :board/registration-url-override)
(use-persisted-attr board :board/registration-page-message)
(use-persisted-attr board :board/invite-email-text)]]
[:div.field-label (t :tr/registration)]
[:div.flex-v.gap-4
(use-persisted-attr board :board/registration-open?)
(use-persisted-attr board :board/registration-url-override)
(use-persisted-attr board :board/registration-page-message)
(use-persisted-attr board :board/invite-email-text)]
]



Expand Down
4 changes: 2 additions & 2 deletions src/sb/app/board/data.cljc
Expand Up @@ -29,8 +29,8 @@
:board/sticky-color {:doc "Deprecate - sticky notes can pick their own colors"
s- :html/color}
:board/member-tags {s- [:sequential :tag/as-map]}
:board/project-fields {s- [:sequential :field/as-map]}
:board/member-fields {s- [:sequential :field/as-map]}
:board/project-fields {s- :entity/fields}
:board/member-fields {s- :entity/fields}
:board/invite-email-text {:hint "Text of email sent when inviting a user to a board."
s- :string},
:board/registration-newsletter-field? {:hint "During registration, request permission to send the user an email newsletter"
Expand Down
6 changes: 3 additions & 3 deletions src/sb/app/board/ui.cljc
Expand Up @@ -44,7 +44,7 @@
(.preventDefault e)
(ui/with-submission [result (data/new! {:board @!board})
:form !board]
(routing/nav! `show {:board-id (:entity/id result)})))
(routing/nav! `show {:board-id (:entity/id result)})))
:ref (ui/use-autofocus-ref)}
[:h2.text-2xl (t :tr/new-board)]

Expand All @@ -53,14 +53,14 @@
[:label.field-label {} (t :tr/owner)]
(radix/select-menu {:value @?owner
:on-value-change (partial reset! ?owner)
:options
:field/options
(->> owners
(map (fn [{:keys [entity/id entity/title image/avatar]}]
{:value (str id)
:text title
:icon [:img.w-5.h-5.rounded-sm {:src (asset.ui/asset-src avatar :avatar)}]})))})])

[field.ui/text-field ?title {:label (t :tr/title)}]
[field.ui/text-field ?title {:field/label (t :tr/title)}]
(domain.ui/domain-field ?domain nil)
[form.ui/submit-form !board (t :tr/create)]])))

Expand Down
71 changes: 38 additions & 33 deletions src/sb/app/domain_name/ui.cljc
@@ -1,46 +1,51 @@
(ns sb.app.domain-name.ui
(:require [clojure.string :as str]
[inside-out.forms :as forms]
(:require [inside-out.forms :as io]
[promesa.core :as p]
[sb.app.domain-name.data :as data]
[sb.app.field.ui :as field.ui]
[sb.app.form.ui :as form.ui]
[sb.app.views.ui :as ui]
[sb.i18n :refer [t]]))
[sb.i18n :refer [t]]
[sb.util :as u]))

#?(:cljs
(defn availability-validator []
(-> (fn [v {:keys [field]}]
(when (not= (:domain-name/name (:init field))
(:domain-name/name v))
(when-let [v (:domain-name/name v)]
(when (>= (count v) 3)
(p/let [res (data/check-availability {:domain v})]
(if (:available? res)
(forms/message :info
[:span.text-green-500.font-bold (t :tr/available)])
(forms/message :invalid
(t :tr/not-available)
{:visibility :always})))))))
(forms/debounce 300))))
(-> (fn [v {:as what :keys [field]}]
(when (and v
(not= v (:init field))
(>= (count v) 3))
(p/let [res (data/check-availability {:domain v})]
(prn :res res)
(if (:available? res)
(io/message :info
[:span.text-green-500.font-bold (t :tr/available)])
(io/message :invalid
(t :tr/not-available)
{:visibility :always})))))
(io/debounce 300))))


(ui/defview domain-field [?domain props]
(defn make-domain-field [init _props]
(io/form {:domain-name/name
(some-> domain-name/?name
u/some-str
data/normalize-domain
data/qualify-domain)}
:meta {domain-name/?name {:init (or (some-> init
:domain-name/name
data/unqualify-domain)
"")
:validators [data/domain-valid-string
#?(:cljs (availability-validator))]}}
))

(ui/defview domain-field [{:as ?field :syms [domain-name/?name]} props]
[:div.field-wrapper
[form.ui/show-label ?domain]
[form.ui/show-label ?field (:field/label props)]
[:div.flex.gap-2.items-stretch
(field.ui/text-field ?domain (merge props
{:wrap (fn [v]
(when-not (str/blank? v)
{:domain-name/name (data/qualify-domain (data/normalize-domain v))}))
:unwrap (fn [v]
(or (some-> v :domain-name/name data/unqualify-domain) ""))
:auto-complete "off"
:spell-check false
:wrapper-class "flex-auto"
:label false}))
[:div.flex.items-center.text-sm.text-gray-500.h-10 ".sparkboard.com"]]])

(defn validators []
[data/domain-valid-string
#?(:cljs (availability-validator))])
(field.ui/text-field ?name (merge props
{:auto-complete "off"
:spell-check false
:field/wrapper-class "flex-auto"
:field/label false}))
[:div.flex.items-center.text-sm.text-gray-500.h-10 ".sparkboard.com"]]])
1 change: 1 addition & 0 deletions src/sb/app/entity/data.cljc
Expand Up @@ -52,6 +52,7 @@
s- :prose/as-map
#_#_:db/fulltext true}
:entity/field-entries {s- [:map-of :uuid :field-entry/as-map]}
:entity/fields {s- [:sequential :field/as-map]}
:entity/video {:doc "Primary video for project (distinct from fields)"
s- :video/url}
:entity/public? {:doc "Contents of this entity can be accessed without authentication (eg. and indexed by search engines)"
Expand Down
24 changes: 6 additions & 18 deletions src/sb/app/entity/ui.cljc
Expand Up @@ -14,19 +14,8 @@
[yawn.hooks :as h]
[yawn.view :as v]))

(defn infer-view [attribute]
(when attribute
(case attribute
:entity/domain-name domain.ui/domain-field
:image/avatar field.ui/image-field
(let [{:keys [malli/schema]} (get @sch/!schema attribute)]
(case schema
:string field.ui/text-field
:http/url field.ui/text-field
:boolean field.ui/checkbox-field
:prose/as-map field.ui/prose-field
[:sequential :field/as-map] @(resolve 'sb.app.field.admin-ui/fields-editor)
(cond (str/ends-with? (name attribute) "?") field.ui/checkbox-field))))))
(defn malli-schema [a]
(some-> (get @sch/!schema a) :malli/schema))

(defn throw-no-persistence! [?field]
(throw (ex-info (str "No persistence for " (:sym ?field)) {:where (->> (iterate io/parent ?field)
Expand All @@ -38,7 +27,7 @@
(defn view-field [?field & [props]]
(let [view (or (:view props)
(:view ?field)
(some-> (:attribute ?field) infer-view)
(some-> (:attribute ?field) io/global-meta :view)
(throw (ex-info (str "No view declared for field: " (:sym ?field) (:attribute ?field)) {:sym (:sym ?field)
:attribute (:attribute ?field)})))]
[view ?field (merge (:props ?field)
Expand All @@ -57,10 +46,9 @@
(let [persisted-value (get e a)
make-field (or (:make-field props)
(:make-field (io/global-meta a))
#(io/field))
?field (h/use-memo #(doto (make-field)
(add-meta! {:init persisted-value
:attribute a
(fn [init _props] (io/field :init init)))
?field (h/use-memo #(doto (make-field persisted-value props)
(add-meta! {:attribute a
:db/id (sch/wrap-id e)
:field/persisted? true}))
;; create a new field when the persisted value changes
Expand Down

0 comments on commit 977a8eb

Please sign in to comment.