0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-25 07:58:49 -05:00

🐛 Fixed several issues with groups and multiple selection

This commit is contained in:
alonso.torres 2020-12-09 18:26:09 +01:00 committed by Andrey Antukh
parent 9822c52573
commit e26ece57d1
11 changed files with 467 additions and 394 deletions

View file

@ -10,62 +10,66 @@
(ns app.common.attrs)
(defn get-attrs-multi
[shapes attrs]
;; Extract some attributes of a list of shapes.
;; For each attribute, if the value is the same in all shapes,
;; wll take this value. If there is any shape that is different,
;; the value of the attribute will be the keyword :multiple.
;;
;; If some shape has the value nil in any attribute, it's
;; considered a different value. If the shape does not contain
;; the attribute, it's ignored in the final result.
;;
;; Example:
;; (def shapes [{:stroke-color "#ff0000"
;; :stroke-width 3
;; :fill-color "#0000ff"
;; :x 1000 :y 2000 :rx nil}
;; {:stroke-width "#ff0000"
;; :stroke-width 5
;; :x 1500 :y 2000}])
;;
;; (get-attrs-multi shapes [:stroke-color
;; :stroke-width
;; :fill-color
;; :rx
;; :ry])
;; >>> {:stroke-color "#ff0000"
;; :stroke-width :multiple
;; :fill-color "#0000ff"
;; :rx nil
;; :ry nil}
;;
(let [defined-shapes (filter some? shapes)
([shapes attrs] (get-attrs-multi shapes attrs = identity))
([shapes attrs eq-fn sel-fn]
;; Extract some attributes of a list of shapes.
;; For each attribute, if the value is the same in all shapes,
;; wll take this value. If there is any shape that is different,
;; the value of the attribute will be the keyword :multiple.
;;
;; If some shape has the value nil in any attribute, it's
;; considered a different value. If the shape does not contain
;; the attribute, it's ignored in the final result.
;;
;; Example:
;; (def shapes [{:stroke-color "#ff0000"
;; :stroke-width 3
;; :fill-color "#0000ff"
;; :x 1000 :y 2000 :rx nil}
;; {:stroke-width "#ff0000"
;; :stroke-width 5
;; :x 1500 :y 2000}])
;;
;; (get-attrs-multi shapes [:stroke-color
;; :stroke-width
;; :fill-color
;; :rx
;; :ry])
;; >>> {:stroke-color "#ff0000"
;; :stroke-width :multiple
;; :fill-color "#0000ff"
;; :rx nil
;; :ry nil}
;;
(let [defined-shapes (filter some? shapes)
combine-value (fn [v1 v2] (cond
(= v1 v2) v1
(= v1 :undefined) v2
(= v2 :undefined) v1
:else :multiple))
combine-value (fn [v1 v2]
(cond
(and (= v1 :undefined) (= v2 :undefined)) :undefined
(= v1 :undefined) (if (= v2 :multiple) :multiple (sel-fn v2))
(= v2 :undefined) (if (= v1 :multiple) :multiple (sel-fn v1))
(or (= v1 :multiple) (= v2 :multiple)) :multiple
(eq-fn v1 v2) (sel-fn v1)
:else :multiple))
combine-values (fn [attrs shape values]
(map #(combine-value (get shape % :undefined)
(get values % :undefined)) attrs))
combine-values (fn [attrs shape values]
(map #(combine-value (get shape % :undefined)
(get values % :undefined)) attrs))
select-attrs (fn [shape attrs]
(zipmap attrs (map #(get shape % :undefined) attrs)))
select-attrs (fn [shape attrs]
(zipmap attrs (map #(get shape % :undefined) attrs)))
reducer (fn [result shape]
(zipmap attrs (combine-values attrs shape result)))
reducer (fn [result shape]
(zipmap attrs (combine-values attrs shape result)))
combined (reduce reducer
(select-attrs (first defined-shapes) attrs)
(rest defined-shapes))
combined (reduce reducer
(select-attrs (first defined-shapes) attrs)
(rest defined-shapes))
cleanup-value (fn [value]
(if (= value :undefined) nil value))
cleanup-value (fn [value]
(if (= value :undefined) nil value))
cleanup (fn [result]
(zipmap attrs (map #(cleanup-value (get result %)) attrs)))]
cleanup (fn [result]
(zipmap attrs (map #(cleanup-value (get result %)) attrs)))]
(cleanup combined)))
(cleanup combined))))

View file

@ -42,31 +42,7 @@
(move shape (gpt/point dx dy))))
;; --- Resize (Dimensions)
(defn resize
[shape width height]
(us/assert map? shape)
(us/assert number? width)
(us/assert number? height)
(let [shape-transform (:transform shape (gmt/matrix))
shape-transform-inv (:transform-inverse shape (gmt/matrix))
shape-center (gco/center-shape shape)
{sr-width :width sr-height :height} (:selrect shape)
origin (-> (gpt/point (:selrect shape))
(gtr/transform-point-center shape-center shape-transform))
scalev (gpt/divide (gpt/point width height)
(gpt/point sr-width sr-height))]
(-> shape
(update :modifiers assoc
:resize-vector scalev
:resize-origin origin
:resize-transform shape-transform
:resize-transform-inverse shape-transform-inv)
(gtr/transform-shape))))
(defn resize-rect
(defn resize-modifiers
[shape attr value]
(us/assert map? shape)
(us/assert #{:width :height} attr)
@ -81,8 +57,24 @@
(assoc :height (/ value proportion)))
(-> size
(assoc :height value)
(assoc :width (* value proportion)))))]
(resize shape (:width new-size) (:height new-size))))
(assoc :width (* value proportion)))))
width (:width new-size)
height (:height new-size)
shape-transform (:transform shape (gmt/matrix))
shape-transform-inv (:transform-inverse shape (gmt/matrix))
shape-center (gco/center-shape shape)
{sr-width :width sr-height :height} (:selrect shape)
origin (-> (gpt/point (:selrect shape))
(gtr/transform-point-center shape-center shape-transform))
scalev (gpt/divide (gpt/point width height)
(gpt/point sr-width sr-height))]
{:resize-vector scalev
:resize-origin origin
:resize-transform shape-transform
:resize-transform-inverse shape-transform-inv}))
;; --- Setup (Initialize)
;; FIXME: Is this the correct place for these functions?

