diff --git a/frontend/resources/images/cap-circle-marker.svg b/frontend/resources/images/cap-circle-marker.svg new file mode 100644 index 0000000000..b41388e506 --- /dev/null +++ b/frontend/resources/images/cap-circle-marker.svg @@ -0,0 +1 @@ + diff --git a/frontend/resources/images/cap-diamond-marker.svg b/frontend/resources/images/cap-diamond-marker.svg new file mode 100644 index 0000000000..0e23183406 --- /dev/null +++ b/frontend/resources/images/cap-diamond-marker.svg @@ -0,0 +1 @@ + diff --git a/frontend/resources/images/cap-line-arrow.svg b/frontend/resources/images/cap-line-arrow.svg new file mode 100644 index 0000000000..0df0673ba7 --- /dev/null +++ b/frontend/resources/images/cap-line-arrow.svg @@ -0,0 +1 @@ + diff --git a/frontend/resources/images/cap-round.svg b/frontend/resources/images/cap-round.svg new file mode 100644 index 0000000000..594e02575f --- /dev/null +++ b/frontend/resources/images/cap-round.svg @@ -0,0 +1 @@ + diff --git a/frontend/resources/images/cap-square-marker.svg b/frontend/resources/images/cap-square-marker.svg new file mode 100644 index 0000000000..2340ce571b --- /dev/null +++ b/frontend/resources/images/cap-square-marker.svg @@ -0,0 +1 @@ + diff --git a/frontend/resources/images/cap-square.svg b/frontend/resources/images/cap-square.svg new file mode 100644 index 0000000000..a2e3b6260b --- /dev/null +++ b/frontend/resources/images/cap-square.svg @@ -0,0 +1 @@ + diff --git a/frontend/resources/images/cap-triangle-arrow.svg b/frontend/resources/images/cap-triangle-arrow.svg new file mode 100644 index 0000000000..f294d01cf5 --- /dev/null +++ b/frontend/resources/images/cap-triangle-arrow.svg @@ -0,0 +1 @@ + diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index 5ddf325c03..0380d9303f 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -1436,5 +1436,57 @@ } } } - +} + +.cap-select { + background-color: transparent; + border: 1px solid transparent; + border-bottom-color: $color-gray-40; + color: $color-gray-10; + cursor: pointer; + font-size: $fs11; + margin: $x-small; + overflow: hidden; + padding: $x-small; + padding-right: 20px; + position: relative; + text-overflow: ellipsis; + white-space: nowrap; + width: 100%; + + & .cap-select-button { + svg { + fill: $color-gray-10; + height: 11px; + position: absolute; + right: 5px; + top: 6px; + width: 11px; + } + } + + &:hover { + border-color: $color-gray-40; + } + + &:focus { + border-color: $color-primary; + } +} + +.cap-select-dropdown { + right: 5px; + top: 30px; + z-index: 12; + min-width: 200px; + position: fixed; + + & li.separator { + border-top: 1px solid $color-gray-10; + } + + & li img { + width: 16px; + margin-right: $small; + } } diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs index 39c996bc17..543d55f526 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs @@ -13,6 +13,7 @@ [app.main.data.workspace.colors :as dc] [app.main.data.workspace.undo :as dwu] [app.main.store :as st] + [app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]] [app.util.dom :as dom] @@ -44,6 +45,26 @@ "" (pr-str value))) +(defn- stroke-cap-names [] + [[nil (tr "workspace.options.stroke-cap.none") false] + [:line-arrow (tr "workspace.options.stroke-cap.line-arrow") true] + [:triangle-arrow (tr "workspace.options.stroke-cap.triangle-arrow") false] + [:square-marker (tr "workspace.options.stroke-cap.square-marker") false] + [:circle-marker (tr "workspace.options.stroke-cap.circle-marker") false] + [:diamond-marker (tr "workspace.options.stroke-cap.diamond-marker") false] + [:round (tr "workspace.options.stroke-cap.round") true] + [:square (tr "workspace.options.stroke-cap.square") false]]) + +(defn- value->name [value] + (if (= value :multiple) + "--" + (-> (d/seek #(= (first %) value) (stroke-cap-names)) + (second)))) + +(defn- value->img [value] + (when (and value (not= value :multiple)) + (str "images/cap-" (name value) ".svg"))) + (mf/defc stroke-menu {::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values" "type" "show-caps"]))]} [{:keys [ids type values show-caps] :as props}] @@ -53,8 +74,14 @@ (tr "workspace.options.stroke")) show-options (not= (:stroke-style values :none) :none) + show-caps (and show-caps (= (:stroke-alignment values) :center)) - show-caps (and show-caps (= (:stroke-alignment values) :center)) + start-caps-state (mf/use-state {:open? false + :top 0 + :left 0}) + end-caps-state (mf/use-state {:open? false + :top 0 + :left 0}) current-stroke-color {:color (:stroke-color values) :opacity (:stroke-opacity values) @@ -105,21 +132,34 @@ (apply (partial assoc %) kvs) %)) + open-caps-select + (fn [caps-state] + (fn [event] + (let [window-size (dom/get-window-size) + + target (dom/get-current-target event) + rect (dom/get-bounding-rect target) + + top (+ (:bottom rect) 5) + left (if (< (+ (:left rect) 200) (:width window-size)) + (:left rect) + (- (:width window-size) 205))] + (swap! caps-state assoc :open? true + :left left + :top top)))) + + close-caps-select + (fn [caps-state] + (fn [_] + (swap! caps-state assoc :open? false))) + on-stroke-cap-start-change - (fn [event] - (let [value (-> (dom/get-target event) - (dom/get-value) - (d/read-string))] - (when-not (str/empty? value) - (st/emit! (dch/update-shapes ids (update-cap-attr :stroke-cap-start value)))))) + (fn [value] + (st/emit! (dch/update-shapes ids (update-cap-attr :stroke-cap-start value)))) on-stroke-cap-end-change - (fn [event] - (let [value (-> (dom/get-target event) - (dom/get-value) - (d/read-string))] - (when-not (str/empty? value) - (st/emit! (dch/update-shapes ids (update-cap-attr :stroke-cap-end value)))))) + (fn [value] + (st/emit! (dch/update-shapes ids (update-cap-attr :stroke-cap-end value)))) on-stroke-cap-switch (fn [_] @@ -199,34 +239,40 @@ ;; Stroke Caps (when show-caps [:div.row-flex - [:select#style.input-select {:value (enum->string (:stroke-cap-start values)) - :on-change on-stroke-cap-start-change} - (when (= (:stroke-cap-start values) :multiple) - [:option {:value ""} "--"]) - [:option {:value ""} (tr "workspace.options.stroke-cap.none")] - [:option {:value ":line-arrow"} (tr "workspace.options.stroke-cap.line-arrow")] - [:option {:value ":triangle-arrow"} (tr "workspace.options.stroke-cap.triangle-arrow")] - [:option {:value ":square-marker"} (tr "workspace.options.stroke-cap.square-marker")] - [:option {:value ":circle-marker"} (tr "workspace.options.stroke-cap.circle-marker")] - [:option {:value ":diamond-marker"} (tr "workspace.options.stroke-cap.diamond-marker")] - [:option {:value ":round"} (tr "workspace.options.stroke-cap.round")] - [:option {:value ":square"} (tr "workspace.options.stroke-cap.square")]] + [:div.cap-select {:tab-index 0 ;; tab-index to make the element focusable + :on-click (open-caps-select start-caps-state)} + (value->name (:stroke-cap-start values)) + [:span.cap-select-button + i/arrow-down]] + [:& dropdown {:show (:open? @start-caps-state) + :on-close (close-caps-select start-caps-state)} + [:ul.dropdown.cap-select-dropdown {:style {:top (:top @start-caps-state) + :left (:left @start-caps-state)}} + (for [[value label separator] (stroke-cap-names)] + (let [img (value->img value)] + [:li {:class (dom/classnames :separator separator) + :on-click #(on-stroke-cap-start-change value)} + (when img [:img {:src (value->img value)}]) + label]))]] [:div.element-set-actions-button {:on-click on-stroke-cap-switch} i/switch] - [:select#style.input-select {:value (enum->string (:stroke-cap-end values)) - :on-change on-stroke-cap-end-change} - (when (= (:stroke-cap-end values) :multiple) - [:option {:value ""} "--"]) - [:option {:value ""} (tr "workspace.options.stroke-cap.none")] - [:option {:value ":line-arrow"} (tr "workspace.options.stroke-cap.line-arrow")] - [:option {:value ":triangle-arrow"} (tr "workspace.options.stroke-cap.triangle-arrow")] - [:option {:value ":square-marker"} (tr "workspace.options.stroke-cap.square-marker")] - [:option {:value ":circle-marker"} (tr "workspace.options.stroke-cap.circle-marker")] - [:option {:value ":diamond-marker"} (tr "workspace.options.stroke-cap.diamond-marker")] - [:option {:value ":round"} (tr "workspace.options.stroke-cap.round")] - [:option {:value ":square"} (tr "workspace.options.stroke-cap.square")]]])]] + [:div.cap-select {:tab-index 0 + :on-click (open-caps-select end-caps-state)} + (value->name (:stroke-cap-end values)) + [:span.cap-select-button + i/arrow-down]] + [:& dropdown {:show (:open? @end-caps-state) + :on-close (close-caps-select end-caps-state)} + [:ul.dropdown.cap-select-dropdown {:style {:top (:top @end-caps-state) + :left (:left @end-caps-state)}} + (for [[value label separator] (stroke-cap-names)] + (let [img (value->img value)] + [:li {:class (dom/classnames :separator separator) + :on-click #(on-stroke-cap-end-change value)} + (when img [:img {:src (value->img value)}]) + label]))]]])]] ;; NO STROKE [:div.element-set diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index 9cf48d13de..91ff3ab2e2 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -86,6 +86,12 @@ [event] (.-target event)) +(defn get-current-target + "Extract the current target from event instance (different from target + when event triggered in a child of the suscribing element)." + [event] + (.-currentTarget event)) + (defn get-parent [dom] (.-parentElement ^js dom))