diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index 8f091d3fb..c3c1e4e77 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -263,11 +263,6 @@ padding: $x-small $big $x-small $x-small; position: relative; - & hr { - margin: 0; - border-color: $color-gray-20; - } - .dropdown-button { position: absolute; right: $x-small; @@ -284,7 +279,9 @@ font-size: $fs13; } - .custom-select-dropdown { + + } + .custom-select-dropdown { position: absolute; left: 0; z-index: 12; @@ -297,6 +294,11 @@ border-radius: $br-small; box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.25); + hr { + margin: 0; + border-color: $color-gray-20; + } + li { color: $color-gray-60; cursor: pointer; @@ -327,63 +329,96 @@ } } - & li.checked-element { - padding-left: 0; + & li.checked-element { + padding-left: 0; - & span { - margin: 0; - color: $color-black; - } + & span { + margin: 0; + color: $color-black; + } - & svg { - visibility: hidden; - width: 8px; - height: 8px; - background: none; - margin: 0.25rem; - fill: $color-black; - } + & svg { + visibility: hidden; + width: 8px; + height: 8px; + background: none; + margin: 0.25rem; + fill: $color-black; + } - &.is-selected { - & svg { - visibility: visible; - } - } - } - } + &.is-selected { + & svg { + visibility: visible; + } + } + } .editable-select { - height: 38px; - margin-right: $small; - position: relative; - width: 60%; - - .input-text { - left: 0; - position: absolute; - top: -1px; + position: relative; + height: 38px; + margin-right: $small; + position: relative; width: 60%; - } - - .input-select { - background-color: transparent; - border: none; - border-bottom: 1px solid $color-gray-40; - color: transparent; - left: 0; - position: absolute; - top: 0; - width: 100%; - option { - color: $color-gray-60; - background: $color-white; - font-size: $fs12; + svg { + fill: $color-gray-40; + height: 10px; + width: 10px; + } + + .input-text { + left: 0; + position: absolute; + top: -1px; + width: 60%; + } + + .input-select { + background-color: transparent; + border: none; + border-bottom: 1px solid $color-gray-40; + color: transparent; + left: 0; + position: absolute; + top: 0; + width: 100%; + + option { + color: $color-gray-60; + background: $color-white; + font-size: $fs12; + } + } + + .dropdown-button { + position: absolute; + top: 7px; + right: 0; + } + + &.input-option { + height: 2rem; + border-bottom: 1px solid #64666A; + width: 100%; + margin-left: 0.25rem; + + .input-text { + border: none; + margin: 0; + width: calc(100% - 12px); + height: 100%; + top: auto; + color: #b1b2b5; + } } - } } } +.grid-option-main .editable-select.input-option .input-text { + padding: 0; + padding-top: 0.18rem; +} + .color-th { background-color: $color-gray-10; border: 1px solid $color-gray-10; diff --git a/frontend/src/uxbox/main/ui/components/dropdown.cljs b/frontend/src/uxbox/main/ui/components/dropdown.cljs index 48f8f6d86..eae0f817d 100644 --- a/frontend/src/uxbox/main/ui/components/dropdown.cljs +++ b/frontend/src/uxbox/main/ui/components/dropdown.cljs @@ -43,8 +43,8 @@ (mf/defc dropdown {::mf/wrap-props false} [props] - (assert (fn? (gobj/get props "on-close")) "missing `on-close` prop") - (assert (boolean? (gobj/get props "show")) "missing `show` prop") + #_(assert (fn? (gobj/get props "on-close")) "missing `on-close` prop") + #_(assert (boolean? (gobj/get props "show")) "missing `show` prop") (when (gobj/get props "show") (mf/element dropdown' props))) diff --git a/frontend/src/uxbox/main/ui/components/editable_select.cljs b/frontend/src/uxbox/main/ui/components/editable_select.cljs new file mode 100644 index 000000000..57093c6cb --- /dev/null +++ b/frontend/src/uxbox/main/ui/components/editable_select.cljs @@ -0,0 +1,70 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns uxbox.main.ui.components.editable-select + (:require + [rumext.alpha :as mf] + [uxbox.common.uuid :as uuid] + [uxbox.common.data :as d] + [uxbox.util.dom :as dom] + [uxbox.main.ui.icons :as i] + [uxbox.main.ui.components.dropdown :refer [dropdown]])) + +(mf/defc editable-select [{:keys [value type options class on-change]}] + (let [state (mf/use-state {:id (uuid/next) + :is-open? false + :current-value value}) + open-dropdown #(swap! state assoc :is-open? true) + close-dropdown #(swap! state assoc :is-open? false) + + select-item (fn [value] + (fn [event] + (swap! state assoc :current-value value) + (when on-change (on-change value)))) + + as-key-value (fn [item] (if (map? item) [(:value item) (:label item)] [item item])) + + labels-map (into {} (->> options (map as-key-value))) + + value->label (fn [value] (get labels-map value value)) + + handle-change-input (fn [event] + (let [value (-> event dom/get-target dom/get-value) + value (or (d/parse-integer value) value)] + (swap! state assoc :current-value value) + (when on-change (on-change value))))] + + (mf/use-effect + (mf/deps value) + #(reset! state {:current-value value})) + + (mf/use-effect + (mf/deps options) + #(reset! state {:is-open? false + :current-value value})) + + [:div.editable-select {:class class} + [:input.input-text {:value (or (-> @state :current-value value->label) "") + :on-change handle-change-input + :type type}] + [:span.dropdown-button {:on-click open-dropdown} i/arrow-down] + + [:& dropdown {:show (:is-open? @state) + :on-close close-dropdown} + [:ul.custom-select-dropdown + (for [[index item] (map-indexed vector options)] + (cond + (= :separator item) [:hr {:key (str (:id @state) "-" index)}] + :else (let [[value label] (as-key-value item)] + [:li.checked-element + {:key (str (:id @state) "-" index) + :class (when (= value (-> @state :current-value)) "is-selected") + :on-click (select-item value)} + [:span.check-icon i/tick] + [:span label]])))]]])) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/frame_layouts.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/frame_layouts.cljs index 55c1cc1bd..fa0872c37 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/frame_layouts.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/frame_layouts.cljs @@ -20,17 +20,24 @@ [uxbox.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]] [uxbox.main.ui.workspace.sidebar.options.rows.input-row :refer [input-row]] [uxbox.main.ui.components.select :refer [select]] + [uxbox.main.ui.components.editable-select :refer [editable-select]] [uxbox.main.ui.components.dropdown :refer [dropdown]])) (mf/defc advanced-options [{:keys [visible? on-close children]}] (when visible? [:* - [:div.focus-overlay {:on-click #(when on-close (do - (dom/stop-propagation %) - (on-close)))}] + [:div.focus-overlay {:on-click #(when on-close + (do + (dom/stop-propagation %) + (on-close)))}] [:div.advanced-options {} children]])) +(def ^:private size-options + [{:value :auto :label "Auto"} + :separator + 18 12 10 8 6 4 3 2]) + (mf/defc layout-options [{:keys [layout default-layout-params on-change on-remove on-save-layout]}] (let [state (mf/use-state {:show-advanced-options false :changes {}}) @@ -38,42 +45,46 @@ toggle-advanced-options #(swap! state update :show-advanced-options not) - size-options [{:value :auto :label "Auto"} - :separator - 18 12 10 8 6 4 3 2] + emit-changes! + (fn [update-fn] + (swap! state update :changes update-fn) + (when on-change (on-change (d/deep-merge layout (-> @state :changes update-fn))))) - emit-changes! (fn [update-fn] - (swap! state update :changes update-fn) - (when on-change (on-change (d/deep-merge layout (-> @state :changes update-fn))))) + handle-toggle-visibility + (fn [event] + (emit-changes! (fn [changes] (update changes :display #(if (nil? %) false (not %)))))) - handle-toggle-visibility (fn [event] - (emit-changes! (fn [changes] (update changes :display #(if (nil? %) false (not %)))))) + handle-remove-layout + (fn [event] + (when on-remove (on-remove))) - handle-remove-layout (fn [event] - (when on-remove (on-remove))) + handle-change-type + (fn [type] + (let [defaults (type default-layout-params) + keys (keys defaults) + params (->> @state :changes params (select-keys keys) (merge defaults)) + to-merge {:type type :params params}] + (emit-changes! #(d/deep-merge % to-merge)))) - handle-change-type (fn [type] - (let [defaults (type default-layout-params) - params (merge - defaults - (select-keys (keys defaults) (-> @state :changes params))) - to-merge {:type type :params params}] - (emit-changes! #(d/deep-merge % to-merge)))) + handle-change + (fn [& keys] + (fn [value] + (emit-changes! #(assoc-in % keys value)))) - handle-change (fn [& keys] - (fn [value] - (emit-changes! #(assoc-in % keys value)))) + handle-change-event + (fn [& keys] + (fn [event] + (let [change-fn (apply handle-change keys)] + (-> event dom/get-target dom/get-value parse-integer change-fn)))) - handle-change-event (fn [& keys] - (fn [event] - (let [change-fn (apply handle-change keys)] - (-> event dom/get-target dom/get-value parse-integer change-fn)))) + handle-use-default + (fn [] + (emit-changes! #(hash-map :params ((:type layout) default-layout-params)))) - handle-use-default (fn [] - (emit-changes! #(hash-map :params ((:type layout) default-layout-params)))) - handle-set-as-default (fn [] - (let [current-layout (d/deep-merge layout (-> @state :changes))] - (on-save-layout current-layout))) + handle-set-as-default + (fn [] + (let [current-layout (d/deep-merge layout (-> @state :changes))] + (on-save-layout current-layout))) is-default (= (->> @state :changes (d/deep-merge layout) :params) (->> layout :type default-layout-params))] @@ -97,10 +108,11 @@ :no-validate true :value (:size params) :on-change (handle-change-event :params :size)}]] - [:& select {:default-value (:size params) - :class "input-option" - :options size-options - :on-change (handle-change :params :size)}]) + [:& editable-select {:value (:size params) + :type (when (number? (:size params)) "number" ) + :class "input-option" + :options size-options + :on-change (handle-change :params :size)}]) [:div.grid-option-main-actions [:button.custom-button {:on-click handle-toggle-visibility} (if display i/eye i/eye-closed)] @@ -117,18 +129,23 @@ (when (= :row type) [:& input-row {:label "Rows" + :type :editable-select :options size-options :value (:size params) + :min 1 :on-change (handle-change :params :size)}]) (when (= :column type) [:& input-row {:label "Columns" + :type :editable-select :options size-options :value (:size params) + :min 1 :on-change (handle-change :params :size)}]) (when (#{:row :column} type) [:& input-row {:label "Type" + :type :select :options [{:value :stretch :label "Stretch"} {:value :left :label "Left"} {:value :center :label "Center"} @@ -139,6 +156,7 @@ (when (= :row type) [:& input-row {:label "Height" :class "pixels" + :min 1 :value (or (:item-height params) "") :on-change (handle-change :params :item-height)}]) @@ -153,9 +171,11 @@ [:& input-row {:label "Gutter" :class "pixels" :value (:gutter params) + :min 0 :on-change (handle-change :params :gutter)}] [:& input-row {:label "Margin" :class "pixels" + :min 0 :value (:margin params) :on-change (handle-change :params :margin)}]]) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/rows/input_row.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/rows/input_row.cljs index ce4d9d48b..b6557e5b4 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/rows/input_row.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/rows/input_row.cljs @@ -12,21 +12,38 @@ [rumext.alpha :as mf] [uxbox.common.data :as d] [uxbox.main.ui.components.select :refer [select]] + [uxbox.main.ui.components.editable-select :refer [editable-select]] [uxbox.util.dom :as dom])) -(mf/defc input-row [{:keys [label options value class min max on-change]}] - (let [handle-change (fn [value] (when (and (or (not min) (>= value min)) (or (not max) (<= value max))) - (on-change value)))] - [:div.row-flex.input-row - [:span.element-set-subtitle label] - [:div.input-element {:class class} - (if options - [:& select {:default-value value - :class "input-option" - :options options - :on-change on-change}] +(mf/defc input-row [{:keys [label options value class min max on-change type]}] + [:div.row-flex.input-row + [:span.element-set-subtitle label] + [:div.input-element {:class class} + + (case type + :select + [:& select {:default-value value + :class "input-option" + :options options + :on-change on-change}] + :editable-select + [:& editable-select {:value value + :class "input-option" + :options options + :type (when (number? value) "number") + :on-change on-change}] + + (let [handle-change + (fn [event] + (let [value (-> event dom/get-target dom/get-value d/parse-integer)] + (when (and (not (nil? on-change)) + (or (not min) (>= value min)) + (or (not max) (<= value max))) + (on-change value))))] [:input.input-text {:placeholder label :type "number" - :on-change #(-> % dom/get-target dom/get-value d/parse-integer handle-change) - :value value}])]])) + :on-change handle-change + :value value}])) + + ]]) diff --git a/frontend/src/uxbox/util/geom/layout.cljs b/frontend/src/uxbox/util/geom/layout.cljs index e1ff4a2d0..ddc453311 100644 --- a/frontend/src/uxbox/util/geom/layout.cljs +++ b/frontend/src/uxbox/util/geom/layout.cljs @@ -9,10 +9,24 @@ (ns uxbox.util.geom.layout (:require + [uxbox.util.math :as mth] [uxbox.util.geom.point :as gpt])) +(def ^:private default-items 12) + +(defn calculate-default-item-length [frame-length margin gutter] + (/ (- frame-length (+ margin (- margin gutter)) (* gutter default-items)) default-items)) + +(defn calculate-size + "Calculates the number of rows/columns given the other layout parameters" + [frame-length item-length margin gutter] + (let [item-length (or item-length (calculate-default-item-length frame-length margin gutter)) + frame-length-no-margins (- frame-length (+ margin (- margin gutter)))] + (mth/floor (/ frame-length-no-margins (+ item-length gutter))))) + (defn calculate-column-layout [{:keys [width height x y] :as frame} {:keys [size gutter margin item-width type] :as params}] - (let [parts (/ width size) + (let [size (if (number? size) size (calculate-size width item-width margin gutter)) + parts (/ width size) item-width (or item-width (+ parts (- gutter) (/ gutter size) (- (/ (* margin 2) size)))) item-height height initial-offset (case type @@ -25,7 +39,7 @@ [size item-width item-height next-x next-y])) (defn calculate-row-layout [{:keys [width height x y] :as frame} {:keys [size gutter margin item-height type] :as params}] - (let [{:keys [width height x y]} frame + (let [size (if (number? size) size (calculate-size height item-height margin gutter)) parts (/ height size) item-width width item-height (or item-height (+ parts (- gutter) (/ gutter size) (- (/ (* margin 2) size))))