diff --git a/frontend/resources/styles/main.scss b/frontend/resources/styles/main.scss index 17dc7cdc7..d6748e947 100644 --- a/frontend/resources/styles/main.scss +++ b/frontend/resources/styles/main.scss @@ -66,6 +66,7 @@ @import 'main/partials/loader'; @import 'main/partials/context-menu'; @import 'main/partials/debug-icons-preview'; +@import 'main/partials/editable-label'; //################################################# // Resources diff --git a/frontend/resources/styles/main/layouts/library-page.scss b/frontend/resources/styles/main/layouts/library-page.scss index 3ba00eb4b..34b0c29c1 100644 --- a/frontend/resources/styles/main/layouts/library-page.scss +++ b/frontend/resources/styles/main/layouts/library-page.scss @@ -20,6 +20,19 @@ } } +.library-content-empty { + display: flex; + flex-direction: column; +} + +.library-content-empty-text { + color: #7C7C7C; + border: 1px dashed #AFB2BF; + text-align: center; + padding: 5rem; + margin: 2rem; +} + .library-page #main-bar { position: relative; } @@ -79,16 +92,13 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - - & a { - color: $color-black; - } + color: $color-black; &:hover { background-color: $color-primary-lighter; } - &.current a { + &.current { font-weight: bold; } } @@ -145,6 +155,12 @@ } } +.library-top-menu-actions-delete { + display: flex; + justify-content: center; + flex-direction: column +} + .library-page-cards-container { align-content: flex-start; display: flex; @@ -335,3 +351,4 @@ font-size: 24px; font-weight: normal; } + diff --git a/frontend/resources/styles/main/partials/editable-label.scss b/frontend/resources/styles/main/partials/editable-label.scss new file mode 100644 index 000000000..83f80cbf7 --- /dev/null +++ b/frontend/resources/styles/main/partials/editable-label.scss @@ -0,0 +1,30 @@ +.editable-label { + display: flex; + + &.is-hidden { + display: none; + } +} + +.editable-label-input { + border: 0; + height: 30px; + padding: 5px; + margin: 0; + width: 100%; + background-color: $color-white; +} + +.editable-label-close { + background-color: $color-white; + cursor: pointer; + padding: 3px 5px; + + & svg { + fill: $color-gray; + height: 15px; + transform: rotate(45deg) translateY(7px); + width: 15px; + margin: 0; + } +} diff --git a/frontend/resources/styles/main/partials/lightbox.scss b/frontend/resources/styles/main/partials/lightbox.scss index 39f5c5aed..47521504a 100644 --- a/frontend/resources/styles/main/partials/lightbox.scss +++ b/frontend/resources/styles/main/partials/lightbox.scss @@ -222,13 +222,62 @@ } -// Confirm dialog -.confirm-dialog { - .btn-gray, - .btn-success, - .btn-delete { - margin: 2rem 1rem 0 1rem; - } +.lightbox .confirm-dialog { + background-color: $color-white; + width: 23rem; + + & .close { + right: 1rem; + top: 1rem; + + & svg { + fill: $color-black; + } + } +} + +.lightbox .confirm-dialog-title { + font-size: 24px; + color: $color-black; + font-weight: normal; + text-align: center; +} + +.confirm-dialog-buttons { + display: flex; + flex-direction: row; + margin-top: 5rem; + width: 100%; +} + +.confirm-dialog-cancel-button { + border: 1px solid $color-gray; + background: $color-gray-lightest; + border-radius: 2px; + padding: 0.5rem; + margin-right: 1rem; + justify-content: space-evenly; + margin-bottom: 0; + width: 100%; + cursor: pointer; + + &:hover { + background: $color-gray-light; + } +} + +.confirm-dialog-accept-button { + width: 100%; + padding: 0.5rem; + border: 1px solid $color-danger; + background: $color-danger; + color: $color-white; + margin-bottom: 0; + cursor: pointer; + + &:hover { + background: $color-danger-dark; + } } // Export dialog diff --git a/frontend/src/uxbox/main/data/colors.cljs b/frontend/src/uxbox/main/data/colors.cljs index 4291bd61d..812844ab0 100644 --- a/frontend/src/uxbox/main/data/colors.cljs +++ b/frontend/src/uxbox/main/data/colors.cljs @@ -246,66 +246,6 @@ ;;;; NEW - -(declare fetch-color-libraries-result) - -(defn fetch-color-libraries - [team-id] - (s/assert ::us/uuid team-id) - (ptk/reify ::fetch-color-libraries - ptk/WatchEvent - (watch [_ state stream] - (->> (rp/query! :color-libraries {:team-id team-id}) - (rx/map fetch-color-libraries-result))))) - -(defn fetch-color-libraries-result [result] - (ptk/reify ::fetch-color-libraries-result - ptk/UpdateEvent - (update [_ state] - (-> state - (assoc-in [:library :color-libraries] result))))) - -(declare fetch-color-library-result) - -(defn fetch-color-library - [library-id] - (ptk/reify ::fetch-color-library - ptk/UpdateEvent - (update [_ state] - (-> state - (assoc-in [:library :selected-items] nil))) - - ptk/WatchEvent - (watch [_ state stream] - (->> (rp/query! :colors {:library-id library-id}) - (rx/map fetch-color-library-result))))) - -(defn fetch-color-library-result - [data] - (ptk/reify ::fetch-color-library - ptk/UpdateEvent - (update [_ state] - (-> state - (assoc-in [:library :selected-items] data))))) - -(declare create-color-library-result) - -(defn create-color-library - [team-id name] - (ptk/reify ::create-color-library - ptk/WatchEvent - (watch [_ state stream] - (->> (rp/mutation! :create-color-library {:team-id team-id - :name name}) - (rx/map create-color-library-result))))) - -(defn create-color-library-result [result] - (ptk/reify ::create-color-library-result - ptk/UpdateEvent - (update [_ state] - (-> state - (update-in [:library :color-libraries] #(into [result] %)))))) - (declare create-color-result) (defn create-color @@ -318,12 +258,12 @@ (->> (rp/mutation! :create-color {:library-id library-id :content color :name color}) - (rx/map create-color-result))))) + (rx/map (partial create-color-result library-id)))))) (defn create-color-result - [item] + [library-id item] (ptk/reify ::create-color-result ptk/UpdateEvent (update [_ state] (-> state - (update-in [:library :selected-items] #(into [item] %) ))))) + (update-in [:library :selected-items library-id] #(into [item] %) ))))) diff --git a/frontend/src/uxbox/main/data/icons.cljs b/frontend/src/uxbox/main/data/icons.cljs index e5cc123fa..132f881fb 100644 --- a/frontend/src/uxbox/main/data/icons.cljs +++ b/frontend/src/uxbox/main/data/icons.cljs @@ -34,67 +34,8 @@ ::modified-at ::user-id])) - -(declare fetch-icon-libraries-result) - -(defn fetch-icon-libraries - [team-id] - (s/assert ::us/uuid team-id) - (ptk/reify ::fetch-icon-libraries - ptk/WatchEvent - (watch [_ state stream] - (->> (rp/query! :icon-libraries {:team-id team-id}) - (rx/map fetch-icon-libraries-result))))) - -(defn fetch-icon-libraries-result [result] - (ptk/reify ::fetch-icon-libraries-result - ptk/UpdateEvent - (update [_ state] - (-> state - (assoc-in [:library :icon-libraries] result))))) - -(declare fetch-icon-library-result) - -(defn fetch-icon-library - [library-id] - (ptk/reify ::fetch-icon-library - ptk/UpdateEvent - (update [_ state] - (-> state - (assoc-in [:library :selected-items] nil))) - - ptk/WatchEvent - (watch [_ state stream] - (->> (rp/query! :icons {:library-id library-id}) - (rx/map fetch-icon-library-result))))) - -(defn fetch-icon-library-result - [data] - (ptk/reify ::fetch-icon-library - ptk/UpdateEvent - (update [_ state] - (-> state - (assoc-in [:library :selected-items] data))))) - -(declare create-icon-library-result) - -(defn create-icon-library - [team-id name] - (ptk/reify ::create-icon-library - ptk/WatchEvent - (watch [_ state stream] - (->> (rp/mutation! :create-icon-library {:team-id team-id - :name name}) - (rx/map create-icon-library-result))))) - -(defn create-icon-library-result [result] - (ptk/reify ::create-icon-library-result - ptk/UpdateEvent - (update [_ state] - (-> state - (update-in [:library :icon-libraries] #(into [result] %)))))) - - +;; rename-icon-library +;; delete-icon-library ;; (declare fetch-icons) ;; @@ -253,16 +194,16 @@ (rx/merge-map parse) (rx/map prepare) (rx/flat-map #(rp/mutation! :create-icon %)) - (rx/map create-icon-result)))))) + (rx/map (partial create-icon-result library-id))))))) (defn create-icon-result - [item] + [library-id item] (ptk/reify ::create-icon-result ptk/UpdateEvent (update [_ state] (let [{:keys [id] :as item} (assoc item :type :icon)] (-> state - (update-in [:library :selected-items] #(into [item] %))))))) + (update-in [:library :selected-items library-id] #(into [item] %))))))) ;; ;; --- Icon Persisted ;; diff --git a/frontend/src/uxbox/main/data/images.cljs b/frontend/src/uxbox/main/data/images.cljs index 4d3cd53fe..d9a5614a4 100644 --- a/frontend/src/uxbox/main/data/images.cljs +++ b/frontend/src/uxbox/main/data/images.cljs @@ -350,67 +350,6 @@ ;;;;;;; NEW -(declare fetch-image-libraries-result) - -(defn fetch-image-libraries - [team-id] - (s/assert ::us/uuid team-id) - (ptk/reify ::fetch-image-libraries - ptk/WatchEvent - (watch [_ state stream] - (->> (rp/query! :image-libraries {:team-id team-id}) - (rx/map fetch-image-libraries-result))))) - -(defn fetch-image-libraries-result [result] - (ptk/reify ::fetch-image-libraries-result - ptk/UpdateEvent - (update [_ state] - (-> state - (assoc-in [:library :image-libraries] result))))) - -(declare fetch-image-library-result) - -(defn fetch-image-library - [library-id] - (ptk/reify ::fetch-image-library - ptk/UpdateEvent - (update [_ state] - (-> state - (assoc-in [:library :selected-items] nil))) - - ptk/WatchEvent - (watch [_ state stream] - (->> (rp/query! :images {:library-id library-id}) - (rx/map fetch-image-library-result))))) - -(defn fetch-image-library-result - [data] - (ptk/reify ::fetch-image-library - ptk/UpdateEvent - (update [_ state] - (-> state - (assoc-in [:library :selected-items] data))))) - -(declare create-image-library-result) - -(defn create-image-library - [team-id name] - (ptk/reify ::create-image-library - ptk/WatchEvent - (watch [_ state stream] - (->> (rp/mutation! :create-image-library {:team-id team-id - :name name}) - (rx/map create-image-library-result))))) - -(defn create-image-library-result [result] - (ptk/reify ::create-image-library-result - ptk/UpdateEvent - (update [_ state] - (-> state - (update-in [:library :image-libraries] #(into [result] %)))))) - - - ;; --- Create Image (declare create-images-result) (def allowed-file-types #{"image/jpeg" "image/png"}) @@ -444,17 +383,17 @@ (rx/reduce conj []) (rx/do on-success) (rx/mapcat identity) - (rx/map create-images-result) + (rx/map (partial create-images-result library-id)) (rx/catch on-error))))))) ;; --- Image Created (defn create-images-result - [item] + [library-id item] #_(us/verify ::image item) (ptk/reify ::create-images-result ptk/UpdateEvent (update [_ state] (-> state - (update-in [:library :selected-items] #(into [item] %)))))) + (update-in [:library :selected-items library-id] #(into [item] %)))))) diff --git a/frontend/src/uxbox/main/data/library.cljs b/frontend/src/uxbox/main/data/library.cljs new file mode 100644 index 000000000..ff9805d12 --- /dev/null +++ b/frontend/src/uxbox/main/data/library.cljs @@ -0,0 +1,207 @@ +(ns uxbox.main.data.library + (:require + [cljs.spec.alpha :as s] + [beicon.core :as rx] + [cuerdas.core :as str] + [potok.core :as ptk] + [uxbox.common.spec :as us] + [uxbox.common.data :as d] + [uxbox.main.repo :as rp] + [uxbox.main.store :as st] + [uxbox.util.dom :as dom] + [uxbox.util.webapi :as wapi] + [uxbox.util.i18n :as i18n :refer [t tr]] + [uxbox.util.router :as r] + [uxbox.util.uuid :as uuid])) + + +;; Retrieve libraries + +(declare retrieve-libraries-result) + +(defn retrieve-libraries + [type team-id] + (s/assert ::us/uuid team-id) + (let [method (case type + :icons :icon-libraries + :images :image-libraries + :palettes :color-libraries)] + (ptk/reify ::retrieve-libraries + ptk/WatchEvent + (watch [_ state stream] + (->> (rp/query! method {:team-id team-id}) + (rx/map (partial retrieve-libraries-result type))))))) + +(defn retrieve-libraries-result [type result] + (ptk/reify ::retrieve-libraries-result + ptk/UpdateEvent + (update [_ state] + (-> state + (assoc-in [:library type] result))))) + +;; Retrieve library data + +(declare retrieve-library-data-result) + +(defn retrieve-library-data + [type library-id] + (ptk/reify ::retrieve-library-data + ptk/WatchEvent + (watch [_ state stream] + (let [method (case type + :icons :icons + :images :images + :palettes :colors)] + (->> (rp/query! method {:library-id library-id}) + (rx/map (partial retrieve-library-data-result library-id))))))) + +(defn retrieve-library-data-result + [library-id data] + (ptk/reify ::retrieve-library-data-result + ptk/UpdateEvent + (update [_ state] + (-> state + (assoc-in [:library :selected-items library-id] data))))) + + +;; Create library + +(declare create-library-result) + +(defn create-library + [type team-id name] + (ptk/reify ::create-library + ptk/WatchEvent + (watch [_ state stream] + (let [method (case type + :icons :create-icon-library + :images :create-image-library + :palettes :create-color-library)] + (->> (rp/mutation! method {:team-id team-id + :name name}) + (rx/map (partial create-library-result type))))))) + +(defn create-library-result + [type result] + (ptk/reify ::create-library-result + ptk/UpdateEvent + (update [_ state] + (-> state + (update-in [:library type] #(into [result] %)))))) + +;; Rename library + +(declare rename-library-result) + +(defn rename-library + [type library-id name] + (ptk/reify ::rename-library + ptk/WatchEvent + (watch [_ state stream] + (let [method (case type + :icons :rename-icon-library + :images :rename-image-library + :palettes :rename-color-library)] + (->> (rp/mutation! method {:id library-id + :name name}) + (rx/map #(rename-library-result type library-id name))))))) + +(defn rename-library-result + [type library-id name] + (ptk/reify ::rename-library-result + ptk/UpdateEvent + (update [_ state] + (letfn [(change-name + [library] (if (= library-id (:id library)) + (assoc library :name name) + library)) + (update-fn [libraries] (map change-name libraries))] + + (-> state + (update-in [:library type] update-fn)))))) + +;; Delete library + +(declare delete-library-result) + +(defn delete-library + [type library-id] + (ptk/reify ::delete-library + ptk/UpdateEvent + (update [_ state] + (-> state + (assoc-in [:library :last-deleted-library] library-id))) + + ptk/WatchEvent + (watch [_ state stream] + (let [method (case type + :icons :delete-icon-library + :images :delete-image-library + :palettes :delete-color-library)] + (->> (rp/mutation! method {:id library-id}) + (rx/map #(delete-library-result type library-id))))))) + +(defn delete-library-result + [type library-id] + (ptk/reify ::create-library-result + ptk/UpdateEvent + (update [_ state] + (let [update-fn (fn [libraries] + (filterv #(not= library-id (:id %)) libraries))] + (-> state + (update-in [:library type] update-fn)))))) + +;; Delete library item + +(declare delete-item-result) + +(defn delete-item + [type library-id item-id] + (ptk/reify ::delete-item + ptk/WatchEvent + (watch [_ state stream] + (let [method (case type + :icons :delete-icon + :images :delete-image + :palettes :delete-color)] + (->> (rp/mutation! method {:id item-id}) + (rx/map #(delete-item-result type library-id item-id))))))) + +(defn delete-item-result + [type library-id item-id] + (ptk/reify ::delete-item-result + ptk/UpdateEvent + (update [_ state] + (let [update-fn (fn [items] + (filterv #(not= item-id (:id %)) items))] + (-> state + (update-in [:library :selected-items library-id] update-fn)))))) + +;; Batch delete + +(declare batch-delete-item-result) + +(defn batch-delete-item + [type library-id item-ids] + (ptk/reify ::batch-delete-item + ptk/WatchEvent + (watch [_ state stream] + (let [method (case type + :icons :delete-icon + :images :delete-image + :palettes :delete-color)] + (->> (rx/from item-ids) + (rx/flat-map #(rp/mutation! method {:id %})) + (rx/last) + (rx/map #(batch-delete-item-result type library-id item-ids))))))) + +(defn batch-delete-item-result + [type library-id item-ids] + (ptk/reify ::batch-delete-item-result + ptk/UpdateEvent + (update [_ state] + (let [item-ids-set (set item-ids) + update-fn (fn [items] + (filterv #(not (item-ids-set (:id %))) items))] + (-> state + (update-in [:library :selected-items library-id] update-fn)))))) diff --git a/frontend/src/uxbox/main/ui/components/editable_label.cljs b/frontend/src/uxbox/main/ui/components/editable_label.cljs new file mode 100644 index 000000000..80eb3c153 --- /dev/null +++ b/frontend/src/uxbox/main/ui/components/editable_label.cljs @@ -0,0 +1,42 @@ +(ns uxbox.main.ui.components.editable-label + (:require + [rumext.alpha :as mf] + [uxbox.builtins.icons :as i] + [uxbox.main.ui.keyboard :as kbd] + [uxbox.util.dom :as dom] + [uxbox.util.timers :as timers] + [uxbox.util.data :refer [classnames]])) + +(mf/defc editable-label + [{:keys [ value on-change on-cancel edit readonly class-name]}] + (let [input (mf/use-ref nil) + state (mf/use-state (:editing false)) + is-editing (or edit (:editing @state)) + start-editing (fn [] + (swap! state assoc :editing true) + (timers/schedule 100 #(dom/focus! (mf/ref-node input)))) + stop-editing (fn [] (swap! state assoc :editing false)) + cancel-editing (fn [] + (stop-editing) + (when on-cancel (on-cancel))) + on-dbl-click (fn [e] (when (not readonly) (start-editing))) + on-key-up (fn [e] + (cond + (kbd/esc? e) + (cancel-editing) + + (kbd/enter? e) + (let [value (-> e dom/get-target dom/get-value)] + (on-change value) + (stop-editing)))) + ] + + (if is-editing + [:div.editable-label {:class class-name} + [:input.editable-label-input {:ref input + :default-value value + :on-key-down on-key-up}] + [:span.editable-label-close {:on-click cancel-editing} i/close]] + [:span.editable-label {:class class-name + :on-double-click on-dbl-click} value] + ))) diff --git a/frontend/src/uxbox/main/ui/confirm.cljs b/frontend/src/uxbox/main/ui/confirm.cljs index d5d8731c7..72bfb1ddd 100644 --- a/frontend/src/uxbox/main/ui/confirm.cljs +++ b/frontend/src/uxbox/main/ui/confirm.cljs @@ -14,31 +14,36 @@ [uxbox.util.dom :as dom])) (mf/defc confirm-dialog - [{:keys [on-accept on-cancel hint] :as ctx}] - (letfn [(accept [event] - (dom/prevent-default event) - (modal/hide!) - (on-accept (dissoc ctx :on-accept :on-cancel))) - (cancel [event] - (dom/prevent-default event) - (modal/hide!) - (when on-cancel - (on-cancel (dissoc ctx :on-accept :on-cancel))))] + [{:keys [message on-accept on-cancel hint cancel-text accept-text] :as ctx}] + (let [message (or message (tr "ds.confirm-title")) + cancel-text (or cancel-text (tr "ds.confirm-cancel")) + accept-text (or accept-text (tr "ds.confirm-ok")) + + accept + (fn [event] + (dom/prevent-default event) + (modal/hide!) + (on-accept (dissoc ctx :on-accept :on-cancel))) + + cancel + (fn [event] + (dom/prevent-default event) + (modal/hide!) + (when on-cancel + (on-cancel (dissoc ctx :on-accept :on-cancel))))] [:div.lightbox-body.confirm-dialog - [:h3 (tr "ds.confirm-title")] - (if hint - [:span hint]) - [:div.row-flex - [:input.btn-success.btn-small + [:h3.confirm-dialog-title message] + (if hint [:span hint]) + + [:div.confirm-dialog-buttons + [:input.confirm-dialog-cancel-button {:type "button" - :value (tr "ds.confirm-ok") - :on-click accept}] - [:input.btn-delete.btn-small + :value cancel-text + :on-click cancel}] + + [:input.confirm-dialog-accept-button {:type "button" - :value (tr "ds.confirm-cancel") - :on-click cancel}]] - [:a.close {:href "#" - :on-click #(do - (dom/prevent-default %) - (modal/hide!))} - i/close]])) + :value accept-text + :on-click accept}]] + + [:a.close {:href "#" :on-click cancel} i/close]])) diff --git a/frontend/src/uxbox/main/ui/dashboard.cljs b/frontend/src/uxbox/main/ui/dashboard.cljs index a1377ec4d..ab0549a75 100644 --- a/frontend/src/uxbox/main/ui/dashboard.cljs +++ b/frontend/src/uxbox/main/ui/dashboard.cljs @@ -87,7 +87,8 @@ :dashboard-library-images-index :dashboard-library-palettes :dashboard-library-palettes-index) - (mf/element library-page #js {:team-id team-id + (mf/element library-page #js {:key library-id + :team-id team-id :library-id library-id :section library-section}) diff --git a/frontend/src/uxbox/main/ui/dashboard/library.cljs b/frontend/src/uxbox/main/ui/dashboard/library.cljs index dd9ef5227..462830382 100644 --- a/frontend/src/uxbox/main/ui/dashboard/library.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/library.cljs @@ -17,6 +17,8 @@ [uxbox.util.i18n :as i18n :refer [t tr]] [uxbox.util.color :as uc] [uxbox.util.dom :as dom] + [uxbox.util.time :as dt] + [uxbox.main.data.library :as dlib] [uxbox.main.data.icons :as dico] [uxbox.main.data.images :as dimg] [uxbox.main.data.colors :as dcol] @@ -27,6 +29,7 @@ [uxbox.main.ui.modal :as modal] [uxbox.main.ui.confirm :refer [confirm-dialog]] [uxbox.main.ui.colorpicker :refer [colorpicker most-used-colors]] + [uxbox.main.ui.components.editable-label :refer [editable-label]] )) (mf/defc modal-create-color @@ -53,19 +56,9 @@ [:a.close {:href "#" :on-click cancel} i/close]]))) - -(defmulti create-library (fn [x _] x)) -(defmethod create-library :icons [_ team-id] - (let [name (str "Icon Library "(gensym "l"))] - (st/emit! (dico/create-icon-library team-id name)))) - -(defmethod create-library :images [_ team-id] - (let [name (str "Image Library "(gensym "l"))] - (st/emit! (dimg/create-image-library team-id name)))) - -(defmethod create-library :palettes [_ team-id] - (let [name (str "Image Library "(gensym "l"))] - (st/emit! (dcol/create-color-library team-id name)))) +(defn create-library [section team-id] + (let [name (str (str (str/title (name section)) " " (gensym "Library ")))] + (st/emit! (dlib/create-library section team-id name)))) (defmulti create-item (fn [x _ _] x)) @@ -126,28 +119,51 @@ :on-click (fn [] (let [path (keyword (str "dashboard-library-" (name section)))] - (dico/fetch-icon-library (:id item)) + (dlib/retrieve-libraries :icons (:id item)) (st/emit! (rt/nav path {:team-id team-id :library-id (:id item)}))))} - [:a (:name item)]])]])) + [:& editable-label {:value (:name item) + :on-change #(st/emit! (dlib/rename-library section library-id %))}] + ])]])) (mf/defc library-top-menu - [{:keys [selected section library-id]}] - (let [state (mf/use-state {:is-open false}) - locale (i18n/use-locale)] + [{:keys [selected section library-id team-id on-delete-selected]}] + (let [state (mf/use-state {:is-open false + :editing-name false}) + locale (i18n/use-locale) + stop-editing #(swap! state assoc :editing-name false)] [:header.library-top-menu [:div.library-top-menu-current-element - [:h2.library-top-menu-current-element-name (:name selected)] + [:& editable-label {:edit (:editing-name @state) + :on-change #(do + (stop-editing) + (st/emit! (dlib/rename-library section library-id %))) + :on-cancel #(swap! state assoc :editing-name false) + :class-name "library-top-menu-current-element-name" + :value (:name selected)}] [:a.library-top-menu-current-action { :on-click #(swap! state update :is-open not)} [:span i/arrow-down]] [:& context-menu {:show (:is-open @state) :on-close #(swap! state update :is-open not) - :options [[(t locale "ds.button.rename") #(println "Rename")] - [(t locale "ds.button.delete") #(println "Delete")]]}]] + :options [[(t locale "ds.button.rename") + #(swap! state assoc :editing-name true)] + + [(t locale "ds.button.delete") + (fn [] + (let [path (keyword (str "dashboard-library-" (name section) "-index"))] + (modal/show! + confirm-dialog + {:on-accept #(do + (st/emit! (dlib/delete-library section library-id)) + (st/emit! (rt/nav path {:team-id team-id}))) + :message "Are you sure you want to delete this library?" + :accept-text "Delete"})))]]}]] [:div.library-top-menu-actions - [:a i/trash] + [:a.library-top-menu-actions-delete + {:on-click #(when on-delete-selected (on-delete-selected))} + i/trash] (if (= section :palettes) [:button.btn-dashboard @@ -158,23 +174,35 @@ [:label {:for "file-upload" :class-name "btn-dashboard"} (t locale (str "dashboard.library.add-item." (name section)))] [:input {:on-change #(create-item section library-id %) - :id "file-upload" :type "file" :style {:display "none"}}]] - - )]])) + :id "file-upload" + :type "file" + :multiple true + :accept (case section + :images "image" + :icons "image/svg+xml" + "") + :style {:display "none"}}]])]])) (mf/defc library-icon-card - [{:keys [id name url content metadata]}] - (let [locale (i18n/use-locale) - state (mf/use-state {:is-open false})] + [{:keys [item on-select on-unselect]}] + (let [{:keys [id name url content metadata library-id modified-at]} item + locale (i18n/use-locale) + state (mf/use-state {:is-open false + :selected false}) + time (dt/timeago modified-at {:locale locale}) + handle-change (fn [] + (swap! state update :selected not) + (if (:selected @state) + (when on-unselect (on-unselect id)) + (when on-select (on-select id))))] [:div.library-card.library-icon [:div.input-checkbox.check-primary [:input {:type "checkbox" :id (str "icon-" id) - :on-change #(println "toggle-selection") - #_(:checked false)}] + :on-change handle-change + :checked (:selected @state)}] [:label {:for (str "icon-" id)}]] [:div.library-card-image - #_[:object { :data url :type "image/svg+xml" }] [:svg {:view-box (->> metadata :view-box (str/join " ")) :width (:width metadata) :height (:height metadata) @@ -182,112 +210,181 @@ [:div.library-card-footer [:div.library-card-footer-name name] - [:div.library-card-footer-timestamp "Less than 5 seconds ago"] + [:div.library-card-footer-timestamp time] [:div.library-card-footer-menu { :on-click #(swap! state update :is-open not) } i/actions] [:& context-menu {:show (:is-open @state) :on-close #(swap! state update :is-open not) - :options [[(t locale "ds.button.delete") #(println "Delete")]]}]]])) + :options [[(t locale "ds.button.delete") + (fn [] + (modal/show! + confirm-dialog + {:on-accept #(st/emit! (dlib/delete-item :icons library-id id)) + :message "Are you sure you want to delete this icon?" + :accept-text "Delete"}))]]}]]])) (mf/defc library-image-card - [{:keys [id name thumb-uri]}] - (let [locale (i18n/use-locale) - state (mf/use-state {:is-open false})] + [{:keys [item on-select on-unselect]}] + (let [{:keys [id name thumb-uri library-id modified-at]} item + locale (i18n/use-locale) + state (mf/use-state {:is-open false}) + time (dt/timeago modified-at {:locale locale}) + handle-change (fn [] + (swap! state update :selected not) + (if (:selected @state) + (when on-unselect (on-unselect id)) + (when on-select (on-select id))))] [:div.library-card.library-image [:div.input-checkbox.check-primary [:input {:type "checkbox" :id (str "image-" id) - :on-change #(println "toggle-selection") - #_(:checked false)}] + :on-change handle-change + :checked (:selected @state)}] [:label {:for (str "image-" id)}]] [:div.library-card-image [:img {:src thumb-uri}]] [:div.library-card-footer [:div.library-card-footer-name name] - [:div.library-card-footer-timestamp "Less than 5 seconds ago"] + [:div.library-card-footer-timestamp time] [:div.library-card-footer-menu { :on-click #(swap! state update :is-open not) } i/actions] [:& context-menu {:show (:is-open @state) :on-close #(swap! state update :is-open not) - :options [[(t locale "ds.button.delete") #(println "Delete")]]}]]])) + :options [[(t locale "ds.button.delete") + (fn [] + (modal/show! + confirm-dialog + {:on-accept #(st/emit! (dlib/delete-item :images library-id id)) + :message "Are you sure you want to delete this image?" + :accept-text "Delete"}))]]}]]])) (mf/defc library-color-card - [{ :keys [ id content ] }] - (when content - (let [locale (i18n/use-locale) - state (mf/use-state {:is-open false})] - [:div.library-card.library-color - [:div.input-checkbox.check-primary - [:input {:type "checkbox" - :id (str "color-" id) - :on-change #(println "toggle-selection") - #_(:checked false)}] - [:label {:for (str "color-" id)}]] - [:div.library-card-image - { :style { :background-color content }}] - [:div.library-card-footer - [:div.library-card-footer-name content ] - [:div.library-card-footer-color - [:span.library-card-footer-color-label "RGB"] - [:span.library-card-footer-color-rgb (str/join " " (uc/hex->rgb content))]] - [:div.library-card-footer-menu - { :on-click #(swap! state update :is-open not) } - i/actions] - [:& context-menu - {:show (:is-open @state) - :on-close #(swap! state update :is-open not) - :options [[(t locale "ds.button.delete") #(println "Delete")]]}]]]))) + [{:keys [item on-select on-unselect]}] + (let [{:keys [ id content library-id modified-at]} item + locale (i18n/use-locale) + state (mf/use-state {:is-open false}) + handle-change (fn [] + (swap! state update :selected not) + (if (:selected @state) + (when on-unselect (on-unselect id)) + (when on-select (on-select id))))] + (when content + [:div.library-card.library-color + [:div.input-checkbox.check-primary + [:input {:type "checkbox" + :id (str "color-" id) + :on-change handle-change + :checked (:selected @state)}] + [:label {:for (str "color-" id)}]] + [:div.library-card-image + { :style { :background-color content }}] + [:div.library-card-footer + [:div.library-card-footer-name content ] + [:div.library-card-footer-color + [:span.library-card-footer-color-label "RGB"] + [:span.library-card-footer-color-rgb (str/join " " (uc/hex->rgb content))]] + [:div.library-card-footer-menu + { :on-click #(swap! state update :is-open not) } + i/actions] + [:& context-menu + {:show (:is-open @state) + :on-close #(swap! state update :is-open not) + :options [[(t locale "ds.button.delete") + (fn [] + (modal/show! + confirm-dialog + {:on-accept #(st/emit! (dlib/delete-item :palettes library-id id)) + :message "Are you sure you want to delete this color?" + :accept-text "Delete"}))]]}]]]))) -(def icon-libraries-ref - (-> (comp (l/key :library) (l/key :icon-libraries)) +(defn libraries-ref [section] + (-> (comp (l/key :library) (l/key section)) (l/derive st/state))) -(def image-libraries-ref - (-> (comp (l/key :library) (l/key :image-libraries)) +(defn selected-items-ref [library-id] + (-> (comp (l/key :library) (l/key :selected-items) (l/key library-id)) (l/derive st/state))) -(def color-libraries-ref - (-> (comp (l/key :library) (l/key :color-libraries)) - (l/derive st/state))) - -(def selected-items-ref - (-> (comp (l/key :library) (l/key :selected-items)) +(def last-deleted-library-ref + (-> (comp (l/key :library) (l/key :last-deleted-library)) (l/derive st/state))) (mf/defc library-page [{:keys [team-id library-id section]}] - (mf/use-effect {:fn #(case section - :icons (st/emit! (dico/fetch-icon-libraries team-id)) - :images (st/emit! (dimg/fetch-image-libraries team-id)) - :palettes (st/emit! (dcol/fetch-color-libraries team-id))) - :deps (mf/deps section team-id)}) - (mf/use-effect {:fn #(when library-id - (case section - :icons (st/emit! (dico/fetch-icon-library library-id)) - :images (st/emit! (dimg/fetch-image-library library-id)) - :palettes (st/emit! (dcol/fetch-color-library library-id)))) - :deps (mf/deps library-id)}) - (let [libraries (case section - :icons (mf/deref icon-libraries-ref) - :images (mf/deref image-libraries-ref) - :palettes (mf/deref color-libraries-ref)) - items (mf/deref selected-items-ref) + (let [state (mf/use-state {:selected #{}}) + libraries (mf/deref (libraries-ref section)) + items (mf/deref (selected-items-ref library-id)) + last-deleted-library (mf/deref last-deleted-library-ref) selected-library (first (filter #(= (:id %) library-id) libraries))] + + (mf/use-effect {:fn #(if (and (nil? library-id) (> (count libraries) 0)) + (let [path (keyword (str "dashboard-library-" (name section)))] + (st/emit! (rt/nav path {:team-id team-id :library-id (:id (first libraries))})))) + :deps (mf/deps libraries)}) + + (mf/use-effect {:fn #(if (and library-id (not (some (fn [{id :id}] (= library-id id)) libraries))) + (let [path (keyword (str "dashboard-library-" (name section) "-index"))] + (st/emit! (rt/nav path {:team-id team-id})))) + :deps (mf/deps libraries)}) + + (mf/use-effect {:fn #(st/emit! (dlib/retrieve-libraries section team-id)) + :deps (mf/deps section team-id)}) + + (mf/use-effect {:fn #(when (and library-id (not= last-deleted-library library-id)) + (st/emit! (dlib/retrieve-library-data section library-id))) + :deps (mf/deps library-id last-deleted-library)}) + [:div.library-page [:& library-header {:section section :team-id team-id}] [:& library-sidebar {:items libraries :team-id team-id :library-id library-id :section section}] - (when library-id + (if library-id [:section.library-content - [:& library-top-menu {:selected selected-library :section section :library-id library-id}] - [:div.library-page-cards-container - (for [item items] - (let [item (assoc item :key (:id item))] - (case section - :icons [:& library-icon-card item] - :images [:& library-image-card item] - :palettes [:& library-color-card item ])))]])])) + [:& library-top-menu + {:selected selected-library + :section section + :library-id library-id + :team-id team-id + :on-delete-selected + (fn [] + (when (-> @state :selected count (> 0)) + (modal/show! + confirm-dialog + {:on-accept #(st/emit! (dlib/batch-delete-item section library-id (:selected @state))) + :message (str "Are you sure you want to delete " (-> @state :selected count) " items?") + :accept-text "Delete"}) + ) + ) + }] + [:* + ;; TODO: Fix the chunked list + #_[:& chunked-list {:items items + :initial-size 30 + :chunk-size 30} + (fn [item] + (let [item (assoc item :key (:id item))] + (case section + :icons [:& library-icon-card item] + :images [:& library-image-card item] + :palettes [:& library-color-card item ])))] + (if (> (count items) 0) + [:div.library-page-cards-container + (for [item items] + (let [item (assoc item :key (:id item)) + props {:item item + :key (:id item) + :on-select #(swap! state update :selected conj %) + :on-unselect #(swap! state update :selected disj %)}] + (case section + :icons [:& library-icon-card props] + :images [:& library-image-card props] + :palettes [:& library-color-card props])))] + [:div.library-content-empty + [:p.library-content-empty-text "You still have no elements in this library"]])]] + + [:div.library-content-empty + [:p.library-content-empty-text "You still have no image libraries."]])])) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs index 6e8547214..ea4cbfd3f 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs @@ -57,7 +57,6 @@ (st/emit! (dw/rename-shape (:id shape) name)) (swap! local assoc :edition false))) on-key-down (fn [event] - (js/console.log event) (when (kbd/enter? event) (on-blur event))) on-click (fn [event] diff --git a/frontend/src/uxbox/util/data.cljs b/frontend/src/uxbox/util/data.cljs index 0ea8ba097..4dd2035f5 100644 --- a/frontend/src/uxbox/util/data.cljs +++ b/frontend/src/uxbox/util/data.cljs @@ -149,7 +149,7 @@ [& params] {:pre [(even? (count params))]} (str/join " " (reduce (fn [acc [k v]] - (if (true? v) + (if (and k (true? v)) (conj acc (name k)) acc)) [] diff --git a/frontend/src/uxbox/util/dom.cljs b/frontend/src/uxbox/util/dom.cljs index a7730eb5e..3c153c42c 100644 --- a/frontend/src/uxbox/util/dom.cljs +++ b/frontend/src/uxbox/util/dom.cljs @@ -142,8 +142,7 @@ y (.-clientY event)] (gpt/point x y))) -(defn get-offset-position - [event] - (let [x (.-offsetX event) - y (.-offsetY event)] - (gpt/point x y))) +(defn focus! + [node] + (.focus node)) +