From c4d7d545ae9d590a3c069ffd217f78f3d432aab9 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 18 Jul 2019 12:28:22 +0200 Subject: [PATCH] feat(frontend): refactor dashboard components --- frontend/src/uxbox/main/data/icons.cljs | 8 +- frontend/src/uxbox/main/ui/dashboard.cljs | 26 +- .../src/uxbox/main/ui/dashboard/colors.cljs | 556 ++++++------- .../src/uxbox/main/ui/dashboard/elements.cljs | 10 +- .../src/uxbox/main/ui/dashboard/header.cljs | 47 +- .../src/uxbox/main/ui/dashboard/icons.cljs | 671 ++++++++-------- .../src/uxbox/main/ui/dashboard/images.cljs | 750 +++++++++--------- .../src/uxbox/main/ui/dashboard/projects.cljs | 368 +++++---- 8 files changed, 1240 insertions(+), 1196 deletions(-) diff --git a/frontend/src/uxbox/main/data/icons.cljs b/frontend/src/uxbox/main/data/icons.cljs index eef0195d0..ee5bb1cee 100644 --- a/frontend/src/uxbox/main/data/icons.cljs +++ b/frontend/src/uxbox/main/data/icons.cljs @@ -26,11 +26,9 @@ (defrecord Initialize [type id] ptk/UpdateEvent (update [_ state] - (let [type (or type :own) - data {:type type :id id :selected #{}}] - (-> state - (assoc-in [:dashboard :icons] data) - (assoc-in [:dashboard :section] :dashboard/icons)))) + (-> state + (assoc-in [:dashboard :icons] {:selected #{}}) + (assoc-in [:dashboard :section] :dashboard/icons))) ptk/WatchEvent (watch [_ state s] diff --git a/frontend/src/uxbox/main/ui/dashboard.cljs b/frontend/src/uxbox/main/ui/dashboard.cljs index baec9e71c..0686fb8ec 100644 --- a/frontend/src/uxbox/main/ui/dashboard.cljs +++ b/frontend/src/uxbox/main/ui/dashboard.cljs @@ -1,12 +1,28 @@ (ns uxbox.main.ui.dashboard - (:require [uxbox.main.ui.dashboard.projects :as projects] - ;; [uxbox.main.ui.dashboard.elements :as elements] - [uxbox.main.ui.dashboard.icons :as icons] - [uxbox.main.ui.dashboard.images :as images] - [uxbox.main.ui.dashboard.colors :as colors])) + (:require + [rumext.core :as mx :include-macros true] + [uxbox.main.ui.dashboard.header :refer [header]] + [uxbox.main.ui.dashboard.projects :as projects] + ;; [uxbox.main.ui.dashboard.elements :as elements] + [uxbox.main.ui.dashboard.icons :as icons] + [uxbox.main.ui.dashboard.images :as images] + [uxbox.main.ui.dashboard.colors :as colors] + [uxbox.main.ui.messages :refer [messages-widget]])) (def projects-page projects/projects-page) ;; (def elements-page elements/elements-page) (def icons-page icons/icons-page) (def images-page images/images-page) (def colors-page colors/colors-page) + +(mx/defc dashboard + [props] + (prn "dashboard" props) + [:main.dashboard-main + (messages-widget) + (header) + (case (:section props) + :icons (icons/icons-page props) + :images (images/images-page props) + :projects (projects/projects-page props) + :colors (colors/colors-page props))]) diff --git a/frontend/src/uxbox/main/ui/dashboard/colors.cljs b/frontend/src/uxbox/main/ui/dashboard/colors.cljs index 6c565503c..2cc178fa2 100644 --- a/frontend/src/uxbox/main/ui/dashboard/colors.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/colors.cljs @@ -28,311 +28,321 @@ ;; --- Refs -(def dashboard-ref - (-> (l/in [:dashboard :colors]) - (l/derive st/state))) - (def collections-ref (-> (l/key :colors-collections) (l/derive st/state))) +(def opts-ref + (-> (l/in [:dashboard :colors]) + (l/derive st/state))) + + ;; --- Page Title -(mx/defcs page-title - {:mixins [(mx/local) mx/static mx/reactive]} - [{:keys [rum/local] :as own} {:keys [id] :as coll}] - (let [dashboard (mx/react dashboard-ref) - own? (= :own (:type coll)) - edit? (:edit @local)] - (letfn [(save [] - (let [dom (mx/ref-node own "input") - name (dom/get-inner-text dom)] - (st/emit! (dc/rename-collection id (str/trim name))) - (swap! local assoc :edit false))) - (cancel [] - (swap! local assoc :edit false)) - (edit [] - (swap! local assoc :edit true)) - (on-input-keydown [e] - (cond - (k/esc? e) (cancel) - (k/enter? e) - (do - (dom/prevent-default e) - (dom/stop-propagation e) - (save)))) - (delete [] - (st/emit! (dc/delete-collection id))) - (on-delete [] - (udl/open! :confirm {:on-accept delete}))] - [: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]] - (if own? - [:span.dashboard-title-field {:on-double-click edit} - (:name coll)] - [:span.dashboard-title-field {} - (:name coll)]))] - (when (and own? coll) - [:div.edition {} +(mx/def page-title + :mixins [(mx/local) mx/static mx/reactive] + + :render + (fn [{:keys [::mx/local] :as own} + {:keys [id] :as coll}] + (let [own? (= :own (:type coll)) + edit? (:edit @local)] + (letfn [(save [] + (let [dom (mx/ref-node own "input") + name (dom/get-inner-text dom)] + (st/emit! (dc/rename-collection id (str/trim name))) + (swap! local assoc :edit false))) + (cancel [] + (swap! local assoc :edit false)) + (edit [] + (swap! local assoc :edit true)) + (on-input-keydown [e] + (cond + (k/esc? e) (cancel) + (k/enter? e) + (do + (dom/prevent-default e) + (dom/stop-propagation e) + (save)))) + (delete [] + (st/emit! (dc/delete-collection id))) + (on-delete [] + (udl/open! :confirm {:on-accept delete}))] + [:div.dashboard-title + [:h2 (if edit? - [:span {:on-click save} ^:inline i/save] - [:span {:on-click edit} ^:inline i/pencil]) - [:span {:on-click on-delete} ^:inline i/trash]])]))) + [: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]] + (if own? + [:span.dashboard-title-field {:on-double-click edit} + (:name coll)] + [:span.dashboard-title-field + (:name coll)]))] + (when (and own? coll) + [:div.edition + (if edit? + [:span {:on-click save} i/save] + [:span {:on-click edit} i/pencil]) + [:span {:on-click on-delete} i/trash]])])))) ;; --- Nav -(mx/defcs nav-item - {:mixins [(mx/local) mx/static]} - [{:keys [rum/local]} {:keys [id type name] :as coll} selected?] - (let [colors (count (:colors coll)) - editable? (= type :own)] - (letfn [(on-click [event] - (let [type (or type :own)] - (st/emit! (rt/navigate :dashboard/colors nil {:type type :id id})))) - (on-input-change [event] - (let [value (dom/get-target event) - value (dom/get-value value)] - (swap! local assoc :name value))) - (on-cancel [event] - (swap! local dissoc :name) - (swap! local dissoc :edit)) - (on-double-click [event] - (when editable? - (swap! local assoc :edit true))) - (on-input-keyup [event] - (when (k/enter? event) +(mx/def nav-item + :mixins [(mx/local) mx/static] + + :render + (fn [{:keys [::mx/local] :as own} + {:keys [id type name ::selected?] :as coll}] + (let [colors (count (:colors coll)) + editable? (= type :own)] + (letfn [(on-click [event] + (let [type (or type :own)] + (st/emit! (rt/navigate :dashboard/colors nil {:type type :id id})))) + (on-input-change [event] (let [value (dom/get-target event) value (dom/get-value value)] - (st/emit! (dc/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) name) - :on-change on-input-change - :on-key-down on-input-keyup}] - [:span.close {:on-click on-cancel} i/close]] - [:span.element-title {} name]) - [:span.element-subtitle {} - (tr "ds.num-elements" (t/c colors))]]))) + (swap! local assoc :name value))) + (on-cancel [event] + (swap! local dissoc :name) + (swap! local dissoc :edit)) + (on-double-click [event] + (when editable? + (swap! local assoc :edit true))) + (on-input-keyup [event] + (when (k/enter? event) + (let [value (dom/get-target event) + value (dom/get-value value)] + (st/emit! (dc/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) name) + :on-change on-input-change + :on-key-down on-input-keyup}] + [:span.close {:on-click on-cancel} i/close]] + [:span.element-title name]) + [:span.element-subtitle + (tr "ds.num-elements" (t/c colors))]])))) -(mx/defc nav-section - {:mixins [mx/static]} - [type selected colls] - (let [own? (= type :own) - builtin? (= type :builtin) - colls (cond->> (vals colls) - own? (filter #(= :own (:type %))) - builtin? (filter #(= :builtin (:type %))) - own? (sort-by :created-at) - builtin? (sort-by :created-at))] - [:ul.library-elements {} - (when own? - [:li {} - [:a.btn-primary {:on-click #(st/emit! (dc/create-collection))} - (tr "ds.colors-collection.new")]]) - (for [coll colls] - (let [id (:id coll) - selected? (= id selected)] - (-> (nav-item coll selected?) - (mx/with-key id))))])) +(mx/def nav + :mixins [mx/static mx/reactive] -(mx/defc nav - {:mixins [mx/static]} - [{:keys [id type] :as state} colls]2 - (letfn [(select-tab [type] - (if-let [coll (->> (map second colls) - (filter #(= type (:type %))) - (sort-by :created-at) - (first))] - (st/emit! (rt/nav :dashboard/colors nil {:type type :id (:id coll)})) - (st/emit! (rt/nav :dashboard/colors nil {:type type}))))] - [:div.library-bar {} - [:div.library-bar-inside {} - [:ul.library-tabs {} - [:li {:class-name (when (= type :own) "current") - :on-click (partial select-tab :own)} - (tr "ds.your-colors-title")] - [:li {:class-name (when (= type :builtin) "current") - :on-click (partial select-tab :builtin)} - (tr "ds.store-colors-title")]] - (nav-section type id colls)]])) + :render + (fn [own {:keys [id type] :as props}] + (let [own? (= type :own) + builtin? (= type :builtin) + colls (mx/react collections-ref) + select-tab (fn [type] + (if-let [coll (->> (vals colls) + (filter #(= type (:type %))) + (sort-by :created-at) + (first))] + (st/emit! (rt/nav :dashboard/colors nil {:type type :id (:id coll)})) + (st/emit! (rt/nav :dashboard/colors nil {:type type}))))] + + [: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-colors-title")] + [:li {:class-name (when builtin? "current") + :on-click (partial select-tab :builtin)} + (tr "ds.store-colors-title")]] + [:ul.library-elements + (when own? + [:li + [:a.btn-primary {:on-click #(st/emit! (dc/create-collection))} + (tr "ds.colors-collection.new")]]) + (for [coll (cond->> (vals colls) + own? (filter #(= :own (:type %))) + builtin? (filter #(= :builtin (:type %))) + true (sort-by :created-at))] + (let [selected? (= (:id coll) id)] + (nav-item (assoc coll ::selected? selected?))))]]]))) ;; --- Grid (mx/defc grid-form [coll-id] - [:div.grid-item.small-item.add-project {:on-click #(udl/open! :color-form {:coll coll-id})} - [:span {} (tr "ds.color-new")]]) + [:div.grid-item.small-item.add-project + {:on-click #(udl/open! :color-form {:coll coll-id})} + [:span (tr "ds.color-new")]]) -(mx/defc grid-options-tooltip - {:mixins [mx/reactive mx/static]} - [& {:keys [selected on-select title]}] - {:pre [(uuid? selected) - (fn? on-select) - (string? title)]} - (let [colls (mx/react collections-ref) - 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] - (for [{:keys [id name] :as coll} colls] - [:li {:key (str id)} - [:a {:on-click #(on-select % id)} name]])])) +(mx/def grid-options-tooltip + :mixins [mx/reactive mx/static] -(mx/defcs grid-options - {:mixins [mx/static (mx/local)]} - [{:keys [rum/local]} {:keys [type id] :as coll}] - (letfn [(delete [event] - (st/emit! (dc/delete-selected-colors))) - (on-delete [event] - (udl/open! :confirm {:on-accept delete})) - (on-toggle-copy [event] - (swap! local update :show-copy-tooltip not) - (swap! local assoc :show-move-tooltip false)) - (on-toggle-move [event] - (swap! local update :show-move-tooltip not) - (swap! local assoc :show-copy-tooltip false)) - (on-copy [selected] - (swap! local assoc - :show-move-tooltip false - :show-copy-tooltip false) - (st/emit! (dc/copy-selected selected))) - (on-move [selected] - (swap! local assoc - :show-move-tooltip false - :show-copy-tooltip false) - (st/emit! (dc/move-selected id selected)))] + :render + (fn [own {:keys [selected on-select title]}] + {:pre [(uuid? selected) + (fn? on-select) + (string? title)]} + (let [colls (mx/react collections-ref) + 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] + (for [{:keys [id name] :as coll} colls] + [:li {:key (str id)} + [:a {:on-click #(on-select % id)} name]])]))) - ;; 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] - [:span.delete.tooltip.tooltip-top - {:alt (tr "ds.multiselect-bar.delete") - :on-click on-delete} - i/trash]] +(mx/def grid-options + :mixins [mx/static (mx/local)] - ;; 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]])])) + :render + (fn [{:keys [::mx/local] :as own} + {:keys [type id] :as coll}] + (letfn [(delete [event] + (st/emit! (dc/delete-selected-colors))) + (on-delete [event] + (udl/open! :confirm {:on-accept delete})) + (on-toggle-copy [event] + (swap! local update :show-copy-tooltip not) + (swap! local assoc :show-move-tooltip false)) + (on-toggle-move [event] + (swap! local update :show-move-tooltip not) + (swap! local assoc :show-copy-tooltip false)) + (on-copy [selected] + (swap! local assoc + :show-move-tooltip false + :show-copy-tooltip false) + (st/emit! (dc/copy-selected selected))) + (on-move [selected] + (swap! local assoc + :show-move-tooltip false + :show-copy-tooltip false) + (st/emit! (dc/move-selected id selected)))] -(mx/defc grid-item - {:mixins [mx/static]} - [color selected?] - (letfn [(toggle-selection [event] - (st/emit! (dc/toggle-color-selection color)))] - [:div.grid-item.small-item.project-th {:on-click toggle-selection} - [: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 ", " (hex->rgb color)))]])) + ;; 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] + [:span.delete.tooltip.tooltip-top + {:alt (tr "ds.multiselect-bar.delete") + :on-click on-delete} + i/trash]] -(mx/defc grid - {:mixins [mx/static]} - [{:keys [id type colors] :as coll} selected] - (let [editable? (or (= :own type) (nil? id)) - colors (->> (remove nil? colors) - (sort-by identity))] - [:div.dashboard-grid-content {} - [:div.dashboard-grid-row {} - (when editable? (grid-form id)) - (for [color colors] - (let [selected? (contains? selected color)] - (-> (grid-item color selected?) - (mx/with-key (str color)))))]])) + ;; 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]])]))) -(mx/defc content - {:mixins [mx/static]} - [{:keys [selected] :as state} coll] - [:section.dashboard-grid.library {} - (page-title coll) - (grid coll selected) - (when (and (seq selected)) - (grid-options coll))]) +(mx/def grid-item + :key-fn :color + :mixins [mx/static] + + :render + (fn [own {:keys [color selected?] :as props}] + (letfn [(toggle-selection [event] + (st/emit! (dc/toggle-color-selection color)))] + [:div.grid-item.small-item.project-th {:on-click toggle-selection} + [:span.color-swatch {:style {:background-color color}}] + [:div.input-checkbox.check-primary + [:input {:type "checkbox" + :id color + :on-change toggle-selection + :checked selected?}] + [:label {:for color}]] + [:span.color-data color] + [:span.color-data (apply str "RGB " (interpose ", " (hex->rgb color)))]]))) + +(mx/def grid + :mixins [mx/static] + + :render + (fn [own {:keys [selected ::coll] :as props}] + (let [{:keys [id type colors]} coll + editable? (or (= :own type) (nil? id)) + colors (->> (remove nil? colors) + (sort-by identity))] + [:div.dashboard-grid-content + [:div.dashboard-grid-row + (when editable? (grid-form props)) + (for [color colors] + (let [selected? (contains? selected color)] + (grid-item {:color color :selected? selected?})))]]))) + +(mx/def content + :mixins [mx/reactive mx/static] + + :init + (fn [own {:keys [id] :as props}] + (assoc own ::coll-ref (-> (l/in [:colors-collections id]) + (l/derive st/state)))) + + :render + (fn [own props] + (let [opts (mx/react opts-ref) + coll (mx/react (::coll-ref own)) + props (merge opts props)] + [:section.dashboard-grid.library + (page-title coll) + (grid (assoc props ::coll coll)) + (when (seq (:selected opts)) + (grid-options props))]))) ;; --- Colors Page -(defn- colors-page-will-mount - [own] - (let [[type id] (:rum/args own)] - (st/emit! (dc/initialize type id)) - own)) +(mx/def colors-page + :key-fn identity + :mixins #{mx/static mx/reactive} -(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)) - (st/emit! (dc/initialize new-type new-id))) - own)) + :init + (fn [own props] + (let [{:keys [type id]} (::mx/props own)] + (st/emit! (dc/initialize type id)) + own)) -(mx/defc colors-page - {:will-mount colors-page-will-mount - :did-remount colors-page-did-remount - :mixins [mx/static mx/reactive]} - [_ _] - (let [state (mx/react dashboard-ref) - colls (mx/react collections-ref) - coll (get colls (:id state))] - [:main.dashboard-main {} - (messages-widget) - (header) - [:section.dashboard-content {} - (nav state colls) - (content state coll)]])) + :render + (fn [own {:keys [type] :as props}] + (let [type (or type :own) + props (assoc props :type type)] + [:section.dashboard-content + (nav props) + (content props)]))) ;; --- Colors Lightbox (Component) (mx/defcs color-lightbox {:mixins [(mx/local {}) mx/static]} - [{:keys [rum/local]} {:keys [coll color] :as params}] + [{:keys [::mx/local]} {:keys [coll color] :as params}] (letfn [(on-submit [event] (let [params {:id coll :from color @@ -344,10 +354,10 @@ (swap! local assoc :hex value))) (on-close [event] (udl/close!))] - [:div.lightbox-body {} - [:h3 {} (tr "ds.color-lightbox.title")] - [:form {} - [:div.row-flex.center {} + [:div.lightbox-body + [:h3 (tr "ds.color-lightbox.title")] + [:form + [:div.row-flex.center (colorpicker :value (or (:hex @local) color "#00ccff") :on-change #(swap! local assoc :hex %))] diff --git a/frontend/src/uxbox/main/ui/dashboard/elements.cljs b/frontend/src/uxbox/main/ui/dashboard/elements.cljs index d04a10aeb..2c87e6b24 100644 --- a/frontend/src/uxbox/main/ui/dashboard/elements.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/elements.cljs @@ -140,21 +140,15 @@ ;; (page-title) ;; (grid)]]])) -;; (defn elements-page-will-mount +;; (defn elements-page-init ;; [own] ;; (st/emit! (dd/initialize :dashboard/elements)) ;; own) -;; (defn elements-page-did-remount -;; [old-state state] -;; (st/emit! (dd/initialize :dashboard/elements)) -;; state) - ;; (def elements-page ;; (mx/component ;; {:render elements-page-render -;; :will-mount elements-page-will-mount -;; :did-remount elements-page-did-remount +;; :init elements-page-init ;; :name "elements-page" ;; :mixins [mx/static]})) diff --git a/frontend/src/uxbox/main/ui/dashboard/header.cljs b/frontend/src/uxbox/main/ui/dashboard/header.cljs index a7bb84cd0..145088704 100644 --- a/frontend/src/uxbox/main/ui/dashboard/header.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/header.cljs @@ -16,38 +16,45 @@ [uxbox.util.router :as rt] [rumext.core :as mx :include-macros true])) -(def header-ref - (-> (l/key :dashboard) - (l/derive st/state))) - (mx/defc header-link - [section content] + [{:keys [section content] :as props}] (let [on-click #(st/emit! (rt/navigate section))] [:a {:on-click on-click} content])) -(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)] +(mx/def header + :mixins [mx/static mx/reactive] + :init + (fn [own props] + (assoc own ::section-ref (-> (l/in [:dashboard :section]) + (l/derive st/state)))) + + :render + (fn [own props] + (let [section (mx/react (::section-ref own)) + projects? (= section :dashboard/projects) + elements? (= section :dashboard/elements) + icons? (= section :dashboard/icons) + images? (= section :dashboard/images) + colors? (= section :dashboard/colors)] [:header#main-bar.main-bar [:div.main-logo - (header-link :dashboard/projects i/logo)] + (header-link {:section :dashboard/projects + :content i/logo})] [:ul.main-nav [:li {:class (when projects? "current")} - (header-link :dashboard/projects (tr "ds.projects"))] + (header-link {:section :dashboard/projects + :content (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"))] + (header-link {:section :dashboard/icons + :content (tr "ds.icons")})] [:li {:class (when images? "current")} - (header-link :dashboard/images (tr "ds.images"))] + (header-link {:section :dashboard/images + :content (tr "ds.images")})] [:li {:class (when colors? "current")} - (header-link :dashboard/colors (tr "ds.colors"))]] - (ui.u/user)])) + (header-link {:section :dashboard/colors + :content (tr "ds.colors")})]] + (ui.u/user)]))) diff --git a/frontend/src/uxbox/main/ui/dashboard/icons.cljs b/frontend/src/uxbox/main/ui/dashboard/icons.cljs index 1a1748a89..825c12ee5 100644 --- a/frontend/src/uxbox/main/ui/dashboard/icons.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/icons.cljs @@ -2,30 +2,31 @@ ;; 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) 2015-2019 Andrey Antukh (ns uxbox.main.ui.dashboard.icons - (:require [lentes.core :as l] - [cuerdas.core :as str] - [uxbox.main.store :as st] - [uxbox.main.data.lightbox :as udl] - [uxbox.main.data.icons :as di] - [uxbox.builtins.icons :as i] - [uxbox.main.ui.shapes.icon :as icon] - [uxbox.main.ui.lightbox :as lbx] - [uxbox.main.ui.dashboard.header :refer (header)] - [uxbox.main.ui.keyboard :as kbd] - [uxbox.util.router :as rt] - [uxbox.util.i18n :as t :refer (tr)] - [uxbox.util.data :refer (read-string)] - [rumext.core :as mx :include-macros true] - [uxbox.util.time :as dt] - [potok.core :as ptk] - [uxbox.util.forms :as sc] - [uxbox.util.lens :as ul] - [uxbox.util.i18n :refer (tr)] - [uxbox.util.dom :as dom])) + (:require + [cuerdas.core :as str] + [lentes.core :as l] + [potok.core :as ptk] + [rumext.core :as mx :include-macros true] + [uxbox.builtins.icons :as i] + [uxbox.main.data.icons :as di] + [uxbox.main.data.lightbox :as udl] + [uxbox.main.store :as st] + [uxbox.main.ui.dashboard.header :refer (header)] + [uxbox.main.ui.keyboard :as kbd] + [uxbox.main.ui.lightbox :as lbx] + [uxbox.main.ui.shapes.icon :as icon] + [uxbox.util.data :refer (read-string)] + [uxbox.util.dom :as dom] + [uxbox.util.forms :as sc] + [uxbox.util.i18n :as t :refer (tr)] + [uxbox.util.i18n :refer (tr)] + [uxbox.util.lens :as ul] + [uxbox.util.router :as rt] + [uxbox.util.time :as dt])) ;; --- Helpers & Constants @@ -55,163 +56,165 @@ ;; --- Refs -(def ^:private dashboard-ref - (-> (l/in [:dashboard :icons]) - (l/derive st/state))) - (def collections-ref (-> (l/key :icons-collections) (l/derive st/state))) +(def opts-ref + (-> (l/in [:dashboard :icons]) + (l/derive st/state))) + +;; TODO: remove when sidebar is refactored (def icons-ref (-> (l/key :icons) (l/derive st/state))) ;; --- Page Title -(mx/defcs page-title - {:mixins [(mx/local) mx/static mx/reactive]} - [{:keys [rum/local] :as own} {:keys [id] :as coll}] - (let [dashboard (mx/react dashboard-ref) - own? (= :own (:type coll)) - edit? (:edit @local)] - (letfn [(on-save [e] - (let [dom (mx/ref-node own "input") - name (.-innerText dom)] - (st/emit! (di/rename-collection id (str/trim name))) - (swap! local assoc :edit false))) - (on-cancel [e] - (swap! local assoc :edit false)) - (on-edit [e] - (swap! local assoc :edit true)) - (on-input-keydown [e] - (cond - (kbd/esc? e) (on-cancel e) - (kbd/enter? e) - (do - (dom/prevent-default e) - (dom/stop-propagation e) - (on-save e)))) - (delete [] - (st/emit! (di/delete-collection id))) - (on-delete [] - (udl/open! :confirm {:on-accept delete}))] - [: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 on-cancel} i/close]] - (if own? - [:span.dashboard-title-field - {:on-double-click on-edit} - (:name coll)] - [:span.dashboard-title-field - (:name coll "Storage")]))] - (when (and own? coll) - [:div.edition {} +(mx/def page-title + :mixins [(mx/local) mx/static] + + :render + (fn [{:keys [::mx/local] :as own} {:keys [id type] :as coll}] + (let [own? (= :own (:type coll)) + edit? (:edit @local)] + (letfn [(on-save [e] + (let [dom (mx/ref-node own "input") + name (.-innerText dom)] + (st/emit! (di/rename-collection id (str/trim name))) + (swap! local assoc :edit false))) + (on-cancel [e] + (swap! local assoc :edit false)) + (on-edit [e] + (swap! local assoc :edit true)) + (on-input-keydown [e] + (cond + (kbd/esc? e) (on-cancel e) + (kbd/enter? e) + (do + (dom/prevent-default e) + (dom/stop-propagation e) + (on-save e)))) + (delete [] + (st/emit! (di/delete-collection id))) + (on-delete [] + (udl/open! :confirm {:on-accept delete}))] + [:div.dashboard-title + [:h2 (if edit? - [:span {:on-click on-save} i/save] - [:span {:on-click on-edit} i/pencil]) - [:span {:on-click on-delete} i/trash]])]))) + [:div.dashboard-title-field + [:span.edit + {:content-editable true + :ref "input" + :on-key-down on-input-keydown} + (:name coll)] + [:span.close {:on-click on-cancel} i/close]] + (if own? + [:span.dashboard-title-field + {:on-double-click on-edit} + (:name coll)] + [:span.dashboard-title-field + (:name coll "Storage")]))] + (when (and own? coll) + [:div.edition + (if edit? + [:span {:on-click on-save} i/save] + [:span {:on-click on-edit} i/pencil]) + [:span {:on-click on-delete} i/trash]])])))) ;; --- Nav -(defn react-count-icons +(defn num-icons-ref [id] - (->> (mx/react icons-ref) - (vals) - (filter #(= id (:collection %))) - (count))) + (let [selector (fn [icons] (count (filter #(= id (:collection %)) (vals icons))))] + (-> (comp (l/key :icons) + (l/lens selector)) + (l/derive st/state)))) -(mx/defcs nav-item - {:mixins [(mx/local) mx/static mx/reactive]} - [own {:keys [id type name num-icons] :as coll} selected?] - (let [num-icons (or num-icons (react-count-icons id)) - editable? (= type :own) - local (:rum/local own)] - (letfn [(on-click [event] - (let [type (or type :own)] - (st/emit! (rt/navigate :dashboard/icons {} {:type type :id id})))) - (on-input-change [event] - (let [value (dom/get-target event) - value (dom/get-value value)] - (swap! local assoc :name value))) - (on-cancel [event] - (swap! local dissoc :name) - (swap! local dissoc :edit)) - (on-double-click [event] - (when editable? - (swap! local assoc :edit true))) - (on-input-keyup [event] - (when (kbd/enter? event) +(mx/def nav-item + :key-fn :id + :mixins [(mx/local) mx/static mx/reactive] + + :init + (fn [own {:keys [id] :as props}] + (assoc own ::num-icons-ref (num-icons-ref id))) + + :render + (fn [{:keys [::mx/local] :as own} + {:keys [id type name num-icons ::selected?] :as coll}] + (let [num-icons (or num-icons (mx/react (::num-icons-ref own))) + editable? (= type :own)] + (letfn [(on-click [event] + (let [type (or type :own)] + (st/emit! (rt/navigate :dashboard/icons {} {:type type :id id})))) + (on-input-change [event] (let [value (dom/get-target event) value (dom/get-value 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 coll 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 coll name "Storage")]) - [:span.element-subtitle {} - (tr "ds.num-elements" (t/c num-icons))]]))) + (swap! local assoc :name value))) + (on-cancel [event] + (swap! local dissoc :name) + (swap! local dissoc :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) + value (dom/get-value 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")]) + [:span.element-subtitle + (tr "ds.num-elements" (t/c num-icons))]])))) -(mx/defc nav-section - {:mixins [mx/static mx/reactive]} - [type selected colls] - (let [own? (= type :own) - builtin? (= type :builtin) - colls (cond->> (vals colls) - own? (filter #(= :own (:type %))) - builtin? (filter #(= :builtin (:type %)))) - colls (sort-by :name colls)] - [: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 nil (nil? selected))) - (for [coll colls] - (let [selected? (= (:id coll) selected)] - (-> (nav-item coll selected?) - (mx/with-key (:id coll)))))])) +(mx/def nav + :mixins [mx/static mx/reactive] -(mx/defc nav - {:mixins [mx/static]} - [{:keys [type id] :as state} colls] - (let [own? (= type :own) - builtin? (= type :builtin)] - (letfn [(select-tab [type] - (if-let [coll (->> (map second colls) - (filter #(= type (:type %))) - (sort-by :created-at) - (first))] - (st/emit! (rt/nav :dashboard/icons nil {:type type :id (:id coll)})) - (st/emit! (rt/nav :dashboard/icons nil {:type type}))))] - [:div.library-bar {} - [:div.library-bar-inside {} - [:ul.library-tabs {} + :render + (fn [own {:keys [id type] :as props}] + (let [own? (= type :own) + builtin? (= type :builtin) + colls (mx/react collections-ref) + select-tab (fn [type] + (if-let [coll (->> (vals colls) + (filter #(= type (:type %))) + (sort-by :created-at) + (first))] + (st/emit! (rt/nav :dashboard/icons nil {:type type :id (:id coll)})) + (st/emit! (rt/nav :dashboard/icons nil {:type type}))))] + [: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-icons-title")] [:li {:class-name (when builtin? "current") :on-click (partial select-tab :builtin)} (tr "ds.store-icons-title")]] - (nav-section type id colls)]]))) + [: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 [coll (cond->> (vals colls) + own? (filter #(= :own (:type %))) + builtin? (filter #(= :builtin (:type %))) + true (sort-by :name))] + (let [selected? (= (:id coll) id)] + (nav-item (assoc coll ::selected? selected?))))]]]))) ;; --- Grid @@ -224,7 +227,7 @@ (let [files (dom/get-event-files event)] (st/emit! (di/create-icons coll-id files))))] [:div.grid-item.small-item.add-project {:on-click forward-click} - [:span {} (tr "ds.icon.new")] + [:span (tr "ds.icon.new")] [:input.upload-image-input {:style {:display "none"} :multiple true @@ -234,34 +237,37 @@ :type "file" :on-change on-file-selected}]])) -(mx/defc grid-options-tooltip - {:mixins [mx/reactive mx/static]} - [& {:keys [selected on-select title]}] - {:pre [(uuid? selected) - (fn? on-select) - (string? title)]} - (let [colls (mx/react collections-ref) - 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 (str id)} - [:a {:on-click #(on-select % id)} name]])])) +(mx/def grid-options-tooltip + :mixins [mx/reactive mx/static] -(mx/defcs grid-options - {:mixins [(mx/local) mx/static]} - [own {:keys [type id] :as coll} selected] - (let [editable? (or (= type :own) (nil? coll)) - local (:rum/local own)] + :render + (fn [own {:keys [selected on-select title]}] + {:pre [(uuid? selected) + (fn? on-select) + (string? title)]} + (let [colls (mx/react collections-ref) + 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]])]))) + +(mx/def grid-options + :mixins [(mx/local) mx/static] + + :render + (fn [{:keys [::mx/local] :as own} + {:keys [id type selected] :as props}] (letfn [(delete [] (st/emit! (di/delete-selected))) (on-delete [event] @@ -283,26 +289,27 @@ (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? coll)) + [: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)) + (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)) + (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 @@ -320,158 +327,164 @@ {: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)) + (grid-options-tooltip {:selected id + :title (tr "ds.multiselect-bar.copy-to-library") + :on-select on-copy})) i/organize]])]))) -(mx/defc grid-item - {:mixins [mx/static]} - [{:keys [id created-at] :as icon} selected? edition?] - (letfn [(toggle-selection [event] - (st/emit! (di/toggle-icon-selection id))) - (toggle-selection-shifted [event] - (when (kbd/shift? event) - (toggle-selection 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 name)))) - (on-key-down [event] - (when (kbd/enter? event) - (on-blur event))) - (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)))] - [:div.grid-item.small-item.project-th - {:on-click toggle-selection - :id (str "grid-item-" id)} - [:div.input-checkbox.check-primary {} - [:input {:type "checkbox" - :id (:id icon) - :on-click toggle-selection - :checked selected?}] - [:label {:for (:id icon)}]] - [:span.grid-item-image (icon/icon-svg icon)] - [:div.item-info - {:on-click ignore-click} - (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 icon)}] - [:h3 {:on-double-click on-edit} - (:name icon)]) - (str (tr "ds.uploaded-at" (dt/format created-at "DD/MM/YYYY")))]])) +(mx/def grid-item + :key-fn :id + :mixins [mx/static] + :render + (fn [own {:keys [id created-at ::selected? ::edition?] :as icon}] + (letfn [(toggle-selection [event] + (prn "toggle-selection") + (st/emit! (di/toggle-icon-selection id))) + (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 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)))] + [:div.grid-item.small-item.project-th {:id (str "grid-item-" id)} + [:div.input-checkbox.check-primary + [:input {:type "checkbox" + :id (:id icon) + :on-change toggle-selection + :checked selected?}] + [:label {:for (:id icon)}]] + [:span.grid-item-image (icon/icon-svg icon)] + [:div.item-info {:on-click ignore-click} + (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 icon)}] + [:h3 {:on-double-click on-edit} + (:name icon)]) + (str (tr "ds.uploaded-at" (dt/format created-at "DD/MM/YYYY")))]]))) -(mx/defc grid - {:mixins [mx/static mx/reactive]} - [{:keys [selected edition id type] :as state}] - (let [editable? (or (= type :own) (nil? id)) - ordering (:order state :name) - filtering (:filter state "") - icons (mx/react icons-ref) - icons (->> (vals icons) - (filter #(= id (:collection %))) - (filter-icons-by filtering) - (sort-icons-by ordering))] - [:div.dashboard-grid-content {} - [:div.dashboard-grid-row {} - (when editable? (grid-form id)) - (for [{:keys [id] :as icon} icons] - (let [edition? (= edition id) - selected? (contains? selected id)] - (-> (grid-item icon selected? edition?) - (mx/with-key (str id)))))]])) +(mx/def grid + :mixins [mx/reactive] + :init + (fn [own {:keys [id] :as props}] + (let [selector (fn [icons] + (->> (vals icons) + (filter #(= id (:collection %)))))] + (assoc own ::icons-ref (-> (comp (l/key :icons) + (l/lens selector)) + (l/derive st/state))))) -(mx/defc content - {:mixins [mx/static]} - [{:keys [selected] :as state} coll] - [:section.dashboard-grid.library {} - (page-title coll) - (grid state) - (when (seq selected) - (grid-options coll selected))]) + :render + (fn [own {:keys [selected edition id type] :as props}] + (let [editable? (or (= type :own) (nil? id)) + icons (->> (mx/react (::icons-ref own)) + (filter-icons-by (:filter props "")) + (sort-icons-by (:order props :name)))] + + [:div.dashboard-grid-content + [:div.dashboard-grid-row + (when editable? (grid-form id)) + (for [icon icons] + (let [edition? (= edition (:id icon)) + selected? (contains? selected (:id icon))] + (grid-item (assoc icon ::selected? selected? ::edition? edition?))))]]))) ;; --- Menu -(mx/defc menu - {:mixins [mx/static mx/reactive]} - [state {:keys [id num-icons] :as coll}] - (let [ordering (:order state :name) - filtering (:filter state "") - num-icons (or num-icons (react-count-icons id))] - (letfn [(on-term-change [event] - (let [term (-> (dom/get-target event) - (dom/get-value))] - (st/emit! (di/update-opts :filter term)))) - (on-ordering-change [event] - (let [value (dom/event->value event) - value (read-string value)] - (st/emit! (di/update-opts :order value)))) - (on-clear [event] - (st/emit! (di/update-opts :filter "")))] - [:section.dashboard-bar.library-gap {} - [:div.dashboard-info {} +(mx/def menu + :mixins [mx/static mx/reactive] - ;; Counter - [:span.dashboard-icons {} (tr "ds.num-icons" (t/c num-icons))] + :init + (fn [own {:keys [id] :as props}] + (assoc own ::num-icons-ref (num-icons-ref id))) + + :render + (fn [own props] + (let [{:keys [id num-icons] :as coll} (::coll props) + num-icons (or num-icons (mx/react (::num-icons-ref own)))] + (letfn [(on-term-change [event] + (let [term (-> (dom/get-target event) + (dom/get-value))] + (st/emit! (di/update-opts :filter term)))) + (on-ordering-change [event] + (let [value (dom/event->value event) + value (read-string value)] + (st/emit! (di/update-opts :order value)))) + (on-clear [event] + (st/emit! (di/update-opts :filter "")))] + [:section.dashboard-bar.library-gap + [:div.dashboard-info + ;; Counter + [:span.dashboard-icons (tr "ds.num-icons" (t/c num-icons))] + + ;; Sorting + [:div + [:span (tr "ds.ordering")] + [:select.input-select {:on-change on-ordering-change + :value (pr-str (:order props :name))} + (for [[key value] (seq +ordering-options+)] + [:option {:key key :value (pr-str key)} (tr value)])]] + ;; Search + [:form.dashboard-search + [:input.input-text {:key :icons-search-box + :type "text" + :on-change on-term-change + :auto-focus true + :placeholder (tr "ds.search.placeholder") + :value (:filter props "")}] + [:div.clear-search {:on-click on-clear} + i/close]]]])))) + +(mx/def content + :mixins [mx/reactive mx/static] + + :init + (fn [own {:keys [id] :as props}] + (assoc own ::coll-ref (-> (l/in [:icons-collections id]) + (l/derive st/state)))) + + :render + (fn [own props] + (let [opts (mx/react opts-ref) + coll (mx/react (::coll-ref own)) + props (merge opts props)] + [:* + (menu (assoc props ::coll coll)) + [:section.dashboard-grid.library + (page-title coll) + (grid props) + (when (seq (:selected opts)) + (grid-options props))]]))) - ;; Sorting - [:div {} - [:span {} (tr "ds.ordering")] - [:select.input-select - {:on-change on-ordering-change - :value (pr-str ordering)} - (for [[key value] (seq +ordering-options+)] - (let [key (pr-str key)] - [:option {:key key :value key} (tr value)]))]] - ;; Search - [:form.dashboard-search {} - [:input.input-text - {:key :icons-search-box - :type "text" - :on-change on-term-change - :auto-focus true - :placeholder (tr "ds.search.placeholder") - :value (or filtering "")}] - [:div.clear-search {:on-click on-clear} i/close]]]]))) ;; --- Icons Page -(defn- icons-page-will-mount - [own] - (let [[type id] (:rum/args own)] - (st/emit! (di/initialize type id)) - own)) +(mx/def icons-page + :key-fn identity + :mixins #{mx/static mx/reactive} -(defn- icons-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)) - (st/emit! (di/initialize new-type new-id))) - own)) + :init + (fn [own props] + (let [{:keys [type id]} (::mx/props own)] + (st/emit! (di/initialize type id)) + own)) -(mx/defc icons-page - {:will-mount icons-page-will-mount - :did-remount icons-page-did-remount - :mixins [mx/static mx/reactive]} - [] - (let [state (mx/react dashboard-ref) - colls (mx/react collections-ref) - coll (get colls (:id state))] - [:main.dashboard-main {} - (header) - [:section.dashboard-content {} - (nav state colls) - (menu state coll) - (content state coll)]])) + :render + (fn [own {:keys [type] :as props}] + (let [type (or type :own) + props (assoc props :type type)] + [:section.dashboard-content + (nav props) + (content props)]))) diff --git a/frontend/src/uxbox/main/ui/dashboard/images.cljs b/frontend/src/uxbox/main/ui/dashboard/images.cljs index 1ef05e505..b62bde456 100644 --- a/frontend/src/uxbox/main/ui/dashboard/images.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/images.cljs @@ -6,21 +6,22 @@ ;; Copyright (c) 2015-2016 Juan de la Cruz (ns uxbox.main.ui.dashboard.images - (:require [cuerdas.core :as str] - [lentes.core :as l] - [rumext.core :as mx :include-macros true] - [uxbox.util.i18n :as t :refer [tr]] - [uxbox.main.store :as st] - [uxbox.main.data.lightbox :as udl] - [uxbox.main.data.images :as di] - [uxbox.builtins.icons :as i] - [uxbox.main.ui.lightbox :as lbx] - [uxbox.main.ui.keyboard :as kbd] - [uxbox.main.ui.dashboard.header :refer [header]] - [uxbox.util.router :as rt] - [uxbox.util.time :as dt] - [uxbox.util.data :refer [read-string jscoll->vec]] - [uxbox.util.dom :as dom])) + (:require + [cuerdas.core :as str] + [lentes.core :as l] + [rumext.core :as mx :include-macros true] + [uxbox.builtins.icons :as i] + [uxbox.main.data.images :as di] + [uxbox.main.data.lightbox :as udl] + [uxbox.main.store :as st] + [uxbox.main.ui.dashboard.header :refer [header]] + [uxbox.main.ui.keyboard :as kbd] + [uxbox.main.ui.lightbox :as lbx] + [uxbox.util.data :refer [read-string jscoll->vec]] + [uxbox.util.dom :as dom] + [uxbox.util.i18n :as t :refer [tr]] + [uxbox.util.router :as rt] + [uxbox.util.time :as dt])) ;; --- Helpers & Constants @@ -48,88 +49,89 @@ ;; --- Refs -(def ^:private dashboard-ref - (-> (l/in [:dashboard :images]) - (l/derive st/state))) - -(def ^:private collections-ref +(def collections-ref (-> (l/key :images-collections) (l/derive st/state))) -(def ^:private images-ref - (-> (l/key :images) +(def opts-ref + (-> (l/in [:dashboard :images]) (l/derive st/state))) -(def ^:private uploading?-ref - (-> (l/key :uploading) - (l/derive dashboard-ref))) - ;; --- Page Title -(mx/defcs page-title - {:mixins [(mx/local {}) mx/static mx/reactive]} - [own {:keys [id] :as coll}] - (let [local (:rum/local own) - dashboard (mx/react dashboard-ref) - own? (= :own (:type coll)) - edit? (:edit @local)] - (letfn [(on-save [e] - (let [dom (mx/ref-node own "input") - name (dom/get-inner-text dom)] - (st/emit! (di/rename-collection id (str/trim name))) - (swap! local assoc :edit false))) - (on-cancel [e] - (swap! local assoc :edit false)) - (on-edit [e] - (swap! local assoc :edit true)) - (on-input-keydown [e] - (cond - (kbd/esc? e) (on-cancel e) - (kbd/enter? e) - (do - (dom/prevent-default e) - (dom/stop-propagation e) - (on-save e)))) - (delete [] - (st/emit! (di/delete-collection id))) - (on-delete [] - (udl/open! :confirm {:on-accept delete}))] - [: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 on-cancel} i/close]] - (if own? - [:span.dashboard-title-field - {:on-double-click on-edit} - (:name coll)] - [:span.dashboard-title-field - (:name coll "Storage")]))] - (when (and own? coll) - [:div.edition {} +(mx/def page-title + :mixins [(mx/local) mx/reactive] + + :render + (fn [{:keys [::mx/local] :as own} + {:keys [id type] :as coll}] + (let [own? (= :own (:type coll)) + edit? (:edit @local)] + (letfn [(on-save [e] + (let [dom (mx/ref-node own "input") + name (dom/get-inner-text dom)] + (st/emit! (di/rename-collection id (str/trim name))) + (swap! local assoc :edit false))) + (on-cancel [e] + (swap! local assoc :edit false)) + (on-edit [e] + (swap! local assoc :edit true)) + (on-input-keydown [e] + (cond + (kbd/esc? e) (on-cancel e) + (kbd/enter? e) + (do + (dom/prevent-default e) + (dom/stop-propagation e) + (on-save e)))) + (delete [] + (st/emit! (di/delete-collection id))) + (on-delete [] + (udl/open! :confirm {:on-accept delete}))] + [:div.dashboard-title + [:h2 (if edit? - [:span {:on-click on-save} ^:inline i/save] - [:span {:on-click on-edit} ^:inline i/pencil]) - [:span {:on-click on-delete} ^:inline i/trash]])]))) + [:div.dashboard-title-field + [:span.edit + {:content-editable true + :ref "input" + :on-key-down on-input-keydown} + (:name coll)] + [:span.close {:on-click on-cancel} i/close]] + (if own? + [:span.dashboard-title-field + {:on-double-click on-edit} + (:name coll)] + [:span.dashboard-title-field + (:name coll "Storage")]))] + (when (and own? coll) + [:div.edition + (if edit? + [:span {:on-click on-save} ^:inline i/save] + [:span {:on-click on-edit} ^:inline i/pencil]) + [:span {:on-click on-delete} ^:inline i/trash]])])))) ;; --- Nav -(defn react-count-images +(defn num-images-ref [id] - (->> (mx/react images-ref) - (vals) - (filter #(= id (:collection %))) - (count))) + (let [selector (fn [images] (count (filter #(= id (:collection %)) (vals images))))] + (-> (comp (l/key :images) + (l/lens selector)) + (l/derive st/state)))) -(mx/defcs nav-item - {:mixins [(mx/local) mx/static mx/reactive]} - [{:keys [rum/local] :as own} {:keys [id type name num-images] :as coll} selected?] - (letfn [(on-click [event] +(mx/def nav-item + :key-fn :id + :mixins [(mx/local) mx/static mx/reactive] + + :init + (fn [own {:keys [id] :as props}] + (assoc own ::num-images-ref (num-images-ref id))) + + :render + (fn [{:keys [::mx/local] :as own} + {:keys [id type name num-images selected?] :as coll}] + (letfn [(on-click [event] (let [type (or type :own)] (st/emit! (rt/nav :dashboard/images {} {:type type :id id})))) (on-input-change [event] @@ -148,59 +150,40 @@ value (dom/get-value 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 coll name "Storage")) - :on-change on-input-change - :on-key-down on-input-keyup}] - [:span.close {:on-click on-cancel} ^:inline i/close]] - [:span.element-title {} - (if coll name "Storage")]) - [:span.element-subtitle - (tr "ds.num-elements" (t/c (or num-images (react-count-images id))))]])) + [: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} ^:inline i/close]] + [:span.element-title {} + (if id name "Storage")]) + [:span.element-subtitle + (tr "ds.num-elements" (t/c (or num-images (mx/react (::num-images-ref own)))))]]))) -(mx/defc nav-section - {:mixins [mx/static]} - [type selected colls] - (let [own? (= type :own) - builtin? (= type :builtin) - collections (cond->> (vals colls) - own? (filter #(= :own (:type %))) - builtin? (filter #(= :builtin (:type %))) - own? (sort-by :name))] - [: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 nil (nil? selected))) - (for [coll collections] - (let [selected? (= (:id coll) selected) - key (str (:id coll))] - (-> (nav-item coll selected?) - (mx/with-key key))))])) +(mx/def nav + :mixins [mx/static mx/reactive] -(mx/defc nav - {:mixins [mx/static]} - [{:keys [type id] :as state} colls] - (let [own? (= type :own) - builtin? (= type :builtin)] - (letfn [(select-tab [type] - (if-let [coll (->> (map second colls) - (filter #(= type (:type %))) - (sort-by :name) - (first))] - (st/emit! (rt/nav :dashboard/images nil {:type type :id (:id coll)})) - (st/emit! (rt/nav :dashboard/images nil {:type type}))))] - [:div.library-bar {} - [:div.library-bar-inside {} - [:ul.library-tabs {} + :render + (fn [own {:keys [id type] :as props}] + (let [own? (= type :own) + builtin? (= type :builtin) + colls (mx/react collections-ref) + select-tab (fn [type] + (if-let [coll (->> (vals colls) + (filter #(= type (:type %))) + (sort-by :created-at) + (first))] + (st/emit! (rt/nav :dashboard/images nil {:type type :id (:id coll)})) + (st/emit! (rt/nav :dashboard/images nil {:type type}))))] + + [: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")] @@ -208,261 +191,298 @@ :on-click (partial select-tab :builtin)} (tr "ds.store-images-title")]] - (nav-section type id colls)]]))) + [: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 [coll (cond->> (vals colls) + own? (filter #(= :own (:type %))) + builtin? (filter #(= :builtin (:type %))) + own? (sort-by :name))] + (let [selected? (= (:id coll) id)] + (nav-item (assoc coll :selected? selected?))))]]]))) ;; --- Grid -(mx/defcs grid-form - {:mixins [mx/static mx/reactive]} - [own coll-id] - (letfn [(forward-click [event] - (dom/click (mx/ref-node own "file-input"))) - (on-file-selected [event] - (let [files (dom/get-event-files event) - files (jscoll->vec files)] - (st/emit! (di/create-images coll-id files))))] - (let [uploading? (mx/react uploading?-ref)] +(mx/def grid-form + :mixins #{mx/static} + + :init + (fn [own props] + (assoc own ::file-input (mx/create-ref))) + + :render + (fn [own {:keys [id] :as props}] + (letfn [(forward-click [event] + (dom/click (mx/ref-node (::file-input own)))) + (on-file-selected [event] + (let [files (dom/get-event-files event) + files (jscoll->vec files)] + (st/emit! (di/create-images id files))))] + (let [uploading? (:uploading props)] [:div.grid-item.add-project {:on-click forward-click} (if uploading? - [:div {} ^:inline i/loader-pencil] - [:span {} (tr "ds.image-new")]) + [:div i/loader-pencil] + [:span (tr "ds.image-new")]) [:input.upload-image-input {:style {:display "none"} :multiple true - :ref "file-input" + :ref (::file-input own) :value "" :accept "image/jpeg,image/png" :type "file" - :on-change on-file-selected}]]))) + :on-change on-file-selected}]])))) -(mx/defc grid-options-tooltip - {:mixins [mx/reactive mx/static]} - [& {:keys [selected on-select title]}] - {:pre [(uuid? selected) - (fn? on-select) - (string? title)]} - (let [colls (mx/react collections-ref) - 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 (str id)} - [:a {:on-click #(on-select % id)} name]])])) +(mx/def grid-options-tooltip + :mixins [mx/reactive mx/static] -(mx/defcs grid-options - {:mixins [(mx/local) mx/static]} - [{:keys [rum/local] :as own} {:keys [type id] :as coll} selected] - (letfn [(delete [] - (st/emit! (di/delete-selected))) - (on-delete [event] - (udl/open! :confirm {: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? coll)) - ;; 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)) - ^:inline 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)) - ^:inline i/move] - (when (= 1 (count selected)) + :render + (fn [own {:keys [selected on-select title]}] + {:pre [(uuid? selected) + (fn? on-select) + (string? title)]} + (let [colls (mx/react collections-ref) + 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]])]))) + +(mx/def grid-options + :mixins [(mx/local)] + + :render + (fn [{:keys [::mx/local] :as own} + {:keys [id type selected] :as props}] + (letfn [(delete [] + (st/emit! (di/delete-selected))) + (on-delete [event] + (udl/open! :confirm {: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.rename") - :on-click on-rename} - ^:inline i/pencil]) - [:span.delete.tooltip.tooltip-top - {:alt (tr "ds.multiselect-bar.delete") - :on-click on-delete} - ^:inline i/trash]] + {: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})) + ^:inline 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})) + ^:inline i/move] + (when (= 1 (count selected)) + [:span.move-item.tooltip.tooltip-top + {:alt (tr "ds.multiselect-bar.rename") + :on-click on-rename} + ^:inline i/pencil]) + [:span.delete.tooltip.tooltip-top + {:alt (tr "ds.multiselect-bar.delete") + :on-click on-delete} + ^:inline 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)) - ^:inline i/organize]])])) + ;; 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})) + ^:inline i/organize]])]))) -(mx/defc grid-item - {:mixins [mx/static]} - [{:keys [id created-at] :as image} selected? edition?] - (letfn [(toggle-selection [event] - (st/emit! (di/toggle-image-selection id))) - (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 name)))) - (on-edit [event] - (dom/stop-propagation event) - (dom/prevent-default event) - (st/emit! (di/update-opts :edition id)))] - [:div.grid-item.images-th {} - [:div.grid-item-th {:on-click toggle-selection - :style {:background-image (str "url('" (:thumbnail image) "')")}} - [:div.input-checkbox.check-primary {} - [:input {:type "checkbox" - :id (:id image) - :on-click 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 "L")))]]])) +(mx/def grid-item + :key-fn :id + :mixins [mx/static] -(mx/defc grid - {:mixins [mx/static mx/reactive]} - [{:keys [id type selected edition] :as state}] - (let [editable? (or (= type :own) (nil? id)) - ordering (:order state :name) - filtering (:filter state "") - images-map (mx/react images-ref) - images (->> (vals images-map) - (filter #(= id (:collection %))) - (filter-images-by filtering) - (sort-images-by ordering))] - [:div.dashboard-grid-content {} - [:div.dashboard-grid-row {} - (when editable? - (grid-form id)) - (for [{:keys [id] :as image} images] - (let [edition? (= edition id) - selected? (contains? selected id)] - (-> (grid-item image selected? edition?) - (mx/with-key (str id)))))]])) + :render + (fn [own {:keys [id created-at ::selected? ::edition?] :as image}] + (letfn [(toggle-selection [event] + (st/emit! (di/toggle-image-selection id))) + (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 name)))) + (on-edit [event] + (dom/stop-propagation event) + (dom/prevent-default event) + (st/emit! (di/update-opts :edition id)))] + [: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 "DD/MM/YYYY")))]]]))) -(mx/defc content - {:mixins [mx/static]} - [{:keys [selected] :as state} coll] - [:section.dashboard-grid.library {} - (page-title coll) - (grid state) - (when (seq selected) - (grid-options coll selected))]) +(mx/def grid + :mixins [mx/reactive] + :init + (fn [own {:keys [id] :as props}] + (let [selector (fn [images] + (->> (vals images) + (filter #(= id (:collection %)))))] + (assoc own ::images-ref (-> (comp (l/key :images) + (l/lens selector)) + (l/derive st/state))))) + + :render + (fn [own {:keys [selected edition id type] :as props}] + (let [editable? (or (= type :own) (nil? id)) + images (->> (mx/react (::images-ref own)) + (filter-images-by (:filter props "")) + (sort-images-by (:order props :name)))] + [:div.dashboard-grid-content + [:div.dashboard-grid-row + (when editable? + (grid-form props)) + (for [{:keys [id] :as image} images] + (let [edition? (= edition id) + selected? (contains? selected id)] + (grid-item (assoc image ::selected? selected? ::edition? edition?))))]]))) ;; --- Menu -(mx/defc menu - {:mixins [mx/static mx/reactive]} - [coll] - (let [state (mx/react dashboard-ref) - ordering (:order state :name) - filtering (:filter state "") - icount (count (:images coll))] - (letfn [(on-term-change [event] - (let [term (-> (dom/get-target event) - (dom/get-value))] - (st/emit! (di/update-opts :filter term)))) - (on-ordering-change [event] - (let [value (dom/event->value event) - value (read-string value)] - (st/emit! (di/update-opts :order value)))) - (on-clear [event] - (st/emit! (di/update-opts :filter "")))] - [:section.dashboard-bar.library-gap {} - [:div.dashboard-info {} +(mx/def menu + :mixins [mx/reactive mx/static] - ;; Counter - [:span.dashboard-images {} (tr "ds.num-images" (t/c icount))] + ;; :init + ;; (fn [own {:keys [id] :as props}] + ;; (assoc own ::num-images-ref (num-images-ref id))) - ;; Sorting - [:div {} - [:span {} (tr "ds.ordering")] - [:select.input-select {:on-change on-ordering-change - :value (pr-str ordering)} - (for [[key value] (seq +ordering-options+)] - (let [key (pr-str key) - label (tr value)] - [:option {:key key :value key} label]))]] - ;; Search - [:form.dashboard-search {} - [:input.input-text {:key :images-search-box - :type "text" - :on-change on-term-change - :auto-focus true - :placeholder (tr "ds.search.placeholder") - :value (or filtering "")}] - [:div.clear-search {:on-click on-clear} i/close]]]]))) + :render + (fn [own props] + (let [{:keys [id] :as coll} (::coll props) + ordering (:order props :name) + filtering (:filter props "") + icount (count (:images coll))] + (letfn [(on-term-change [event] + (let [term (-> (dom/get-target event) + (dom/get-value))] + (prn "on-term-change" term) + (st/emit! (di/update-opts :filter term)))) + (on-ordering-change [event] + (let [value (dom/event->value event) + value (read-string value)] + (st/emit! (di/update-opts :order value)))) + (on-clear [event] + (st/emit! (di/update-opts :filter "")))] + [:section.dashboard-bar.library-gap + [:div.dashboard-info + + ;; Counter + [:span.dashboard-images (tr "ds.num-images" (t/c icount))] + + ;; Sorting + [:div + [:span (tr "ds.ordering")] + [:select.input-select {:on-change on-ordering-change + :value (pr-str ordering)} + (for [[key value] (seq +ordering-options+)] + [:option {:key key :value (pr-str key)} (tr value)])]] + + ;; Search + [:form.dashboard-search + [:input.input-text {:key :images-search-box + :type "text" + :on-change on-term-change + :auto-focus true + :placeholder (tr "ds.search.placeholder") + :value filtering}] + [:div.clear-search {:on-click on-clear} + i/close]]]])))) + +(mx/def content + :mixins [mx/reactive mx/static] + + :init + (fn [own {:keys [id] :as props}] + (assoc own ::coll-ref (-> (l/in [:images-collections id]) + (l/derive st/state)))) + + :render + (fn [own props] + (let [opts (mx/react opts-ref) + coll (mx/react (::coll-ref own)) + props (merge opts props)] + [:* + (menu (assoc props ::coll coll)) + [:section.dashboard-grid.library + (page-title coll) + (grid props) + (when (seq (:selected opts)) + (grid-options props))]]))) ;; --- Images Page -(defn- images-page-will-mount - [own] - (let [[type id] (:rum/args own)] - (st/emit! (di/initialize type id)) - own)) +(mx/def images-page + :key-fn identity + :mixins #{mx/static mx/reactive} -(defn- images-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)) - (st/emit! (di/initialize new-type new-id))) - own)) + :init + (fn [own props] + (let [{:keys [type id]} (::mx/props own)] + (st/emit! (di/initialize type id)) + own)) -(mx/defc images-page - {:will-mount images-page-will-mount - :did-remount images-page-did-remount - :mixins [mx/static mx/reactive]} - [_ _] - (let [state (mx/react dashboard-ref) - colls (mx/react collections-ref) - coll (get colls (:id state))] - [:main.dashboard-main {} - (header) - [:section.dashboard-content {} - (nav state colls) - (menu coll) - (content state coll)]])) + :render + (fn [own {:keys [type] :as props}] + (let [type (or type :own) + props (assoc props :type type)] + [:section.dashboard-content + (nav props) + (content props)]))) diff --git a/frontend/src/uxbox/main/ui/dashboard/projects.cljs b/frontend/src/uxbox/main/ui/dashboard/projects.cljs index 328f9b66f..e29073c22 100644 --- a/frontend/src/uxbox/main/ui/dashboard/projects.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/projects.cljs @@ -6,26 +6,25 @@ ;; Copyright (c) 2015-2017 Juan de la Cruz (ns uxbox.main.ui.dashboard.projects - (:require [lentes.core :as l] - [cuerdas.core :as str] - [uxbox.builtins.icons :as i] - [uxbox.main.store :as st] - [uxbox.main.constants :as c] - [uxbox.main.data.projects :as udp] - [uxbox.main.data.lightbox :as udl] - [uxbox.main.ui.dashboard.header :refer [header]] - [uxbox.main.ui.dashboard.projects-createform] - [uxbox.main.ui.lightbox :as lbx] - [uxbox.main.ui.messages :refer [messages-widget]] - [uxbox.main.ui.keyboard :as kbd] - [uxbox.main.exports :as exports] - [uxbox.util.i18n :as t :refer (tr)] - [uxbox.util.router :as r] - [uxbox.util.data :refer [read-string]] - [uxbox.util.dom :as dom] - [uxbox.util.blob :as blob] - [rumext.core :as mx :include-macros true] - [uxbox.util.time :as dt])) + (:require + [cuerdas.core :as str] + [lentes.core :as l] + [rumext.core :as mx] + [rumext.func :as mxf] + [uxbox.builtins.icons :as i] + [uxbox.main.constants :as c] + [uxbox.main.data.lightbox :as udl] + [uxbox.main.data.projects :as udp] + [uxbox.main.exports :as exports] + [uxbox.main.store :as st] + [uxbox.main.ui.dashboard.projects-createform] + [uxbox.main.ui.keyboard :as kbd] + [uxbox.util.blob :as blob] + [uxbox.util.data :refer [read-string]] + [uxbox.util.dom :as dom] + [uxbox.util.i18n :as t :refer (tr)] + [uxbox.util.router :as r] + [uxbox.util.time :as dt])) ;; --- Helpers & Constants @@ -35,22 +34,10 @@ ;; --- Refs -(def projects-map-ref - (-> (l/key :projects) - (l/derive st/state))) - -(def dashboard-ref +(def opts-ref (-> (l/in [:dashboard :projects]) (l/derive st/state))) -(def project-ordering-ref - (-> (l/key :project-order) - (l/derive dashboard-ref))) - -(def project-filtering-ref - (-> (l/in [:project-filter]) - (l/derive dashboard-ref))) - ;; --- Helpers (defn sort-projects-by @@ -73,181 +60,180 @@ ;; --- Menu (Filter & Sort) -(mx/defc menu - {:mixins [mx/static]} - [state projects] - (let [ordering (:order state :created) - filtering (:filter state "") - count (count projects)] - (letfn [(on-term-change [event] - (let [term (-> (dom/get-target event) - (dom/get-value))] - (st/emit! (udp/update-opts :filter term)))) - (on-ordering-change [event] - (let [value (dom/event->value event) - value (read-string value)] - (st/emit! (udp/update-opts :order value)))) - (on-clear [event] - (st/emit! (udp/update-opts :filter "")))] - [:section.dashboard-bar {} - [:div.dashboard-info {} +(mx/def menu + :mixins #{mx/static mx/reactive} + :init + (fn [own props] + (assoc own ::num-projects (-> (comp (l/key :projects) + (l/lens #(-> % vals count))) + (l/derive st/state)))) + :render + (fn [own props] + (let [ordering (:order props :created) + filtering (:filter props "") + num-projects (mx/react (::num-projects own))] + (letfn [(on-term-change [event] + (let [term (-> (dom/get-target event) + (dom/get-value))] + (st/emit! (udp/update-opts :filter term)))) + (on-ordering-change [event] + (let [value (dom/event->value event) + value (read-string value)] + (st/emit! (udp/update-opts :order value)))) + (on-clear [event] + (st/emit! (udp/update-opts :filter "")))] + [:section.dashboard-bar + [:div.dashboard-info - ;; Counter - [:span.dashboard-images {} (tr "ds.num-projects" (t/c count))] + ;; Counter + [:span.dashboard-images (tr "ds.num-projects" (t/c num-projects))] - ;; Sorting - [:div {} - [:span {} (tr "ds.ordering")] - [:select.input-select - {:on-change on-ordering-change - :value (pr-str ordering)} - (for [[key value] (seq +ordering-options+)] - (let [key (pr-str key)] - [:option {:key key :value key} (tr value)]))]] - ;; Search - [:form.dashboard-search {} - [:input.input-text - {:key :images-search-box - :type "text" - :on-change on-term-change - :auto-focus true - :placeholder (tr "ds.search.placeholder") - :value (or filtering "")}] - [:div.clear-search {:on-click on-clear} i/close]]]]))) + ;; Sorting + [:div + [:span (tr "ds.ordering")] + [:select.input-select + {:on-change on-ordering-change + :value (pr-str ordering)} + (for [[key value] (seq +ordering-options+)] + (let [key (pr-str key)] + [:option {:key key :value key} (tr value)]))]] + ;; Search + [:form.dashboard-search + [:input.input-text + {:key :images-search-box + :type "text" + :on-change on-term-change + :auto-focus true + :placeholder (tr "ds.search.placeholder") + :value (or filtering "")}] + [:div.clear-search {:on-click on-clear} i/close]]]])))) ;; --- Grid Item Thumbnail -(defn- grid-item-thumbnail-will-mount - [own] - (let [[project] (:rum/args own) - svg (exports/render-page (:page-id project)) - url (some-> svg - (blob/create "image/svg+xml") - (blob/create-uri))] - (assoc own ::url url))) +(mx/def grid-item-thumbnail + :mixins #{mx/static} -(defn- grid-item-thumbnail-will-unmount - [own] - (let [url (::url own)] - (when url (blob/revoke-uri url)) - own)) + :init + (fn [own project] + (let [svg (exports/render-page (:page-id project)) + url (some-> svg + (blob/create "image/svg+xml") + (blob/create-uri))] + (assoc own ::url url))) -(defn- grid-item-thumbnail-did-remount - [oldown own] - (when-let [url (::url oldown)] - (blob/revoke-uri url)) - (grid-item-thumbnail-will-mount own)) + :will-unmount + (fn [own] + (let [url (::url own)] + (when url (blob/revoke-uri url)) + own)) -(mx/defcs grid-item-thumbnail - {:mixins [mx/static] - :will-mount grid-item-thumbnail-will-mount - :will-unmount grid-item-thumbnail-will-unmount - :did-remount grid-item-thumbnail-did-remount} - [own project] - (if-let [url (::url own)] - [:div.grid-item-th - {:style {:background-image (str "url('" url "')")}}] - [:div.grid-item-th - [:img.img-th {:src "/images/project-placeholder.svg" :alt "Project title"}]])) + :render + (fn [own project] + (if-let [url (::url own)] + [:div.grid-item-th + {:style {:background-image (str "url('" url "')")}}] + [:div.grid-item-th + [:img.img-th {:src "/images/project-placeholder.svg" + :alt "Project title"}]]))) ;; --- Grid Item -(mx/defcs grid-item - {:mixins [mx/static (mx/local)]} - [{:keys [rum/local] :as own} project] - (letfn [(on-navigate [event] - (st/emit! (udp/go-to (:id project)))) - (delete [] - (st/emit! (udp/delete-project project))) - (on-delete [event] - (dom/stop-propagation event) - (udl/open! :confirm {:on-accept delete})) - (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) - id (:id project)] - (swap! local assoc :edition false) - (st/emit! (udp/rename-project id name)))) - (on-edit [event] - (dom/stop-propagation event) - (dom/prevent-default event) - (swap! local assoc :edition true))] - [:div.grid-item.project-th {:on-click on-navigate} - (grid-item-thumbnail project) - [:div.item-info {} - (if (:edition @local) - [: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 project)}] - [:h3 {} (:name project)]) - [:span.date {} - (str "Updated " (dt/timeago (:modified-at project)))]] - [:div.project-th-actions {} - [:div.project-th-icon.pages {} - i/page - [:span {} (:total-pages project)]] - #_[:div.project-th-icon.comments - i/chat - [:span "0"]] - [:div.project-th-icon.edit - {:on-click on-edit} - i/pencil] - [:div.project-th-icon.delete - {:on-click on-delete} - i/trash]]])) +(mx/def grid-item + :key-fn :id + :mixins #{mx/static (mx/local)} + + :render + (fn [{:keys [::mx/local] :as own} project] + (letfn [(on-navigate [event] + (st/emit! (udp/go-to (:id project)))) + (delete [] + (st/emit! (udp/delete-project project))) + (on-delete [event] + (dom/stop-propagation event) + (udl/open! :confirm {:on-accept delete})) + (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) + id (:id project)] + (swap! local assoc :edition false) + (st/emit! (udp/rename-project id name)))) + (on-edit [event] + (dom/stop-propagation event) + (dom/prevent-default event) + (swap! local assoc :edition true))] + [:div.grid-item.project-th {:on-click on-navigate} + (grid-item-thumbnail project) + [:div.item-info + (if (:edition @local) + [: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 project)}] + [:h3 (:name project)]) + [:span.date + (str "Updated " (dt/timeago (:modified-at project)))]] + [:div.project-th-actions + [:div.project-th-icon.pages + i/page + [:span (:total-pages project)]] + #_[:div.project-th-icon.comments + i/chat + [:span "0"]] + [:div.project-th-icon.edit + {:on-click on-edit} + i/pencil] + [:div.project-th-icon.delete + {:on-click on-delete} + i/trash]]]))) ;; --- Grid -(mx/defc grid - {:mixins [mx/static]} - [state projects] - (let [ordering (:order state :created) - filtering (:filter state "") - projects (->> (vals projects) - (filter-projects-by filtering) - (sort-projects-by ordering))] - (letfn [(on-click [e] - (dom/prevent-default e) - (udl/open! :create-project))] - [:section.dashboard-grid {} - [:h2 {} (tr "ds.project-title")] - [:div.dashboard-grid-content {} - [:div.dashboard-grid-row {} - [:div.grid-item.add-project - {:on-click on-click} - [:span {} (tr "ds.project-new")]] +(mx/def grid + :mixins #{mx/static mx/reactive} + + :init + (fn [own props] + (assoc own ::projects (-> (l/key :projects) + (l/derive st/state)))) + + :render + (fn [own props] + (let [ordering (:order props :created) + filtering (:filter props "") + projects (->> (vals (mx/react (::projects own))) + (filter-projects-by filtering) + (sort-projects-by ordering))] + [:section.dashboard-grid + [:h2 (tr "ds.project-title")] + [:div.dashboard-grid-content + [:div.dashboard-grid-row + [:div.grid-item.add-project {:on-click (fn [e] + (dom/prevent-default e) + (udl/open! :create-project))} + [:span (tr "ds.project-new")]] (for [item projects] - (-> (grid-item item) - (mx/with-key (:id item))))]]]))) + (grid-item item))]]]))) ;; --- Projects Page -(defn projects-page-will-mount - [own] - (st/emit! (udp/initialize)) - own) +(mx/def projects-page + :mixins [mx/static mx/reactive] -(defn projects-page-did-remount - [old-own own] - (st/emit! (udp/initialize)) - own) + :init + (fn [own props] + (st/emit! (udp/initialize)) + own) + + :render + (fn [own props] + (let [opts (mx/react opts-ref) + props (merge opts props)] + [:section.dashboard-content + (menu props) + (grid props)]))) -(mx/defc projects-page - {:will-mount projects-page-will-mount - :did-remount projects-page-did-remount - :mixins [mx/static mx/reactive]} - [] - (let [state (mx/react dashboard-ref) - projects-map (mx/react projects-map-ref)] - [:main.dashboard-main {} - (messages-widget) - (header) - [:section.dashboard-content {} - (menu state projects-map) - (grid state projects-map)]]))