diff --git a/src/uxbox/main/data/colors.cljs b/src/uxbox/main/data/colors.cljs index a5e7cc2679..f4c206e5ff 100644 --- a/src/uxbox/main/data/colors.cljs +++ b/src/uxbox/main/data/colors.cljs @@ -9,44 +9,82 @@ (:require [clojure.set :as set] [beicon.core :as rx] [uxbox.util.uuid :as uuid] - [uxbox.main.state :as st] [uxbox.util.rstore :as rs] - [uxbox.main.state.colors :as stc] + [uxbox.util.router :as r] + [uxbox.util.color :as color] + [uxbox.main.state :as st] [uxbox.main.repo :as rp])) + +;; --- Helpers + +(defn- assoc-collection + [state coll] + (let [id (:id coll) + coll (assoc coll :type :own)] + (assoc-in state [:colors-by-id id] coll))) + +(defn- dissoc-collection + "A reduce function for dissoc the color collection + to the state map." + [state id] + (update state :colors-by-id dissoc id)) + ;; --- Initialize (declare fetch-collections) (declare collections-fetched?) -(defrecord Initialize [] +(defrecord Initialize [type id] + rs/UpdateEvent + (-apply-update [_ state] + (let [type (or type :builtin) + id (or id (if (= type :builtin) 1 nil)) + data {:type type :id id :selected #{} + :section :dashboard/colors}] + (assoc state :dashboard data))) + rs/EffectEvent (-apply-effect [_ state] - (when-not (seq (:colors-by-id state)) + (when (nil? (:colors-by-id state)) (reset! st/loader true))) rs/WatchEvent (-apply-watch [_ state s] - (let [colors (seq (:colors-by-id state))] - (if colors - (rx/empty) - (rx/merge - (rx/of (fetch-collections)) + (if (nil? (:colors-by-id state)) + (rx/merge + (rx/of (fetch-collections)) (->> (rx/filter collections-fetched? s) (rx/take 1) (rx/do #(reset! st/loader false)) - (rx/ignore))))))) + (rx/ignore))) + (rx/empty)))) (defn initialize - [] - (Initialize.)) + [type id] + (Initialize. type id)) + +;; --- Select a Collection + +(defrecord SelectCollection [type id] + rs/WatchEvent + (-apply-watch [_ state stream] + (rx/of (r/navigate :dashboard/colors + {:type type :id id})))) + +(defn select-collection + ([type] + (select-collection type nil)) + ([type id] + {:pre [(keyword? type)]} + (SelectCollection. type id))) ;; --- Collections Fetched (defrecord CollectionFetched [items] rs/UpdateEvent (-apply-update [_ state] - (reduce stc/assoc-collection state items))) + (reduce assoc-collection state items))) (defn collections-fetched [items] @@ -75,7 +113,7 @@ rs/UpdateEvent (-apply-update [_ state] (-> state - (stc/assoc-collection item) + (assoc-collection item) (assoc-in [:dashboard :collection-id] (:id item)) (assoc-in [:dashboard :collection-type] :own)))) @@ -103,13 +141,12 @@ (defrecord CollectionUpdated [item] rs/UpdateEvent (-apply-update [_ state] - (stc/assoc-collection state item))) + (assoc-collection state item))) (defn collection-updated [item] (CollectionUpdated. item)) - ;; --- Update Collection (defrecord UpdateCollection [id] @@ -144,7 +181,7 @@ (defrecord DeleteCollection [id] rs/UpdateEvent (-apply-update [_ state] - (stc/dissoc-collection state id)) + (dissoc-collection state id)) rs/WatchEvent (-apply-watch [_ state s] @@ -188,3 +225,42 @@ "Remove color in a collection." [id colors] (RemoveColors. id colors)) + +;; --- Select color + +(defrecord SelectColor [color] + rs/UpdateEvent + (-apply-update [_ state] + (update-in state [:dashboard :selected] conj color))) + +(defrecord DeselectColor [color] + rs/UpdateEvent + (-apply-update [_ state] + (update-in state [:dashboard :selected] disj color))) + +(defrecord ToggleColorSelection [color] + rs/WatchEvent + (-apply-watch [_ state stream] + (let [selected (get-in state [:dashboard :selected])] + (rx/of + (if (selected color) + (DeselectColor. color) + (SelectColor. color)))))) + +(defn toggle-color-selection + [color] + {:pre [(color/hex? color)]} + (ToggleColorSelection. color)) + +;; --- Delete Selected Colors + +(defrecord DeleteSelectedColors [] + rs/WatchEvent + (-apply-watch [_ state stream] + (let [{:keys [id selected]} (get state :dashboard)] + (rx/of (remove-colors id selected) + #(assoc-in % [:dashboard :selected] #{}))))) + +(defn delete-selected-colors + [] + (DeleteSelectedColors.)) diff --git a/src/uxbox/main/ui.cljs b/src/uxbox/main/ui.cljs index 4f7ea218d1..f6e147732e 100644 --- a/src/uxbox/main/ui.cljs +++ b/src/uxbox/main/ui.cljs @@ -6,16 +6,12 @@ ;; Copyright (c) 2015-2016 Juan de la Cruz (ns uxbox.main.ui - (:require [sablono.core :as html :refer-macros [html]] - [promesa.core :as p] + (:require [promesa.core :as p] [beicon.core :as rx] - [goog.dom :as gdom] - [rum.core :as rum] [lentes.core :as l] + [cuerdas.core :as str] + [bide.core :as bc] [uxbox.main.state :as st] - [uxbox.util.router :as rt] - [uxbox.util.rstore :as rs] - [uxbox.util.i18n :refer (tr)] [uxbox.main.data.projects :as dp] [uxbox.main.data.users :as udu] [uxbox.main.data.auth :as dauth] @@ -27,6 +23,11 @@ [uxbox.main.ui.dashboard :as dashboard] [uxbox.main.ui.settings :as settings] [uxbox.main.ui.workspace :refer (workspace)] + [uxbox.util.router :as rt] + [uxbox.util.rstore :as rs] + [uxbox.util.i18n :refer (tr)] + [uxbox.util.data :refer (parse-int uuid-str?)] + [uxbox.util.dom :as dom] [uxbox.util.mixins :as mx] [uxbox.main.ui.shapes])) @@ -99,7 +100,13 @@ :dashboard/elements (dashboard/elements-page) :dashboard/icons (dashboard/icons-page) :dashboard/images (dashboard/images-page) - :dashboard/colors (dashboard/colors-page) + :dashboard/colors (let [{:keys [id type]} params + type (when (str/alpha? type) (keyword type)) + id (cond + (str/digits? id) (parse-int id) + (uuid-str? id) (uuid id) + :else nil)] + (dashboard/colors-page type id)) :settings/profile (settings/profile-page) :settings/password (settings/password-page) :settings/notifications (settings/notifications-page) @@ -122,6 +129,8 @@ ["/dashboard/icons" :dashboard/icons] ["/dashboard/images" :dashboard/images] ["/dashboard/colors" :dashboard/colors] + ["/dashboard/colors/:type/:id" :dashboard/colors] + ["/dashboard/colors/:type" :dashboard/colors] ["/workspace/:project/:page" :workspace/page]]) (extend-protocol bc/IPathRepr diff --git a/src/uxbox/main/ui/dashboard/colors.cljs b/src/uxbox/main/ui/dashboard/colors.cljs index 1bd3ff2337..06477e3caf 100644 --- a/src/uxbox/main/ui/dashboard/colors.cljs +++ b/src/uxbox/main/ui/dashboard/colors.cljs @@ -29,260 +29,269 @@ [uxbox.util.lens :as ul] [uxbox.util.color :refer (hex->rgb)])) -;; --- Lenses +;; --- Refs (def ^:private dashboard-ref (-> (l/key :dashboard) (l/derive st/state))) -(def ^:private collections-by-id-ref +(def ^:private collections-map-ref (-> (comp (l/key :colors-by-id) (ul/merge library/+color-collections-by-id+)) (l/derive st/state))) +(def ^:private collections-ref + (-> (l/lens vals) + (l/derive collections-map-ref))) + (defn- focus-collection - [collid] - (-> (l/key collid) - (l/derive collections-by-id-ref))) + [id] + (-> (l/key id) + (l/derive collections-map-ref))) ;; --- Page Title -(defn page-title-render - [own coll] +(mx/defcs page-title + {:mixins [(rum/local {}) mx/static mx/reactive]} + [own {:keys [id] :as coll}] (let [local (:rum/local own) dashboard (mx/react dashboard-ref) - own? (:builtin coll false)] - (letfn [(on-title-save [e] - (rs/emit! (dc/rename-collection (:id coll) (:coll-name @local))) + own? (= :builtin (:type coll)) + edit? (:edit @local)] + (letfn [(save [] + (let [dom (mx/ref-node own "input") + name (.-innerText dom)] + (rs/emit! (dc/rename-collection id (str/trim name))) + (swap! local assoc :edit false))) + (cancel [] (swap! local assoc :edit false)) - (on-title-edited [e] + (edit [] + (swap! local assoc :edit true)) + (on-input-keydown [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] + (k/esc? e) (cancel) + (k/enter? e) + (do + (dom/prevent-default e) + (dom/stop-propagation e) + (save)))) + (delete-collection [] (rs/emit! (dc/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 ^:private page-title - (mx/component - {:render page-title-render - :name "page-title" - :mixins [(rum/local {}) mx/static mx/reactive]})) - -;; --- Nav - -(defn nav-render - [own] - (let [dashboard (mx/react dashboard-ref) - collections-by-id (mx/react collections-by-id-ref) - 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! (dd/set-collection-type :builtin))} - "STANDARD"] - [:li {:class-name (when own? "current") - :on-click #(rs/emit! (dd/set-collection-type :own))} - "YOUR LIBRARIES"]] - [:ul.library-elements - (if own? - [:li - [:a.btn-primary - {:on-click #(rs/emit! (dc/create-collection))} - "+ New library"]]) - (for [props collections - :let [num (count (:data props))]] - [:li {:key (str (:id props)) - :on-click #(rs/emit! (dd/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 ^:private nav - (mx/component - {:render nav-render - :name "nav" - :mixins [mx/reactive]})) + [:div.dashboard-title + [:h2 + (if edit? + [:div.dashboard-title-field + [:span.edit + {:content-editable true + :ref "input" + :on-key-down on-input-keydown} + (:name coll)] + [:span.close {:on-click cancel} i/close]] + [:span.dashboard-title-field + {:on-double-click edit} + (:name coll)])] + (if (and (not own?) coll) + [:div.edition + (if edit? + [:span {:on-click save} i/save] + [:span {:on-click edit} i/pencil]) + [:span {:on-click delete-collection} i/trash]])]))) ;; --- Grid -(defn grid-render - [own] - (let [local (:rum/local own) - dashboard (mx/react dashboard-ref) - coll-type (:collection-type dashboard) - coll-id (:collection-id dashboard) - own? (= coll-type :own) - coll (mx/react (focus-collection coll-id)) - toggle-color-check (fn [color] - (swap! local update :selected #(if (% color) (disj % color) (conj % color)))) - delete-selected #(rs/emit! (dc/remove-colors (:id coll) (:selected @local)))] - (when coll - (html - [:section.dashboard-grid.library - (page-title coll) - [:div.dashboard-grid-content - [:div.dashboard-grid-row - (if own? - [:div.grid-item.small-item.add-project - {:on-click #(udl/open! :color-form {:coll coll})} - [:span "+ New color"]]) - (for [color (remove nil? (:data coll)) - :let [color-rgb (hex->rgb color)]] - [:div.grid-item.small-item.project-th - {:key color :on-click #(when (k/shift? %) (toggle-color-check color))} - [:span.color-swatch {:style {:background-color color}}] - [:div.input-checkbox.check-primary - [:input {:type "checkbox" - :id color - :on-click #(toggle-color-check color) - :checked ((:selected @local) color)}] - [:label {:for color}]] - [:span.color-data color] - [:span.color-data (apply str "RGB " (interpose ", " color-rgb))]])]] +(mx/defc grid-item + [color selected?] + (let [color-rgb (hex->rgb color)] + (letfn [(toggle-selection [event] + (rs/emit! (dc/toggle-color-selection color))) + (toggle-selection-shifted [event] + (when (k/shift? event) + (toggle-selection event)))] + [:div.grid-item.small-item.project-th + {:on-click toggle-selection-shifted} + [:span.color-swatch {:style {:background-color color}}] + [:div.input-checkbox.check-primary + [:input {:type "checkbox" + :id color + :on-click toggle-selection + :checked selected?}] + [:label {:for color}]] + [:span.color-data color] + [:span.color-data (apply str "RGB " (interpose ", " color-rgb))]]))) - (when (not (empty? (:selected @local))) - ;; MULTISELECT OPTIONS BAR - [:div.multiselect-bar - (if own? - [:div.multiselect-nav - [:span.move-item.tooltip.tooltip-top - {:alt "Move to"} - i/organize] - [:span.delete.tooltip.tooltip-top - {:alt "Delete" :on-click delete-selected} - i/trash]] - [:div.multiselect-nav - [:span.move-item.tooltip.tooltip-top - {:alt "Copy to"} - i/organize]])])])))) +(mx/defc grid-options + [coll] + (let [own? (= (:type coll) :own)] + (letfn [(on-delete [event] + (rs/emit! (dc/delete-selected-colors)))] + ;; MULTISELECT OPTIONS BAR + [:div.multiselect-bar + (if own? + [:div.multiselect-nav + #_[:span.move-item.tooltip.tooltip-top + {:alt "Move to"} + i/organize] + [:span.delete.tooltip.tooltip-top + {:alt "Delete" + :on-click on-delete} + i/trash]] + [:div.multiselect-nav + [:span.move-item.tooltip.tooltip-top + {:alt "Copy to"} + i/organize]])]))) -(def ^:private grid - (mx/component - {:render grid-render - :name "grid" - :mixins [(rum/local {:selected #{}}) - mx/static - mx/reactive]})) +(mx/defc grid + {:mixins [mx/static]} + [selected coll] + (let [own? (= (:type coll) :own)] + [:div.dashboard-grid-content + [:div.dashboard-grid-row + (when own? + [:div.grid-item.small-item.add-project + {:on-click #(udl/open! :color-form {:coll coll})} + [:span "+ New color"]]) + (for [color (remove nil? (:data coll)) + :let [selected? (contains? selected color)]] + (-> (grid-item color selected?) + (mx/with-key (str color))))]])) -;; --- Menu - -(defn menu-render +(mx/defc content + {:mixins [mx/static mx/reactive]} [] (let [dashboard (mx/react dashboard-ref) - coll-id (:collection-id dashboard) + selected (:selected dashboard) + coll-type (:type dashboard) + coll-id (:id dashboard) coll (mx/react (focus-collection coll-id)) - ccount (count (:data coll)) ] - (html - [:section.dashboard-bar.library-gap - [:div.dashboard-info - [:span.dashboard-colors (tr "ds.num-colors" (t/c ccount))]]]))) + own? (= coll-type :own)] + (when coll + [:section.dashboard-grid.library + (page-title coll) + (grid selected coll) + (when (and (seq selected)) + (grid-options coll))]))) -(def menu - (mx/component - {:render menu-render - :name "menu" - :mixins [mx/reactive mx/static]})) +;; --- Nav +(mx/defc nav-collection + {:mixins [mx/static]} + [collection selected?] + (letfn [(on-click [event] + (let [type (:type collection) + id (:id collection)] + (rs/emit! (dc/select-collection type id))))] + (let [colors (count (:data collection))] + [:li {:on-click on-click + :class-name (when selected? "current")} + [:span.element-title (:name collection)] + [:span.element-subtitle + (tr "ds.num-elements" (t/c colors))]]))) + +(mx/defc nav-collections + {:mixins [mx/static mx/reactive]} + [type selected] + (let [own? (= type :own) + builtin? (= type :builtin) + collections (cond->> (rum/react collections-ref) + own? (filter #(= :own (:type %))) + builtin? (filter #(= :builtin (:type %))) + own? (sort-by :id))] + [:ul.library-elements + (when own? + [:li + [:a.btn-primary + {:on-click #(rs/emit! (dc/create-collection))} + "+ New library"]]) + (for [coll collections + :let [selected? (= (:id coll) selected) + key (str (:id coll))]] + (-> (nav-collection coll selected?) + (mx/with-key key)))])) + +(mx/defc nav + {:mixins [mx/static mx/reactive]} + [] + (let [dashboard (mx/react dashboard-ref) + collections (rum/react collections-ref) + selected (:id dashboard) + type (:type dashboard) + own? (= type :own) + builtin? (= type :builtin)] + (letfn [(select-tab [type] + (let [xf (filter #(= type (:type %))) + colls (sequence xf collections)] + (if-let [item (first colls)] + (rs/emit! (dc/select-collection type (:id item))) + (rs/emit! (dc/select-collection type)))))] + [: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"]] + (nav-collections type selected)]]))) ;; --- Colors Page -(defn colors-page-render +(defn- colors-page-will-mount [own] - (html - [:main.dashboard-main - (header) - [:section.dashboard-content - (nav) - (menu) - (grid)]])) + (let [[type id] (:rum/args own)] + (rs/emit! (dc/initialize type id)) + own)) -(defn colors-page-will-mount - [own] - (rs/emit! (dd/initialize :dashboard/colors) - (dc/initialize)) - own) +(defn- colors-page-did-remount + [old-own own] + (let [[old-type old-id] (:rum/args old-own) + [new-type new-id] (:rum/args own)] + (when (or (not= old-type new-type) + (not= old-id new-id)) + (rs/emit! (dc/initialize new-type new-id))) + own)) -(defn colors-page-did-remount - [old-state state] - (rs/emit! (dd/initialize :dashboard/colors)) - state) +(mx/defc colors-page + {:will-mount colors-page-will-mount + :did-remount colors-page-did-remount + :mixins [mx/static]} + [type id] + [:main.dashboard-main + (header) + [:section.dashboard-content + (nav) + (content)]]) -(def colors-page - (mx/component - {:render colors-page-render - :will-mount colors-page-will-mount - :did-remount colors-page-did-remount - :name "colors" - :mixins [mx/static]})) +;; --- Colors Lightbox (Component) -;; --- Colors Create / Edit Lightbox - -(defn- color-lightbox-render +(mx/defcs color-lightbox + {:mixins [(rum/local {}) mx/static]} [own {:keys [coll color]}] - (html (let [local (:rum/local own)] - (letfn [(submit [e] - (let [params {:id (:id coll) :from color :to (:hex @local)}] + (letfn [(on-submit [event] + (let [params {:id (:id coll) + :from color + :to (:hex @local)}] (rs/emit! (dc/replace-color params)) (udl/close!))) - (on-change [e] - (let [value (str/trim (dom/event->value e))] - (swap! local assoc :hex value)))] - (html - [:div.lightbox-body - [:h3 "New color"] - [:form - [:div.row-flex.center - (colorpicker - :value (or (:hex @local) color "#00ccff") - :on-change #(swap! local assoc :hex %))] + (on-change [event] + (let [value (str/trim (dom/event->value event))] + (swap! local assoc :hex value))) + (on-close [event] + (udl/close!))] + [:div.lightbox-body + [:h3 "New color"] + [:form + [:div.row-flex.center + (colorpicker + :value (or (:hex @local) color "#00ccff") + :on-change #(swap! local assoc :hex %))] - [:input#project-btn.btn-primary - {:value "+ Add color" - :on-click submit - :type "button"}]] - [:a.close {:on-click #(udl/close!)} - i/close]]))))) - -(def color-lightbox - (mx/component - {:render color-lightbox-render - :name "color-lightbox" - :mixins [(rum/local {}) - mx/static]})) + [:input#project-btn.btn-primary + {:value "+ Add color" + :on-click on-submit + :type "button"}]] + [:a.close {:on-click on-close} i/close]]))) (defmethod lbx/render-lightbox :color-form [params] diff --git a/src/uxbox/main/ui/dashboard/header.cljs b/src/uxbox/main/ui/dashboard/header.cljs index 5e831e1f86..2674def94b 100644 --- a/src/uxbox/main/ui/dashboard/header.cljs +++ b/src/uxbox/main/ui/dashboard/header.cljs @@ -6,9 +6,7 @@ ;; Copyright (c) 2015-2016 Juan de la Cruz (ns uxbox.main.ui.dashboard.header - (:require [sablono.core :as html :refer-macros [html]] - [rum.core :as rum] - [lentes.core :as l] + (:require [lentes.core :as l] [uxbox.util.i18n :refer (tr)] [uxbox.util.router :as r] [uxbox.util.rstore :as rs] @@ -20,43 +18,37 @@ [uxbox.util.mixins :as mx])) (def header-ref - (as-> (l/in [:dashboard]) $ - (l/derive $ s/state))) + (-> (l/in [:dashboard]) + (l/derive s/state))) -(defn- header-link +(mx/defc header-link [section content] (let [link (r/route-for section)] - (html - [:a {:href (str "/#" link)} content]))) + [:a {:href (str "/#" link)} content])) -(defn header-render - [own] +(mx/defc header + {:mixins [mx/static mx/reactive]} + [] (let [local (mx/react header-ref) projects? (= (:section local) :dashboard/projects) elements? (= (:section local) :dashboard/elements) icons? (= (:section local) :dashboard/icons) images? (= (:section local) :dashboard/images) colors? (= (:section local) :dashboard/colors)] - (html - [:header#main-bar.main-bar - [:div.main-logo - (header-link :dashboard/projects i/logo)] - [:ul.main-nav - [:li {:class (when projects? "current")} - (header-link :dashboard/projects (tr "ds.projects"))] - [:li {:class (when elements? "current")} - (header-link :dashboard/elements (tr "ds.elements"))] - [:li {:class (when icons? "current")} - (header-link :dashboard/icons (tr "ds.icons"))] - [:li {:class (when images? "current")} - (header-link :dashboard/images (tr "ds.images"))] - [:li {:class (when colors? "current")} - (header-link :dashboard/colors (tr "ds.colors"))]] - (ui.u/user)]))) + [:header#main-bar.main-bar + [:div.main-logo + (header-link :dashboard/projects i/logo)] + [:ul.main-nav + [:li {:class (when projects? "current")} + (header-link :dashboard/projects (tr "ds.projects"))] + [:li {:class (when elements? "current")} + (header-link :dashboard/elements (tr "ds.elements"))] + [:li {:class (when icons? "current")} + (header-link :dashboard/icons (tr "ds.icons"))] + [:li {:class (when images? "current")} + (header-link :dashboard/images (tr "ds.images"))] + [:li {:class (when colors? "current")} + (header-link :dashboard/colors (tr "ds.colors"))]] + (ui.u/user)])) + -(def header - (mx/component - {:render header-render - :name "header" - :mixins [rum/static - mx/reactive]}))