File diff suppressed because it is too large Load diff

View file

@ -868,6 +868,13 @@
fill: $color-primary;
}
}
.element-set-label {
font-size: $fs11;
padding: 0.5rem;
color: $color-gray-10;
}
.element-set-actions {
display: flex;
visibility: hidden;

View file

@ -1441,8 +1441,6 @@
(defn change-canvas-color
[color]
;; TODO: Create a color spec
#_(s/assert string? color)
(ptk/reify ::change-canvas-color
ptk/WatchEvent
(watch [_ state stream]

View file

@ -477,6 +477,34 @@
(us/verify #{:width :height} attr)
(us/verify ::us/number value)
(ptk/reify ::update-dimensions
ptk/UpdateEvent
(update [_ state]
(let [page-id (:current-page-id state)
objects (dwc/lookup-page-objects state page-id)
update-children
(fn [objects ids modifiers]
(reduce #(assoc-in %1 [%2 :modifiers] modifiers) objects ids))
;; For each shape updates the modifiers given as arguments
update-shape
(fn [objects shape-id]
(let [shape (get objects shape-id)
modifier (gsh/resize-modifiers shape attr value)]
(-> objects
(assoc-in [shape-id :modifiers] modifier)
(cond-> (not (= :frame (:type shape)))
(update-children (cp/get-children shape-id objects) modifier)))))]
(d/update-in-when
state
[:workspace-data :pages-index page-id :objects]
#(reduce update-shape % ids))))
ptk/WatchEvent
(watch [_ state stream]
(rx/of (dwc/update-shapes ids #(gsh/resize-rect % attr value) {:reg-objects? true})))))
(let [page-id (:current-page-id state)
objects (dwc/lookup-page-objects state page-id)
ids (d/concat [] ids (mapcat #(cp/get-children % objects) ids))]
(rx/of (apply-modifiers ids))))))

View file

@ -74,7 +74,8 @@
:page-id page-id
:file-id file-id
:shapes-with-children shapes-with-children}]
[:& multiple/options {:shapes shapes-with-children}])]]
[:& multiple/options {:shapes-with-children shapes-with-children
:shapes shapes}])]]
[:& tab-element {:id :prototype
:title (t locale "workspace.options.prototype")}

View file

@ -21,6 +21,8 @@
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [t]]))
(def blur-attrs [:blur])
(defn create-blur []
(let [id (uuid/next)]
{:id id
@ -28,10 +30,11 @@
:value 4
:hidden false}))
(mf/defc blur-menu [{:keys [ids values]}]
(mf/defc blur-menu [{:keys [ids type values]}]
(let [locale (i18n/use-locale)
blur (:blur values)
has-value? (not (nil? blur))
multiple? (= blur :multiple)
change! (fn [update-fn] (st/emit! (dwc/update-shapes ids update-fn)))
@ -53,18 +56,26 @@
[:div.element-set
[:div.element-set-title
[:span (t locale "workspace.options.blur-options.title")]
[:span
(case type
:multiple (t locale "workspace.options.blur-options.title.multiple")
:group (t locale "workspace.options.blur-options.title.group")
(t locale "workspace.options.blur-options.title"))]
[:div.element-set-title-actions
(if has-value?
(when (and has-value? (not multiple?))
[:div.add-page {:on-click handle-toggle-visibility} (if (:hidden blur) i/eye-closed i/eye)])
(if has-value?
[:div.add-page {:on-click handle-delete} i/minus]
[:div.add-page {:on-click handle-add} i/close])]]
(when has-value?
(cond
has-value?
[:div.element-set-content
[:& input-row {:label "Value"
:class "pixels"
:min 0
:value (:value blur)
:placeholder (t locale "settings.multiple")
:on-change handle-change}]])]))

View file

@ -15,20 +15,14 @@
[app.common.geom.shapes :as geom]
[app.main.refs :as refs]
[app.main.data.workspace.texts :as dwt]
[app.main.ui.workspace.sidebar.options.multiple :refer [get-shape-attrs]]
[app.main.ui.workspace.sidebar.options.multiple :refer [get-shape-attrs extract]]
[app.main.ui.workspace.sidebar.options.measures :refer [measure-attrs measures-menu]]
[app.main.ui.workspace.sidebar.options.component :refer [component-attrs component-menu]]
[app.main.ui.workspace.sidebar.options.fill :refer [fill-attrs fill-menu]]
[app.main.ui.workspace.sidebar.options.blur :refer [blur-menu]]
[app.main.ui.workspace.sidebar.options.shadow :refer [shadow-menu]]
[app.main.ui.workspace.sidebar.options.stroke :refer [stroke-attrs stroke-menu]]
[app.main.ui.workspace.sidebar.options.text :refer [text-fill-attrs
text-font-attrs
text-align-attrs
text-spacing-attrs
text-valign-attrs
text-decoration-attrs
text-transform-attrs
text-menu]]))
[app.main.ui.workspace.sidebar.options.text :as ot]))
(mf/defc options
[{:keys [shape shape-with-children] :as props}]
@ -76,65 +70,17 @@
shape-with-children)
stroke-attrs)
font-values
(attrs/get-attrs-multi (map #(get-shape-attrs
%
nil
text-font-attrs
nil
dwt/current-text-values)
shape-with-children)
text-font-attrs)
root-values (extract {:shapes shape-with-children
:text-attrs ot/root-attrs
:extract-fn dwt/current-root-values})
align-values
(attrs/get-attrs-multi (map #(get-shape-attrs
%
nil
text-align-attrs
nil
dwt/current-paragraph-values)
shape-with-children)
text-align-attrs)
paragraph-values (extract {:shapes shape-with-children
:text-attrs ot/paragraph-attrs
:extract-fn dwt/current-paragraph-values})
spacing-values
(attrs/get-attrs-multi (map #(get-shape-attrs
%
nil
text-spacing-attrs
nil
dwt/current-text-values)
shape-with-children)
text-spacing-attrs)
valign-values
(attrs/get-attrs-multi (map #(get-shape-attrs
%
nil
text-valign-attrs
nil
dwt/current-root-values)
shape-with-children)
text-valign-attrs)
decoration-values
(attrs/get-attrs-multi (map #(get-shape-attrs
%
nil
text-decoration-attrs
nil
dwt/current-text-values)
shape-with-children)
text-decoration-attrs)
transform-values
(attrs/get-attrs-multi (map #(get-shape-attrs
%
nil
text-transform-attrs
nil
dwt/current-text-values)
shape-with-children)
text-transform-attrs)]
text-values (extract {:shapes shape-with-children
:text-attrs ot/text-attrs
:extract-fn dwt/current-text-values})]
[:*
[:& measures-menu {:ids [id]
:ids-with-children ids-with-children
@ -146,7 +92,12 @@
:type type
:values fill-values}]
[:& shadow-menu {:ids [id]
:type type
:values (select-keys shape [:shadow])}]
[:& blur-menu {:ids [id]
:type type
:values (select-keys shape [:blur])}]
(when-not (empty? other-ids)
@ -154,13 +105,11 @@
:type type
:values stroke-values}])
(when-not (empty? text-ids)
[:& text-menu {:ids text-ids
[:& ot/text-menu {:ids text-ids
:type type
:editor nil
:font-values font-values
:align-values align-values
:spacing-values spacing-values
:valign-values valign-values
:decoration-values decoration-values
:transform-values transform-values}])]))
:shapes shape-with-children
:values (merge root-values
paragraph-values
text-values)}])]))

