From b3e9e7d1aafa6d0b7177137989f8c0b77988feaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Wed, 11 May 2016 23:46:24 +0200 Subject: [PATCH] Images dashboard --- src/uxbox/data/dashboard.cljs | 1 + src/uxbox/data/images.cljs | 299 ++++++++++++++++++++++++ src/uxbox/library.cljs | 11 + src/uxbox/library/images.cljs | 65 ++++++ src/uxbox/locales/en.cljs | 3 + src/uxbox/repo.cljs | 1 + src/uxbox/repo/core.cljs | 4 +- src/uxbox/repo/images.cljs | 80 +++++++ src/uxbox/state.cljs | 4 +- src/uxbox/state/images.cljs | 28 +++ src/uxbox/ui/dashboard/images.cljs | 356 ++++++++++++++++++++++------- src/uxbox/util/dom.cljs | 15 ++ 12 files changed, 778 insertions(+), 89 deletions(-) create mode 100644 src/uxbox/data/images.cljs create mode 100644 src/uxbox/library/images.cljs create mode 100644 src/uxbox/repo/images.cljs create mode 100644 src/uxbox/state/images.cljs diff --git a/src/uxbox/data/dashboard.cljs b/src/uxbox/data/dashboard.cljs index c25ca4b5d..cbcdfa392 100644 --- a/src/uxbox/data/dashboard.cljs +++ b/src/uxbox/data/dashboard.cljs @@ -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 diff --git a/src/uxbox/data/images.cljs b/src/uxbox/data/images.cljs new file mode 100644 index 000000000..a7c4f2a5a --- /dev/null +++ b/src/uxbox/data/images.cljs @@ -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 +;; Copyright (c) 2015-2016 Juan de la Cruz + +(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] "")))) diff --git a/src/uxbox/library.cljs b/src/uxbox/library.cljs index a8d4ad791..8ee528ff4 100644 --- a/src/uxbox/library.cljs +++ b/src/uxbox/library.cljs @@ -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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/src/uxbox/library/images.cljs b/src/uxbox/library/images.cljs new file mode 100644 index 000000000..2b93dacfd --- /dev/null +++ b/src/uxbox/library/images.cljs @@ -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 +;; Copyright (c) 2015-2016 Juan de la Cruz + +(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"}]}]) diff --git a/src/uxbox/locales/en.cljs b/src/uxbox/locales/en.cljs index 9278db90f..6de34898f 100644 --- a/src/uxbox/locales/en.cljs +++ b/src/uxbox/locales/en.cljs @@ -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" diff --git a/src/uxbox/repo.cljs b/src/uxbox/repo.cljs index 5841db709..a18ca21de 100644 --- a/src/uxbox/repo.cljs +++ b/src/uxbox/repo.cljs @@ -13,6 +13,7 @@ [uxbox.repo.projects] [uxbox.repo.pages] [uxbox.repo.colors] + [uxbox.repo.images] [httpurr.status :as status] [beicon.core :as rx])) diff --git a/src/uxbox/repo/core.cljs b/src/uxbox/repo/core.cljs index 30e55c956..6f254027b 100644 --- a/src/uxbox/repo/core.cljs +++ b/src/uxbox/repo/core.cljs @@ -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) diff --git a/src/uxbox/repo/images.cljs b/src/uxbox/repo/images.cljs new file mode 100644 index 000000000..f42bcbe54 --- /dev/null +++ b/src/uxbox/repo/images.cljs @@ -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 + +(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}))) diff --git a/src/uxbox/state.cljs b/src/uxbox/state.cljs index 66438c494..129965e6b 100644 --- a/src/uxbox/state.cljs +++ b/src/uxbox/state.cljs @@ -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 [] diff --git a/src/uxbox/state/images.cljs b/src/uxbox/state/images.cljs new file mode 100644 index 000000000..84f244dc2 --- /dev/null +++ b/src/uxbox/state/images.cljs @@ -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)) diff --git a/src/uxbox/ui/dashboard/images.cljs b/src/uxbox/ui/dashboard/images.cljs index 1f9c21226..2e5173d17 100644 --- a/src/uxbox/ui/dashboard/images.cljs +++ b/src/uxbox/ui/dashboard/images.cljs @@ -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 diff --git a/src/uxbox/util/dom.cljs b/src/uxbox/util/dom.cljs index a6de94824..ea3bb779f 100644 --- a/src/uxbox/util/dom.cljs +++ b/src/uxbox/util/dom.cljs @@ -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)))