diff --git a/src/uxbox/main/data/images.cljs b/src/uxbox/main/data/images.cljs index 422bb7b43..09236c069 100644 --- a/src/uxbox/main/data/images.cljs +++ b/src/uxbox/main/data/images.cljs @@ -2,8 +2,7 @@ ;; 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 +;; Copyright (c) 2016 Andrey Antukh (ns uxbox.main.data.images (:require [cuerdas.core :as str] @@ -26,10 +25,8 @@ (defrecord Initialize [type id] rs/UpdateEvent (-apply-update [_ state] - (let [type (or type :builtin) - id (or id (if (= type :builtin) 1 nil)) + (let [type (or type :own) data {:type type :id id :selected #{}}] - (println "initialize:" data) (-> state (assoc-in [:dashboard :images] data) (assoc-in [:dashboard :section] :dashboard/images)))) @@ -159,8 +156,8 @@ (rx/of (update-collection id)))) (defn rename-collection - [item name] - (RenameCollection. item name)) + [id name] + (RenameCollection. id name)) ;; --- Delete Collection @@ -193,7 +190,7 @@ (def allowed-file-types #{"image/jpeg" "image/png"}) -(defrecord CreateImages [coll-id files] +(defrecord CreateImages [id files] rs/WatchEvent (-apply-watch [_ state s] (letfn [(image-size [file] @@ -202,7 +199,7 @@ (allowed-file? [file] (contains? allowed-file-types (.-type file))) (prepare [[file [width height]]] - {:coll coll-id + {:collection id :mimetype (.-type file) :id (uuid/random) :file file @@ -217,12 +214,13 @@ (rx/map image-created))))) (defn create-images - [coll-id files] - (CreateImages. coll-id files)) + [id files] + {:pre [(or (uuid? id) (nil? id))]} + (CreateImages. id files)) ;; --- Images Fetched -(defrecord ImagesFetched [coll-id items] +(defrecord ImagesFetched [items] rs/UpdateEvent (-apply-update [_ state] (reduce (fn [state {:keys [id] :as image}] @@ -231,58 +229,77 @@ items))) (defn images-fetched - [coll-id items] - (ImagesFetched. coll-id items)) + [items] + (ImagesFetched. items)) ;; --- Fetch Images -(defrecord FetchImages [coll-id] +(defrecord FetchImages [id] rs/WatchEvent (-apply-watch [_ state s] - (let [params {:coll coll-id}] + (let [params {:coll id}] (->> (rp/req :fetch/images params) (rx/map :payload) - (rx/map #(images-fetched coll-id %)))))) + (rx/map images-fetched))))) (defn fetch-images - [coll-id] - (FetchImages. coll-id)) + "Fetch a list of images of the selected collection" + [id] + {:pre [(or (uuid? id) (nil? id))]} + (FetchImages. id)) + +;; --- Fetch Image + +(declare image-fetched) + +(defrecord FetchImage [id] + rs/WatchEvent + (-apply-watch [_ state stream] + (let [existing (get-in state [:images-by-id id])] + (if existing + (rx/empty) + (->> (rp/req :fetch/image {:id id}) + (rx/map :payload) + (rx/map image-fetched)))))) + +(defn fetch-image + "Conditionally fetch image by its id. If image + is already loaded, this event is noop." + [id] + {:pre [(uuid? id)]} + (FetchImage. id)) + +;; --- Image Fetched + +(defrecord ImageFetched [image] + rs/UpdateEvent + (-apply-update [_ state] + (let [id (:id image)] + (update state :images-by-id assoc id image)))) + +(defn image-fetched + [image] + {:pre [(map? image)]} + (ImageFetched. image)) ;; --- Delete Images -(defrecord DeleteImage [coll-id image-id] +(defrecord DeleteImage [id] rs/UpdateEvent (-apply-update [_ state] - (update state [:images-by-id] dissoc image-id)) + (-> state + (update :images-by-id dissoc id) + (update-in [:dashboard :images :selected] disj id))) rs/WatchEvent (-apply-watch [_ state s] - (->> (rp/req :delete/image image-id) + (->> (rp/req :delete/image id) (rx/ignore)))) (defn delete-image - [coll-id image-id] - {:pre [(uuid? coll-id) - (uuid? image-id)]} - (DeleteImage. coll-id image-id)) - -;; --- Remove Image - -(defrecord RemoveImages [id images] - rs/UpdateEvent - (-apply-update [_ state] - #_(update-in state [:image-colls-by-id id :data] - #(set/difference % images))) - - rs/WatchEvent - (-apply-watch [_ state s] - (rx/of (update-collection id)))) - -(defn remove-images - "Remove image in a collection." - [id images] - (RemoveImages. id images)) - + [id] + {:pre [(uuid? id)]} + (DeleteImage. id)) ;; --- Select image @@ -314,9 +331,9 @@ (defrecord DeleteSelected [] rs/WatchEvent (-apply-watch [_ state stream] - (let [{:keys [id selected]} (get-in state [:dashboard :images])] - (rx/of (remove-images id selected) - #(assoc-in % [:dashboard :images :selected] #{}))))) + (let [selected (get-in state [:dashboard :images :selected])] + (->> (rx/from-coll selected) + (rx/map delete-image))))) (defn delete-selected [] diff --git a/src/uxbox/main/repo/images.cljs b/src/uxbox/main/repo/images.cljs index f0021b6ea..9fa0f7192 100644 --- a/src/uxbox/main/repo/images.cljs +++ b/src/uxbox/main/repo/images.cljs @@ -40,20 +40,28 @@ (defmethod request :fetch/images [_ {:keys [coll]}] - (let [params {:url (str url "/library/image-collections/" coll "/images") + (let [url (if coll + (str url "/library/image-collections/" coll "/images") + (str url "/library/image-collections/images")) + params {:url url :method :get}] + (send! params))) + +(defmethod request :fetch/image + [_ {:keys [id]}] + (let [params {:url (str url "/library/images/" id) :method :get}] (send! params))) (defmethod request :create/image - [_ {:keys [coll id file width height mimetype] :as body}] + [_ {:keys [collection id file width height mimetype] :as body}] (let [body (doto (js/FormData.) (.append "mimetype" mimetype) - ;; (.append "collection" (str coll)) + (.append "collection" (str collection)) (.append "file" file) (.append "width" width) (.append "height" height) (.append "id" id)) - params {:url (str url "/library/image-collections/" coll "/images") + params {:url (str url "/library/images/") :method :post :body body}] (send! params))) diff --git a/src/uxbox/main/ui/dashboard/elements.cljs b/src/uxbox/main/ui/dashboard/elements.cljs index f9a580095..9f3d10c85 100644 --- a/src/uxbox/main/ui/dashboard/elements.cljs +++ b/src/uxbox/main/ui/dashboard/elements.cljs @@ -166,19 +166,148 @@ (defn- new-element-lightbox-render [own] (html - [:div.lightbox-body - [:h3 "New element"] - [:div.row-flex - [:div.lightbox-big-btn - [:span.big-svg i/shapes] - [:span.text "Go to workspace"]] - [:div.lightbox-big-btn - [:span.big-svg.upload i/exit] - [:span.text "Upload file"]]] + ;;------Element lightbox + + ;;[:div.lightbox-body + ;;[:h3 "New element"] + ;;[:div.row-flex + ;;[:div.lightbox-big-btn + ;;[:span.big-svg i/shapes] + ;;[:span.text "Go to workspace"]] + ;;[:div.lightbox-big-btn + ;;[:span.big-svg.upload i/exit] + ;;[:span.text "Upload file"]]] + ;;[:a.close {:href "#" + ;;:on-click #(do (dom/prevent-default %) + ;;(udl/close!))} + ;;i/close]] + + ;;------Upload image lightbox + + ;;[:div.lightbox-body + ;;[:h3 "Import image"] + ;;[:div.row-flex + ;;[:div.lightbox-big-btn + ;;[:span.big-svg i/image] + ;;[:span.text "Select from library"]] + ;;[:div.lightbox-big-btn + ;;[:span.big-svg.upload i/exit] + ;;[:span.text "Upload file"]]] + ;;[:a.close {:href "#" + ;;:on-click #(do (dom/prevent-default %) + ;;(udl/close!))} + ;;i/close]] + + ;;------Upload image library lightbox + + [:div.lightbox-body.big-lightbox + [:h3 "Import image from library"] + [:div.import-img-library + [:div.library-actions + [:ul.toggle-library + [:li.standard.current "STANDARD"] + [:li.your-images "YOUR IMAGES"]] + [:select.input-select + [:option "Library 1"] + [:option "Library 2"] + [:option "Library 3"] + [:option "Library 4"] + [:option "Library 5"] + [:option "Library 6"]]] + + [:div.library-content + + [:div.library-item + [:div.library-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')"}}] + [:span "image_name.jpg"]] + + [:div.library-item + [:div.library-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')"}}] + [:span "image_name.jpg"]] + + [:div.library-item + [:div.library-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')"}}] + [:span "image_name.jpg"]] + + [:div.library-item + [:div.library-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')"}}] + [:span "image_name.jpg"]] + + [:div.library-item + [:div.library-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')"}}] + [:span "image_name.jpg"]] + + [:div.library-item + [:div.library-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')"}}] + [:span "image_name.jpg"]] + + [:div.library-item + [:div.library-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')"}}] + [:span "image_name.jpg"]] + + [:div.library-item + [:div.library-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')"}}] + [:span "image_name.jpg"]] + + [:div.library-item + [:div.library-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')"}}] + [:span "image_name.jpg"]] + + [:div.library-item + [:div.library-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')"}}] + [:span "image_name.jpg"]] + + [:div.library-item + [:div.library-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')"}}] + [:span "image_name.jpg"]] + + [:div.library-item + [:div.library-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')"}}] + [:span "image_name.jpg"]] + + [:div.library-item + [:div.library-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')"}}] + [:span "image_name.jpg"]] + + [:div.library-item + [:div.library-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')"}}] + [:span "image_name.jpg"]] + + ]] [:a.close {:href "#" :on-click #(do (dom/prevent-default %) (udl/close!))} - i/close]])) + i/close]] + + )) (def ^:private new-element-lightbox (mx/component diff --git a/src/uxbox/main/ui/dashboard/images.cljs b/src/uxbox/main/ui/dashboard/images.cljs index d8352e91c..8e5f6a8ca 100644 --- a/src/uxbox/main/ui/dashboard/images.cljs +++ b/src/uxbox/main/ui/dashboard/images.cljs @@ -6,25 +6,19 @@ ;; Copyright (c) 2015-2016 Juan de la Cruz (ns uxbox.main.ui.dashboard.images - (:require [sablono.core :as html :refer-macros [html]] - [rum.core :as rum] - [cuerdas.core :as str] + (:require [cuerdas.core :as str] [lentes.core :as l] [uxbox.util.i18n :as t :refer (tr)] [uxbox.main.state :as st] [uxbox.util.rstore :as rs] - [uxbox.main.library :as library] - [uxbox.main.data.dashboard :as dd] [uxbox.main.data.lightbox :as udl] [uxbox.main.data.images :as di] [uxbox.main.ui.icons :as i] [uxbox.util.mixins :as mx :include-macros true] [uxbox.main.ui.lightbox :as lbx] [uxbox.main.ui.keyboard :as kbd] - [uxbox.main.ui.library-bar :as ui.library-bar] [uxbox.main.ui.dashboard.header :refer (header)] [uxbox.util.data :as data :refer (read-string)] - [uxbox.util.lens :as ul] [uxbox.util.dom :as dom])) ;; --- Helpers & Constants @@ -65,15 +59,6 @@ (-> (l/key :images-by-id) (l/derive st/state))) -;; (def ^:private collections-ref -;; (-> (l/lens vals) -;; (l/derive collections-map-ref))) - -;; (defn- focus-collection -;; [id] -;; (-> (l/key id) -;; (l/derive collections-map-ref))) - ;; --- Page Title (mx/defcs page-title @@ -117,7 +102,7 @@ [:span.close {:on-click on-cancel} i/close]] [:span.dashboard-title-field {:on-double-click on-edit} - (:name coll)])] + (:name coll "Storage")])] (if (and (not own?) coll) [:div.edition (if edit? @@ -127,19 +112,26 @@ ;; --- Nav +(defn react-count-images + [id] + (->> (mx/react images-ref) + (vals) + (filter #(= id (:collection %))) + (count))) + (mx/defc nav-item - {:mixins [mx/static]} - [collection selected?] + {:mixins [mx/static mx/reactive]} + [{:keys [id type name] :as coll} selected?] (letfn [(on-click [event] - (let [type (:type collection) - id (:id collection)] + (let [type (or type :own)] (rs/emit! (di/select-collection type id))))] - (let [images (count (:images collection []))] + (let [num-images (react-count-images id)] [:li {:on-click on-click :class-name (when selected? "current")} - [:span.element-title (:name collection)] + [:span.element-title + (if coll name "Storage")] [:span.element-subtitle - (tr "ds.num-elements" (t/c images))]]))) + (tr "ds.num-elements" (t/c num-images))]]))) (mx/defc nav-section {:mixins [mx/static mx/reactive]} @@ -156,6 +148,8 @@ [:a.btn-primary {:on-click #(rs/emit! (di/create-collection))} "+ New library"]]) + (when own? + (nav-item nil (nil? selected))) (for [coll collections :let [selected? (= (:id coll) selected) key (str (:id coll))]] @@ -179,12 +173,13 @@ [:div.library-bar [:div.library-bar-inside [:ul.library-tabs - [:li {:class-name (when builtin? "current") - :on-click (partial select-tab :builtin)} - "STANDARD"] [:li {:class-name (when own? "current") :on-click (partial select-tab :own)} - "YOUR LIBRARIES"]] + "YOUR IMAGES"] + [:li {:class-name (when builtin? "current") + :on-click (partial select-tab :builtin)} + "IMAGES STORE"]] + (nav-section type id colls)]]))) ;; --- Grid @@ -208,15 +203,15 @@ :on-change on-file-selected}]])) (mx/defc grid-options - [coll] - (let [own? (= (:type coll) :own)] + [{:keys [type] :as coll}] + (let [editable? (or (= type :own) (nil? coll))] (letfn [(delete [] (rs/emit! (di/delete-selected))) (on-delete [event] (udl/open! :confirm {:on-accept delete}))] ;; MULTISELECT OPTIONS BAR [:div.multiselect-bar - (if own? + (if editable? [:div.multiselect-nav #_[:span.move-item.tooltip.tooltip-top {:alt "Move to"} @@ -251,19 +246,18 @@ (mx/defc grid {:mixins [mx/static mx/reactive]} - [{:keys [id type selected] :as state}] - (let [filtering (:filter state) + [{:keys [id type selected] :as state}] + (let [editable? (or (= type :own) (nil? id)) + filtering (:filter state) ordering (:order state) - own? (= type :own) - images (rum/react images-ref) + images (mx/react images-ref) images (->> (vals images) (filter #(= id (:collection %))) (filter-images-by filtering) (sort-images-by ordering))] [:div.dashboard-grid-content [:div.dashboard-grid-row - (when own? - (grid-form id)) + (when editable? (grid-form id)) (for [image images :let [id (:id image) selected? (contains? selected id)]] @@ -273,13 +267,11 @@ (mx/defc content {:mixins [mx/static]} [{:keys [type id selected] :as state} coll] - (let [own? (= type :own)] - (when coll - [:section.dashboard-grid.library - (page-title coll) - (grid state) - (when (seq selected) - (grid-options coll))]))) + [:section.dashboard-grid.library + (page-title coll) + (grid state) + (when (seq selected) + (grid-options coll))]) ;; --- Menu diff --git a/src/uxbox/main/ui/shapes/group.cljs b/src/uxbox/main/ui/shapes/group.cljs index 76a90856b..2a46c9acd 100644 --- a/src/uxbox/main/ui/shapes/group.cljs +++ b/src/uxbox/main/ui/shapes/group.cljs @@ -15,6 +15,7 @@ [uxbox.main.ui.shapes.circle :as circle] [uxbox.main.ui.shapes.text :as text] [uxbox.main.ui.shapes.path :as path] + [uxbox.main.ui.shapes.image :as image] [uxbox.main.geom :as geom])) ;; --- Helpers @@ -34,6 +35,7 @@ :icon (icon/icon-component shape) :rect (rect/rect-component shape) :path (path/path-component shape) + :image (image/image-component shape) :circle (circle/circle-component shape))) (mx/defc component-container diff --git a/src/uxbox/main/ui/shapes/image.cljs b/src/uxbox/main/ui/shapes/image.cljs new file mode 100644 index 000000000..3c66e5cc2 --- /dev/null +++ b/src/uxbox/main/ui/shapes/image.cljs @@ -0,0 +1,73 @@ +;; 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.main.ui.shapes.image + (:require [beicon.core :as rx] + [lentes.core :as l] + [uxbox.util.mixins :as mx :include-macros true] + [uxbox.util.http :as http] + [uxbox.util.rstore :as rs] + [uxbox.main.state :as st] + [uxbox.main.ui.shapes.common :as common] + [uxbox.main.ui.shapes.attrs :as attrs] + [uxbox.main.data.images :as udi] + [uxbox.main.geom :as geom])) + +;; --- Refs + +(defn image-ref + [id] + (-> (l/in [:images-by-id id]) + (l/derive st/state))) + +;; --- Image Component + +(declare image-shape) + +(defn- will-mount + [own] + (let [{:keys [image-id]} (first (:rum/args own))] + (rs/emit! (udi/fetch-image image-id)) + own)) + +(mx/defcs image-component + {:mixins [mx/static mx/reactive] + :will-mount will-mount} + [own {:keys [id image-id] :as shape}] + (let [selected (mx/react common/selected-ref) + image (mx/react (image-ref image-id)) + selected? (contains? selected id) + local (:rum/local own) + on-mouse-down #(common/on-mouse-down % shape selected)] + (when image + [:g.shape {:class (when selected? "selected") + :on-mouse-down on-mouse-down} + (image-shape (assoc shape :image image))]))) + +;; --- Image Shape + +(mx/defc image-shape + {:mixins [mx/static]} + [{:keys [id x1 y1 image] :as shape}] + (let [key (str "shape-" id) + ;; rfm (geom/transformation-matrix shape) + size (geom/size shape) + props {:x x1 :y y1 :id key :key key + :preserve-aspect-ratio "none" + :xlink-href (:url image)} + attrs (-> (attrs/extract-style-attrs shape) + (merge props size))] + [:image attrs])) + +;; --- Image SVG + +(mx/defc image-svg + {:mixins [mx/static]} + [{:keys [data id view-box] :as shape}] + (let [key (str "image-svg-" id) + view-box (apply str (interpose " " view-box)) + props {:view-box view-box :id key :key key}] + [:svg props data]))