Skip to content

Commit

Permalink
video fields, role granularity, nil attribute transactions,
Browse files Browse the repository at this point in the history
  • Loading branch information
mhuebert committed Jan 5, 2024
1 parent 5c97b2d commit b571c09
Show file tree
Hide file tree
Showing 15 changed files with 167 additions and 143 deletions.
9 changes: 6 additions & 3 deletions src/sb/app.cljc
Expand Up @@ -22,7 +22,8 @@
[sb.app.social-feed.ui]
[sb.app.vote.ui]
[sb.i18n :refer [t]]
[sb.transit :as t]))
[sb.transit :as t]
[sb.util :as u]))

#?(:cljs
(def client-endpoints (t/read (shadow.resource/inline "public/js/sparkboard.views.transit.json"))))
Expand All @@ -38,8 +39,9 @@
:init init))}
:prose/as-map {:view field.ui/prose-field
:make-field (fn [init _props]
(io/form {:prose/format (prose/?format :init :prose.format/markdown)
:prose/string prose/?string}
(io/form (-> {:prose/format (prose/?format :init :prose.format/markdown)
:prose/string prose/?string}
(u/guard :prose/string))
:init init))}
:account/email {:props {:type "email"
:placeholder (t :tr/email)}
Expand All @@ -52,6 +54,7 @@
:field/options {:view field.admin-ui/options-editor}
:entity/domain-name {:view domain.ui/domain-field
:make-field domain.ui/make-domain-field}
:entity/video {:view field.ui/video-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
Expand Down
10 changes: 5 additions & 5 deletions src/sb/app/board/data.cljc
Expand Up @@ -137,12 +137,12 @@
(mapv (db/pull `[~@entity.data/fields]))))

(defn authorize-edit! [board account-id]
(when-not (or (validate/can-edit? board account-id)
(validate/can-edit? (:entity/parent board) account-id))
(when-not (or (validate/can-edit? account-id board)
(validate/can-edit? account-id (:entity/parent board) ))
(validate/permission-denied!)))

(defn authorize-create! [board account-id]
(when-not (validate/can-edit? (:entity/parent board) account-id)
(when-not (validate/can-edit? account-id (:entity/parent board))
(validate/permission-denied!)))

(q/defx new!
Expand All @@ -161,8 +161,8 @@
(q/defquery settings
{:prepare [az/with-account-id!
(az/with-roles :board-id)
(fn [_ {:as params :keys [member/roles board-id account-id]}]
(validate/assert-can-edit! board-id account-id)
(fn [_ {:as params :keys [board-id account-id]}]
(validate/assert-can-edit! account-id board-id)
params)]}
[{:keys [board-id member/roles]}]
(some->
Expand Down
4 changes: 2 additions & 2 deletions src/sb/app/content/data.cljc
@@ -1,5 +1,5 @@
(ns sb.app.content.data
(:require [sb.schema :as sch :refer [s-]]))
(:require [sb.schema :as sch :refer [s- ?]]))

(sch/register!
(merge
Expand All @@ -9,7 +9,7 @@
:prose/string {s- :string}
:prose/as-map {s- [:map {:closed true}
:prose/format
:prose/string]}}
(? :prose/string)]}}

{:content/badge {s- [:map {:closed true}
:badge/label
Expand Down
38 changes: 32 additions & 6 deletions src/sb/app/entity/data.cljc
Expand Up @@ -88,20 +88,46 @@
:entity/description
:entity/created-at
:entity/deleted-at
:entity/video
{:image/avatar [:entity/id]}
{:image/background [:entity/id]}
{:entity/domain-name [:domain-name/name]}])

(defn required? [parent-schema child-attr]
(-> parent-schema
(mu/find child-attr)
(mu/-required-map-entry?)))

(defn ignore-optional-nils [parent-schema m]
(reduce-kv (fn [m k v]
(if (and (nil? v) (not (required? parent-schema k)))
(dissoc m k)
m))
m
m))

(defn retract-nils
[m]
(let [nils (->> m (filter #(nil? (val %))) (map key))
m (apply dissoc m nils)
e (:db/id m)]
(cond-> []
(seq m) (conj m)
(seq nils) (into (for [a nils] [:db/retract e a])))))

(q/defx save-attributes!
{:prepare [az/with-account-id!]}
[{:keys [account-id]} e m]
(let [e (sch/wrap-id e)
_ (validate/assert-can-edit! e account-id)
{:as entity :keys [entity/id entity/kind]} (db/entity e)
parent-schema (-> (keyword (name kind) "as-map")
(@sch/!malli-registry))
txs [(assoc m :db/id e)]]
(validate/assert m (mu/select-keys parent-schema (keys m)))
_ (validate/assert-can-edit! account-id e)
txs (-> (assoc m :db/id e)
retract-nils)]

(let [parent-schema (-> (keyword (name (:entity/kind (db/entity e))) "as-map")
(@sch/!malli-registry))
without-nils (ignore-optional-nils parent-schema m)]
(validate/assert without-nils (mu/select-keys parent-schema (keys without-nils))))

(try
(db/transact! txs)
(catch Exception e (def E e) (throw e)))
Expand Down
12 changes: 3 additions & 9 deletions src/sb/app/entity/ui.cljc
@@ -1,18 +1,12 @@
(ns sb.app.entity.ui
(:require [clojure.string :as str]
[inside-out.forms :as io]
[re-db.api :as db]
(:require [inside-out.forms :as io]
[sb.app.asset.ui :as asset.ui]
[sb.app.domain-name.ui :as domain.ui]
[sb.app.entity.data :as data]
[sb.app.field.ui :as field.ui]
[sb.app.form.ui :as form.ui]
[sb.app.views.ui :as ui]
[sb.authorize :as az]
[sb.icons :as icons]
[sb.routing :as routing]
[sb.schema :as sch]
[sb.util :as u]
[sb.validate :as validate]
[yawn.hooks :as h]
[yawn.view :as v]))

Expand Down Expand Up @@ -84,7 +78,7 @@
[:div.line-clamp-2 title]]])

(ui/defview settings-button [entity]
(when-let [path (and (validate/editing-role? (:member/roles entity))
(when-let [path (and (az/editor-role? (:member/roles entity))
(some-> (routing/entity-route entity 'admin-ui/settings)
routing/path-for))]
[:a.button
Expand Down
6 changes: 3 additions & 3 deletions src/sb/app/field/data.cljc
Expand Up @@ -202,7 +202,7 @@
(q/defx add-field
{:prepare [az/with-account-id!]}
[{:keys [account-id]} e a new-field]
(validate/assert-can-edit! e account-id)
(validate/assert-can-edit! account-id e)
(let [e (sch/wrap-id e)
existing-fields (a (db/entity e))
field (assoc new-field :field/id (dl/new-uuid :field))]
Expand All @@ -213,7 +213,7 @@
(q/defx remove-field
{:prepare [az/with-account-id!]}
[{:keys [account-id]} parent-id a field-id]
(validate/assert-can-edit! parent-id account-id)
(validate/assert-can-edit! account-id parent-id)
(let [parent (db/entity (sch/wrap-id parent-id))]
(db/transact! [[:db/add (:db/id parent) a (->> (get parent a)
(remove (comp #{field-id} :field/id))
Expand Down Expand Up @@ -246,7 +246,7 @@
:prose/format (:prose/format entry)}))

(q/defx save-entry! [{:keys [account-id]} parent-id field-id entry]
(validate/assert-can-edit! parent-id account-id)
(validate/assert-can-edit! account-id parent-id)
(let [field (db/entity (sch/wrap-id field-id))
parent (db/entity (sch/wrap-id parent-id))
entries (assoc (get parent :entity/field-entries) field-id entry)]
Expand Down
96 changes: 50 additions & 46 deletions src/sb/app/field/ui.cljc
Expand Up @@ -152,13 +152,16 @@
{:disabled (not can-edit?)
:class ["w-full" (:input classes)]
:placeholder (:placeholder props)
:on-key-down (let [save #(when (io/ancestor-by ?field :field/persisted?)
(j/call % :preventDefault)
(entity.data/maybe-save-field ?field))]
(ui/keydown-handler (merge {:Meta-Enter save
:Escape cancel!
:on-key-down (let [save (fn [^js e]
(if (io/ancestor-by ?field :field/persisted?)
(entity.data/maybe-save-field ?field)
(some-> (j/get-in e [:target :form])
(j/call :requestSubmit)))
(.preventDefault e))]
(ui/keydown-handler (merge {:Escape cancel!
:Meta-. cancel!}
(when-not multi-line?
(if multi-line?
{:Meta-Enter save}
{:Enter save})
keybindings)))}))])
(when-let [postfix (or (:field/postfix props)
Expand Down Expand Up @@ -209,22 +212,17 @@
(ui/defview video-field
{:key (fn [?field _] #?(:cljs (goog/getUid ?field)))}
[?field {:as props :keys [field/can-edit?]}]
(let [!editing? (h/use-state (nil? @?field))]
[:div.field-wrapper
;; preview shows persisted value?
[:div.flex.items-center
[:div.flex-auto (form.ui/show-label ?field (:field/label props))]
#_(when can-edit?
[:div.place-self-end [:a {:on-click #(swap! !editing? not)}
[(if @!editing? icons/chevron-up icons/chevron-down) "icon-gray"]]])]
(when-let [url (:video/url @?field)]
[show-video url])
(when can-edit?
(text-field ?field (merge props
{:field/label false
:placeholder "YouTube or Vimeo url"
:field/wrap (partial hash-map :video/url)
:field/unwrap :video/url})))]))
[:div.field-wrapper
;; preview shows persisted value?
[:div.flex.items-center
(when (and can-edit? (not (u/some-str @?field)))
[:div.flex-auto (form.ui/show-label ?field (:field/label props))])]
(when-let [url @?field]
[show-video url])
(when can-edit?
(text-field ?field (merge props
{:field/label false
:placeholder "YouTube or Vimeo url"})))])

(ui/defview select-field [?field {:as props :field/keys [label options]}]
[:div.field-wrapper
Expand Down Expand Up @@ -294,28 +292,36 @@
[:button.flex.items-center {:type "submit"} [icons/checkmark "w-5 h-5 icon-gray"]]]
(form.ui/show-field-messages ?badges)]]]))))

(ui/defview badges-field [?badges {:field/keys [can-edit?]}]
[:div.flex.gap-1
(for [{:as ?badge
:syms [?label ?color]} ?badges
:let [bg (or (u/some-str @?color) "#ffffff")
color (color/contrasting-text-color bg)]]
[radix/context-menu {:trigger [:div.rounded.bg-badge.text-badge-txt.py-1.px-2.text-sm.inline-flex
{:key @?label
:style {:background-color bg :color color}} @?label]
:items [[radix/context-menu-item
{:on-select (fn []
(io/remove-many! ?badge)
(entity.data/maybe-save-field ?badges))}
(t :tr/remove)]]}])
(let [!open (h/use-state false)]
(when can-edit?
[:div.inline-flex.text-sm.gap-1.items-center
{:on-click #(reset! !open true)}
(when-not (seq ?badges) "Add Badge")
[icons/plus "w-4 h-4 icon-gray"]
#?(:cljs
(defn use-new-badge [?badges]
(let [!open (h/use-state false)]
[#(reset! !open true)
(when @!open
[new-badge ?badges #(reset! !open false)])]))])
[new-badge ?badges #(reset! !open false)])])))

(ui/defview badges-field [?badges {:keys [field/can-edit? member/roles]}]
(let [[new! new-screen] (use-new-badge ?badges)
board-admin? (:role/board-admin roles)]
(when (or (seq ?badges) board-admin?)
[:div.flex.gap-1
(for [{:as ?badge
:syms [?label ?color]} ?badges
:let [bg (or (u/some-str @?color) "#ffffff")
color (color/contrasting-text-color bg)]]
[radix/context-menu {:trigger [:div.rounded.bg-badge.text-badge-txt.py-1.px-2.text-sm.inline-flex
{:key @?label
:style {:background-color bg :color color}} @?label]
:items [[radix/context-menu-item
{:on-select (fn []
(io/remove-many! ?badge)
(entity.data/maybe-save-field ?badges))}
(t :tr/remove)]]}])
(when board-admin?
[:div.inline-flex.text-sm.gap-1.items-center.rounded.hover:bg-gray-100.p-1
{:on-click new!}
(when-not (seq ?badges) [:span.cursor-default (t :tr/add-badge)])
[icons/plus "w-4 h-4 icon-gray"]
new-screen])])))


(ui/defview image-field [?field props]
Expand Down Expand Up @@ -481,9 +487,7 @@
props (merge (select-keys field [:field/label :field/hint :field/options])
(select-keys props [:field/can-edit?]))]
(case (:field/type field)
:field.type/video [video-field
('video/?url ?entry)
props]
:field.type/video [video-field ('video/?url ?entry) props]
:field.type/select [select-field ('select/?value ?entry) props]
:field.type/link-list [link-list-field ('link-list/?links ?entry) props]
:field.type/image-list [images-field ('image-list/?images ?entry) props]
Expand Down
3 changes: 1 addition & 2 deletions src/sb/app/member/data.cljc
Expand Up @@ -20,8 +20,7 @@
:member/as-map]]}
:member/role {s- [:enum
:role/admin
:role/owner
:role/collaborate
:role/editor
:role/member]}
:member/roles (merge sch/keyword
sch/many
Expand Down
4 changes: 2 additions & 2 deletions src/sb/app/org/data.cljc
Expand Up @@ -95,7 +95,7 @@
(q/defx settings!
{:prepare [az/with-account-id!]}
[{:keys [account-id]} {:as org :keys [entity.data/id]}]
(validate/assert-can-edit! id account-id)
(validate/assert-can-edit! account-id id)
(let [org (validate/conform org :org/as-map)]
(db/transact! [org])
{:body org}))
Expand All @@ -107,7 +107,7 @@
(validate/conform :org/as-map))
member (-> {:member/entity org
:member/account account-id
:member/roles #{:role/owner}}
:member/roles #{:role/admin}}
(dl/new-entity :member))]
(db/transact! [member])
org))
2 changes: 1 addition & 1 deletion src/sb/app/project/data.cljc
Expand Up @@ -102,7 +102,7 @@
;; TODO
;; verify that user is allowed to create a new project in parent
(let [project (dl/new-entity project :project :by account-id)
membership (member.data/new-entity-with-membership project account-id #{:role/owner})]
membership (member.data/new-entity-with-membership project account-id #{:role/admin})]
(validate/assert project :project/as-map)
(db/transact! [membership])
{:entity/id (:entity/id project)}))

0 comments on commit b571c09

Please sign in to comment.