View file

@ -15,6 +15,8 @@
[app.main.data.workspace.texts :as dwt]
[app.main.ui.workspace.sidebar.options.measures :refer [measure-attrs measures-menu]]
[app.main.ui.workspace.sidebar.options.fill :refer [fill-attrs fill-menu]]
[app.main.ui.workspace.sidebar.options.shadow :refer [shadow-attrs shadow-menu]]
[app.main.ui.workspace.sidebar.options.blur :refer [blur-attrs blur-menu]]
[app.main.ui.workspace.sidebar.options.stroke :refer [stroke-attrs stroke-menu]]
[app.main.ui.workspace.sidebar.options.text :as ot]))
@ -34,47 +36,81 @@
text-values)]
converted-values))))
(defn extract [{:keys [shapes attrs text-attrs convert-attrs extract-fn]}]
(let [mapfn
(fn [shape]
(get-shape-attrs shape
attrs
text-attrs
convert-attrs
extract-fn))]
(attrs/get-attrs-multi (map mapfn shapes) (or attrs text-attrs))))
(mf/defc options
{::mf/wrap [mf/memo]}
[{:keys [shapes] :as props}]
[{:keys [shapes shapes-with-children] :as props}]
(let [ids (map :id shapes)
text-ids (map :id (filter #(= (:type %) :text) shapes))
other-ids (map :id (filter #(not= (:type %) :text) shapes))
ids-with-children (map :id shapes-with-children)
text-ids (map :id (filter #(= (:type %) :text) shapes-with-children))
other-ids (map :id (filter #(not= (:type %) :text) shapes-with-children))
extract (fn [{:keys [attrs text-attrs convert-attrs extract-fn]}]
(let [mapfn
(fn [shape]
(get-shape-attrs shape
attrs
text-attrs
convert-attrs
extract-fn))]
(attrs/get-attrs-multi (map mapfn shapes) (or attrs text-attrs))))
measure-values (attrs/get-attrs-multi shapes measure-attrs)
fill-values (extract {:attrs fill-attrs
shadow-values (let [keys [:style :color :offset-x :offset-y :blur :spread]]
(attrs/get-attrs-multi
shapes shadow-attrs
(fn [s1 s2]
(and (= (count s1) (count s2))
(->> (map vector s1 s2)
(every? (fn [[v1 v2]]
(= (select-keys v1 keys) (select-keys v2 keys)))))))
(fn [v]
(mapv #(select-keys % keys) v))))
blur-values (let [keys [:type :value]]
(attrs/get-attrs-multi
shapes blur-attrs
(fn [v1 v2]
(= (select-keys v1 keys) (select-keys v2 keys)))
(fn [v] (select-keys v keys))))
fill-values (extract {:shapes shapes-with-children
:attrs fill-attrs
:text-attrs ot/text-fill-attrs
:convert-attrs fill-attrs
:extract-fn dwt/current-text-values})
stroke-values (extract {:attrs stroke-attrs})
stroke-values (extract {:shapes shapes-with-children
:attrs stroke-attrs})
root-values (extract {:text-attrs ot/root-attrs
root-values (extract {:shapes shapes-with-children
:text-attrs ot/root-attrs
:extract-fn dwt/current-root-values})
paragraph-values (extract {:text-attrs ot/paragraph-attrs
paragraph-values (extract {:shapes shapes-with-children
:text-attrs ot/paragraph-attrs
:extract-fn dwt/current-paragraph-values})
text-values (extract {:text-attrs ot/text-attrs
text-values (extract {:shapes shapes-with-children
:text-attrs ot/text-attrs
:extract-fn dwt/current-text-values})]
[:*
[:& measures-menu {:ids ids
:type :multiple
:values measure-values}]
[:& fill-menu {:ids ids
[:& fill-menu {:ids ids-with-children
:type :multiple
:values fill-values}]
[:& shadow-menu {:ids ids
:type :multiple
:values shadow-values}]
[:& blur-menu {:ids ids
:type :multiple
:values blur-values}]
(when-not (empty? other-ids)
[:& stroke-menu {:ids other-ids
:type :multiple
@ -83,8 +119,8 @@
[:& ot/text-menu {:ids text-ids
:type :multiple
:editor nil
:shapes shapes-with-children
:values (merge root-values
paragraph-values
text-values)
:shapes shapes}])]))
text-values)}])]))

View file

@ -21,6 +21,8 @@
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [t]]))
(def shadow-attrs [:shadow])
(defn create-shadow []
(let [id (uuid/next)]
{:id id
@ -49,13 +51,15 @@
adv-blur-ref (mf/use-ref nil)
adv-spread-ref (mf/use-ref nil)
remove-shadow-by-id
(fn [values id] (->> values (filterv (fn [s] (not= (:id s) id)))))
remove-shadow-by-index
(fn [values index] (->> (d/enumerate values)
(filterv (fn [[idx s]] (not= idx index)))
(mapv second)))
on-remove-shadow
(fn [id]
(fn [index]
(fn []
(st/emit! (dwc/update-shapes ids #(update % :shadow remove-shadow-by-id id) ))))
(st/emit! (dwc/update-shapes ids #(update % :shadow remove-shadow-by-index index) ))))
select-text
(fn [ref] (fn [event] (dom/select-text! (mf/ref-val ref))))
@ -111,7 +115,7 @@
[:div.element-set-actions
[:div.element-set-actions-button {:on-click (toggle-visibility index)}
(if (:hidden value) i/eye-closed i/eye)]
[:div.element-set-actions-button {:on-click (on-remove-shadow (:id value))}
[:div.element-set-actions-button {:on-click (on-remove-shadow index)}
i/minus]]]
[:& advanced-options {:visible? @open-shadow
@ -175,21 +179,39 @@
:on-open #(st/emit! (dwc/start-undo-transaction))
:on-close #(st/emit! (dwc/commit-undo-transaction))}]]]]))
(mf/defc shadow-menu
[{:keys [ids values] :as props}]
[{:keys [ids type values] :as props}]
(let [locale (i18n/use-locale)
on-remove-all-shadows
(fn [event]
(st/emit! (dwc/update-shapes ids #(dissoc % :shadow) )))
on-add-shadow
(fn []
(st/emit! (dwc/update-shapes ids #(update % :shadow (fnil conj []) (create-shadow)) )))]
[:div.element-set.shadow-options
[:div.element-set-title
[:span (t locale "workspace.options.shadow-options.title")]
[:div.add-page {:on-click on-add-shadow} i/close]]
[:span
(case type
:multiple (t locale "workspace.options.shadow-options.title.multiple")
:group (t locale "workspace.options.shadow-options.title.group")
(t locale "workspace.options.shadow-options.title"))]
(when (seq (:shadow values))
(when-not (= :multiple (:shadow values))
[:div.add-page {:on-click on-add-shadow} i/close])]
(cond
(= :multiple (:shadow values))
[:div.element-set-content
[:div.element-set-options-group
[:div.element-set-label (t locale "settings.multiple")]
[:div.element-set-actions
[:div.element-set-actions-button {:on-click on-remove-all-shadows}
i/minus]]]]
(not (empty? (:shadow values)))
[:div.element-set-content
(for [[index {:keys [id] :as value}] (d/enumerate (:shadow values []))]
[:& shadow-entry {:key (str "shadow-" id)
[:& shadow-entry {:key (str "shadow-" index)
:ids ids
:value value
:index index}])])]))