mirror of
https://github.com/penpot/penpot.git
synced 2025-05-25 15:56:11 +02:00
♻️ Refactor images storage.
This commit is contained in:
parent
b98d8519d4
commit
2cebbbc2f8
34 changed files with 2032 additions and 1630 deletions
|
@ -10,12 +10,13 @@
|
|||
[beicon.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.common.spec :as us]
|
||||
[uxbox.common.data :as d]
|
||||
[uxbox.main.repo :as rp]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.util.data :refer (jscoll->vec)]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.files :as files]
|
||||
[uxbox.util.i18n :refer [tr]]
|
||||
[uxbox.util.webapi :as wapi]
|
||||
[uxbox.util.i18n :as i18n :refer [t tr]]
|
||||
[uxbox.util.router :as r]
|
||||
[uxbox.util.uuid :as uuid]))
|
||||
|
||||
|
@ -24,14 +25,7 @@
|
|||
(s/def ::created-at inst?)
|
||||
(s/def ::modified-at inst?)
|
||||
(s/def ::user-id uuid?)
|
||||
|
||||
;; (s/def ::collection-id (s/nilable ::us/uuid))
|
||||
|
||||
;; (s/def ::mimetype string?)
|
||||
;; (s/def ::thumbnail us/url-str?)
|
||||
;; (s/def ::width number?)
|
||||
;; (s/def ::height number?)
|
||||
;; (s/def ::url us/url-str?)
|
||||
(s/def ::collection-id ::us/uuid)
|
||||
|
||||
(s/def ::collection
|
||||
(s/keys :req-un [::id
|
||||
|
@ -40,6 +34,32 @@
|
|||
::modified-at
|
||||
::user-id]))
|
||||
|
||||
|
||||
(declare fetch-icons)
|
||||
|
||||
(defn initialize
|
||||
[collection-id]
|
||||
(s/assert ::us/uuid collection-id)
|
||||
(ptk/reify ::initialize
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:dashboard-icons :selected] #{}))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/of (fetch-icons collection-id)))))
|
||||
|
||||
;; --- Fetch Collections
|
||||
|
||||
(declare collections-fetched)
|
||||
|
||||
(def fetch-collections
|
||||
(ptk/reify ::fetch-collections
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(->> (rp/query! :icons-collections)
|
||||
(rx/map collections-fetched)))))
|
||||
|
||||
;; --- Collections Fetched
|
||||
|
||||
(defn collections-fetched
|
||||
|
@ -58,14 +78,20 @@
|
|||
state
|
||||
items))))
|
||||
|
||||
;; --- Fetch Collections
|
||||
|
||||
(def fetch-collections
|
||||
(ptk/reify ::fetch-collections
|
||||
;; --- Create Collection
|
||||
|
||||
(declare collection-created)
|
||||
|
||||
(def create-collection
|
||||
(ptk/reify ::create-collection
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(->> (rp/query! :icons-collections)
|
||||
(rx/map collections-fetched)))))
|
||||
(let [name (tr "ds.default-library-title" (gensym "c"))
|
||||
data {:name name}]
|
||||
(->> (rp/mutation! :create-icons-collection data)
|
||||
(rx/map collection-created))))))
|
||||
|
||||
|
||||
;; --- Collection Created
|
||||
|
||||
|
@ -78,70 +104,35 @@
|
|||
(let [{:keys [id] :as item} (assoc item :type :own)]
|
||||
(update state :icons-collections assoc id item)))))
|
||||
|
||||
;; --- Create Collection
|
||||
|
||||
(def create-collection
|
||||
(ptk/reify ::create-collection
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(let [name (tr "ds.default-library-title" (gensym "c"))
|
||||
data {:name name}]
|
||||
(->> (rp/mutation! :create-icons-collection data)
|
||||
(rx/map collection-created))))))
|
||||
|
||||
;; --- Collection Updated
|
||||
|
||||
(defn collection-updated
|
||||
[item]
|
||||
(ptk/reify ::collection-updated
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:icons-collections (:id item)] merge item))))
|
||||
|
||||
;; --- Update Collection
|
||||
|
||||
(defrecord UpdateCollection [id]
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(let [data (get-in state [:icons-collections id])]
|
||||
(->> (rp/mutation! :update-icons-collection data)
|
||||
(rx/map collection-updated)))))
|
||||
|
||||
(defn update-collection
|
||||
[id]
|
||||
(UpdateCollection. id))
|
||||
|
||||
;; --- Rename Collection
|
||||
|
||||
(defrecord RenameCollection [id name]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:icons-collections id :name] name))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(rx/of (update-collection id))))
|
||||
|
||||
(defn rename-collection
|
||||
[id name]
|
||||
(RenameCollection. id name))
|
||||
(ptk/reify ::rename-collection
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:icons-collections id :name] name))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(let [params {:id id :name name}]
|
||||
(->> (rp/mutation! :rename-icons-collection params)
|
||||
(rx/ignore))))))
|
||||
|
||||
;; --- Delete Collection
|
||||
|
||||
(defrecord DeleteCollection [id]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :icons-collections dissoc id))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(let [type (get-in state [:dashboard :icons :type])]
|
||||
(->> (rp/mutation! :delete-icons-collection {:id id})
|
||||
(rx/map #(r/nav :dashboard-icons {:type type}))))))
|
||||
|
||||
(defn delete-collection
|
||||
[id]
|
||||
(DeleteCollection. id))
|
||||
[id on-success]
|
||||
(ptk/reify ::delete-collection
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :icons-collections dissoc id))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(->> (rp/mutation! :delete-icons-collection {:id id})
|
||||
(rx/tap on-success)
|
||||
(rx/ignore)))))
|
||||
|
||||
;; --- Icon Created
|
||||
|
||||
|
@ -157,9 +148,11 @@
|
|||
|
||||
;; --- Create Icon
|
||||
|
||||
(declare icon-created)
|
||||
|
||||
(defn- parse-svg
|
||||
[data]
|
||||
{:pre [(string? data)]}
|
||||
(s/assert ::us/string data)
|
||||
(let [valid-tags #{"defs" "path" "circle" "rect" "metadata" "g"
|
||||
"radialGradient" "stop"}
|
||||
div (dom/create-element "div")
|
||||
|
@ -194,7 +187,7 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(letfn [(parse [file]
|
||||
(->> (files/read-as-text file)
|
||||
(->> (wapi/read-file-as-text file)
|
||||
(rx/map parse-svg)))
|
||||
(allowed? [file]
|
||||
(= (.-type file) "image/svg+xml"))
|
||||
|
@ -207,7 +200,7 @@
|
|||
:metadata metadata})]
|
||||
(->> (rx/from files)
|
||||
(rx/filter allowed?)
|
||||
(rx/flat-map parse)
|
||||
(rx/merge-map parse)
|
||||
(rx/map prepare)
|
||||
(rx/flat-map #(rp/mutation! :create-icon %))
|
||||
(rx/map icon-created))))))
|
||||
|
@ -226,184 +219,158 @@
|
|||
|
||||
;; --- Persist Icon
|
||||
|
||||
(defrecord PersistIcon [id]
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [data (get-in state [:icons id])]
|
||||
(->> (rp/mutation! :update-icon data)
|
||||
(rx/map icon-persisted)))))
|
||||
|
||||
(defn persist-icon
|
||||
[id]
|
||||
{:pre [(uuid? id)]}
|
||||
(PersistIcon. id))
|
||||
|
||||
;; --- Icons Fetched
|
||||
|
||||
(defrecord IconsFetched [items]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(reduce (fn [state {:keys [id] :as icon}]
|
||||
(let [icon (assoc icon :type :icon)]
|
||||
(assoc-in state [:icons id] icon)))
|
||||
state
|
||||
items)))
|
||||
|
||||
(defn icons-fetched
|
||||
[items]
|
||||
(IconsFetched. items))
|
||||
(s/assert ::us/uuid id)
|
||||
(ptk/reify ::persist-icon
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [data (get-in state [:icons id])]
|
||||
(->> (rp/mutation! :update-icon data)
|
||||
(rx/ignore))))))
|
||||
|
||||
;; --- Load Icons
|
||||
|
||||
(defrecord FetchIcons [id]
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(let [params (cond-> {} id (assoc :collection-id id))]
|
||||
(->> (rp/query! :icons-by-collection params)
|
||||
(rx/map icons-fetched)))))
|
||||
(declare icons-fetched)
|
||||
|
||||
(defn fetch-icons
|
||||
[id]
|
||||
{:pre [(or (uuid? id) (nil? id))]}
|
||||
(FetchIcons. id))
|
||||
(ptk/reify ::fetch-icons
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(let [params (cond-> {} id (assoc :collection-id id))]
|
||||
(->> (rp/query! :icons-by-collection params)
|
||||
(rx/map icons-fetched))))))
|
||||
|
||||
;; --- Delete Icons
|
||||
;; --- Icons Fetched
|
||||
|
||||
(defrecord DeleteIcon [id]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(update :icons dissoc id)
|
||||
(update-in [:dashboard :icons :selected] disj id)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(->> (rp/mutation! :delete-icon {:id id})
|
||||
(rx/ignore))))
|
||||
|
||||
(defn delete-icon
|
||||
[id]
|
||||
{:pre [(uuid? id)]}
|
||||
(DeleteIcon. id))
|
||||
(defn icons-fetched
|
||||
[items]
|
||||
;; TODO: specs
|
||||
(ptk/reify ::icons-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [icons (d/index-by :id items)]
|
||||
(assoc state :icons icons)))))
|
||||
|
||||
;; --- Rename Icon
|
||||
|
||||
(defrecord RenameIcon [id name]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:icons id :name] name))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/of (persist-icon id))))
|
||||
|
||||
(defn rename-icon
|
||||
[id name]
|
||||
{:pre [(uuid? id) (string? name)]}
|
||||
(RenameIcon. id name))
|
||||
(s/assert ::us/uuid id)
|
||||
(s/assert ::us/string name)
|
||||
(ptk/reify ::rename-icon
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:icons id :name] name))
|
||||
|
||||
;; --- Select icon
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/of (persist-icon id)))))
|
||||
|
||||
(defrecord SelectIcon [id]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:dashboard :icons :selected] conj id)))
|
||||
;; --- Icon Selection
|
||||
|
||||
(defrecord DeselectIcon [id]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:dashboard :icons :selected] disj id)))
|
||||
|
||||
(defrecord ToggleIconSelection [id]
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [selected (get-in state [:dashboard :icons :selected])]
|
||||
(rx/of
|
||||
(if (selected id)
|
||||
(DeselectIcon. id)
|
||||
(SelectIcon. id))))))
|
||||
(defn select-icon
|
||||
[id]
|
||||
(ptk/reify ::select-icon
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:dashboard-icons :selected] (fnil conj #{}) id))))
|
||||
|
||||
(defn deselect-icon
|
||||
[id]
|
||||
{:pre [(uuid? id)]}
|
||||
(DeselectIcon. id))
|
||||
(ptk/reify ::deselect-icon
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:dashboard-icons :selected] (fnil disj #{}) id))))
|
||||
|
||||
(defn toggle-icon-selection
|
||||
(def deselect-all-icons
|
||||
(ptk/reify ::deselect-all-icons
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:dashboard-icons :selected] #{}))))
|
||||
|
||||
;; --- Delete Icons
|
||||
|
||||
(defn delete-icon
|
||||
[id]
|
||||
(ToggleIconSelection. id))
|
||||
(ptk/reify ::delete-icon
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :icons dissoc id))
|
||||
|
||||
;; --- Copy Selected Icon
|
||||
|
||||
(defrecord CopySelected [id]
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [selected (get-in state [:dashboard :icons :selected])]
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(rx/merge
|
||||
(->> (rx/from selected)
|
||||
(rx/map #(get-in state [:icons %]))
|
||||
(rx/map #(dissoc % :id))
|
||||
(rx/map #(assoc % :collection-id id))
|
||||
(rx/flat-map #(rp/mutation :create-icon %))
|
||||
(rx/map :payload)
|
||||
(rx/map icon-created))
|
||||
(->> (rx/from selected)
|
||||
(rx/map deselect-icon))))))
|
||||
|
||||
(defn copy-selected
|
||||
[id]
|
||||
{:pre [(or (uuid? id) (nil? id))]}
|
||||
(CopySelected. id))
|
||||
|
||||
;; --- Move Selected Icon
|
||||
|
||||
(defrecord MoveSelected [id]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [selected (get-in state [:dashboard :icons :selected])]
|
||||
(reduce (fn [state icon]
|
||||
(assoc-in state [:icons icon :collection] id))
|
||||
state
|
||||
selected)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [selected (get-in state [:dashboard :icons :selected])]
|
||||
(rx/merge
|
||||
(->> (rx/from selected)
|
||||
(rx/map persist-icon))
|
||||
(->> (rx/from selected)
|
||||
(rx/map deselect-icon))))))
|
||||
|
||||
(defn move-selected
|
||||
[id]
|
||||
{:pre [(or (uuid? id) (nil? id))]}
|
||||
(MoveSelected. id))
|
||||
(rx/of deselect-all-icons)
|
||||
(->> (rp/mutation! :delete-icon {:id id})
|
||||
(rx/ignore))))))
|
||||
|
||||
;; --- Delete Selected
|
||||
|
||||
(defrecord DeleteSelected []
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [selected (get-in state [:dashboard :icons :selected])]
|
||||
(->> (rx/from selected)
|
||||
(rx/map delete-icon)))))
|
||||
|
||||
(defn delete-selected
|
||||
[]
|
||||
(DeleteSelected.))
|
||||
|
||||
(def delete-selected
|
||||
(ptk/reify ::delete-selected
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [selected (get-in state [:dashboard-icons :selected])]
|
||||
(->> (rx/from selected)
|
||||
(rx/map delete-icon))))))
|
||||
;; --- Update Opts (Filtering & Ordering)
|
||||
|
||||
(defrecord UpdateOpts [order filter edition]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:dashboard :icons] merge
|
||||
{:edition edition}
|
||||
(when order {:order order})
|
||||
(when filter {:filter filter}))))
|
||||
|
||||
(defn update-opts
|
||||
[& {:keys [order filter edition]
|
||||
:or {edition false}
|
||||
:as opts}]
|
||||
(UpdateOpts. order filter edition))
|
||||
:or {edition false}}]
|
||||
(ptk/reify ::update-opts
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :dashboard-icons merge
|
||||
{:edition edition}
|
||||
(when order {:order order})
|
||||
(when filter {:filter filter})))))
|
||||
|
||||
;; --- Copy Selected Icon
|
||||
|
||||
;; (defrecord CopySelected [id]
|
||||
;; ptk/WatchEvent
|
||||
;; (watch [_ state stream]
|
||||
;; (let [selected (get-in state [:dashboard :icons :selected])]
|
||||
;; (rx/merge
|
||||
;; (->> (rx/from selected)
|
||||
;; (rx/map #(get-in state [:icons %]))
|
||||
;; (rx/map #(dissoc % :id))
|
||||
;; (rx/map #(assoc % :collection-id id))
|
||||
;; (rx/flat-map #(rp/mutation :create-icon %))
|
||||
;; (rx/map :payload)
|
||||
;; (rx/map icon-created))
|
||||
;; (->> (rx/from selected)
|
||||
;; (rx/map deselect-icon))))))
|
||||
|
||||
;; (defn copy-selected
|
||||
;; [id]
|
||||
;; {:pre [(or (uuid? id) (nil? id))]}
|
||||
;; (CopySelected. id))
|
||||
|
||||
;; --- Move Selected Icon
|
||||
|
||||
;; (defrecord MoveSelected [id]
|
||||
;; ptk/UpdateEvent
|
||||
;; (update [_ state]
|
||||
;; (let [selected (get-in state [:dashboard :icons :selected])]
|
||||
;; (reduce (fn [state icon]
|
||||
;; (assoc-in state [:icons icon :collection] id))
|
||||
;; state
|
||||
;; selected)))
|
||||
|
||||
;; ptk/WatchEvent
|
||||
;; (watch [_ state stream]
|
||||
;; (let [selected (get-in state [:dashboard :icons :selected])]
|
||||
;; (rx/merge
|
||||
;; (->> (rx/from selected)
|
||||
;; (rx/map persist-icon))
|
||||
;; (->> (rx/from selected)
|
||||
;; (rx/map deselect-icon))))))
|
||||
|
||||
;; (defn move-selected
|
||||
;; [id]
|
||||
;; {:pre [(or (uuid? id) (nil? id))]}
|
||||
;; (MoveSelected. id))
|
||||
|
|
|
@ -11,11 +11,11 @@
|
|||
[beicon.core :as rx]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.common.spec :as us]
|
||||
[uxbox.common.data :as d]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.repo :as rp]
|
||||
[uxbox.util.i18n :refer [tr]]
|
||||
[uxbox.util.router :as rt]
|
||||
[uxbox.util.data :refer (jscoll->vec)]
|
||||
[uxbox.util.uuid :as uuid]
|
||||
[uxbox.util.time :as ts]
|
||||
[uxbox.util.router :as r]
|
||||
|
@ -28,38 +28,66 @@
|
|||
(s/def ::height number?)
|
||||
(s/def ::modified-at inst?)
|
||||
(s/def ::created-at inst?)
|
||||
(s/def ::mimetype string?)
|
||||
(s/def ::mtype string?)
|
||||
(s/def ::thumbnail string?)
|
||||
(s/def ::id uuid?)
|
||||
(s/def ::url string?)
|
||||
(s/def ::collection-id (s/nilable uuid?))
|
||||
(s/def ::collection-id uuid?)
|
||||
(s/def ::user-id uuid?)
|
||||
|
||||
(s/def ::collection-entity
|
||||
(s/def ::collection
|
||||
(s/keys :req-un [::id
|
||||
::name
|
||||
::created-at
|
||||
::modified-at
|
||||
::user-id]))
|
||||
|
||||
(s/def ::image-entity
|
||||
(s/keys :opt-un [::collection-id]
|
||||
:req-un [::id
|
||||
(s/def ::image
|
||||
(s/keys :req-un [::id
|
||||
::name
|
||||
::width
|
||||
::height
|
||||
::mtype
|
||||
::collection-id
|
||||
::created-at
|
||||
::modified-at
|
||||
::mimetype
|
||||
::thumbnail
|
||||
::url
|
||||
::uri
|
||||
::thumb-uri
|
||||
::user-id]))
|
||||
|
||||
;; --- Initialize Collection Page
|
||||
|
||||
(declare fetch-images)
|
||||
|
||||
(defn initialize
|
||||
[collection-id]
|
||||
(us/verify ::us/uuid collection-id)
|
||||
(ptk/reify ::initialize
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:dashboard-images :selected] #{}))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/of (fetch-images collection-id)))))
|
||||
|
||||
;; --- Fetch Collections
|
||||
|
||||
(declare collections-fetched)
|
||||
|
||||
(def fetch-collections
|
||||
(ptk/reify ::fetch-collections
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(->> (rp/query! :images-collections)
|
||||
(rx/map collections-fetched)))))
|
||||
|
||||
|
||||
;; --- Collections Fetched
|
||||
|
||||
(defn collections-fetched
|
||||
[items]
|
||||
(us/verify (s/every ::collection-entity) items)
|
||||
(us/verify (s/every ::collection) items)
|
||||
(ptk/reify ::collections-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
|
@ -70,105 +98,63 @@
|
|||
state
|
||||
items))))
|
||||
|
||||
;; --- Fetch Color Collections
|
||||
|
||||
(def fetch-collections
|
||||
(ptk/reify ::fetch-collections
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(->> (rp/query! :images-collections)
|
||||
(rx/map collections-fetched)))))
|
||||
|
||||
;; --- Collection Created
|
||||
|
||||
(defn collection-created
|
||||
[item]
|
||||
(us/verify ::collection-entity item)
|
||||
(ptk/reify ::collection-created
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [{:keys [id] :as item} (assoc item :type :own)]
|
||||
(update state :images-collections assoc id item)))))
|
||||
|
||||
;; --- Create Collection
|
||||
|
||||
(declare collection-created)
|
||||
|
||||
(def create-collection
|
||||
(ptk/reify ::create-collection
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(let [data {:name (tr "ds.default-library-title" (gensym "c"))}]
|
||||
(->> (rp/mutation! :create-image-collection data)
|
||||
(->> (rp/mutation! :create-images-collection data)
|
||||
(rx/map collection-created))))))
|
||||
|
||||
;; --- Collection Updated
|
||||
;; --- Collection Created
|
||||
|
||||
(defrecord CollectionUpdated [item]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:images-collections (:id item)] merge item)))
|
||||
|
||||
(defn collection-updated
|
||||
(defn collection-created
|
||||
[item]
|
||||
(us/verify ::collection-entity item)
|
||||
(CollectionUpdated. item))
|
||||
|
||||
;; --- Update Collection
|
||||
|
||||
(defrecord UpdateCollection [id]
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(let [item (get-in state [:images-collections id])]
|
||||
(->> (rp/mutation! :update-images-collection item)
|
||||
(rx/map collection-updated)))))
|
||||
|
||||
(defn update-collection
|
||||
[id]
|
||||
(UpdateCollection. id))
|
||||
(us/verify ::collection item)
|
||||
(ptk/reify ::collection-created
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [{:keys [id] :as item} (assoc item :type :own)]
|
||||
(update state :images-collections assoc id item)))))
|
||||
|
||||
;; --- Rename Collection
|
||||
|
||||
(defrecord RenameCollection [id name]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:images-collections id :name] name))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(rx/of (update-collection id))))
|
||||
|
||||
(defn rename-collection
|
||||
[id name]
|
||||
(RenameCollection. id name))
|
||||
(ptk/reify ::rename-collection
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:images-collections id :name] name))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(let [params {:id id :name name}]
|
||||
(->> (rp/mutation! :rename-images-collection params)
|
||||
(rx/ignore))))))
|
||||
|
||||
;; --- Delete Collection
|
||||
|
||||
(defrecord DeleteCollection [id]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :images-collections dissoc id))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(let [type (get-in state [:dashboard :images :type])]
|
||||
(->> (rp/mutation! :delete-images-collection {:id id})
|
||||
(rx/map #(rt/nav :dashboard/images nil {:type type}))))))
|
||||
|
||||
(defn delete-collection
|
||||
[id]
|
||||
(DeleteCollection. id))
|
||||
|
||||
;; --- Image Created
|
||||
|
||||
(defn image-created
|
||||
[item]
|
||||
(us/verify ::image-entity item)
|
||||
(ptk/reify ::image-created
|
||||
[id on-success]
|
||||
(ptk/reify ::delete-collection
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :images assoc (:id item) item))))
|
||||
(update state :images-collections dissoc id))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(->> (rp/mutation! :delete-images-collection {:id id})
|
||||
(rx/tap on-success)
|
||||
(rx/ignore)))))
|
||||
|
||||
;; --- Create Image
|
||||
|
||||
(declare image-created)
|
||||
(def allowed-file-types #{"image/jpeg" "image/png"})
|
||||
|
||||
(defn create-images
|
||||
|
@ -179,41 +165,49 @@
|
|||
(ptk/reify ::create-images
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:dashboard :images :uploading] true))
|
||||
(assoc-in state [:dashboard-images :uploading] true))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(letfn [(image-size [file]
|
||||
(->> (files/get-image-size file)
|
||||
(rx/map (partial vector file))))
|
||||
(allowed-file? [file]
|
||||
(letfn [(allowed-file? [file]
|
||||
(contains? allowed-file-types (.-type file)))
|
||||
(finalize-upload [state]
|
||||
(assoc-in state [:dashboard :images :uploading] false))
|
||||
(prepare [[file [width height]]]
|
||||
(cond-> {:name (.-name file)
|
||||
:mimetype (.-type file)
|
||||
:id (uuid/next)
|
||||
:file file
|
||||
:width width
|
||||
:height height}
|
||||
id (assoc :collection-id id)))]
|
||||
(assoc-in state [:dashboard-images :uploading] false))
|
||||
(on-success [_]
|
||||
(st/emit! finalize-upload)
|
||||
(on-uploaded))
|
||||
(on-error [e]
|
||||
(st/emit! finalize-upload)
|
||||
(rx/throw e))
|
||||
(prepare [file]
|
||||
{:name (.-name file)
|
||||
:collection-id id
|
||||
:content file})]
|
||||
(->> (rx/from files)
|
||||
(rx/filter allowed-file?)
|
||||
(rx/mapcat image-size)
|
||||
(rx/map prepare)
|
||||
(rx/mapcat #(rp/mutation! :create-image %))
|
||||
(rx/mapcat #(rp/mutation! :upload-image %))
|
||||
(rx/reduce conj [])
|
||||
(rx/do #(st/emit! finalize-upload))
|
||||
(rx/do on-uploaded)
|
||||
(rx/do on-success)
|
||||
(rx/mapcat identity)
|
||||
(rx/map image-created)))))))
|
||||
(rx/map image-created)
|
||||
(rx/catch on-error)))))))
|
||||
|
||||
;; --- Image Created
|
||||
|
||||
(defn image-created
|
||||
[item]
|
||||
(us/verify ::image item)
|
||||
(ptk/reify ::image-created
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :images assoc (:id item) item))))
|
||||
|
||||
;; --- Update Image
|
||||
|
||||
(defn persist-image
|
||||
[id]
|
||||
{:pre [(uuid? id)]}
|
||||
(us/verify ::us/uuid id)
|
||||
(ptk/reify ::persist-image
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
|
@ -221,31 +215,34 @@
|
|||
(->> (rp/mutation! :update-image data)
|
||||
(rx/ignore))))))
|
||||
|
||||
;; --- Images Fetched
|
||||
|
||||
(defn images-fetched
|
||||
[items]
|
||||
(us/verify (s/every ::image-entity) items)
|
||||
(ptk/reify ::images-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(reduce (fn [state {:keys [id] :as image}]
|
||||
(assoc-in state [:images id] image))
|
||||
state
|
||||
items))))
|
||||
|
||||
;; --- Fetch Images
|
||||
|
||||
(declare images-fetched)
|
||||
|
||||
(defn fetch-images
|
||||
"Fetch a list of images of the selected collection"
|
||||
[id]
|
||||
(us/verify (s/nilable ::us/uuid) id)
|
||||
(us/verify ::us/uuid id)
|
||||
(ptk/reify ::fetch-images
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(let [params (cond-> {} id (assoc :collection-id id))]
|
||||
(let [params {:collection-id id}]
|
||||
(->> (rp/query! :images-by-collection params)
|
||||
(rx/map images-fetched))))))
|
||||
(rx/map (partial images-fetched id)))))))
|
||||
|
||||
;; --- Images Fetched
|
||||
|
||||
(s/def ::images (s/every ::image))
|
||||
|
||||
(defn images-fetched
|
||||
[collection-id items]
|
||||
(us/verify ::us/uuid collection-id)
|
||||
(us/verify ::images items)
|
||||
(ptk/reify ::images-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [images (d/index-by :id items)]
|
||||
(assoc state :images images)))))
|
||||
|
||||
;; --- Fetch Image
|
||||
|
||||
|
@ -281,139 +278,123 @@
|
|||
{:pre [(map? image)]}
|
||||
(ImageFetched. image))
|
||||
|
||||
;; --- Delete Images
|
||||
|
||||
(defrecord DeleteImage [id]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(update :images dissoc id)
|
||||
(update-in [:dashboard :images :selected] disj id)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(->> (rp/mutation! :delete-image {:id id})
|
||||
(rx/ignore))))
|
||||
|
||||
(defn delete-image
|
||||
[id]
|
||||
{:pre [(uuid? id)]}
|
||||
(DeleteImage. id))
|
||||
|
||||
;; --- Rename Image
|
||||
|
||||
(defrecord RenameImage [id name]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:images id :name] name))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/of (persist-image id))))
|
||||
|
||||
(defn rename-image
|
||||
[id name]
|
||||
{:pre [(uuid? id) (string? name)]}
|
||||
(RenameImage. id name))
|
||||
(us/verify ::us/uuid id)
|
||||
(us/verify ::us/string name)
|
||||
(ptk/reify ::rename-image
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:images id :name] name))
|
||||
|
||||
;; --- Select image
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/of (persist-image id)))))
|
||||
|
||||
(defrecord SelectImage [id]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:dashboard :images :selected] conj id)))
|
||||
;; --- Image Selection
|
||||
|
||||
(defrecord DeselectImage [id]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:dashboard :images :selected] disj id)))
|
||||
|
||||
(defrecord ToggleImageSelection [id]
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [selected (get-in state [:dashboard :images :selected])]
|
||||
(rx/of
|
||||
(if (selected id)
|
||||
(DeselectImage. id)
|
||||
(SelectImage. id))))))
|
||||
(defn select-image
|
||||
[id]
|
||||
(ptk/reify ::select-image
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:dashboard-images :selected] (fnil conj #{}) id))))
|
||||
|
||||
(defn deselect-image
|
||||
[id]
|
||||
{:pre [(uuid? id)]}
|
||||
(DeselectImage. id))
|
||||
(ptk/reify ::deselect-image
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:dashboard-images :selected] (fnil disj #{}) id))))
|
||||
|
||||
(defn toggle-image-selection
|
||||
(def deselect-all-images
|
||||
(ptk/reify ::deselect-all-images
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:dashboard-images :selected] #{}))))
|
||||
|
||||
;; --- Delete Images
|
||||
|
||||
(defn delete-image
|
||||
[id]
|
||||
(ToggleImageSelection. id))
|
||||
(us/verify ::us/uuid id)
|
||||
(ptk/reify ::delete-image
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :images dissoc id))
|
||||
|
||||
;; --- Copy Selected Image
|
||||
|
||||
(defrecord CopySelected [id]
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [selected (get-in state [:dashboard :images :selected])]
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(rx/merge
|
||||
(->> (rx/from selected)
|
||||
(rx/flat-map #(rp/mutation! :copy-image {:id % :collection-id id}))
|
||||
(rx/map image-created))
|
||||
(->> (rx/from selected)
|
||||
(rx/map deselect-image))))))
|
||||
|
||||
(defn copy-selected
|
||||
[id]
|
||||
{:pre [(or (uuid? id) (nil? id))]}
|
||||
(CopySelected. id))
|
||||
|
||||
;; --- Move Selected Image
|
||||
|
||||
(defrecord MoveSelected [id]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [selected (get-in state [:dashboard :images :selected])]
|
||||
(reduce (fn [state image]
|
||||
(assoc-in state [:images image :collection] id))
|
||||
state
|
||||
selected)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [selected (get-in state [:dashboard :images :selected])]
|
||||
(rx/merge
|
||||
(->> (rx/from selected)
|
||||
(rx/map persist-image))
|
||||
(->> (rx/from selected)
|
||||
(rx/map deselect-image))))))
|
||||
|
||||
(defn move-selected
|
||||
[id]
|
||||
{:pre [(or (uuid? id) (nil? id))]}
|
||||
(MoveSelected. id))
|
||||
(rx/of deselect-all-images)
|
||||
(->> (rp/mutation! :delete-image {:id id})
|
||||
(rx/ignore))))))
|
||||
|
||||
;; --- Delete Selected
|
||||
|
||||
(defrecord DeleteSelected []
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [selected (get-in state [:dashboard :images :selected])]
|
||||
(->> (rx/from selected)
|
||||
(rx/map delete-image)))))
|
||||
|
||||
(defn delete-selected
|
||||
[]
|
||||
(DeleteSelected.))
|
||||
(def delete-selected
|
||||
(ptk/reify ::delete-selected
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [selected (get-in state [:dashboard-images :selected])]
|
||||
(->> (rx/from selected)
|
||||
(rx/map delete-image))))))
|
||||
|
||||
;; --- Update Opts (Filtering & Ordering)
|
||||
|
||||
(defrecord UpdateOpts [order filter edition]
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:dashboard :images] merge
|
||||
{:edition edition}
|
||||
(when order {:order order})
|
||||
(when filter {:filter filter}))))
|
||||
|
||||
(defn update-opts
|
||||
[& {:keys [order filter edition]
|
||||
:or {edition false}
|
||||
:as opts}]
|
||||
(UpdateOpts. order filter edition))
|
||||
:or {edition false}}]
|
||||
(ptk/reify ::update-opts
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :dashboard-images merge
|
||||
{:edition edition}
|
||||
(when order {:order order})
|
||||
(when filter {:filter filter})))))
|
||||
|
||||
;; --- Copy Selected Image
|
||||
|
||||
;; (defrecord CopySelected [id]
|
||||
;; ptk/WatchEvent
|
||||
;; (watch [_ state stream]
|
||||
;; (let [selected (get-in state [:dashboard-images :selected])]
|
||||
;; (rx/merge
|
||||
;; (->> (rx/from selected)
|
||||
;; (rx/flat-map #(rp/mutation! :copy-image {:id % :collection-id id}))
|
||||
;; (rx/map image-created))
|
||||
;; (->> (rx/from selected)
|
||||
;; (rx/map deselect-image))))))
|
||||
|
||||
;; (defn copy-selected
|
||||
;; [id]
|
||||
;; {:pre [(or (uuid? id) (nil? id))]}
|
||||
;; (CopySelected. id))
|
||||
|
||||
;; --- Move Selected Image
|
||||
|
||||
;; (defrecord MoveSelected [id]
|
||||
;; ptk/UpdateEvent
|
||||
;; (update [_ state]
|
||||
;; (let [selected (get-in state [:dashboard-images :selected])]
|
||||
;; (reduce (fn [state image]
|
||||
;; (assoc-in state [:images image :collection] id))
|
||||
;; state
|
||||
;; selected)))
|
||||
|
||||
;; ptk/WatchEvent
|
||||
;; (watch [_ state stream]
|
||||
;; (let [selected (get-in state [:dashboard-images :selected])]
|
||||
;; (rx/merge
|
||||
;; (->> (rx/from selected)
|
||||
;; (rx/map persist-image))
|
||||
;; (->> (rx/from selected)
|
||||
;; (rx/map deselect-image))))))
|
||||
|
||||
;; (defn move-selected
|
||||
;; [id]
|
||||
;; {:pre [(or (uuid? id) (nil? id))]}
|
||||
;; (MoveSelected. id))
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
;; --- Declarations
|
||||
|
||||
(declare fetch-users)
|
||||
(declare fetch-images)
|
||||
(declare handle-who)
|
||||
(declare handle-pointer-update)
|
||||
(declare handle-pointer-send)
|
||||
|
@ -289,7 +290,8 @@
|
|||
(rx/of (dp/fetch-file file-id)
|
||||
(dp/fetch-pages file-id)
|
||||
(initialize-layout file-id)
|
||||
(fetch-users file-id))
|
||||
(fetch-users file-id)
|
||||
(fetch-images file-id))
|
||||
(->> (rx/zip (rx/filter (ptk/type? ::dp/pages-fetched) stream)
|
||||
(rx/filter (ptk/type? ::dp/files-fetched) stream))
|
||||
(rx/take 1)
|
||||
|
@ -399,6 +401,7 @@
|
|||
state
|
||||
users))))
|
||||
|
||||
|
||||
;; --- Toggle layout flag
|
||||
|
||||
(defn toggle-layout-flag
|
||||
|
@ -1303,6 +1306,94 @@
|
|||
query-params {:page-id page-id}]
|
||||
(rx/of (rt/nav :workspace path-params query-params))))))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Workspace Images
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;; --- Fetch Workspace Images
|
||||
|
||||
(declare images-fetched)
|
||||
|
||||
(defn fetch-images
|
||||
[file-id]
|
||||
(ptk/reify ::fetch-images
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(->> (rp/query :project-file-images {:file-id file-id})
|
||||
(rx/map images-fetched)))))
|
||||
|
||||
(defn images-fetched
|
||||
[images]
|
||||
(ptk/reify ::images-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [images (d/index-by :id images)]
|
||||
(assoc state :workspace-images images)))))
|
||||
|
||||
|
||||
;; --- Upload Image
|
||||
|
||||
(declare image-uploaded)
|
||||
(def allowed-file-types #{"image/jpeg" "image/png"})
|
||||
|
||||
(defn upload-image
|
||||
([file] (upload-image file identity))
|
||||
([file on-uploaded]
|
||||
(us/verify fn? on-uploaded)
|
||||
(ptk/reify ::upload-image
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:workspace-local :uploading] true))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [allowed-file? #(contains? allowed-file-types (.-type %))
|
||||
finalize-upload #(assoc-in % [:workspace-local :uploading] false)
|
||||
file-id (get-in state [:workspace-page :file-id])
|
||||
|
||||
on-success #(do (st/emit! finalize-upload)
|
||||
(on-uploaded %))
|
||||
on-error #(do (st/emit! finalize-upload)
|
||||
(rx/throw %))
|
||||
|
||||
prepare
|
||||
(fn [file]
|
||||
{:name (.-name file)
|
||||
:file-id file-id
|
||||
:content file})]
|
||||
(->> (rx/of file)
|
||||
(rx/filter allowed-file?)
|
||||
(rx/map prepare)
|
||||
(rx/mapcat #(rp/mutation! :upload-project-file-image %))
|
||||
(rx/do on-success)
|
||||
(rx/map image-uploaded)
|
||||
(rx/catch on-error)))))))
|
||||
|
||||
(s/def ::id ::us/uuid)
|
||||
(s/def ::name ::us/string)
|
||||
(s/def ::width ::us/number)
|
||||
(s/def ::height ::us/number)
|
||||
(s/def ::mtype ::us/string)
|
||||
(s/def ::uri ::us/string)
|
||||
(s/def ::thumb-uri ::us/string)
|
||||
|
||||
(s/def ::image
|
||||
(s/keys :req-un [::id
|
||||
::name
|
||||
::width
|
||||
::height
|
||||
::uri
|
||||
::thumb-uri]))
|
||||
|
||||
(defn image-uploaded
|
||||
[item]
|
||||
(us/verify ::image item)
|
||||
(ptk/reify ::image-created
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :workspace-images assoc (:id item) item))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Page Changes Reactions
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2017-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.refs
|
||||
|
@ -35,6 +38,10 @@
|
|||
(-> (l/key :workspace-file)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def workspace-images
|
||||
(-> (l/key :workspace-images)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def workspace-users
|
||||
(-> (l/key :workspace-users)
|
||||
(l/derive st/state)))
|
||||
|
|
|
@ -112,7 +112,15 @@
|
|||
([id] (mutation id {}))
|
||||
([id params] (mutation id params)))
|
||||
|
||||
(defmethod mutation :create-image
|
||||
(defmethod mutation :upload-image
|
||||
[id params]
|
||||
(let [form (js/FormData.)]
|
||||
(run! (fn [[key val]]
|
||||
(.append form (name key) val))
|
||||
(seq params))
|
||||
(send-mutation! id form)))
|
||||
|
||||
(defmethod mutation :upload-project-file-image
|
||||
[id params]
|
||||
(let [form (js/FormData.)]
|
||||
(run! (fn [[key val]]
|
||||
|
|
|
@ -7,9 +7,12 @@
|
|||
|
||||
(ns uxbox.main.ui.dashboard.icons
|
||||
(:require
|
||||
[cljs.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[lentes.core :as l]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.common.data :as d]
|
||||
[uxbox.common.spec :as us]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.icons :as di]
|
||||
[uxbox.main.store :as st]
|
||||
|
@ -21,7 +24,7 @@
|
|||
[uxbox.util.components :refer [chunked-list]]
|
||||
[uxbox.util.data :refer [read-string jscoll->vec seek]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.i18n :as t :refer [tr]]
|
||||
[uxbox.util.i18n :as i18n :refer [tr t]]
|
||||
[uxbox.util.router :as rt]
|
||||
[uxbox.util.time :as dt]))
|
||||
|
||||
|
@ -51,111 +54,200 @@
|
|||
icons
|
||||
(filter #(contains-term? (:name %) term) icons)))
|
||||
|
||||
;; --- Refs
|
||||
|
||||
(def collections-iref
|
||||
(-> (l/key :icons-collections)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def opts-iref
|
||||
(-> (l/in [:dashboard :icons])
|
||||
(l/derive st/state)))
|
||||
|
||||
;; --- Component: Grid Header
|
||||
|
||||
(mf/defc grid-header
|
||||
[{:keys [coll] :as props}]
|
||||
(letfn [(on-change [name]
|
||||
(st/emit! (di/rename-collection (:id coll) name)))
|
||||
(delete []
|
||||
(st/emit!
|
||||
(di/delete-collection (:id coll))
|
||||
(rt/nav :dashboard/icons nil {:type (:type coll)})))
|
||||
(on-delete []
|
||||
(modal/show! confirm-dialog {:on-accept delete}))]
|
||||
[:& common/grid-header {:value (:name coll)
|
||||
[{:keys [collection] :as props}]
|
||||
(let [{:keys [id type]} collection
|
||||
on-change #(st/emit! (di/rename-collection id %))
|
||||
on-deleted #(st/emit! (rt/nav :dashboard-icons nil {:type type}))
|
||||
delete #(st/emit! (di/delete-collection id on-deleted))
|
||||
on-delete #(modal/show! confirm-dialog {:on-accept delete})]
|
||||
[:& common/grid-header {:value (:name collection)
|
||||
:on-change on-change
|
||||
:on-delete on-delete}]))
|
||||
|
||||
;; --- Nav
|
||||
|
||||
(mf/defc nav-item
|
||||
[{:keys [coll selected?] :as props}]
|
||||
[{:keys [collection selected?] :as props}]
|
||||
(let [local (mf/use-state {})
|
||||
{:keys [id type name]} coll
|
||||
editable? (= type :own)]
|
||||
(letfn [(on-click [event]
|
||||
(let [type (or type :own)]
|
||||
(st/emit! (rt/nav :dashboard-icons {} {:type type :id id}))))
|
||||
(on-input-change [event]
|
||||
(-> (dom/get-target event)
|
||||
(dom/get-value)
|
||||
(swap! local assoc :name)))
|
||||
(on-cancel [event]
|
||||
(swap! local dissoc :name :edit))
|
||||
(on-double-click [event]
|
||||
(when editable?
|
||||
(swap! local assoc :edit true)))
|
||||
(on-input-keyup [event]
|
||||
(when (kbd/enter? event)
|
||||
(let [value (-> (dom/get-target event) (dom/get-value))]
|
||||
(st/emit! (di/rename-collection id (str/trim (:name @local))))
|
||||
(swap! local assoc :edit false))))]
|
||||
[:li {:on-click on-click
|
||||
:on-double-click on-double-click
|
||||
:class-name (when selected? "current")}
|
||||
(if (:edit @local)
|
||||
[:div
|
||||
[:input.element-title {:value (if (:name @local)
|
||||
(:name @local)
|
||||
(if id name "Storage"))
|
||||
:on-change on-input-change
|
||||
:on-key-down on-input-keyup}]
|
||||
[:span.close {:on-click on-cancel} i/close]]
|
||||
[:span.element-title (if id name "Storage")])])))
|
||||
{:keys [id type name]} collection
|
||||
editable? (= type :own)
|
||||
|
||||
on-click
|
||||
(fn [event]
|
||||
(let [type (or type :own)]
|
||||
(st/emit! (rt/nav :dashboard-icons {} {:type type :id id}))))
|
||||
|
||||
|
||||
on-input-change
|
||||
(fn [event]
|
||||
(-> (dom/get-target event)
|
||||
(dom/get-value)
|
||||
(swap! local assoc :name)))
|
||||
|
||||
on-cancel #(swap! local dissoc :name :edit)
|
||||
on-double-click #(when editable? (swap! local assoc :edit true))
|
||||
|
||||
on-input-keyup
|
||||
(fn [event]
|
||||
(when (kbd/enter? event)
|
||||
(let [value (-> (dom/get-target event) (dom/get-value))]
|
||||
(st/emit! (di/rename-collection id (str/trim (:name @local))))
|
||||
(swap! local assoc :edit false))))]
|
||||
|
||||
[:li {:on-click on-click
|
||||
:on-double-click on-double-click
|
||||
:class-name (when selected? "current")}
|
||||
(if (:edit @local)
|
||||
[:div
|
||||
[:input.element-title {:value (or (:name @local) name)
|
||||
:on-change on-input-change
|
||||
:on-key-down on-input-keyup}]
|
||||
[:span.close {:on-click on-cancel} i/close]]
|
||||
[:span.element-title name])]))
|
||||
|
||||
|
||||
(mf/defc nav
|
||||
[{:keys [id type colls selected-coll] :as props}]
|
||||
(let [own? (= type :own)
|
||||
[{:keys [id type collections] :as props}]
|
||||
(let [locale (i18n/use-locale)
|
||||
own? (= type :own)
|
||||
builtin? (= type :builtin)
|
||||
select-tab #(st/emit! (rt/nav :dashboard-icons nil {:type %}))]
|
||||
create-collection #(st/emit! di/create-collection)
|
||||
select-own-tab #(st/emit! (rt/nav :dashboard-icons nil {:type :own}))
|
||||
select-buitin-tab #(st/emit! (rt/nav :dashboard-icons nil {:type :builtin}))]
|
||||
|
||||
[:div.library-bar
|
||||
[:div.library-bar-inside
|
||||
;; Tabs
|
||||
[:ul.library-tabs
|
||||
[:li {:class-name (when own? "current")
|
||||
:on-click (partial select-tab :own)}
|
||||
(tr "ds.your-icons-title")]
|
||||
[:li {:class-name (when builtin? "current")
|
||||
:on-click (partial select-tab :builtin)}
|
||||
(tr "ds.store-icons-title")]]
|
||||
[:li {:class (when own? "current")
|
||||
:on-click select-own-tab}
|
||||
(t locale "ds.your-icons-title")]
|
||||
|
||||
[:li {:class (when builtin? "current")
|
||||
:on-click select-buitin-tab}
|
||||
(t locale "ds.store-icons-title")]]
|
||||
|
||||
|
||||
;; Collections List
|
||||
[:ul.library-elements
|
||||
(when own?
|
||||
[:li
|
||||
[:a.btn-primary {:on-click #(st/emit! di/create-collection)}
|
||||
(tr "ds.icons-collection.new")]])
|
||||
(when own?
|
||||
[:& nav-item {:selected? (nil? id)}])
|
||||
(for [item colls]
|
||||
[:& nav-item {:coll item
|
||||
(for [item collections]
|
||||
[:& nav-item {:collection item
|
||||
:selected? (= (:id item) id)
|
||||
:key (:id item)}])]]]))
|
||||
|
||||
|
||||
;; --- Grid
|
||||
;; (mf/def grid-options-tooltip
|
||||
;; :mixins [mf/reactive mf/memo]
|
||||
|
||||
;; :render
|
||||
;; (fn [own {:keys [selected on-select title]}]
|
||||
;; {:pre [(uuid? selected)
|
||||
;; (fn? on-select)
|
||||
;; (string? title)]}
|
||||
;; (let [colls (mf/react collections-iref)
|
||||
;; colls (->> (vals colls)
|
||||
;; (filter #(= :own (:type %)))
|
||||
;; (remove #(= selected (:id %)))
|
||||
;; (sort-by :name colls))
|
||||
;; on-select (fn [event id]
|
||||
;; (dom/prevent-default event)
|
||||
;; (dom/stop-propagation event)
|
||||
;; (on-select id))]
|
||||
;; [:ul.move-list
|
||||
;; [:li.title title]
|
||||
;; [:li
|
||||
;; [:a {:href "#" :on-click #(on-select % nil)} "Storage"]]
|
||||
;; (for [{:keys [id name] :as coll} colls]
|
||||
;; [:li {:key (pr-str id)}
|
||||
;; [:a {:on-click #(on-select % id)} name]])])))
|
||||
|
||||
(mf/defc grid-options
|
||||
[{:keys [id type selected] :as props}]
|
||||
(let [local (mf/use-state {})
|
||||
delete #(st/emit! di/delete-selected)
|
||||
on-delete #(modal/show! confirm-dialog {:on-accept delete})
|
||||
|
||||
;; (on-toggle-copy [event]
|
||||
;; (swap! local update :show-copy-tooltip not))
|
||||
;; (on-toggle-move [event]
|
||||
;; (swap! local update :show-move-tooltip not))
|
||||
;; (on-copy [selected]
|
||||
;; (swap! local assoc
|
||||
;; :show-move-tooltip false
|
||||
;; :show-copy-tooltip false)
|
||||
;; (st/emit! (di/copy-selected selected)))
|
||||
;; (on-move [selected]
|
||||
;; (swap! local assoc
|
||||
;; :show-move-tooltip false
|
||||
;; :show-copy-tooltip false)
|
||||
;; (st/emit! (di/move-selected selected)))
|
||||
;; (on-rename [event]
|
||||
;; (let [selected (first selected)]
|
||||
;; (st/emit! (di/update-opts :edition selected))))
|
||||
]
|
||||
;; MULTISELECT OPTIONS BAR
|
||||
[:div.multiselect-bar
|
||||
(when (= type :own)
|
||||
;; If editable
|
||||
[:div.multiselect-nav
|
||||
;; [:span.move-item.tooltip.tooltip-top
|
||||
;; {:alt (tr "ds.multiselect-bar.copy")
|
||||
;; :on-click on-toggle-copy}
|
||||
;; (when (:show-copy-tooltip @local)
|
||||
;; [:& grid-options-tooltip {:selected id
|
||||
;; :title (tr "ds.multiselect-bar.copy-to-library")
|
||||
;; :on-select on-copy}])
|
||||
;; i/copy]
|
||||
;; [:span.move-item.tooltip.tooltip-top
|
||||
;; {:alt (tr "ds.multiselect-bar.move")
|
||||
;; :on-click on-toggle-move}
|
||||
;; (when (:show-move-tooltip @local)
|
||||
;; [:& grid-options-tooltip {:selected id
|
||||
;; :title (tr "ds.multiselect-bar.move-to-library")
|
||||
;; :on-select on-move}])
|
||||
;; i/move]
|
||||
;; (when (= 1 (count selected))
|
||||
;; [:span.move-item.tooltip.tooltip-top {:alt (tr "ds.multiselect-bar.rename")
|
||||
;; :on-click on-rename}
|
||||
;; i/pencil])
|
||||
[:span.delete.tooltip.tooltip-top
|
||||
{:alt (tr "ds.multiselect-bar.delete")
|
||||
:on-click on-delete}
|
||||
i/trash]]
|
||||
|
||||
;; If not editable
|
||||
;; [:div.multiselect-nav
|
||||
;; [:span.move-item.tooltip.tooltip-top {:alt (tr "ds.multiselect-bar.copy")
|
||||
;; :on-click on-toggle-copy}
|
||||
;; (when (:show-copy-tooltip @local)
|
||||
;; [:& grid-options-tooltip {:selected id
|
||||
;; :title (tr "ds.multiselect-bar.copy-to-library")
|
||||
;; :on-select on-copy}])
|
||||
;; i/organize]]
|
||||
)]))
|
||||
|
||||
;; --- Grid Form
|
||||
|
||||
(mf/defc grid-form
|
||||
[{:keys [id type uploading?] :as props}]
|
||||
(let [input (mf/use-ref nil)
|
||||
(let [locale (i18n/use-locale)
|
||||
input (mf/use-ref nil)
|
||||
on-click #(dom/click (mf/ref-node input))
|
||||
on-select #(st/emit! (->> (dom/get-event-files %)
|
||||
(jscoll->vec)
|
||||
on-select #(st/emit! (->> (dom/get-target %)
|
||||
(dom/get-files)
|
||||
(array-seq)
|
||||
(di/create-icons id)))]
|
||||
[:div.grid-item.add-project {:on-click on-click}
|
||||
(if uploading?
|
||||
[:div i/loader-pencil]
|
||||
[:span (tr "ds.icon-new")])
|
||||
[:span (t locale "ds.icon-new")])
|
||||
[:input.upload-icon-input
|
||||
{:style {:display "none"}
|
||||
:multiple true
|
||||
|
@ -165,122 +257,36 @@
|
|||
:type "file"
|
||||
:on-change on-select}]]))
|
||||
|
||||
(mf/def grid-options-tooltip
|
||||
:mixins [mf/reactive mf/memo]
|
||||
|
||||
:render
|
||||
(fn [own {:keys [selected on-select title]}]
|
||||
{:pre [(uuid? selected)
|
||||
(fn? on-select)
|
||||
(string? title)]}
|
||||
(let [colls (mf/react collections-iref)
|
||||
colls (->> (vals colls)
|
||||
(filter #(= :own (:type %)))
|
||||
(remove #(= selected (:id %)))
|
||||
(sort-by :name colls))
|
||||
on-select (fn [event id]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(on-select id))]
|
||||
[:ul.move-list
|
||||
[:li.title title]
|
||||
[:li
|
||||
[:a {:href "#" :on-click #(on-select % nil)} "Storage"]]
|
||||
(for [{:keys [id name] :as coll} colls]
|
||||
[:li {:key (pr-str id)}
|
||||
[:a {:on-click #(on-select % id)} name]])])))
|
||||
|
||||
;; (mf/def grid-options
|
||||
;; :mixins [(mf/local) mf/memo]
|
||||
|
||||
;; :render
|
||||
;; (fn [{:keys [::mf/local] :as own}
|
||||
;; {:keys [id type selected] :as props}]
|
||||
;; (letfn [(delete []
|
||||
;; (st/emit! (di/delete-selected)))
|
||||
;; (on-delete [event]
|
||||
;; (modal/show! confirm-dialog {:on-accept delete}))
|
||||
;; (on-toggle-copy [event]
|
||||
;; (swap! local update :show-copy-tooltip not))
|
||||
;; (on-toggle-move [event]
|
||||
;; (swap! local update :show-move-tooltip not))
|
||||
;; (on-copy [selected]
|
||||
;; (swap! local assoc
|
||||
;; :show-move-tooltip false
|
||||
;; :show-copy-tooltip false)
|
||||
;; (st/emit! (di/copy-selected selected)))
|
||||
;; (on-move [selected]
|
||||
;; (swap! local assoc
|
||||
;; :show-move-tooltip false
|
||||
;; :show-copy-tooltip false)
|
||||
;; (st/emit! (di/move-selected selected)))
|
||||
;; (on-rename [event]
|
||||
;; (let [selected (first selected)]
|
||||
;; (st/emit! (di/update-opts :edition selected))))]
|
||||
|
||||
;; ;; MULTISELECT OPTIONS BAR
|
||||
;; [:div.multiselect-bar
|
||||
;; (if (or (= type :own) (nil? id))
|
||||
;; ;; if editable
|
||||
;; [:div.multiselect-nav {}
|
||||
;; [:span.move-item.tooltip.tooltip-top
|
||||
;; {:alt (tr "ds.multiselect-bar.copy")
|
||||
;; :on-click on-toggle-copy}
|
||||
;; (when (:show-copy-tooltip @local)
|
||||
;; (grid-options-tooltip {:selected id
|
||||
;; :title (tr "ds.multiselect-bar.copy-to-library")
|
||||
;; :on-select on-copy}))
|
||||
;; i/copy]
|
||||
;; [:span.move-item.tooltip.tooltip-top
|
||||
;; {:alt (tr "ds.multiselect-bar.move")
|
||||
;; :on-click on-toggle-move}
|
||||
;; (when (:show-move-tooltip @local)
|
||||
;; (grid-options-tooltip {:selected id
|
||||
;; :title (tr "ds.multiselect-bar.move-to-library")
|
||||
;; :on-select on-move}))
|
||||
;; i/move]
|
||||
;; (when (= 1 (count selected))
|
||||
;; [:span.move-item.tooltip.tooltip-top
|
||||
;; {:alt (tr "ds.multiselect-bar.rename")
|
||||
;; :on-click on-rename}
|
||||
;; i/pencil])
|
||||
;; [:span.delete.tooltip.tooltip-top
|
||||
;; {:alt (tr "ds.multiselect-bar.delete")
|
||||
;; :on-click on-delete}
|
||||
;; i/trash]]
|
||||
|
||||
;; ;; if not editable
|
||||
;; [:div.multiselect-nav
|
||||
;; [:span.move-item.tooltip.tooltip-top
|
||||
;; {:alt (tr "ds.multiselect-bar.copy")
|
||||
;; :on-click on-toggle-copy}
|
||||
;; (when (:show-copy-tooltip @local)
|
||||
;; (grid-options-tooltip {:selected id
|
||||
;; :title (tr "ds.multiselect-bar.copy-to-library")
|
||||
;; :on-select on-copy}))
|
||||
;; i/organize]])])))
|
||||
|
||||
;; --- Grid Item
|
||||
|
||||
(mf/defc grid-item
|
||||
[{:keys [icon selected? edition?] :as props}]
|
||||
(letfn [(toggle-selection [event]
|
||||
(st/emit! (di/toggle-icon-selection (:id icon))))
|
||||
(on-key-down [event]
|
||||
(when (kbd/enter? event)
|
||||
(on-blur event)))
|
||||
(on-blur [event]
|
||||
(let [target (dom/event->target event)
|
||||
name (dom/get-value target)]
|
||||
(st/emit! (di/update-opts :edition false)
|
||||
(di/rename-icon (:id icon) name))))
|
||||
(ignore-click [event]
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event))
|
||||
(on-edit [event]
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (di/update-opts :edition (:id icon))))]
|
||||
(let [toggle-selection #(st/emit! (if selected?
|
||||
(di/deselect-icon (:id icon))
|
||||
(di/select-icon (:id icon))))
|
||||
on-blur
|
||||
(fn [event]
|
||||
(let [target (dom/get-target event)
|
||||
name (dom/get-value target)]
|
||||
(st/emit! (di/update-opts :edition false)
|
||||
(di/rename-icon (:id icon) name))))
|
||||
|
||||
on-key-down
|
||||
(fn [event]
|
||||
(when (kbd/enter? event)
|
||||
(on-blur event)))
|
||||
|
||||
ignore-click
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event))
|
||||
|
||||
on-edit
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (di/update-opts :edition (:id icon))))]
|
||||
|
||||
[:div.grid-item.small-item.project-th
|
||||
[:div.input-checkbox.check-primary
|
||||
[:input {:type "checkbox"
|
||||
|
@ -304,18 +310,13 @@
|
|||
|
||||
;; --- Grid
|
||||
|
||||
(defn make-icons-iref
|
||||
[id]
|
||||
(-> (comp (l/key :icons)
|
||||
(l/lens (fn [icons]
|
||||
(->> (vals icons)
|
||||
(filter #(= id (:collection-id %)))))))
|
||||
(def icons-iref
|
||||
(-> (comp (l/key :icons) (l/lens vals))
|
||||
(l/derive st/state)))
|
||||
|
||||
(mf/defc grid
|
||||
[{:keys [id type coll opts] :as props}]
|
||||
(let [editable? (or (= type :own) (nil? id))
|
||||
icons-iref (mf/use-memo #(make-icons-iref id) #js [id])
|
||||
[{:keys [id type collection opts] :as props}]
|
||||
(let [editable? (= type :own)
|
||||
icons (->> (mf/deref icons-iref)
|
||||
(filter-icons-by (:filter opts ""))
|
||||
(sort-icons-by (:order opts :name)))]
|
||||
|
@ -336,44 +337,48 @@
|
|||
|
||||
;; --- Content
|
||||
|
||||
(def opts-iref
|
||||
(-> (l/key :dashboard-icons)
|
||||
(l/derive st/state)))
|
||||
|
||||
(mf/defc content
|
||||
[{:keys [id type coll] :as props}]
|
||||
(let [opts (mf/deref opts-iref)]
|
||||
[:*
|
||||
[:section.dashboard-grid.library
|
||||
(when coll
|
||||
[:& grid-header {:coll coll}])
|
||||
[:& grid {:id id
|
||||
:key [id type]
|
||||
:type type
|
||||
:coll coll
|
||||
:opts opts}]
|
||||
(when (seq (:selected opts))
|
||||
#_[:& grid-options {:id id :type type :selected (:selected opts)}])]]))
|
||||
[{:keys [id type collection] :as props}]
|
||||
(let [{:keys [selected] :as opts} (mf/deref opts-iref)]
|
||||
[:section.dashboard-grid.library
|
||||
(when collection
|
||||
[:& grid-header {:collection collection}])
|
||||
(if collection
|
||||
[:& grid {:id id :type type :collection collection :opts opts}]
|
||||
[:span "EMPTY STATE TODO"])
|
||||
(when-not (empty? selected)
|
||||
#_[:& grid-options {:id id :type type :selected (:selected opts)}])]))
|
||||
|
||||
;; --- Icons Page
|
||||
|
||||
(def collections-iref
|
||||
(-> (l/key :icons-collections)
|
||||
(l/derive st/state)))
|
||||
|
||||
(mf/defc icons-page
|
||||
[{:keys [id type] :as props}]
|
||||
(let [type (or type :own)
|
||||
colls (mf/deref collections-iref)
|
||||
colls (cond->> (vals colls)
|
||||
(= type :own) (filter #(= :own (:type %)))
|
||||
(= type :builtin) (filter #(= :builtin (:type %)))
|
||||
true (sort-by :created-at))
|
||||
selected-coll (cond
|
||||
(and (= type :own) (nil? id)) nil
|
||||
(uuid? id) (seek #(= id (:id %)) colls)
|
||||
:else (first colls))
|
||||
id (:id selected-coll)]
|
||||
collections (mf/deref collections-iref)
|
||||
collections (cond->> (vals collections)
|
||||
(= type :own) (filter #(= :own (:type %)))
|
||||
(= type :builtin) (filter #(= :builtin (:type %)))
|
||||
true (sort-by :created-at))
|
||||
|
||||
collection (cond
|
||||
(uuid? id) (seek #(= id (:id %)) collections)
|
||||
:else (first collections))
|
||||
|
||||
id (:id collection)]
|
||||
|
||||
(mf/use-effect #(st/emit! di/fetch-collections))
|
||||
(mf/use-effect {:fn #(st/emit! (di/fetch-icons id))
|
||||
:deps #js [(str id)]})
|
||||
(mf/use-effect
|
||||
{:fn #(when id (st/emit! (di/initialize id)))
|
||||
:deps (mf/deps id)})
|
||||
|
||||
[:section.dashboard-content
|
||||
[:& nav {:type type
|
||||
:id id
|
||||
:colls colls}]
|
||||
[:& content {:type type
|
||||
:id id
|
||||
:coll selected-coll}]]))
|
||||
[:& nav {:type type :id id :collections collections}]
|
||||
[:& content {:type type :id id :collection collection}]]))
|
||||
|
|
|
@ -2,55 +2,42 @@
|
|||
;; 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>
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2015-2020 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2020 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.dashboard.images
|
||||
(:require
|
||||
[cljs.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[lentes.core :as l]
|
||||
[rumext.core :as mx]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.common.data :as d]
|
||||
[uxbox.common.spec :as us]
|
||||
[uxbox.main.data.images :as di]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.confirm :refer [confirm-dialog]]
|
||||
[uxbox.main.ui.dashboard.common :as common]
|
||||
[uxbox.main.ui.keyboard :as kbd]
|
||||
[uxbox.main.ui.lightbox :as lbx]
|
||||
[uxbox.main.ui.confirm :refer [confirm-dialog]]
|
||||
[uxbox.main.ui.modal :as modal]
|
||||
[uxbox.util.data :refer [read-string jscoll->vec seek]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.i18n :as t :refer [tr]]
|
||||
[uxbox.util.i18n :as i18n :refer [t tr]]
|
||||
[uxbox.util.router :as rt]
|
||||
[uxbox.util.time :as dt]))
|
||||
|
||||
;; --- Refs
|
||||
|
||||
(def collections-iref
|
||||
(-> (l/key :images-collections)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def opts-iref
|
||||
(-> (l/in [:dashboard :images])
|
||||
(l/derive st/state)))
|
||||
|
||||
;; --- Page Title
|
||||
|
||||
(mf/defc grid-header
|
||||
[{:keys [coll] :as props}]
|
||||
(letfn [(on-change [name]
|
||||
(st/emit! (di/rename-collection (:id coll) name)))
|
||||
|
||||
(delete []
|
||||
(st/emit!
|
||||
(di/delete-collection (:id coll))
|
||||
(rt/nav :dashboard/images nil {:type (:type coll)})))
|
||||
|
||||
(on-delete []
|
||||
(modal/show! confirm-dialog {:on-accept delete}))]
|
||||
[:& common/grid-header {:value (:name coll)
|
||||
[{:keys [collection] :as props}]
|
||||
(let [{:keys [id type]} collection
|
||||
on-change #(st/emit! (di/rename-collection id %))
|
||||
on-deleted #(st/emit! (rt/nav :dashboard-images nil {:type type}))
|
||||
delete #(st/emit! (di/delete-collection id on-deleted))
|
||||
on-delete #(modal/show! confirm-dialog {:on-accept delete})]
|
||||
[:& common/grid-header {:value (:name collection)
|
||||
:on-change on-change
|
||||
:on-delete on-delete}]))
|
||||
|
||||
|
@ -61,6 +48,7 @@
|
|||
(let [local (mf/use-state {})
|
||||
{:keys [id type name num-images]} coll
|
||||
editable? (= type :own)
|
||||
|
||||
on-click
|
||||
(fn [event]
|
||||
(let [type (or type :own)]
|
||||
|
@ -68,6 +56,7 @@
|
|||
|
||||
on-cancel-edition #(swap! local dissoc :edit)
|
||||
on-double-click #(when editable? (swap! local assoc :edit true))
|
||||
|
||||
on-input-keyup
|
||||
(fn [event]
|
||||
(when (kbd/enter? event)
|
||||
|
@ -76,6 +65,7 @@
|
|||
(str/trim))]
|
||||
(st/emit! (di/rename-collection id value))
|
||||
(swap! local assoc :edit false))))]
|
||||
|
||||
[:li {:on-click on-click
|
||||
:on-double-click on-double-click
|
||||
:class-name (when selected? "current")}
|
||||
|
@ -87,155 +77,128 @@
|
|||
[:span.element-title (if id name "Storage")])]))
|
||||
|
||||
(mf/defc nav
|
||||
[{:keys [id type colls] :as props}]
|
||||
(let [own? (= type :own)
|
||||
[{:keys [id type collections] :as props}]
|
||||
(let [locale (i18n/use-locale)
|
||||
own? (= type :own)
|
||||
builtin? (= type :builtin)
|
||||
select-tab #(st/emit! (rt/nav :dashboard-images nil {:type %}))]
|
||||
create-collection #(st/emit! di/create-collection)
|
||||
select-own-tab #(st/emit! (rt/nav :dashboard-images nil {:type :own}))
|
||||
select-buitin-tab #(st/emit! (rt/nav :dashboard-images nil {:type :builtin}))]
|
||||
[:div.library-bar
|
||||
[:div.library-bar-inside
|
||||
[:ul.library-tabs
|
||||
[:li {:class-name (when own? "current")
|
||||
:on-click (partial select-tab :own)}
|
||||
(tr "ds.your-images-title")]
|
||||
[:li {:class-name (when builtin? "current")
|
||||
:on-click (partial select-tab :builtin)}
|
||||
(tr "ds.store-images-title")]]
|
||||
|
||||
;; Tabs
|
||||
[:ul.library-tabs
|
||||
[:li {:class (when own? "current")
|
||||
:on-click select-own-tab}
|
||||
(t locale "ds.your-images-title")]
|
||||
|
||||
[:li {:class (when builtin? "current")
|
||||
:on-click select-buitin-tab}
|
||||
(t locale "ds.store-images-title")]]
|
||||
|
||||
;; Collections List
|
||||
[:ul.library-elements
|
||||
(when own?
|
||||
[:li
|
||||
[:a.btn-primary {:on-click #(st/emit! di/create-collection)}
|
||||
(tr "ds.images-collection.new")]])
|
||||
(when own?
|
||||
[:& nav-item {:selected? (nil? id)}])
|
||||
(for [item colls]
|
||||
[:a.btn-primary {:on-click create-collection}
|
||||
(t locale "ds.images-collection.new")]])
|
||||
|
||||
(for [item collections]
|
||||
[:& nav-item {:coll item
|
||||
:selected? (= (:id item) id)
|
||||
:key (:id item)}])]]]))
|
||||
|
||||
;; --- Grid
|
||||
|
||||
(mf/defc grid-options-tooltip
|
||||
[{:keys [selected on-select title] :as props}]
|
||||
{:pre [(uuid? selected)
|
||||
(fn? on-select)
|
||||
(string? title)]}
|
||||
(let [colls (mf/deref collections-iref)
|
||||
colls (->> (vals colls)
|
||||
(filter #(= :own (:type %)))
|
||||
(remove #(= selected (:id %)))
|
||||
#_(sort-by :name colls))
|
||||
on-select (fn [event id]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(on-select id))]
|
||||
[:ul.move-list
|
||||
[:li.title title]
|
||||
[:li
|
||||
(when (not (nil? selected))
|
||||
[:a {:href "#" :on-click #(on-select % nil)} "Storage"])]
|
||||
(for [{:keys [id name] :as coll} colls]
|
||||
[:li {:key (pr-str id)}
|
||||
[:a {:on-click #(on-select % id)} name]])]))
|
||||
;; (mf/defc grid-options-tooltip
|
||||
;; [{:keys [selected on-select title] :as props}]
|
||||
;; {:pre [(uuid? selected)
|
||||
;; (fn? on-select)
|
||||
;; (string? title)]}
|
||||
;; (let [colls (mf/deref collections-iref)
|
||||
;; colls (->> (vals colls)
|
||||
;; (filter #(= :own (:type %)))
|
||||
;; (remove #(= selected (:id %)))
|
||||
;; #_(sort-by :name colls))
|
||||
;; on-select (fn [event id]
|
||||
;; (dom/prevent-default event)
|
||||
;; (dom/stop-propagation event)
|
||||
;; (on-select id))]
|
||||
;; [:ul.move-list
|
||||
;; [:li.title title]
|
||||
;; [:li
|
||||
;; (when (not (nil? selected))
|
||||
;; [:a {:href "#" :on-click #(on-select % nil)} "Storage"])]
|
||||
;; (for [{:keys [id name] :as coll} colls]
|
||||
;; [:li {:key (pr-str id)}
|
||||
;; [:a {:on-click #(on-select % id)} name]])]))
|
||||
|
||||
(mf/defc grid-options
|
||||
[{:keys [id type selected] :as props}]
|
||||
(let [local (mf/use-state {})]
|
||||
(letfn [(delete []
|
||||
(st/emit! (di/delete-selected)))
|
||||
(on-delete [event]
|
||||
(modal/show! confirm-dialog {:on-accept delete}))
|
||||
(on-toggle-copy [event]
|
||||
(swap! local update :show-copy-tooltip not))
|
||||
(on-toggle-move [event]
|
||||
(swap! local update :show-move-tooltip not))
|
||||
(on-copy [selected]
|
||||
(swap! local assoc
|
||||
:show-move-tooltip false
|
||||
:show-copy-tooltip false)
|
||||
(st/emit! (di/copy-selected selected)))
|
||||
(on-move [selected]
|
||||
(swap! local assoc
|
||||
:show-move-tooltip false
|
||||
:show-copy-tooltip false)
|
||||
(st/emit! (di/move-selected selected)))
|
||||
(on-rename [event]
|
||||
(let [selected (first selected)]
|
||||
(st/emit! (di/update-opts :edition selected))))]
|
||||
;; MULTISELECT OPTIONS BAR
|
||||
[:div.multiselect-bar
|
||||
(if (or (= type :own) (nil? id))
|
||||
;; If editable
|
||||
[:div.multiselect-nav
|
||||
[:span.move-item.tooltip.tooltip-top
|
||||
{:alt (tr "ds.multiselect-bar.copy")
|
||||
:on-click on-toggle-copy}
|
||||
(when (:show-copy-tooltip @local)
|
||||
[:& grid-options-tooltip {:selected id
|
||||
:title (tr "ds.multiselect-bar.copy-to-library")
|
||||
:on-select on-copy}])
|
||||
i/copy]
|
||||
[:span.move-item.tooltip.tooltip-top
|
||||
{:alt (tr "ds.multiselect-bar.move")
|
||||
:on-click on-toggle-move}
|
||||
(when (:show-move-tooltip @local)
|
||||
[:& grid-options-tooltip {:selected id
|
||||
:title (tr "ds.multiselect-bar.move-to-library")
|
||||
:on-select on-move}])
|
||||
i/move]
|
||||
(when (= 1 (count selected))
|
||||
[:span.move-item.tooltip.tooltip-top {:alt (tr "ds.multiselect-bar.rename")
|
||||
:on-click on-rename}
|
||||
i/pencil])
|
||||
[:span.delete.tooltip.tooltip-top {:alt (tr "ds.multiselect-bar.delete")
|
||||
:on-click on-delete}
|
||||
i/trash]]
|
||||
(let [local (mf/use-state {})
|
||||
delete #(st/emit! di/delete-selected)
|
||||
on-delete #(modal/show! confirm-dialog {:on-accept delete})
|
||||
|
||||
;; If not editable
|
||||
[:div.multiselect-nav
|
||||
[:span.move-item.tooltip.tooltip-top {:alt (tr "ds.multiselect-bar.copy")
|
||||
:on-click on-toggle-copy}
|
||||
(when (:show-copy-tooltip @local)
|
||||
[:& grid-options-tooltip {:selected id
|
||||
:title (tr "ds.multiselect-bar.copy-to-library")
|
||||
:on-select on-copy}])
|
||||
i/organize]])])))
|
||||
;; (on-toggle-copy [event]
|
||||
;; (swap! local update :show-copy-tooltip not))
|
||||
;; (on-toggle-move [event]
|
||||
;; (swap! local update :show-move-tooltip not))
|
||||
;; (on-copy [selected]
|
||||
;; (swap! local assoc
|
||||
;; :show-move-tooltip false
|
||||
;; :show-copy-tooltip false)
|
||||
;; (st/emit! (di/copy-selected selected)))
|
||||
;; (on-move [selected]
|
||||
;; (swap! local assoc
|
||||
;; :show-move-tooltip false
|
||||
;; :show-copy-tooltip false)
|
||||
;; (st/emit! (di/move-selected selected)))
|
||||
;; (on-rename [event]
|
||||
;; (let [selected (first selected)]
|
||||
;; (st/emit! (di/update-opts :edition selected))))
|
||||
]
|
||||
;; MULTISELECT OPTIONS BAR
|
||||
[:div.multiselect-bar
|
||||
(when (= type :own)
|
||||
;; If editable
|
||||
[:div.multiselect-nav
|
||||
;; [:span.move-item.tooltip.tooltip-top
|
||||
;; {:alt (tr "ds.multiselect-bar.copy")
|
||||
;; :on-click on-toggle-copy}
|
||||
;; (when (:show-copy-tooltip @local)
|
||||
;; [:& grid-options-tooltip {:selected id
|
||||
;; :title (tr "ds.multiselect-bar.copy-to-library")
|
||||
;; :on-select on-copy}])
|
||||
;; i/copy]
|
||||
;; [:span.move-item.tooltip.tooltip-top
|
||||
;; {:alt (tr "ds.multiselect-bar.move")
|
||||
;; :on-click on-toggle-move}
|
||||
;; (when (:show-move-tooltip @local)
|
||||
;; [:& grid-options-tooltip {:selected id
|
||||
;; :title (tr "ds.multiselect-bar.move-to-library")
|
||||
;; :on-select on-move}])
|
||||
;; i/move]
|
||||
;; (when (= 1 (count selected))
|
||||
;; [:span.move-item.tooltip.tooltip-top {:alt (tr "ds.multiselect-bar.rename")
|
||||
;; :on-click on-rename}
|
||||
;; i/pencil])
|
||||
[:span.delete.tooltip.tooltip-top
|
||||
{:alt (tr "ds.multiselect-bar.delete")
|
||||
:on-click on-delete}
|
||||
i/trash]]
|
||||
|
||||
;; If not editable
|
||||
;; [:div.multiselect-nav
|
||||
;; [:span.move-item.tooltip.tooltip-top {:alt (tr "ds.multiselect-bar.copy")
|
||||
;; :on-click on-toggle-copy}
|
||||
;; (when (:show-copy-tooltip @local)
|
||||
;; [:& grid-options-tooltip {:selected id
|
||||
;; :title (tr "ds.multiselect-bar.copy-to-library")
|
||||
;; :on-select on-copy}])
|
||||
;; i/organize]]
|
||||
)]))
|
||||
|
||||
(mf/defc grid-item
|
||||
[{:keys [image selected? edition?] :as props}]
|
||||
(letfn [(toggle-selection [event]
|
||||
(st/emit! (di/toggle-image-selection (:id image))))
|
||||
(on-key-down [event]
|
||||
(when (kbd/enter? event)
|
||||
(on-blur event)))
|
||||
(on-blur [event]
|
||||
(let [target (dom/event->target event)
|
||||
name (dom/get-value target)]
|
||||
(st/emit! (di/update-opts :edition false)
|
||||
(di/rename-image (:id image) name))))
|
||||
(on-edit [event]
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (di/update-opts :edition (:id image))))]
|
||||
[:div.grid-item.images-th
|
||||
[:div.grid-item-th {:style {:background-image (str "url('" (:thumbnail image) "')")}}
|
||||
[:div.input-checkbox.check-primary
|
||||
[:input {:type "checkbox"
|
||||
:id (:id image)
|
||||
:on-change toggle-selection
|
||||
:checked selected?}]
|
||||
[:label {:for (:id image)}]]]
|
||||
[:div.item-info
|
||||
(if edition?
|
||||
[:input.element-name {:type "text"
|
||||
:auto-focus true
|
||||
:on-key-down on-key-down
|
||||
:on-blur on-blur
|
||||
:on-click on-edit
|
||||
:default-value (:name image)}]
|
||||
[:h3 {:on-double-click on-edit} (:name image)])
|
||||
[:span.date (str (tr "ds.uploaded-at"
|
||||
(dt/format (:created-at image) "dd/MM/yyyy")))]]]))
|
||||
|
||||
;; --- Grid Form
|
||||
|
||||
|
@ -243,8 +206,9 @@
|
|||
[{:keys [id type uploading?] :as props}]
|
||||
(let [input (mf/use-ref nil)
|
||||
on-click #(dom/click (mf/ref-node input))
|
||||
on-select #(st/emit! (->> (dom/get-event-files %)
|
||||
(jscoll->vec)
|
||||
on-select #(st/emit! (->> (dom/get-target %)
|
||||
(dom/get-files)
|
||||
(array-seq)
|
||||
(di/create-images id)))]
|
||||
[:div.grid-item.add-project {:on-click on-click}
|
||||
(if uploading?
|
||||
|
@ -255,26 +219,76 @@
|
|||
:multiple true
|
||||
:ref input
|
||||
:value ""
|
||||
:accept "image/jpeg,image/png"
|
||||
:accept "image/jpeg,image/png,image/webp"
|
||||
:type "file"
|
||||
:on-change on-select}]]))
|
||||
|
||||
;; --- Grid Item
|
||||
|
||||
(mf/defc grid-item
|
||||
[{:keys [image selected? edition?] :as props}]
|
||||
(let [toggle-selection #(st/emit! (if selected?
|
||||
(di/deselect-image (:id image))
|
||||
(di/select-image (:id image))))
|
||||
on-blur
|
||||
(fn [event]
|
||||
(let [target (dom/get-target event)
|
||||
name (dom/get-value target)]
|
||||
(st/emit! (di/update-opts :edition false)
|
||||
(di/rename-image (:id image) name))))
|
||||
|
||||
on-key-down
|
||||
(fn [event]
|
||||
(when (kbd/enter? event)
|
||||
(on-blur event)))
|
||||
|
||||
on-edit
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (di/update-opts :edition (:id image))))
|
||||
|
||||
background (str "url('" (:thumb-uri image) "')")]
|
||||
|
||||
[:div.grid-item.images-th
|
||||
[:div.grid-item-th {:style {:background-image background}}
|
||||
[:div.input-checkbox.check-primary
|
||||
[:input {:type "checkbox"
|
||||
:id (:id image)
|
||||
:on-change toggle-selection
|
||||
:checked selected?}]
|
||||
[:label {:for (:id image)}]]]
|
||||
|
||||
[:div.item-info
|
||||
(if edition?
|
||||
[:input.element-name {:type "text"
|
||||
:auto-focus true
|
||||
:on-key-down on-key-down
|
||||
:on-blur on-blur
|
||||
:on-click on-edit
|
||||
:default-value (:name image)}]
|
||||
[:h3 {:on-double-click on-edit} (:name image)])
|
||||
[:span.date (tr "ds.uploaded-at" (dt/format (:created-at image) "dd/MM/yyyy"))]]]))
|
||||
|
||||
;; --- Grid
|
||||
|
||||
(defn- make-images-iref
|
||||
[id type]
|
||||
(letfn [(selector-fn [state]
|
||||
(let [images (vals (:images state))]
|
||||
(filterv #(= id (:collection-id %)) images)))]
|
||||
(-> (l/lens selector-fn)
|
||||
(l/derive st/state))))
|
||||
;; (defn- make-images-iref
|
||||
;; [collection-id]
|
||||
;; (letfn [(selector [state]
|
||||
;; (->> (vals (:images state))
|
||||
;; (filterv #(= (:collection-id %) collection-id))))]
|
||||
;; (-> (l/lens selector)
|
||||
;; (l/derive st/state))))
|
||||
|
||||
(def images-iref
|
||||
(-> (comp (l/key :images) (l/lens vals))
|
||||
(l/derive st/state)))
|
||||
|
||||
(mf/defc grid
|
||||
[{:keys [id type coll opts] :as props}]
|
||||
(let [editable? (or (= type :own) (nil? id))
|
||||
images-iref (mf/use-memo
|
||||
{:fn #(make-images-iref id type)
|
||||
:deps (mf/deps id type)})
|
||||
[{:keys [id type collection opts] :as props}]
|
||||
(let [editable? (= type :own)
|
||||
;; images-iref (mf/use-memo {:fn #(make-images-iref id)
|
||||
;; :deps (mf/deps id)})
|
||||
images (->> (mf/deref images-iref)
|
||||
(sort-by :created-at))]
|
||||
[:div.dashboard-grid-content
|
||||
|
@ -328,45 +342,46 @@
|
|||
;; :value filtering}]
|
||||
;; [:div.clear-search {:on-click on-clear} i/close]]]])))
|
||||
|
||||
(mf/defc content
|
||||
[{:keys [id type coll] :as props}]
|
||||
(let [opts (mf/deref opts-iref)]
|
||||
[:*
|
||||
[:section.dashboard-grid.library
|
||||
(when coll
|
||||
[:& grid-header {:coll coll}])
|
||||
(def opts-iref
|
||||
(-> (l/key :dashboard-images)
|
||||
(l/derive st/state)))
|
||||
|
||||
[:& grid {:id id
|
||||
:type type
|
||||
:coll coll
|
||||
:opts opts}]
|
||||
(when (seq (:selected opts))
|
||||
[:& grid-options {:id id :type type :selected (:selected opts)}])]]))
|
||||
(mf/defc content
|
||||
[{:keys [id type collection] :as props}]
|
||||
(let [{:keys [selected] :as opts} (mf/deref opts-iref)]
|
||||
[:section.dashboard-grid.library
|
||||
(when collection
|
||||
[:& grid-header {:collection collection}])
|
||||
(if collection
|
||||
[:& grid {:id id :type type :collection collection :opts opts}]
|
||||
[:span "EMPTY STATE TODO"])
|
||||
(when-not (empty? selected)
|
||||
[:& grid-options {:id id :type type :selected selected}])]))
|
||||
|
||||
;; --- Images Page
|
||||
|
||||
(def collections-iref
|
||||
(-> (l/key :images-collections)
|
||||
(l/derive st/state)))
|
||||
|
||||
(mf/defc images-page
|
||||
[{:keys [id type] :as props}]
|
||||
(let [colls (mf/deref collections-iref)
|
||||
colls (cond->> (vals colls)
|
||||
(= type :own) (filter #(= :own (:type %)))
|
||||
(= type :builtin) (filter #(= :builtin (:type %)))
|
||||
true (sort-by :created-at))
|
||||
(let [collections (mf/deref collections-iref)
|
||||
collections (cond->> (vals collections)
|
||||
(= type :own) (filter #(= :own (:type %)))
|
||||
(= type :builtin) (filter #(= :builtin (:type %)))
|
||||
true (sort-by :created-at))
|
||||
|
||||
coll (cond
|
||||
(and (= type :own) (nil? id)) nil
|
||||
(uuid? id) (seek #(= id (:id %)) colls)
|
||||
:else (first colls))
|
||||
id (:id coll)]
|
||||
collection (cond
|
||||
(uuid? id) (d/seek #(= id (:id %)) collections)
|
||||
:else (first collections))
|
||||
id (:id collection)]
|
||||
|
||||
(mf/use-effect #(st/emit! di/fetch-collections))
|
||||
(mf/use-effect {:fn #(st/emit! (di/fetch-images (:id coll)))
|
||||
:deps #js [(str (:id coll))]})
|
||||
(mf/use-effect
|
||||
{:fn #(when id (st/emit! (di/initialize id)))
|
||||
:deps (mf/deps id)})
|
||||
|
||||
[:section.dashboard-content
|
||||
[:& nav {:type type
|
||||
:id id
|
||||
:colls colls}]
|
||||
[:& content {:type type
|
||||
:id id
|
||||
:coll coll}]]))
|
||||
[:& nav {:type type :id id :collections collections}]
|
||||
[:& content {:type type :id id :collection collection}]]))
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
(ns uxbox.main.ui.shapes.image
|
||||
(:require
|
||||
[lentes.core :as l]
|
||||
[rumext.alpha :as mf]
|
||||
[cuerdas.core :as str]
|
||||
[uxbox.main.data.images :as udi]
|
||||
|
@ -17,13 +16,6 @@
|
|||
[uxbox.main.ui.shapes.common :as common]
|
||||
[uxbox.util.geom.matrix :as gmt]))
|
||||
|
||||
;; --- Refs
|
||||
|
||||
(defn image-ref
|
||||
[id]
|
||||
(-> (l/in [:images id])
|
||||
(l/derive st/state)))
|
||||
|
||||
;; --- Image Wrapper
|
||||
|
||||
(declare image-shape)
|
||||
|
@ -31,23 +23,17 @@
|
|||
(mf/defc image-wrapper
|
||||
[{:keys [shape] :as props}]
|
||||
(let [selected (mf/deref refs/selected-shapes)
|
||||
image (mf/deref (image-ref (:image shape)))
|
||||
selected? (contains? selected (:id shape))
|
||||
on-mouse-down #(common/on-mouse-down % shape selected)]
|
||||
|
||||
(mf/use-effect #(st/emit! (udi/fetch-image (:image shape))))
|
||||
|
||||
(when image
|
||||
[:g.shape {:class (when selected? "selected")
|
||||
:on-mouse-down on-mouse-down}
|
||||
[:& image-shape {:shape shape
|
||||
:image image}]])))
|
||||
[:g.shape {:class (when selected? "selected")
|
||||
:on-mouse-down on-mouse-down}
|
||||
[:& image-shape {:shape shape}]]))
|
||||
|
||||
;; --- Image Shape
|
||||
|
||||
(mf/defc image-shape
|
||||
[{:keys [shape image] :as props}]
|
||||
(let [{:keys [id rotation modifier-mtx]} shape
|
||||
[{:keys [shape] :as props}]
|
||||
(let [{:keys [id rotation modifier-mtx metadata]} shape
|
||||
|
||||
shape (cond
|
||||
(gmt/matrix? modifier-mtx) (geom/transform shape modifier-mtx)
|
||||
|
@ -60,13 +46,18 @@
|
|||
rotation
|
||||
(+ x (/ width 2))
|
||||
(+ y (/ height 2))))
|
||||
uri (if (or (> (:thumb-width metadata) width)
|
||||
(> (:thumb-height metadata) height))
|
||||
(:thumb-uri metadata)
|
||||
(:uri metadata))
|
||||
|
||||
|
||||
props (-> (attrs/extract-style-attrs shape)
|
||||
(assoc :x x
|
||||
:y y
|
||||
:id (str "shape-" id)
|
||||
:preserveAspectRatio "none"
|
||||
:xlinkHref (:url image)
|
||||
:xlinkHref uri
|
||||
:transform transform
|
||||
:width width
|
||||
:height height))]
|
||||
|
|
|
@ -11,13 +11,14 @@
|
|||
[rumext.alpha :as mf]
|
||||
[rumext.core :as mx]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.common.data :as d]
|
||||
[uxbox.main.data.images :as udi]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.ui.modal :as modal]
|
||||
[uxbox.util.data :refer [read-string jscoll->vec]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.i18n :as t :refer [tr]]
|
||||
[uxbox.util.i18n :as i18n :refer [tr t]]
|
||||
[uxbox.util.uuid :as uuid]))
|
||||
|
||||
;; --- Refs
|
||||
|
@ -30,8 +31,13 @@
|
|||
(-> (l/key :images)
|
||||
(l/derive st/state)))
|
||||
|
||||
(def ^:private workspace-images-iref
|
||||
(-> (comp (l/key :workspace-images)
|
||||
(l/lens vals))
|
||||
(l/derive st/state)))
|
||||
|
||||
(def ^:private uploading-iref
|
||||
(-> (l/in [:dashboard :images :uploading])
|
||||
(-> (l/in [:workspace-local :uploading])
|
||||
(l/derive st/state)))
|
||||
|
||||
;; --- Import Image Modal
|
||||
|
@ -41,116 +47,152 @@
|
|||
(mf/defc import-image-modal
|
||||
[props]
|
||||
(let [input (mf/use-ref nil)
|
||||
uploading? (mf/deref uploading-iref)]
|
||||
(letfn [(on-upload-click [event]
|
||||
(let [input-el (mf/ref-node input)]
|
||||
(dom/click input-el)))
|
||||
uploading? (mf/deref uploading-iref)
|
||||
|
||||
(on-uploaded [[image]]
|
||||
(let [{:keys [id name width height]} image
|
||||
shape {:name name
|
||||
:metadata {:width width
|
||||
:height height}
|
||||
:image id}]
|
||||
(st/emit! (dw/select-for-drawing :image shape))
|
||||
(modal/hide!)))
|
||||
on-upload-click #(dom/click (mf/ref-node input))
|
||||
|
||||
(on-files-selected [event]
|
||||
(let [files (dom/get-event-files event)
|
||||
files (jscoll->vec files)]
|
||||
(st/emit! (udi/create-images nil files on-uploaded))))
|
||||
on-uploaded
|
||||
(fn [{:keys [id name] :as image}]
|
||||
(let [shape {:name name
|
||||
:metadata {:width (:width image)
|
||||
:height (:height image)
|
||||
:uri (:uri image)
|
||||
:thumb-width (:thumb-width image)
|
||||
:thumb-height (:thumb-height image)
|
||||
:thumb-uri (:thumb-uri image)}}]
|
||||
(st/emit! (dw/select-for-drawing :image shape))
|
||||
(modal/hide!)))
|
||||
|
||||
(on-select-from-library [event]
|
||||
(dom/prevent-default event)
|
||||
(modal/show! import-image-from-coll-modal {}))
|
||||
on-files-selected
|
||||
(fn [event]
|
||||
(st/emit! (-> (dom/get-target event)
|
||||
(dom/get-files)
|
||||
(array-seq)
|
||||
(first)
|
||||
(dw/upload-image on-uploaded))))
|
||||
|
||||
(on-close [event]
|
||||
(dom/prevent-default event)
|
||||
(modal/hide!))]
|
||||
[:div.lightbox-body
|
||||
[:h3 (tr "image.new")]
|
||||
[:div.row-flex
|
||||
[:div.lightbox-big-btn {:on-click on-select-from-library}
|
||||
[:span.big-svg i/image]
|
||||
[:span.text (tr "image.select")]]
|
||||
[:div.lightbox-big-btn {:on-click on-upload-click}
|
||||
(if uploading?
|
||||
[:span.big-svg.upload i/loader-pencil]
|
||||
[:span.big-svg.upload i/exit])
|
||||
[:span.text (tr "image.upload")]
|
||||
[:input.upload-image-input
|
||||
{:style {:display "none"}
|
||||
:accept "image/jpeg,image/png"
|
||||
:type "file"
|
||||
:ref input
|
||||
:on-change on-files-selected}]]]
|
||||
[:a.close {:on-click on-close} i/close]])))
|
||||
on-select-from-library
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(modal/show! import-image-from-coll-modal {}))
|
||||
|
||||
on-close
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(modal/hide!))]
|
||||
[:div.lightbox-body
|
||||
[:h3 (tr "image.new")]
|
||||
[:div.row-flex
|
||||
|
||||
;; Select from collections
|
||||
[:div.lightbox-big-btn {:on-click on-select-from-library}
|
||||
[:span.big-svg i/image]
|
||||
[:span.text (tr "image.select")]]
|
||||
|
||||
;; Select from workspace
|
||||
[:div.lightbox-big-btn {:on-click on-select-from-library}
|
||||
[:span.big-svg i/image]
|
||||
[:span.text (tr "image.select")]]
|
||||
|
||||
;; Direct image upload
|
||||
[:div.lightbox-big-btn {:on-click on-upload-click}
|
||||
(if uploading?
|
||||
[:span.big-svg.upload i/loader-pencil]
|
||||
[:span.big-svg.upload i/exit])
|
||||
[:span.text (tr "image.upload")]
|
||||
[:input.upload-image-input
|
||||
{:style {:display "none"}
|
||||
:multiple false
|
||||
:accept "image/jpeg,image/png,image/webp"
|
||||
:type "file"
|
||||
:ref input
|
||||
:on-change on-files-selected}]]]
|
||||
[:a.close {:on-click on-close} i/close]]))
|
||||
|
||||
;; --- Import Image from Collection Modal
|
||||
|
||||
(mf/defc image-item
|
||||
[{:keys [image] :as props}]
|
||||
(letfn [(on-click [event]
|
||||
;; TODO: deduplicate this code...
|
||||
(let [shape {:name (:name image)
|
||||
:metadata {:width (:width image)
|
||||
:height (:height image)}
|
||||
:image (:id image)}]
|
||||
:height (:height image)
|
||||
:uri (:uri image)
|
||||
:thumb-width (:thumb-width image)
|
||||
:thumb-height (:thumb-height image)
|
||||
:thumb-uri (:thumb-uri image)}}]
|
||||
(st/emit! (dw/select-for-drawing :image shape))
|
||||
(modal/hide!)))]
|
||||
[:div.library-item {:on-click on-click}
|
||||
[:div.library-item-th
|
||||
{:style {:background-image (str "url('" (:thumbnail image) "')")}}]
|
||||
{:style {:background-image (str "url('" (:thumb-uri image) "')")}}]
|
||||
[:span (:name image)]]))
|
||||
|
||||
(mf/defc image-collection
|
||||
[{:keys [images] :as props}]
|
||||
[:div.library-content
|
||||
(for [image images]
|
||||
[:& image-item {:image image :key (:id image)}])])
|
||||
|
||||
(mf/defc import-image-from-coll-modal
|
||||
[props]
|
||||
(let [local (mf/use-state {:id nil :type :own})
|
||||
id (:id @local)
|
||||
type (:type @local)
|
||||
own? (= type :own)
|
||||
builtin? (= type :builtin)
|
||||
colls (mf/deref collections-iref)
|
||||
colls (->> (vals colls)
|
||||
(filter #(= type (:type %)))
|
||||
(sort-by :name))
|
||||
(let [locale (i18n/use-locale)
|
||||
local (mf/use-state {:collection-id nil :tab :file})
|
||||
|
||||
collections (mf/deref collections-iref)
|
||||
collections (->> (vals collections)
|
||||
(sort-by :name))
|
||||
|
||||
select-tab #(swap! local assoc :tab %)
|
||||
|
||||
collection-id (or (:collection-id @local)
|
||||
(:id (first collections)))
|
||||
|
||||
tab (:tab @local)
|
||||
|
||||
|
||||
images (mf/deref images-iref)
|
||||
images (->> (vals images)
|
||||
(filter #(= id (:collection %))))
|
||||
(filter #(= collection-id (:collection-id %))))
|
||||
|
||||
workspace-images (mf/deref workspace-images-iref)
|
||||
|
||||
on-close #(do (dom/prevent-default %)
|
||||
(modal/hide!))
|
||||
select-type #(swap! local assoc :type %)
|
||||
on-change #(-> (dom/event->value %)
|
||||
(read-string)
|
||||
(swap! local assoc :id))]
|
||||
|
||||
on-change #(->> (dom/get-target %)
|
||||
(dom/get-value)
|
||||
(d/read-string)
|
||||
(swap! local assoc :collection-id))]
|
||||
|
||||
(mf/use-effect #(st/emit! udi/fetch-collections))
|
||||
(mf/use-effect {:deps #js [(str id)]
|
||||
:fn #(st/emit! (udi/fetch-images id))})
|
||||
(mf/use-effect
|
||||
{:deps (mf/deps collection-id)
|
||||
:fn #(when collection-id
|
||||
(st/emit! (udi/fetch-images collection-id)))})
|
||||
|
||||
[:div.lightbox-body.big-lightbox
|
||||
[:h3 (tr "image.import-library")]
|
||||
[:div.import-img-library
|
||||
[:div.library-actions
|
||||
[:ul.toggle-library
|
||||
[:li.your-images {:class (when own? "current")
|
||||
:on-click #(select-type :own)}
|
||||
(tr "ds.your-images-title")]
|
||||
[:li.standard {:class (when builtin? "current")
|
||||
:on-click #(select-type :builtin)}
|
||||
(tr "ds.store-images-title")]]
|
||||
[:select.input-select {:on-change on-change}
|
||||
(when own?
|
||||
[:option {:value (pr-str nil)} "Storage"])
|
||||
(for [coll colls]
|
||||
(let [id (:id coll)
|
||||
name (:name coll)]
|
||||
[:option {:key (str id) :value (pr-str id)} name]))]]
|
||||
|
||||
[:& image-collection {:images images}]]
|
||||
;; Tabs
|
||||
[:ul.toggle-library
|
||||
[:li.your-images {:class (when (= tab :file) "current")
|
||||
:on-click #(select-tab :file)}
|
||||
(t locale "ds.your-images-title")]
|
||||
[:li.standard {:class (when (not= tab :file) "current")
|
||||
:on-click #(select-tab :collection)}
|
||||
(t locale "ds.store-images-title")]]
|
||||
|
||||
;; Collections dropdown
|
||||
(when (= tab :collection)
|
||||
[:select.input-select {:on-change on-change}
|
||||
(for [coll collections]
|
||||
(let [id (:id coll)
|
||||
name (:name coll)]
|
||||
[:option {:key (str id) :value (pr-str id)} name]))])]
|
||||
|
||||
(if (= tab :collection)
|
||||
[:div.library-content
|
||||
(for [image images]
|
||||
[:& image-item {:image image :key (:id image)}])]
|
||||
[:div.library-content
|
||||
(for [image workspace-images]
|
||||
[:& image-item {:image image :key (:id image)}])])]
|
||||
[:a.close {:href "#" :on-click on-close} i/close]]))
|
||||
|
|
|
@ -40,10 +40,7 @@
|
|||
|
||||
(mf/defc icons-list
|
||||
[{:keys [collection-id] :as props}]
|
||||
(let [icons-iref (mf/use-memo
|
||||
{:fn #(icons/make-icons-iref collection-id)
|
||||
:deps (mf/deps collection-id)})
|
||||
icons (mf/deref icons-iref)
|
||||
(let [icons (mf/deref icons/icons-iref)
|
||||
|
||||
on-select
|
||||
(fn [event data]
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
(ns uxbox.util.blob
|
||||
"Helpers for work with HTML5 Blob objects.")
|
||||
|
||||
;; TODO: DEPRECATED
|
||||
|
||||
(defn ^boolean blob?
|
||||
[v]
|
||||
(instance? js/Blob v))
|
||||
|
|
|
@ -11,7 +11,10 @@
|
|||
(ns uxbox.util.dom
|
||||
(:require
|
||||
[goog.dom :as dom]
|
||||
[cuerdas.core :as str]))
|
||||
[cuerdas.core :as str]
|
||||
[beicon.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[uxbox.util.blob :as blob]))
|
||||
|
||||
;; --- Deprecated methods
|
||||
|
||||
|
@ -131,3 +134,5 @@
|
|||
(defn query
|
||||
[el query]
|
||||
(.querySelector el query))
|
||||
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
[cuerdas.core :as str]
|
||||
[uxbox.util.blob :as blob]))
|
||||
|
||||
;; TODO: DEPRECATED
|
||||
|
||||
(defn read-as-text
|
||||
[file]
|
||||
(rx/create
|
||||
|
|
68
frontend/src/uxbox/util/webapi.cljs
Normal file
68
frontend/src/uxbox/util/webapi.cljs
Normal file
|
@ -0,0 +1,68 @@
|
|||
;; 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) 2020 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.util.webapi
|
||||
"HTML5 web api helpers."
|
||||
(:require
|
||||
[beicon.core :as rx]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
(defn read-file-as-text
|
||||
[file]
|
||||
(rx/create
|
||||
(fn [sink]
|
||||
(let [fr (js/FileReader.)]
|
||||
(aset fr "onload" #(sink (rx/end (.-result fr))))
|
||||
(.readAsText fr file)
|
||||
(constantly nil)))))
|
||||
|
||||
(defn read-file-as-dataurl
|
||||
[file]
|
||||
(rx/create
|
||||
(fn [sick]
|
||||
(let [fr (js/FileReader.)]
|
||||
(aset fr "onload" #(sick (rx/end (.-result fr))))
|
||||
(.readAsDataURL fr file))
|
||||
(constantly nil))))
|
||||
|
||||
(defn ^boolean blob?
|
||||
[v]
|
||||
(instance? js/Blob v))
|
||||
|
||||
(defn create-blob
|
||||
"Create a blob from content."
|
||||
([content]
|
||||
(create-blob content "application/octet-stream"))
|
||||
([content mtype]
|
||||
(js/Blob. #js [content] #js {:type mtype})))
|
||||
|
||||
(defn revoke-uri
|
||||
[url]
|
||||
(assert (string? url) "invalid arguments")
|
||||
(js/URL.revokeObjectURL url))
|
||||
|
||||
(defn create-uri
|
||||
"Create a url from blob."
|
||||
[b]
|
||||
(assert (blob? b) "invalid arguments")
|
||||
(js/URL.createObjectURL b))
|
||||
|
||||
|
||||
;; (defn get-image-size
|
||||
;; [file]
|
||||
;; (letfn [(on-load [sink img]
|
||||
;; (let [size [(.-width img) (.-height img)]]
|
||||
;; (sink (rx/end size))))
|
||||
;; (on-subscribe [sink]
|
||||
;; (let [img (js/Image.)
|
||||
;; uri (blob/create-uri file)]
|
||||
;; (set! (.-onload img) (partial on-load sink img))
|
||||
;; (set! (.-src img) uri)
|
||||
;; #(blob/revoke-uri uri)))]
|
||||
;; (rx/create on-subscribe)))
|
||||
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue