mirror of
https://github.com/penpot/penpot.git
synced 2025-01-24 23:49:45 -05:00
Images dashboard
This commit is contained in:
parent
963d22d930
commit
b3e9e7d1aa
12 changed files with 778 additions and 89 deletions
|
@ -15,6 +15,7 @@
|
|||
[uxbox.repo :as rp]
|
||||
[uxbox.data.projects :as dp]
|
||||
[uxbox.data.colors :as dc]
|
||||
[uxbox.data.images :as di]
|
||||
[uxbox.util.data :refer (deep-merge)]))
|
||||
|
||||
;; --- Events
|
||||
|
|
299
src/uxbox/data/images.cljs
Normal file
299
src/uxbox/data/images.cljs
Normal file
|
@ -0,0 +1,299 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.data.images
|
||||
(:require [cuerdas.core :as str]
|
||||
[beicon.core :as rx]
|
||||
[uuid.core :as uuid]
|
||||
[uxbox.rstore :as rs]
|
||||
[uxbox.state :as st]
|
||||
[uxbox.state.images :as sti]
|
||||
[uxbox.repo :as rp]))
|
||||
|
||||
;; --- Initialize
|
||||
|
||||
(declare fetch-collections)
|
||||
(declare collections-fetched?)
|
||||
|
||||
(defrecord Initialize []
|
||||
rs/EffectEvent
|
||||
(-apply-effect [_ state]
|
||||
(when-not (seq (:images-by-id state))
|
||||
(reset! st/loader true)))
|
||||
|
||||
rs/WatchEvent
|
||||
(-apply-watch [_ state s]
|
||||
(let [images (seq (:images-by-id state))]
|
||||
(if images
|
||||
(rx/empty)
|
||||
(rx/merge
|
||||
(rx/of (fetch-collections))
|
||||
(->> (rx/filter collections-fetched? s)
|
||||
(rx/take 1)
|
||||
(rx/do #(reset! st/loader false))
|
||||
(rx/ignore)))))))
|
||||
|
||||
(defn initialize
|
||||
[]
|
||||
(Initialize.))
|
||||
|
||||
;; --- Color Collections Fetched
|
||||
|
||||
(defrecord CollectionFetched [items]
|
||||
rs/UpdateEvent
|
||||
(-apply-update [_ state]
|
||||
(reduce sti/assoc-collection state items)))
|
||||
|
||||
(defn collections-fetched
|
||||
[items]
|
||||
(CollectionFetched. items))
|
||||
|
||||
;; --- Fetch Color Collections
|
||||
|
||||
(defrecord FetchCollections []
|
||||
rs/WatchEvent
|
||||
(-apply-watch [_ state s]
|
||||
(->> (rp/req :fetch/image-collections)
|
||||
(rx/map :payload)
|
||||
(rx/map collections-fetched))))
|
||||
|
||||
(defn fetch-collections
|
||||
[]
|
||||
(FetchCollections.))
|
||||
|
||||
;; --- Collection Created
|
||||
|
||||
(defrecord CollectionCreated [item]
|
||||
rs/UpdateEvent
|
||||
(-apply-update [_ state]
|
||||
(-> state
|
||||
(sti/assoc-collection item)
|
||||
(assoc-in [:dashboard :collection-id] (:id item))
|
||||
(assoc-in [:dashboard :collection-type] :own))))
|
||||
|
||||
(defn collection-created
|
||||
[item]
|
||||
(CollectionCreated. item))
|
||||
|
||||
;; --- Create Collection
|
||||
|
||||
(defrecord CreateCollection []
|
||||
rs/WatchEvent
|
||||
(-apply-watch [_ state s]
|
||||
(let [coll {:name "Unnamed collection"
|
||||
:id (uuid/random)}]
|
||||
(->> (rp/req :create/image-collection coll)
|
||||
(rx/map :payload)
|
||||
(rx/map collection-created)))))
|
||||
|
||||
(defn create-collection
|
||||
[]
|
||||
(CreateCollection.))
|
||||
|
||||
(defn collections-fetched?
|
||||
[v]
|
||||
(instance? CollectionFetched v))
|
||||
|
||||
;; --- Collection Updated
|
||||
|
||||
(defrecord CollectionUpdated [item]
|
||||
rs/UpdateEvent
|
||||
(-apply-update [_ state]
|
||||
(sti/assoc-collection state item)))
|
||||
|
||||
(defn collection-updated
|
||||
[item]
|
||||
(CollectionUpdated. item))
|
||||
|
||||
;; --- Update Collection
|
||||
|
||||
(defrecord UpdateCollection [id]
|
||||
rs/WatchEvent
|
||||
(-apply-watch [_ state s]
|
||||
(let [item (get-in state [:images-by-id id])]
|
||||
(->> (rp/req :update/image-collection item)
|
||||
(rx/map :payload)
|
||||
(rx/map collection-updated)))))
|
||||
|
||||
(defn update-collection
|
||||
[id]
|
||||
(UpdateCollection. id))
|
||||
|
||||
;; --- Rename Collection
|
||||
|
||||
(defrecord RenameCollection [id name]
|
||||
rs/UpdateEvent
|
||||
(-apply-update [_ state]
|
||||
(assoc-in state [:images-by-id id :name] name))
|
||||
|
||||
rs/WatchEvent
|
||||
(-apply-watch [_ state s]
|
||||
(rx/of (update-collection id))))
|
||||
|
||||
(defn rename-collection
|
||||
[item name]
|
||||
(RenameCollection. item name))
|
||||
|
||||
;; --- Delete Collection
|
||||
|
||||
(defrecord DeleteCollection [id]
|
||||
rs/UpdateEvent
|
||||
(-apply-update [_ state]
|
||||
(sti/dissoc-collection state id))
|
||||
|
||||
rs/WatchEvent
|
||||
(-apply-watch [_ state s]
|
||||
(->> (rp/req :delete/image-collection id)
|
||||
(rx/ignore))))
|
||||
|
||||
(defn delete-collection
|
||||
[id]
|
||||
(DeleteCollection. id))
|
||||
|
||||
;; --- Image Created
|
||||
|
||||
(defrecord ImageCreated [item]
|
||||
rs/UpdateEvent
|
||||
(-apply-update [_ state]
|
||||
(update-in state [:images-by-id (:collection item) :images]
|
||||
#(conj % item))))
|
||||
|
||||
(defn image-created
|
||||
[item]
|
||||
(ImageCreated. item))
|
||||
|
||||
;; --- Create Image
|
||||
|
||||
(defrecord CreateImages [coll-id files]
|
||||
rs/WatchEvent
|
||||
(-apply-watch [_ state s]
|
||||
(let [image-data {:coll coll-id
|
||||
:id (uuid/random)
|
||||
:files files}]
|
||||
(->> (rp/req :create/image image-data)
|
||||
(rx/map :payload)
|
||||
(rx/map image-created)))))
|
||||
|
||||
(defn create-images
|
||||
[coll-id files]
|
||||
(CreateImages. coll-id files))
|
||||
|
||||
;; --- Images Loaded
|
||||
|
||||
(defrecord ImagesLoaded [coll-id items]
|
||||
rs/UpdateEvent
|
||||
(-apply-update [_ state]
|
||||
(assoc-in state [:images-by-id coll-id :images] (set items))))
|
||||
|
||||
(defn images-loaded
|
||||
[coll-id items]
|
||||
(ImagesLoaded. coll-id items))
|
||||
|
||||
;; --- Load Images
|
||||
|
||||
(defrecord LoadImages [coll-id]
|
||||
rs/WatchEvent
|
||||
(-apply-watch [_ state s]
|
||||
(let [params {:coll coll-id}]
|
||||
(->> (rp/req :fetch/images params)
|
||||
(rx/map :payload)
|
||||
(rx/map #(images-loaded coll-id %))))))
|
||||
|
||||
(defn load-images
|
||||
[coll-id]
|
||||
(LoadImages. coll-id))
|
||||
|
||||
;; --- Delete Images
|
||||
|
||||
(defrecord DeleteImage [coll-id image]
|
||||
rs/UpdateEvent
|
||||
(-apply-update [_ state]
|
||||
(sti/dissoc-image state coll-id image))
|
||||
|
||||
rs/WatchEvent
|
||||
(-apply-watch [_ state s]
|
||||
(->> (rp/req :delete/image (:id image))
|
||||
(rx/ignore))))
|
||||
|
||||
(defn delete-image
|
||||
[coll-id image]
|
||||
(DeleteImage. coll-id image))
|
||||
|
||||
;; --- Set Collection
|
||||
|
||||
(defrecord SetCollection [id]
|
||||
rs/UpdateEvent
|
||||
(-apply-update [_ state]
|
||||
(assoc-in state [:dashboard :collection-id] id))
|
||||
|
||||
rs/WatchEvent
|
||||
(-apply-watch [_ state s]
|
||||
(rx/of (load-images id))))
|
||||
|
||||
(defn set-collection
|
||||
[id]
|
||||
(SetCollection. id))
|
||||
|
||||
;; --- Set Collection Type
|
||||
|
||||
(defrecord SetCollectionType [type]
|
||||
rs/WatchEvent
|
||||
(-apply-watch [_ state s]
|
||||
(if (= type :builtin)
|
||||
(rx/of (set-collection 1))
|
||||
(let [colls (sort-by :id (vals (:images-by-id state)))]
|
||||
(rx/of (set-collection (:id (first colls)))))))
|
||||
|
||||
rs/UpdateEvent
|
||||
(-apply-update [_ state]
|
||||
(as-> state $
|
||||
(assoc-in $ [:dashboard :collection-type] type))))
|
||||
|
||||
(defn set-collection-type
|
||||
[type]
|
||||
{:pre [(contains? #{:builtin :own} type)]}
|
||||
(SetCollectionType. type))
|
||||
|
||||
;; --- Helpers
|
||||
|
||||
(defn sort-images-by
|
||||
[ordering projs]
|
||||
(case ordering
|
||||
:name (sort-by :name projs)
|
||||
:created (reverse (sort-by :created-at projs))
|
||||
projs))
|
||||
|
||||
(defn contains-term?
|
||||
[phrase term]
|
||||
(str/contains? (str/lower phrase) (str/trim (str/lower term))))
|
||||
|
||||
(defn filter-images-by
|
||||
[term projs]
|
||||
(if (str/blank? term)
|
||||
projs
|
||||
(filter #(contains-term? (:name %) term) projs)))
|
||||
|
||||
(defn set-images-ordering
|
||||
[order]
|
||||
(reify
|
||||
rs/UpdateEvent
|
||||
(-apply-update [_ state]
|
||||
(assoc-in state [:dashboard :images-order] order))))
|
||||
|
||||
(defn set-images-filtering
|
||||
[term]
|
||||
(reify
|
||||
rs/UpdateEvent
|
||||
(-apply-update [_ state]
|
||||
(assoc-in state [:dashboard :images-filter] term))))
|
||||
|
||||
(defn clear-images-filtering
|
||||
[]
|
||||
(reify
|
||||
rs/UpdateEvent
|
||||
(-apply-update [_ state]
|
||||
(assoc-in state [:dashboard :images-filter] ""))))
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
(ns uxbox.library
|
||||
(:require [uxbox.library.colors :as colors]
|
||||
[uxbox.library.images :as images]
|
||||
[uxbox.library.icons :as icons]
|
||||
[uxbox.library.fonts :as fonts]
|
||||
[uxbox.util.data :refer (index-by-id)]))
|
||||
|
@ -31,6 +32,16 @@
|
|||
(def ^:const +icon-collections-by-id+
|
||||
(index-by-id icons/+collections+))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Images
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(def ^:const +image-collections+
|
||||
images/+collections+)
|
||||
|
||||
(def ^:const +image-collections-by-id+
|
||||
(index-by-id images/+collections+))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Fonts
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
|
65
src/uxbox/library/images.cljs
Normal file
65
src/uxbox/library/images.cljs
Normal file
|
@ -0,0 +1,65 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.library.images)
|
||||
|
||||
(def ^:static +collections+
|
||||
[{:name "Generic 1"
|
||||
:id 1
|
||||
:builtin true
|
||||
:images [{:id 1
|
||||
:name "image_name.jpg"
|
||||
:url "https://images.unsplash.com/photo-1455098934982-64c622c5e066?crop=entropy&fit=crop&fm=jpg&h=1025&ixjsv=2.1.0&ixlib=rb-0.3.5&q=80&w=1400"
|
||||
:thumbnail "https://images.unsplash.com/photo-1455098934982-64c622c5e066?crop=entropy&fit=crop&fm=jpg&h=1025&ixjsv=2.1.0&ixlib=rb-0.3.5&q=80&w=1400"}
|
||||
{:id 2
|
||||
:name "image_name_long_name.jpg"
|
||||
:url "https://images.unsplash.com/photo-1422393462206-207b0fbd8d6b?crop=entropy&fit=crop&fm=jpg&h=1025&ixjsv=2.1.0&ixlib=rb-0.3.5&q=80&w=1925"
|
||||
:thumbnail "https://images.unsplash.com/photo-1422393462206-207b0fbd8d6b?crop=entropy&fit=crop&fm=jpg&h=1025&ixjsv=2.1.0&ixlib=rb-0.3.5&q=80&w=1925"}
|
||||
{:id 3
|
||||
:name "image_name.jpg"
|
||||
:url "https://images.unsplash.com/photo-1441986380878-c4248f5b8b5b?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&w=1080&fit=max&s=486f09671860a11e70bdd0a45e7c5014"
|
||||
:thumbnail "https://images.unsplash.com/photo-1441986380878-c4248f5b8b5b?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&w=1080&fit=max&s=486f09671860a11e70bdd0a45e7c5014"}
|
||||
{:id 4
|
||||
:name "image_name_big_long_name.jpg"
|
||||
:url "https://images.unsplash.com/photo-1423768164017-3f27c066407f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&w=1080&fit=max&s=712b919f3a2f6fc34f29040e8082b6d9"
|
||||
:thumbnail "https://images.unsplash.com/photo-1423768164017-3f27c066407f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&w=1080&fit=max&s=712b919f3a2f6fc34f29040e8082b6d9"}
|
||||
{:id 5
|
||||
:name "image_name.jpg"
|
||||
:url "https://images.unsplash.com/photo-1456428199391-a3b1cb5e93ab?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&w=1080&fit=max&s=765abd6dc931b7184e9795d8494966ed"
|
||||
:thumbnail "https://images.unsplash.com/photo-1456428199391-a3b1cb5e93ab?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&w=1080&fit=max&s=765abd6dc931b7184e9795d8494966ed"}
|
||||
{:id 6
|
||||
:name "image_name.jpg"
|
||||
:url "https://images.unsplash.com/photo-1447678523326-1360892abab8?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&w=1080&fit=max&s=91663afcd23da14f76cc8229c1780d47"
|
||||
:thumbnail "https://images.unsplash.com/photo-1447678523326-1360892abab8?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&w=1080&fit=max&s=91663afcd23da14f76cc8229c1780d47"}]}
|
||||
|
||||
{:name "Generic 2"
|
||||
:id 2
|
||||
:builtin true
|
||||
:images [{:id 7
|
||||
:name "image_name.jpg"
|
||||
:url "https://images.unsplash.com/photo-1455098934982-64c622c5e066?crop=entropy&fit=crop&fm=jpg&h=1025&ixjsv=2.1.0&ixlib=rb-0.3.5&q=80&w=1400"
|
||||
:thumbnail "https://images.unsplash.com/photo-1455098934982-64c622c5e066?crop=entropy&fit=crop&fm=jpg&h=1025&ixjsv=2.1.0&ixlib=rb-0.3.5&q=80&w=1400"}
|
||||
{:id 8
|
||||
:name "image_name_long_name.jpg"
|
||||
:url "https://images.unsplash.com/photo-1422393462206-207b0fbd8d6b?crop=entropy&fit=crop&fm=jpg&h=1025&ixjsv=2.1.0&ixlib=rb-0.3.5&q=80&w=1925"
|
||||
:thumbnail "https://images.unsplash.com/photo-1422393462206-207b0fbd8d6b?crop=entropy&fit=crop&fm=jpg&h=1025&ixjsv=2.1.0&ixlib=rb-0.3.5&q=80&w=1925"}
|
||||
{:id 9
|
||||
:name "image_name.jpg"
|
||||
:url "https://images.unsplash.com/photo-1441986380878-c4248f5b8b5b?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&w=1080&fit=max&s=486f09671860a11e70bdd0a45e7c5014"
|
||||
:thumbnail "https://images.unsplash.com/photo-1441986380878-c4248f5b8b5b?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&w=1080&fit=max&s=486f09671860a11e70bdd0a45e7c5014"}
|
||||
{:id 10
|
||||
:name "image_name_big_long_name.jpg"
|
||||
:url "https://images.unsplash.com/photo-1423768164017-3f27c066407f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&w=1080&fit=max&s=712b919f3a2f6fc34f29040e8082b6d9"
|
||||
:thumbnail "https://images.unsplash.com/photo-1423768164017-3f27c066407f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&w=1080&fit=max&s=712b919f3a2f6fc34f29040e8082b6d9"}
|
||||
{:id 11
|
||||
:name "image_name.jpg"
|
||||
:url "https://images.unsplash.com/photo-1456428199391-a3b1cb5e93ab?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&w=1080&fit=max&s=765abd6dc931b7184e9795d8494966ed"
|
||||
:thumbnail "https://images.unsplash.com/photo-1456428199391-a3b1cb5e93ab?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&w=1080&fit=max&s=765abd6dc931b7184e9795d8494966ed"}
|
||||
{:id 12
|
||||
:name "image_name.jpg"
|
||||
:url "https://images.unsplash.com/photo-1447678523326-1360892abab8?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&w=1080&fit=max&s=91663afcd23da14f76cc8229c1780d47"
|
||||
:thumbnail "https://images.unsplash.com/photo-1447678523326-1360892abab8?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&w=1080&fit=max&s=91663afcd23da14f76cc8229c1780d47"}]}])
|
|
@ -15,6 +15,9 @@
|
|||
"ds.num-colors" ["No colors"
|
||||
"%s color"
|
||||
"%s colors"]
|
||||
"ds.num-images" ["No images"
|
||||
"%s image"
|
||||
"%s images"]
|
||||
"ds.project-ordering" "Sort by"
|
||||
"ds.project-ordering.by-name" "name"
|
||||
"ds.project-ordering.by-last-update" "last update"
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
[uxbox.repo.projects]
|
||||
[uxbox.repo.pages]
|
||||
[uxbox.repo.colors]
|
||||
[uxbox.repo.images]
|
||||
[httpurr.status :as status]
|
||||
[beicon.core :as rx]))
|
||||
|
||||
|
|
|
@ -47,14 +47,14 @@
|
|||
(defn- send!
|
||||
[{:keys [body headers auth method query url] :or {auth true} :as request}]
|
||||
(let [headers (merge {}
|
||||
(when body +headers+)
|
||||
(when (map? body) +headers+)
|
||||
headers
|
||||
(when auth (auth-headers)))
|
||||
request {:method method
|
||||
:url url
|
||||
:headers headers
|
||||
:query-string (when query (encode-query query))
|
||||
:body (when body (t/encode body))}]
|
||||
:body (if (map? body) (t/encode body) body)}]
|
||||
(->> (http/send! request)
|
||||
(rx/from-promise)
|
||||
(rx/map conditional-decode)
|
||||
|
|
80
src/uxbox/repo/images.cljs
Normal file
80
src/uxbox/repo/images.cljs
Normal file
|
@ -0,0 +1,80 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.repo.images
|
||||
"A main interface for access to remote resources."
|
||||
(:require [beicon.core :as rx]
|
||||
[uxbox.repo.core :refer (request url send!)]
|
||||
[uxbox.state :as ust]
|
||||
[uxbox.util.transit :as t]))
|
||||
|
||||
(defn- decode-image-collection
|
||||
[{:keys [data] :as coll}]
|
||||
coll)
|
||||
;; (merge coll
|
||||
;; (when data {:data (t/decode data)})))
|
||||
|
||||
(defn- decode-payload
|
||||
[{:keys [payload] :as rsp}]
|
||||
rsp)
|
||||
;; (if (sequential? payload)
|
||||
;; (assoc rsp :payload (mapv decode-image-collection payload))
|
||||
;; (assoc rsp :payload (decode-image-collection payload))))
|
||||
|
||||
(defmethod request :fetch/image-collections
|
||||
[_]
|
||||
(let [params {:url (str url "/library/image-collections")
|
||||
:method :get}]
|
||||
(->> (send! params)
|
||||
(rx/map decode-payload))))
|
||||
|
||||
(defmethod request :delete/image-collection
|
||||
[_ id]
|
||||
(let [url (str url "/library/image-collections/" id)]
|
||||
(send! {:url url :method :delete})))
|
||||
|
||||
(defmethod request :create/image-collection
|
||||
[_ {:keys [data] :as body}]
|
||||
(let [body (assoc body :data (t/encode data))
|
||||
params {:url (str url "/library/image-collections")
|
||||
:method :post
|
||||
:body body}]
|
||||
(->> (send! params)
|
||||
(rx/map decode-payload))))
|
||||
|
||||
(defmethod request :update/image-collection
|
||||
[_ {:keys [id data] :as body}]
|
||||
(let [body (assoc body :data (t/encode data))
|
||||
params {:url (str url "/library/image-collections/" id)
|
||||
:method :put
|
||||
:body body}]
|
||||
(->> (send! params)
|
||||
(rx/map decode-payload))))
|
||||
|
||||
(defmethod request :fetch/images
|
||||
[_ {:keys [coll]}]
|
||||
(let [params {:url (str url "/library/images/" coll)
|
||||
:method :get}]
|
||||
(->> (send! params)
|
||||
(rx/map decode-payload))))
|
||||
|
||||
(defmethod request :create/image
|
||||
[_ {:keys [coll id files] :as body}]
|
||||
(let [build-body (fn []
|
||||
(let [data (js/FormData.)]
|
||||
(.append data "file" (aget files 0))
|
||||
(.append data "id" id)
|
||||
data))
|
||||
params {:url (str url "/library/images/" coll)
|
||||
:method :post
|
||||
:body (build-body)}]
|
||||
(->> (send! params)
|
||||
(rx/map decode-payload))))
|
||||
|
||||
(defmethod request :delete/image
|
||||
[_ id]
|
||||
(let [url (str url "/library/images/" id)]
|
||||
(send! {:url url :method :delete})))
|
|
@ -25,7 +25,9 @@
|
|||
(defn- get-initial-state
|
||||
[]
|
||||
{:dashboard {:project-order :name
|
||||
:project-filter ""}
|
||||
:project-filter ""
|
||||
:images-order :name
|
||||
:images-filter ""}
|
||||
:route nil
|
||||
:auth (:uxbox/auth storage nil)
|
||||
:clipboard #queue []
|
||||
|
|
28
src/uxbox/state/images.cljs
Normal file
28
src/uxbox/state/images.cljs
Normal file
|
@ -0,0 +1,28 @@
|
|||
(ns uxbox.state.images
|
||||
"A collection of functions for manage dashboard data insinde the state.")
|
||||
|
||||
(defn assoc-collection
|
||||
"A reduce function for assoc the image collection
|
||||
to the state map."
|
||||
[state coll]
|
||||
(let [id (:id coll)]
|
||||
(assoc-in state [:images-by-id id] coll)))
|
||||
|
||||
(defn dissoc-collection
|
||||
"A reduce function for dissoc the image collection
|
||||
to the state map."
|
||||
[state id]
|
||||
(update state :images-by-id dissoc id))
|
||||
|
||||
(defn select-first-collection
|
||||
"A reduce function for select the first image collection
|
||||
to the state map."
|
||||
[state]
|
||||
(let [colls (sort-by :id (vals (:images-by-id state)))]
|
||||
(assoc-in state [:dashboard :collection-id] (:id (first colls)))))
|
||||
|
||||
(defn dissoc-image
|
||||
"A reduce function for dissoc the image collection
|
||||
to the state map."
|
||||
[state coll-id image]
|
||||
(update-in state [:images-by-id coll-id :images] disj image))
|
|
@ -8,116 +8,300 @@
|
|||
(ns uxbox.ui.dashboard.images
|
||||
(:require [sablono.core :as html :refer-macros [html]]
|
||||
[rum.core :as rum]
|
||||
[lentes.core :as l]
|
||||
[uxbox.locales :as t :refer (tr)]
|
||||
[uxbox.state :as st]
|
||||
[uxbox.rstore :as rs]
|
||||
[uxbox.library :as library]
|
||||
[uxbox.data.dashboard :as dd]
|
||||
[uxbox.data.lightbox :as udl]
|
||||
[uxbox.data.images :as di]
|
||||
[uxbox.ui.icons :as i]
|
||||
[uxbox.ui.mixins :as mx]
|
||||
[uxbox.ui.lightbox :as lbx]
|
||||
[uxbox.ui.keyboard :as k]
|
||||
[uxbox.ui.library-bar :as ui.library-bar]
|
||||
[uxbox.ui.dashboard.header :refer (header)]
|
||||
[uxbox.util.lens :as ul]
|
||||
[uxbox.util.dom :as dom]))
|
||||
|
||||
;; --- Helpers & Constants
|
||||
|
||||
(def +ordering-options+
|
||||
{:name "ds.project-ordering.by-name"
|
||||
:created "ds.project-ordering.by-creation-date"})
|
||||
|
||||
;; --- Lenses
|
||||
|
||||
(def ^:const ^:private dashboard-l
|
||||
(-> (l/key :dashboard)
|
||||
(l/focus-atom st/state)))
|
||||
|
||||
(def ^:const ^:private collections-by-id-l
|
||||
(-> (comp (l/key :images-by-id)
|
||||
(ul/merge library/+image-collections-by-id+))
|
||||
(l/focus-atom st/state)))
|
||||
|
||||
(def images-ordering-l
|
||||
(as-> (l/in [:dashboard :images-order]) $
|
||||
(l/focus-atom $ st/state)))
|
||||
|
||||
(def images-filtering-l
|
||||
(as-> (l/in [:dashboard :images-filter]) $
|
||||
(l/focus-atom $ st/state)))
|
||||
|
||||
|
||||
(defn- focus-collection
|
||||
[collid]
|
||||
(-> (l/key collid)
|
||||
(l/focus-atom collections-by-id-l)))
|
||||
|
||||
|
||||
;; --- Page Title
|
||||
|
||||
(defn page-title-render
|
||||
[]
|
||||
(html
|
||||
[:div.dashboard-title
|
||||
[:h2 "Element library name"]
|
||||
[:div.edition
|
||||
[:span i/pencil]
|
||||
[:span i/trash]]]))
|
||||
[own coll]
|
||||
(let [local (:rum/local own)
|
||||
dashboard (rum/react dashboard-l)
|
||||
own? (:builtin coll false)]
|
||||
(letfn [(on-title-save [e]
|
||||
(rs/emit! (di/rename-collection (:id coll) (:coll-name @local)))
|
||||
(swap! local assoc :edit false))
|
||||
(on-title-edited [e]
|
||||
(cond
|
||||
(k/esc? e) (swap! local assoc :edit false)
|
||||
(k/enter? e) (on-title-save e)
|
||||
:else (let [content (dom/event->inner-text e)]
|
||||
(swap! local assoc :coll-name content))))
|
||||
(on-title-edit [e]
|
||||
(swap! local assoc :edit true :coll-name (:name coll)))
|
||||
(on-delete [e]
|
||||
(rs/emit! (di/delete-collection (:id coll))))]
|
||||
(html
|
||||
[:div.dashboard-title {}
|
||||
[:h2 {}
|
||||
(if (:edit @local)
|
||||
[:div.dashboard-title-field
|
||||
[:span.edit
|
||||
{:content-editable ""
|
||||
:on-key-up on-title-edited}
|
||||
(:name coll)]
|
||||
[:span.close
|
||||
{:on-click #(swap! local assoc :edit false)}
|
||||
i/close]]
|
||||
[:span.dashboard-title-field
|
||||
(:name coll)])]
|
||||
(if (and (not own?) coll)
|
||||
[:div.edition
|
||||
(if (:edit @local)
|
||||
[:span {:on-click on-title-save} i/save]
|
||||
[:span {:on-click on-title-edit} i/pencil])
|
||||
[:span {:on-click on-delete} i/trash]])]))))
|
||||
|
||||
(def ^:const ^:private page-title
|
||||
(def ^:private page-title
|
||||
(mx/component
|
||||
{:render page-title-render
|
||||
:name "page-title"
|
||||
:mixins [mx/static]}))
|
||||
:mixins [(rum/local {}) mx/static rum/reactive]}))
|
||||
|
||||
;; --- Nav
|
||||
|
||||
(defn nav-render
|
||||
[own]
|
||||
(let [dashboard (rum/react dashboard-l)
|
||||
collections-by-id (rum/react collections-by-id-l)
|
||||
collid (:collection-id dashboard)
|
||||
own? (= (:collection-type dashboard) :own)
|
||||
builtin? (= (:collection-type dashboard) :builtin)
|
||||
collections (as-> (vals collections-by-id) $
|
||||
(if own?
|
||||
(filter (comp not :builtin) $)
|
||||
(filter :builtin $)))]
|
||||
(html
|
||||
[:div.library-bar
|
||||
[:div.library-bar-inside
|
||||
[:ul.library-tabs
|
||||
[:li {:class-name (when builtin? "current")
|
||||
:on-click #(rs/emit! (di/set-collection-type :builtin))}
|
||||
"STANDARD"]
|
||||
[:li {:class-name (when own? "current")
|
||||
:on-click #(rs/emit! (di/set-collection-type :own))}
|
||||
"YOUR LIBRARIES"]]
|
||||
[:ul.library-elements
|
||||
(when own?
|
||||
[:li
|
||||
[:a.btn-primary
|
||||
{:on-click #(rs/emit! (di/create-collection))}
|
||||
"+ New library"]])
|
||||
(for [props collections
|
||||
:let [num (count (:images props))]]
|
||||
[:li {:key (str (:id props))
|
||||
:on-click #(rs/emit! (di/set-collection (:id props)))
|
||||
:class-name (when (= (:id props) collid) "current")}
|
||||
[:span.element-title (:name props)]
|
||||
[:span.element-subtitle
|
||||
(tr "ds.num-elements" (t/c num))]])]]])))
|
||||
|
||||
(def ^:const ^:private nav
|
||||
(mx/component
|
||||
{:render nav-render
|
||||
:name "nav"
|
||||
:mixins [rum/reactive]}))
|
||||
|
||||
|
||||
;; --- Grid
|
||||
|
||||
(defn grid-render
|
||||
[own]
|
||||
(html
|
||||
[:div.dashboard-grid-content
|
||||
[:div.dashboard-grid-row
|
||||
[:div.grid-item.add-project
|
||||
{on-click #(udl/open! :new-element)}
|
||||
[:span "+ New image"]]
|
||||
(let [local (:rum/local own)
|
||||
dashboard (rum/react dashboard-l)
|
||||
coll-type (:collection-type dashboard)
|
||||
coll-id (:collection-id dashboard)
|
||||
own? (= coll-type :own)
|
||||
coll (rum/react (focus-collection coll-id))
|
||||
images-filtering (rum/react images-filtering-l)
|
||||
images-ordering (rum/react images-ordering-l)
|
||||
coll-images (->> (:images coll)
|
||||
(remove nil?)
|
||||
(di/filter-images-by images-filtering)
|
||||
(di/sort-images-by images-ordering))
|
||||
toggle-image-check (fn [image]
|
||||
(swap! local update :selected #(if (% image) (disj % image) (conj % image))))
|
||||
delete-selected-images #(doseq [image (:selected @local)]
|
||||
(rs/emit! (di/delete-image coll-id image)))]
|
||||
(when coll
|
||||
(html
|
||||
[:section.dashboard-grid.library
|
||||
(page-title coll)
|
||||
[:div.dashboard-grid-content
|
||||
[:div.dashboard-grid-row
|
||||
(if own?
|
||||
[:div.grid-item.add-project
|
||||
{:on-click #(dom/click (dom/get-element-by-class "upload-image-input"))}
|
||||
[:span "+ New image"]
|
||||
[:input.upload-image-input {:style {:display "none"}
|
||||
:type "file"
|
||||
:on-change #(rs/emit! (di/create-images coll-id (dom/get-event-files %)))}]])
|
||||
|
||||
[:div.grid-item.images-th
|
||||
[:div.grid-item-th
|
||||
{:style
|
||||
{:background-image "url('https://images.unsplash.com/photo-1455098934982-64c622c5e066?crop=entropy&fit=crop&fm=jpg&h=1025&ixjsv=2.1.0&ixlib=rb-0.3.5&q=80&w=1400')"}}
|
||||
[:div.input-checkbox.check-primary
|
||||
[:input {:type "checkbox" :id "item-1" :value "Yes"}]
|
||||
[:label {:for "item-1"}]]]
|
||||
[:span "image_name.jpg"]]
|
||||
(for [image coll-images]
|
||||
[:div.grid-item.images-th
|
||||
{:key (:id image) :on-click #(when (k/shift? %) (toggle-image-check image))}
|
||||
[:div.grid-item-th
|
||||
{:style
|
||||
{:background-image (str "url('" (:thumbnail image) "')")}}
|
||||
[:div.input-checkbox.check-primary
|
||||
[:input {:type "checkbox"
|
||||
:id (:id image)
|
||||
:on-click #(toggle-image-check image)
|
||||
:checked ((:selected @local) image)}]
|
||||
[:label {:for (:id image)}]]]
|
||||
[:span (:name image)]])]
|
||||
|
||||
[:div.grid-item.images-th
|
||||
[:div.grid-item-th
|
||||
{:style
|
||||
{:background-image "url('https://images.unsplash.com/photo-1422393462206-207b0fbd8d6b?crop=entropy&fit=crop&fm=jpg&h=1025&ixjsv=2.1.0&ixlib=rb-0.3.5&q=80&w=1925')"}}
|
||||
[:div.input-checkbox.check-primary
|
||||
[:input {:type "checkbox" :id "item-2" :value "Yes"}]
|
||||
[:label {:for "item-2"}]]]
|
||||
[:span "image_name_long_name.jpg"]]
|
||||
|
||||
[:div.grid-item.images-th
|
||||
[:div.grid-item-th
|
||||
{:style
|
||||
{:background-image "url('https://images.unsplash.com/photo-1441986380878-c4248f5b8b5b?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&w=1080&fit=max&s=486f09671860a11e70bdd0a45e7c5014')"}}
|
||||
[:div.input-checkbox.check-primary
|
||||
[:input {:type "checkbox" :id "item-3" :value "Yes"}]
|
||||
[:label {:for "item-3"}]]]
|
||||
[:span "image_name.jpg"]]
|
||||
|
||||
[:div.grid-item.images-th
|
||||
[:div.grid-item-th
|
||||
{:style
|
||||
{:background-image "url('https://images.unsplash.com/photo-1423768164017-3f27c066407f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&w=1080&fit=max&s=712b919f3a2f6fc34f29040e8082b6d9')"}}
|
||||
[:div.input-checkbox.check-primary
|
||||
[:input {:type "checkbox" :id "item-4" :value "Yes"}]
|
||||
[:label {:for "item-4"}]]]
|
||||
[:span "image_name_big_long_name.jpg"]]
|
||||
|
||||
[:div.grid-item.images-th
|
||||
[:div.grid-item-th
|
||||
{:style
|
||||
{:background-image "url('https://images.unsplash.com/photo-1456428199391-a3b1cb5e93ab?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&w=1080&fit=max&s=765abd6dc931b7184e9795d8494966ed')"}}
|
||||
[:div.input-checkbox.check-primary
|
||||
[:input {:type "checkbox" :id "item-5" :value "Yes"}]
|
||||
[:label {:for "item-5"}]]]
|
||||
[:span "image_name.jpg"]]
|
||||
|
||||
[:div.grid-item.images-th
|
||||
[:div.grid-item-th
|
||||
{:style
|
||||
{:background-image "url('https://images.unsplash.com/photo-1447678523326-1360892abab8?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&w=1080&fit=max&s=91663afcd23da14f76cc8229c1780d47')"}}
|
||||
[:div.input-checkbox.check-primary
|
||||
[:input {:type "checkbox" :id "item-6" :value "Yes"}]
|
||||
[:label {:for "item-6"}]]]
|
||||
[:span "image_name.jpg"]]]
|
||||
|
||||
;; MULTISELECT OPTIONS BAR
|
||||
[:div.multiselect-bar
|
||||
[:div.multiselect-nav
|
||||
[:span.move-item.tooltip.tooltip-top
|
||||
{:alt "Move to"}
|
||||
i/organize]
|
||||
[:span.copy.tooltip.tooltip-top
|
||||
{:alt "Duplicate"}
|
||||
i/copy]
|
||||
[:span.delete.tooltip.tooltip-top
|
||||
{:alt "Delete"}
|
||||
i/trash]]]]))
|
||||
(when (not (empty? (:selected @local)))
|
||||
;; MULTISELECT OPTIONS BAR
|
||||
[:div.multiselect-bar
|
||||
[:div.multiselect-nav
|
||||
[:span.move-item.tooltip.tooltip-top
|
||||
{:alt "Copy to"}
|
||||
i/organize]
|
||||
(if own?
|
||||
[:span.copy.tooltip.tooltip-top
|
||||
{:alt "Duplicate"}
|
||||
i/copy])
|
||||
(if own?
|
||||
[:span.delete.tooltip.tooltip-top
|
||||
{:alt "Delete"
|
||||
:on-click #(delete-selected-images)}
|
||||
i/trash])]])]]))))
|
||||
|
||||
(def ^:const ^:private grid
|
||||
(mx/component
|
||||
{:render grid-render
|
||||
:name "grid"
|
||||
:mixins [mx/static]}))
|
||||
:mixins [(rum/local {:selected #{}})
|
||||
mx/static
|
||||
rum/reactive]}))
|
||||
|
||||
;; --- Sort Widget
|
||||
|
||||
(defn sort-widget-render
|
||||
[]
|
||||
(let [ordering (rum/react images-ordering-l)
|
||||
on-change #(rs/emit! (di/set-images-ordering
|
||||
(keyword (.-value (.-target %)))))]
|
||||
(html
|
||||
[:div
|
||||
[:span (tr "ds.project-ordering")]
|
||||
[:select.input-select
|
||||
{:on-change on-change
|
||||
:value (:name ordering)}
|
||||
(for [option (keys +ordering-options+)
|
||||
:let [option-id (get +ordering-options+ option)
|
||||
option-value (:name option)
|
||||
option-text (tr option-id)]]
|
||||
[:option
|
||||
{:key option-id
|
||||
:value option-value}
|
||||
option-text])]])))
|
||||
|
||||
(def ^:private sort-widget
|
||||
(mx/component
|
||||
{:render sort-widget-render
|
||||
:name "sort-widget-render"
|
||||
:mixins [rum/reactive mx/static]}))
|
||||
|
||||
;; --- Filtering Widget
|
||||
|
||||
(defn search-widget-render
|
||||
[]
|
||||
(letfn [(on-term-change [event]
|
||||
(-> (dom/get-target event)
|
||||
(dom/get-value)
|
||||
(di/set-images-filtering)
|
||||
(rs/emit!)))
|
||||
(on-clear [event]
|
||||
(rs/emit! (di/clear-images-filtering)))]
|
||||
(html
|
||||
[:form.dashboard-search
|
||||
[:input.input-text
|
||||
{:key :images-search-box
|
||||
:type "text"
|
||||
:on-change on-term-change
|
||||
:auto-focus true
|
||||
:placeholder (tr "ds.project-search.placeholder")
|
||||
:value (rum/react images-filtering-l)}]
|
||||
[:div.clear-search
|
||||
{:on-click on-clear}
|
||||
i/close]])))
|
||||
|
||||
(def ^:private search-widget
|
||||
(mx/component
|
||||
{:render search-widget-render
|
||||
:name "search-widget"
|
||||
:mixins [rum/reactive mx/static]}))
|
||||
|
||||
|
||||
;; --- Menu
|
||||
|
||||
(defn menu-render
|
||||
[]
|
||||
(let [dashboard (rum/react dashboard-l)
|
||||
coll-id (:collection-id dashboard)
|
||||
coll (rum/react (focus-collection coll-id))
|
||||
icount (count (:images coll)) ]
|
||||
(html
|
||||
[:section.dashboard-bar.library-gap
|
||||
[:div.dashboard-info
|
||||
[:span.dashboard-images (tr "ds.num-images" (t/c icount))]
|
||||
(sort-widget)
|
||||
(search-widget)]])))
|
||||
|
||||
(def menu
|
||||
(mx/component
|
||||
{:render menu-render
|
||||
:name "menu"
|
||||
:mixins [rum/reactive mx/static]}))
|
||||
|
||||
|
||||
;; --- Images Page
|
||||
|
||||
|
@ -127,14 +311,14 @@
|
|||
[:main.dashboard-main
|
||||
(header)
|
||||
[:section.dashboard-content
|
||||
(ui.library-bar/library-bar)
|
||||
[:section.dashboard-grid.library
|
||||
(page-title)
|
||||
(grid)]]]))
|
||||
(nav)
|
||||
(menu)
|
||||
(grid)]]))
|
||||
|
||||
(defn images-page-will-mount
|
||||
[own]
|
||||
(rs/emit! (dd/initialize :dashboard/images))
|
||||
(rs/emit! (dd/initialize :dashboard/images)
|
||||
(di/initialize))
|
||||
own)
|
||||
|
||||
(defn images-page-transfer-state
|
||||
|
|
|
@ -50,6 +50,16 @@
|
|||
[node]
|
||||
(.-value node))
|
||||
|
||||
(defn click
|
||||
"Click a node"
|
||||
[node]
|
||||
(.click node))
|
||||
|
||||
(defn get-files
|
||||
"Extract the files from dom node."
|
||||
[node]
|
||||
(.-files node))
|
||||
|
||||
(defn checked?
|
||||
"Check if the node that reprsents a radio
|
||||
or checkbox is checked or not."
|
||||
|
@ -59,3 +69,8 @@
|
|||
(defn ^boolean equals?
|
||||
[node-a node-b]
|
||||
(.isEqualNode node-a node-b))
|
||||
|
||||
(defn get-event-files
|
||||
"Extract the files from event instance."
|
||||
[event]
|
||||
(get-files (get-target event)))
|
||||
|
|
Loading…
Add table
Reference in a new issue