Skip to content

Commit

Permalink
image fields: add, remove
Browse files Browse the repository at this point in the history
  • Loading branch information
mhuebert committed Jan 3, 2024
1 parent 2bc083b commit f7825f2
Show file tree
Hide file tree
Showing 10 changed files with 137 additions and 24 deletions.
2 changes: 1 addition & 1 deletion deps.edn
Expand Up @@ -7,7 +7,7 @@
datalevin/datalevin {:mvn/version "0.8.21"}
io.github.mhuebert/re-db {:git/sha "d9d0196403e346261e3007ab0f4d5e32e8a8e7a3"}
io.github.mhuebert/yawn {:git/sha "68285f6c132f26a2ff3cc2f7dffc3fe68c9856d9"}
io.github.mhuebert/inside-out {:git/sha "31dd485dd21f18115d3572b450c9234c5bc598e8"}
io.github.mhuebert/inside-out {:git/sha "fe175565b5840455e9ca6cd410b6c84a554c3003"}

;; Google Cloud
com.google.firebase/firebase-admin {:mvn/version "6.13.0"}
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -19,6 +19,7 @@
"@nextjournal/lezer-clojure": "1.0.0",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-context-menu": "^2.1.5",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-popover": "^1.0.7",
Expand Down
17 changes: 8 additions & 9 deletions src/sb/app/field/admin_ui.cljc
Expand Up @@ -114,12 +114,12 @@
:field/wrapper-class "flex-auto"
:class "rounded-sm relative focus:z-2"
:style {:background-color @?color
:color (color/contrasting-text-color @?color)}}]
:color (color/contrasting-text-color @?color)}}]
[:div.relative.w-10.focus-within-ring.rounded.overflow-hidden.self-stretch
[field.ui/color-field ?color {:style {:top -10
:left -10
:width 100
:height 100
[field.ui/color-field ?color {:style {:top -10
:left -10
:width 100
:height 100
:position "absolute"}}]]
[radix/dropdown-menu {:id :field-option
:trigger [:button.p-1.relative.icon-gray.cursor-default.rounded.hover:bg-gray-200.self-stretch
Expand Down Expand Up @@ -167,9 +167,8 @@
[:div.bg-gray-100.gap-3.grid.grid-cols-2.pl-12.pr-7.pt-4.pb-6

[:div.col-span-2.flex-v.gap-3
(view-field ?label {:field/multi-line? true})
(view-field ?hint {:field/multi-line? true
:placeholder "Further instructions"})]
(view-field ?label)
(view-field ?hint {:placeholder (t :tr/further-instructions)})]

(when (= :field.type/select @?type)
[:div.col-span-2.text-sm
Expand All @@ -188,7 +187,7 @@
:confirm-fn (fn []
(io/remove-many! ?field)
(entity.data/save-field ?field))})}
[:div.w-5.h-5.rounded.flex.items-center.justify-center.text-destructive [icons/trash "w-4 h-4"]]
[:div.w-5.h-5.rounded.flex.items-center.justify-center.text-destructive [icons/trash "w-4 h-4"]]
(t :tr/remove)]]]]))

(ui/defview field-row
Expand Down
2 changes: 1 addition & 1 deletion src/sb/app/field/data.cljc
Expand Up @@ -70,7 +70,7 @@
:field-option/label {s- :string},
:field-option/value {s- :string},
:video/url {s- :string}
:image-list/images {s- [:sequential :entity/id]}
:image-list/images {s- [:sequential [:map {:closed true} :entity/id]]}
:link-list/links {s- [:sequential :link-list/link]}
:select/value {s- :string}
:field-entry/as-map {s- [:map {:closed true}
Expand Down
86 changes: 76 additions & 10 deletions src/sb/app/field/ui.cljc
Expand Up @@ -287,16 +287,82 @@
:on-change #(some-> (j/get-in % [:target :files 0]) on-file)}]]
(form.ui/show-field-messages ?field)]]))

(ui/defview add-image-button [?image-list]
(let [loading? (:loading? ?image-list)
!selected-blob (h/use-state nil)
!dragging? (h/use-state false)
thumbnail @!selected-blob
!input (h/use-ref)
on-file (fn [file]
(reset! !selected-blob (js/URL.createObjectURL file))
(io/touch! ?image-list)
(ui/with-submission [id (routing/POST `asset.data/upload!
(doto (js/FormData.)
(.append "files" file)))
:form ?image-list]
(io/add-many! ?image-list {:entity/id (sch/unwrap-id id)})
(entity.data/maybe-save-field ?image-list)
(reset! !selected-blob nil)
(j/!set @!input :value nil)))]
;; TODO handle on-save
[:label.absolute.inset-0.gap-2.flex-v.items-center.justify-center.p-3.gap-3.default-ring.default-ring-hover
{:for (form.ui/field-id ?image-list)
:class ["rounded"
(if @!dragging?
"outline-2 outline-focus-accent")]
:on-drag-over (fn [^js e]
(.preventDefault e)
(reset! !dragging? true))
:on-drag-leave (fn [^js e]
(reset! !dragging? false))
:on-drop (fn [^js e]
(.preventDefault e)
(some-> (j/get-in e [:dataTransfer :files 0]) on-file))}

[:div.block.absolute.inset-0.rounded.cursor-pointer.flex.items-center.justify-center.rounded-lg
(v/props {:class "text-muted-txt hover:text-txt bg-contain bg-no-repeat bg-center"}
(when thumbnail
{:style {:background-image (asset.ui/css-url thumbnail)}})
#_{:style {:background-image "url(\"/assets/9d0dac6c-46bb-4086-8551-5bd533a9a2e8?op=bound&width=600\")"}})

(cond loading? [:div.rounded.bg-white.p-1 [icons/loading "w-4 h-4 text-txt/60"]]
(not thumbnail) (ui/upload-icon "w-5 h-5 m-auto"))

[:input.hidden
{:id (form.ui/field-id ?image-list)
:ref !input
:type "file"
:accept "image/webp, image/jpeg, image/gif, image/png, image/svg+xml"
:on-change #(some-> (j/get-in % [:target :files 0]) on-file)}]]
;; put messages in a popover
(form.ui/show-field-messages ?image-list)]))

(ui/defview images-field [?images {:field/keys [label can-edit?]}]
(for [{:syms [?id]} ?images
:let [url (asset.ui/asset-src @?id :card)]]
;; TODO
;; upload image,
;; re-order images
[:div.relative {:key url}
[form.ui/show-label ?images label]
[:div.inset-0.bg-black.absolute.opacity-10]
[:img {:src url}]]))
(let [?current (h/use-state (first ?images))]
[:div.field-wrapper
(form.ui/show-label ?images label)
(when-let [{:syms [?id]} @?current]
(let [[url loading?] (ui/use-last-loaded (asset.ui/asset-src @?id :avatar))]
[:div.relative {:key url}
(when loading? [icons/loading "w-4 h-4 text-txt/60 absolute top-2 right-2"])
[:div.inset-0.bg-black.absolute.opacity-10]
[:img {:src url}]]))
;; thumbnails
[:div.flex.gap-2.flex-wrap
(when can-edit? [:div.relative.h-16.w-16.flex-none [add-image-button ?images]])
(for [{:as ?image :syms [?id]} ?images
:let [url (asset.ui/asset-src @?id :card)
current? (= ?image @?current)]]
[radix/context-menu [:div.relative.w-16.h-16.rounded.overflow-hidden.bg-gray-50
{:class (when current? "outline outline-2 outline-black")
:on-click #(reset! ?current ?image)
:key url}
[:div.absolute.inset-0.bg-black.opacity-10.z-1]
[:div.absolute.inset-0.z-2.bg-contain {:style {:background-image (asset.ui/css-url url)}}]]
{:items [[radix/context-menu-item {:on-select (fn []
(io/remove-many! ?image)
(entity.data/maybe-save-field ?images))}
"Delete"]]}])]]))

(ui/defview link-list-field [?links {:field/keys [label]}]
[:div.field-wrapper
Expand Down Expand Up @@ -329,7 +395,7 @@
(io/form
(->> (?entries :many
{:field-entry/field field-entry/?field
:image-list/images (image-list/?images :many {:entity/id (sch/unwrap-id ?id)})
:image-list/images (image-list/?images :many {:entity/id ?id})
:video/url video/?url
:select/value select/?value
:link-list/links (link-list/?links :many {:text link/?text
Expand Down
14 changes: 12 additions & 2 deletions src/sb/app/views/radix.cljc
@@ -1,5 +1,6 @@
(ns sb.app.views.radix
(:require #?(:cljs ["@radix-ui/react-accordion" :as accordion])
(:require #?(:cljs ["@radix-ui/react-context-menu" :as ContextMenu])
#?(:cljs ["@radix-ui/react-accordion" :as accordion])
#?(:cljs ["@radix-ui/react-alert-dialog" :as alert])
#?(:cljs ["@radix-ui/react-dialog" :as dialog])
#?(:cljs ["@radix-ui/react-dropdown-menu" :as dm])
Expand Down Expand Up @@ -211,4 +212,13 @@
[:el accordion/Header
[:el.accordion-trigger accordion/Trigger (v/x trigger) [icons/chevron-down]]]
[:el.accordion-content accordion/Content
(v/x content)]])))]))
(v/x content)]])))]))

(def context-menu-item (v/from-element :el.text-sm.flex.items-center.outline-none.user-select-none.rounded.px-2.py-1 ContextMenu/Item
{:class "data-[highlighted]:bg-gray-100"}))

(v/defview context-menu [trigger {:keys [id items] :or {id (str ::context-menu)}}]
[:el ContextMenu/Root
[:el ContextMenu/Trigger (v/x trigger)]
[:el.bg-white.rounded.overflow-hidden.p-1.shadow-md.min-w-32 ContextMenu/Content {:style {:z-index 20}}
(into [:<>] items)]])
21 changes: 21 additions & 0 deletions src/sb/app/views/ui.cljs
Expand Up @@ -116,6 +116,27 @@
url
fallback)))

(defn use-last-loaded [image-url]
(let [!last-loaded (h/use-state image-url)
!loaded (h/use-state #{})]
(h/use-effect
(fn []
(when (and image-url (not (@!loaded image-url)))
(let [^js img (doto (js/document.createElement "img")
(j/assoc-in! [:style :display] "none")
(js/document.body.appendChild))]
(j/assoc! img
:onload #(do (swap! !loaded conj image-url)
(.remove img))
:src image-url)))
(when (and image-url
(not= image-url @!last-loaded)
(@!loaded image-url))
(reset! !last-loaded image-url))
nil)
[image-url (@!loaded image-url)])
[@!last-loaded (not= @!last-loaded image-url)]))

(def email-schema [:re #"^[^@]+@[^@]+$"])

(comment
Expand Down
3 changes: 3 additions & 0 deletions src/sb/i18n.cljc
Expand Up @@ -36,6 +36,9 @@ See https://iso639-3.sil.org/code_tables/639/data/all for list of codes"
{:tr/cancel {:en "Cancel"
:fr "Annuler"
:es "Cancelar"}
:tr/further-instructions {:en "Further instructions"
:fr "Instructions supplémentaires"
:es "Instrucciones adicionales"}
:tr/community-actions {:en "Community actions"
:fr "Actions communautaires"
:es "Acciones comunitarias"}
Expand Down
2 changes: 1 addition & 1 deletion src/sb/migration/one_time.clj
Expand Up @@ -456,7 +456,7 @@
:field.type/image-list (let [v (cond-> v (string? v) vector)]
(when (seq v)
(let [assets (mapv assets/link-asset v)]
{:image-list/images (mapv :entity/id assets)})))
{:image-list/images (mapv #(select-keys % [:entity/id]) assets)})))
:field.type/link-list {:link-list/links
(mapv #(rename-keys % {:label :text
:url :url}) v)}
Expand Down
13 changes: 13 additions & 0 deletions yarn.lock
Expand Up @@ -1148,6 +1148,19 @@
dependencies:
"@babel/runtime" "^7.13.10"

"@radix-ui/react-context-menu@^2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-context-menu/-/react-context-menu-2.1.5.tgz#1bdbd72761439f9166f75dc4598f276265785c83"
integrity sha512-R5XaDj06Xul1KGb+WP8qiOh7tKJNz2durpLBXAGZjSVtctcRFCuEvy2gtMwRJGePwQQE5nV77gs4FwRi8T+r2g==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "1.0.1"
"@radix-ui/react-context" "1.0.1"
"@radix-ui/react-menu" "2.0.6"
"@radix-ui/react-primitive" "1.0.3"
"@radix-ui/react-use-callback-ref" "1.0.1"
"@radix-ui/react-use-controllable-state" "1.0.1"

"@radix-ui/react-context@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.0.1.tgz#fe46e67c96b240de59187dcb7a1a50ce3e2ec00c"
Expand Down

0 comments on commit f7825f2

Please sign in to comment.