Merge remote-tracking branch 'origin/staging' into develop

This commit is contained in:
Andrey Antukh 2023-07-13 12:13:06 +02:00
commit 8eb64de062
43 changed files with 827 additions and 532 deletions

View file

@ -70,7 +70,25 @@
- Fix focus handling on comments edition [Taiga #5560](https://tree.taiga.io/project/penpot/issue/5560) - Fix focus handling on comments edition [Taiga #5560](https://tree.taiga.io/project/penpot/issue/5560)
- Fix incorrect fullname use on registring user after OIDC authentication [Taiga #5517](https://tree.taiga.io/project/penpot/issue/5517) - Fix incorrect fullname use on registring user after OIDC authentication [Taiga #5517](https://tree.taiga.io/project/penpot/issue/5517)
- Fix incorrect modified-at on project after import file [Taiga #5268](https://tree.taiga.io/project/penpot/issue/5268) - Fix incorrect modified-at on project after import file [Taiga #5268](https://tree.taiga.io/project/penpot/issue/5268)
- Fix incorrect message after sending invitation to already member [Taiga 5599](https://tree.taiga.io/project/penpot/issue/5599)
- Fix text decoration on button [Taiga #5301](https://tree.taiga.io/project/penpot/issue/5301)
- Fix menu order on design tab [Taiga #5195](https://tree.taiga.io/project/penpot/issue/5195)
- Fix search bar width on layer tab [Taiga #5445](https://tree.taiga.io/project/penpot/issue/5445)
- Fix border radius values with decimals [Taiga #5283](https://tree.taiga.io/project/penpot/issue/5283)
- Fix shortcuts translations not homogenized [Taiga #5141](https://tree.taiga.io/project/penpot/issue/5141)
- Fix overlay manual position in nested boards [Taiga #5135](https://tree.taiga.io/project/penpot/issue/5135)
- Fix close overlay from a nested board [Taiga #5587](https://tree.taiga.io/project/penpot/issue/5587)
- Fix overlay position when it has shadow or blur [Taiga #4752](https://tree.taiga.io/project/penpot/issue/4752)
- Fix overlay position when there are elements fixed when scrolling [Taiga #4383](https://tree.taiga.io/project/penpot/issue/4383)
- Fix problem when sliding color picker in selected-colors [#3150](https://github.com/penpot/penpot/issues/3150)
- Fix error screen on upload image error [Taiga #5608](https://tree.taiga.io/project/penpot/issue/5608)
- Fix bad frame-id for certain componentes [#3205](https://github.com/penpot/penpot/issues/3205)
- Fix paste elements at bottom of frame [Taig #5253](https://tree.taiga.io/project/penpot/issue/5253)
- Fix new-file button on project not redirecting to the new file [Taiga #5610](https://tree.taiga.io/project/penpot/issue/5610)
- Fix retrieve user comments in dashboard [Taiga #5607](https://tree.taiga.io/project/penpot/issue/5607)
- Locks shapes when moved inside a locked parent [Taiga #5252](https://tree.taiga.io/project/penpot/issue/5252)
- Fix rotate several elements in bulk [Taiga #5165](https://tree.taiga.io/project/penpot/issue/5165)
### :arrow_up: Deps updates ### :arrow_up: Deps updates
- Update google fonts catalog (at 2023/07/06) [Taiga #5592](https://tree.taiga.io/project/penpot/issue/5592) - Update google fonts catalog (at 2023/07/06) [Taiga #5592](https://tree.taiga.io/project/penpot/issue/5592)

View file

@ -10,6 +10,7 @@
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.exceptions :as ex] [app.common.exceptions :as ex]
[app.common.logging :as l] [app.common.logging :as l]
[app.common.schema :as sm]
[app.common.spec :as us] [app.common.spec :as us]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.config :as cf] [app.config :as cf]
@ -719,29 +720,22 @@
itoken)))) itoken))))
(s/def ::email ::us/email) (def ^:private schema:create-team-invitations
(s/def ::emails ::us/set-of-valid-emails) [:map {:title "create-team-invitations"}
(s/def ::create-team-invitations [:team-id ::sm/uuid]
(s/keys :req [::rpc/profile-id] [:role [::sm/one-of #{:owner :admin :editor}]]
:req-un [::team-id ::role] [:emails ::sm/set-of-emails]])
:opt-un [::email ::emails]))
(sv/defmethod ::create-team-invitations (sv/defmethod ::create-team-invitations
"A rpc call that allow to send a single or multiple invitations to "A rpc call that allow to send a single or multiple invitations to
join the team." join the team."
{::doc/added "1.17"} {::doc/added "1.17"
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id email emails role] :as params}] ::sm/params schema:create-team-invitations}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id emails role] :as params}]
(db/with-atomic [conn pool] (db/with-atomic [conn pool]
(let [perms (get-permissions conn profile-id team-id) (let [perms (get-permissions conn profile-id team-id)
profile (db/get-by-id conn :profile profile-id) profile (db/get-by-id conn :profile profile-id)
team (db/get-by-id conn :team team-id) team (db/get-by-id conn :team team-id)]
;; Members emails. We don't re-send inviation to already existing members
member? (into #{}
(map :email)
(db/exec! conn [sql:team-members team-id]))
emails (cond-> (or emails #{}) (string? email) (conj email))]
(run! (partial quotes/check-quote! conn) (run! (partial quotes/check-quote! conn)
(list {::quotes/id ::quotes/invitations-per-team (list {::quotes/id ::quotes/invitations-per-team
@ -764,9 +758,13 @@
:hint "looks like the profile has reported repeatedly as spam or has permanent bounces")) :hint "looks like the profile has reported repeatedly as spam or has permanent bounces"))
(let [cfg (assoc cfg ::db/conn conn) (let [cfg (assoc cfg ::db/conn conn)
invitations (into [] members (->> (db/exec! conn [sql:team-members team-id])
(into #{} (map :email)))
invitations (into #{}
(comp (comp
(remove member?) ;; We don't re-send inviation to already existing members
(remove (partial contains? members))
(map (fn [email] (map (fn [email]
{:email (str/lower email) {:email (str/lower email)
:team team :team team
@ -774,7 +772,8 @@
:role role})) :role role}))
(keep (partial create-invitation cfg))) (keep (partial create-invitation cfg)))
emails)] emails)]
(with-meta invitations (with-meta {:total (count invitations)
:invitations invitations}
{::audit/props {:invitations (count invitations)}}))))) {::audit/props {:invitations (count invitations)}})))))

View file

@ -37,7 +37,7 @@
:role :editor}] :role :editor}]
;; invite external user without complaints ;; invite external user without complaints
(let [data (assoc data :email "foo@bar.com") (let [data (assoc data :emails ["foo@bar.com"])
out (th/command! data) out (th/command! data)
;; retrieve the value from the database and check its content ;; retrieve the value from the database and check its content
invitation (db/exec-one! invitation (db/exec-one!
@ -52,7 +52,7 @@
;; invite internal user without complaints ;; invite internal user without complaints
(th/reset-mock! mock) (th/reset-mock! mock)
(let [data (assoc data :email (:email profile2)) (let [data (assoc data :emails [(:email profile2)])
out (th/command! data)] out (th/command! data)]
(t/is (th/success? out)) (t/is (th/success? out))
(t/is (= 1 (:call-count (deref mock))))) (t/is (= 1 (:call-count (deref mock)))))
@ -60,7 +60,7 @@
;; invite user with complaint ;; invite user with complaint
(th/create-global-complaint-for pool {:type :complaint :email "foo@bar.com"}) (th/create-global-complaint-for pool {:type :complaint :email "foo@bar.com"})
(th/reset-mock! mock) (th/reset-mock! mock)
(let [data (assoc data :email "foo@bar.com") (let [data (assoc data :emails ["foo@bar.com"])
out (th/command! data)] out (th/command! data)]
(t/is (th/success? out)) (t/is (th/success? out))
(t/is (= 1 (:call-count (deref mock))))) (t/is (= 1 (:call-count (deref mock)))))
@ -79,7 +79,7 @@
(th/reset-mock! mock) (th/reset-mock! mock)
(th/create-global-complaint-for pool {:type :bounce :email "foo@bar.com"}) (th/create-global-complaint-for pool {:type :bounce :email "foo@bar.com"})
(let [data (assoc data :email "foo@bar.com") (let [data (assoc data :emails ["foo@bar.com"])
out (th/command! data)] out (th/command! data)]
(t/is (not (th/success? out))) (t/is (not (th/success? out)))
@ -92,7 +92,7 @@
;; invite internal user that is muted ;; invite internal user that is muted
(th/reset-mock! mock) (th/reset-mock! mock)
(let [data (assoc data :email (:email profile3)) (let [data (assoc data :emails [(:email profile3)])
out (th/command! data)] out (th/command! data)]
(t/is (not (th/success? out))) (t/is (not (th/success? out)))
@ -118,7 +118,7 @@
;; Try to invite a not existing user ;; Try to invite a not existing user
(let [data {::th/type :create-team-invitations (let [data {::th/type :create-team-invitations
::rpc/profile-id (:id profile1) ::rpc/profile-id (:id profile1)
:email "notexisting@example.com" :emails ["notexisting@example.com"]
:team-id (:id team) :team-id (:id team)
:role :editor} :role :editor}
out (th/command! data)] out (th/command! data)]
@ -126,15 +126,15 @@
;; (th/print-result! out) ;; (th/print-result! out)
(t/is (th/success? out)) (t/is (th/success? out))
(t/is (= 1 (:call-count @mock))) (t/is (= 1 (:call-count @mock)))
(t/is (= 1 (-> out :result count))) (t/is (= 1 (-> out :result :total)))
(let [token (-> out :result first) (let [token (-> out :result :invitations first)
claims (tokens/decode sprops token)] claims (tokens/decode sprops token)]
(t/is (= :team-invitation (:iss claims))) (t/is (= :team-invitation (:iss claims)))
(t/is (= (:id profile1) (:profile-id claims))) (t/is (= (:id profile1) (:profile-id claims)))
(t/is (= :editor (:role claims))) (t/is (= :editor (:role claims)))
(t/is (= (:id team) (:team-id claims))) (t/is (= (:id team) (:team-id claims)))
(t/is (= (:email data) (:member-email claims))) (t/is (= (first (:emails data)) (:member-email claims)))
(t/is (nil? (:member-id claims))))) (t/is (nil? (:member-id claims)))))
(th/reset-mock! mock) (th/reset-mock! mock)
@ -142,7 +142,7 @@
;; Try to invite existing user ;; Try to invite existing user
(let [data {::th/type :create-team-invitations (let [data {::th/type :create-team-invitations
::rpc/profile-id (:id profile1) ::rpc/profile-id (:id profile1)
:email (:email profile2) :emails [(:email profile2)]
:team-id (:id team) :team-id (:id team)
:role :editor} :role :editor}
out (th/command! data)] out (th/command! data)]
@ -150,15 +150,15 @@
;; (th/print-result! out) ;; (th/print-result! out)
(t/is (th/success? out)) (t/is (th/success? out))
(t/is (= 1 (:call-count @mock))) (t/is (= 1 (:call-count @mock)))
(t/is (= 1 (-> out :result count))) (t/is (= 1 (-> out :result :total)))
(let [token (-> out :result first) (let [token (-> out :result :invitations first)
claims (tokens/decode sprops token)] claims (tokens/decode sprops token)]
(t/is (= :team-invitation (:iss claims))) (t/is (= :team-invitation (:iss claims)))
(t/is (= (:id profile1) (:profile-id claims))) (t/is (= (:id profile1) (:profile-id claims)))
(t/is (= :editor (:role claims))) (t/is (= :editor (:role claims)))
(t/is (= (:id team) (:team-id claims))) (t/is (= (:id team) (:team-id claims)))
(t/is (= (:email data) (:member-email claims))) (t/is (= (first (:emails data)) (:member-email claims)))
(t/is (= (:id profile2) (:member-id claims))))) (t/is (= (:id profile2) (:member-id claims)))))
))) )))
@ -264,7 +264,7 @@
;; invite internal user without complaints ;; invite internal user without complaints
(with-redefs [app.config/flags #{}] (with-redefs [app.config/flags #{}]
(th/reset-mock! mock) (th/reset-mock! mock)
(let [data (assoc data :email (:email profile2)) (let [data (assoc data :emails [(:email profile2)])
out (th/command! data)] out (th/command! data)]
(t/is (th/success? out)) (t/is (th/success? out))
(t/is (= 0 (:call-count (deref mock))))) (t/is (= 0 (:call-count (deref mock)))))

View file

@ -280,13 +280,15 @@
:else :else
(let [objects (lookup-objects file) (let [objects (lookup-objects file)
bool-content (gsh/calc-bool-content bool objects)
bool' (gsh/update-bool-selrect bool children objects)] bool' (gsh/update-bool-selrect bool children objects)]
(commit-change (commit-change
file file
{:type :mod-obj {:type :mod-obj
:id bool-id :id bool-id
:operations :operations
[{:type :set :attr :selrect :val (:selrect bool') :ignore-touched true} [{:type :set :attr :bool-content :val bool-content :ignore-touched true}
{:type :set :attr :selrect :val (:selrect bool') :ignore-touched true}
{:type :set :attr :points :val (:points bool') :ignore-touched true} {:type :set :attr :points :val (:points bool') :ignore-touched true}
{:type :set :attr :x :val (-> bool' :selrect :x) :ignore-touched true} {:type :set :attr :x :val (-> bool' :selrect :x) :ignore-touched true}
{:type :set :attr :y :val (-> bool' :selrect :y) :ignore-touched true} {:type :set :attr :y :val (-> bool' :selrect :y) :ignore-touched true}

View file

@ -6,4 +6,4 @@
(ns app.common.files.defaults) (ns app.common.files.defaults)
(def version 23) (def version 24)

View file

@ -436,32 +436,6 @@
(update :pages-index update-vals update-container) (update :pages-index update-vals update-container)
(update :components update-vals update-container)))) (update :components update-vals update-container))))
(defmethod migrate 20
[data]
(letfn [(update-object [objects object]
(let [frame-id (:frame-id object)
calculated-frame-id
(or (->> (cph/get-parent-ids objects (:id object))
(map (d/getf objects))
(d/seek cph/frame-shape?)
:id)
;; If we cannot find any we let the frame-id as it was before
frame-id)]
(when (not= frame-id calculated-frame-id)
(log/info :hint "Fix wrong frame-id"
:shape (:name object)
:id (:id object)
:current (dm/get-in objects [frame-id :name])
:calculated (get-in objects [calculated-frame-id :name])))
(assoc object :frame-id calculated-frame-id)))
(update-container [container]
(update container :objects #(update-vals % (partial update-object %))))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(defmethod migrate 21 (defmethod migrate 21
[data] [data]
(letfn [(update-object [object] (letfn [(update-object [object]
@ -517,3 +491,29 @@
(-> data (-> data
(update :pages-index update-vals update-container) (update :pages-index update-vals update-container)
(update :components update-vals update-container)))) (update :components update-vals update-container))))
(defmethod migrate 24
[data]
(letfn [(update-object [objects object]
(let [frame-id (:frame-id object)
calculated-frame-id
(or (->> (cph/get-parent-ids objects (:id object))
(map (d/getf objects))
(d/seek cph/frame-shape?)
:id)
;; If we cannot find any we let the frame-id as it was before
frame-id)]
(when (not= frame-id calculated-frame-id)
(log/info :hint "Fix wrong frame-id"
:shape (:name object)
:id (:id object)
:current (dm/get-in objects [frame-id :name])
:calculated (get-in objects [calculated-frame-id :name])))
(assoc object :frame-id calculated-frame-id)))
(update-container [container]
(update container :objects #(update-vals % (partial update-object %))))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))

View file

@ -163,6 +163,7 @@
:else :else
(cph/reduce-objects (cph/reduce-objects
objects objects
(fn [shape] (fn [shape]
(and (d/not-empty? (:shapes shape)) (and (d/not-empty? (:shapes shape))
(or (not (cph/frame-shape? shape)) (or (not (cph/frame-shape? shape))

View file

@ -406,7 +406,7 @@
(update page :objects update-vals root-to-board))] (update page :objects update-vals root-to-board))]
(-> file-data (-> file-data
(add-instance-grid (sort-by :name components)) (add-instance-grid (reverse (sort-by :name components)))
(update :pages-index update-vals roots-to-board) (update :pages-index update-vals roots-to-board)
(assoc-in [:options :components-v2] true)))))))) (assoc-in [:options :components-v2] true))))))))

View file

@ -495,8 +495,7 @@
"expected compatible interaction map" "expected compatible interaction map"
(has-overlay-opts interaction)) (has-overlay-opts interaction))
(let [ (let [;; When the interactive item is inside a nested frame we need to add to the offset the position
;; When the interactive item is inside a nested frame we need to add to the offset the position
;; of the parent-frame otherwise the position won't match ;; of the parent-frame otherwise the position won't match
shape-frame (cph/get-frame objects shape) shape-frame (cph/get-frame objects shape)
@ -505,10 +504,10 @@
(cph/root-frame? shape-frame) (cph/root-frame? shape-frame)
(cph/root? shape-frame)) (cph/root? shape-frame))
frame-offset frame-offset
(gpt/add frame-offset (gpt/point shape-frame))) (gpt/add frame-offset (gpt/point shape-frame)))]
]
(if (nil? dest-frame) (if (nil? dest-frame)
(gpt/point 0 0) [(gpt/point 0 0) [:top :left]]
(let [overlay-size (gsb/get-object-bounds objects dest-frame) (let [overlay-size (gsb/get-object-bounds objects dest-frame)
base-frame-size (:selrect base-frame) base-frame-size (:selrect base-frame)
relative-to-shape-size (:selrect relative-to-shape) relative-to-shape-size (:selrect relative-to-shape)
@ -526,37 +525,46 @@
overlay-position overlay-position
{:x (- (:x overlay-position) (:x relative-to-adjusted-to-base-frame)) {:x (- (:x overlay-position) (:x relative-to-adjusted-to-base-frame))
:y (- (:y overlay-position) (:y relative-to-adjusted-to-base-frame))})] :y (- (:y overlay-position) (:y relative-to-adjusted-to-base-frame))})]
(case (:overlay-pos-type interaction) (case (:overlay-pos-type interaction)
:center :center
(gpt/point (+ (:x base-position) (/ (- (:width relative-to-shape-size) (:width overlay-size)) 2)) [(gpt/point (+ (:x base-position) (/ (- (:width relative-to-shape-size) (:width overlay-size)) 2))
(+ (:y base-position) (/ (- (:height relative-to-shape-size) (:height overlay-size)) 2))) (+ (:y base-position) (/ (- (:height relative-to-shape-size) (:height overlay-size)) 2)))
[:center :center]]
:top-left :top-left
(gpt/point (:x base-position) (:y base-position)) [(gpt/point (:x base-position) (:y base-position))
[:top :left]]
:top-right :top-right
(gpt/point (+ (:x base-position) (- (:width relative-to-shape-size) (:width overlay-size))) [(gpt/point (+ (:x base-position) (- (:width relative-to-shape-size) (:width overlay-size)))
(:y base-position)) (:y base-position))
[:top :right]]
:top-center :top-center
(gpt/point (+ (:x base-position) (/ (- (:width relative-to-shape-size) (:width overlay-size)) 2)) [(gpt/point (+ (:x base-position) (/ (- (:width relative-to-shape-size) (:width overlay-size)) 2))
(:y base-position)) (:y base-position))
[:top :center]]
:bottom-left :bottom-left
(gpt/point (:x base-position) [(gpt/point (:x base-position)
(+ (:y base-position) (- (:height relative-to-shape-size) (:height overlay-size)))) (+ (:y base-position) (- (:height relative-to-shape-size) (:height overlay-size))))
[:bottom :left]]
:bottom-right :bottom-right
(gpt/point (+ (:x base-position) (- (:width relative-to-shape-size) (:width overlay-size))) [(gpt/point (+ (:x base-position) (- (:width relative-to-shape-size) (:width overlay-size)))
(+ (:y base-position) (- (:height relative-to-shape-size) (:height overlay-size)))) (+ (:y base-position) (- (:height relative-to-shape-size) (:height overlay-size))))
[:bottom :right]]
:bottom-center :bottom-center
(gpt/point (+ (:x base-position) (/ (- (:width relative-to-shape-size) (:width overlay-size)) 2)) [(gpt/point (+ (:x base-position) (/ (- (:width relative-to-shape-size) (:width overlay-size)) 2))
(+ (:y base-position) (- (:height relative-to-shape-size) (:height overlay-size)))) (+ (:y base-position) (- (:height relative-to-shape-size) (:height overlay-size))))
[:bottom :center]]
:manual :manual
(gpt/point (+ (:x base-position) (:x overlay-position)) [(gpt/point (+ (:x base-position) (:x overlay-position))
(+ (:y base-position) (:y overlay-position)))))))) (+ (:y base-position) (:y overlay-position)))
[:top :left]])))))
(defn has-animation? (defn has-animation?
[interaction] [interaction]

View file

@ -323,209 +323,275 @@
interaction-rect (ctsi/set-position-relative-to interaction (:id rect))] interaction-rect (ctsi/set-position-relative-to interaction (:id rect))]
(t/testing "Overlay top-left relative to auto" (t/testing "Overlay top-left relative to auto"
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :top-left base-frame objects) (let [i2 (ctsi/set-overlay-pos-type interaction-auto :top-left base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 0)) (t/is (= (:x overlay-pos) 0))
(t/is (= (:y overlay-pos) 0)))) (t/is (= (:y overlay-pos) 0))
(t/is (= snap-v :top))
(t/is (= snap-h :left))))
(t/testing "Overlay top-center relative to auto" (t/testing "Overlay top-center relative to auto"
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :top-center base-frame objects) (let [i2 (ctsi/set-overlay-pos-type interaction-auto :top-center base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 35)) (t/is (= (:x overlay-pos) 35))
(t/is (= (:y overlay-pos) 0)))) (t/is (= (:y overlay-pos) 0))
(t/is (= snap-v :top))
(t/is (= snap-h :center))))
(t/testing "Overlay top-right relative to auto" (t/testing "Overlay top-right relative to auto"
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :top-right base-frame objects) (let [i2 (ctsi/set-overlay-pos-type interaction-auto :top-right base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 70)) (t/is (= (:x overlay-pos) 70))
(t/is (= (:y overlay-pos) 0)))) (t/is (= (:y overlay-pos) 0))
(t/is (= snap-v :top))
(t/is (= snap-h :right))))
(t/testing "Overlay bottom-left relative to auto" (t/testing "Overlay bottom-left relative to auto"
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :bottom-left base-frame objects) (let [i2 (ctsi/set-overlay-pos-type interaction-auto :bottom-left base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 0)) (t/is (= (:x overlay-pos) 0))
(t/is (= (:y overlay-pos) 80)))) (t/is (= (:y overlay-pos) 80))
(t/is (= snap-v :bottom))
(t/is (= snap-h :left))))
(t/testing "Overlay bottom-center relative to auto" (t/testing "Overlay bottom-center relative to auto"
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :bottom-center base-frame objects) (let [i2 (ctsi/set-overlay-pos-type interaction-auto :bottom-center base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 35)) (t/is (= (:x overlay-pos) 35))
(t/is (= (:y overlay-pos) 80)))) (t/is (= (:y overlay-pos) 80))
(t/is (= snap-v :bottom))
(t/is (= snap-h :center))))
(t/testing "Overlay bottom-right relative to auto" (t/testing "Overlay bottom-right relative to auto"
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :bottom-right base-frame objects) (let [i2 (ctsi/set-overlay-pos-type interaction-auto :bottom-right base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 70)) (t/is (= (:x overlay-pos) 70))
(t/is (= (:y overlay-pos) 80)))) (t/is (= (:y overlay-pos) 80))
(t/is (= snap-v :bottom))
(t/is (= snap-h :right))))
(t/testing "Overlay center relative to auto" (t/testing "Overlay center relative to auto"
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :center base-frame objects) (let [i2 (ctsi/set-overlay-pos-type interaction-auto :center base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 35)) (t/is (= (:x overlay-pos) 35))
(t/is (= (:y overlay-pos) 40)))) (t/is (= (:y overlay-pos) 40))
(t/is (= snap-v :center))
(t/is (= snap-h :center))))
(t/testing "Overlay manual relative to auto" (t/testing "Overlay manual relative to auto"
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :center base-frame objects) (let [i2 (ctsi/set-overlay-pos-type interaction-auto :center base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 35)) (t/is (= (:x overlay-pos) 35))
(t/is (= (:y overlay-pos) 40)))) (t/is (= (:y overlay-pos) 40))
(t/is (= snap-v :center))
(t/is (= snap-h :center))))
(t/testing "Overlay manual relative to auto" (t/testing "Overlay manual relative to auto"
(let [i2 (-> interaction-auto (let [i2 (-> interaction-auto
(ctsi/set-overlay-pos-type :manual base-frame objects) (ctsi/set-overlay-pos-type :manual base-frame objects)
(ctsi/set-overlay-position (gpt/point 12 62))) (ctsi/set-overlay-position (gpt/point 12 62)))
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 17)) (t/is (= (:x overlay-pos) 17))
(t/is (= (:y overlay-pos) 67)))) (t/is (= (:y overlay-pos) 67))
(t/is (= snap-v :top))
(t/is (= snap-h :left))))
(t/testing "Overlay top-left relative to base-frame" (t/testing "Overlay top-left relative to base-frame"
(let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :top-left base-frame objects) (let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :top-left base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 5)) (t/is (= (:x overlay-pos) 5))
(t/is (= (:y overlay-pos) 5)))) (t/is (= (:y overlay-pos) 5))
(t/is (= snap-v :top))
(t/is (= snap-h :left))))
(t/testing "Overlay top-center relative to base-frame" (t/testing "Overlay top-center relative to base-frame"
(let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :top-center base-frame objects) (let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :top-center base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 40)) (t/is (= (:x overlay-pos) 40))
(t/is (= (:y overlay-pos) 5)))) (t/is (= (:y overlay-pos) 5))
(t/is (= snap-v :top))
(t/is (= snap-h :center))))
(t/testing "Overlay top-right relative to base-frame" (t/testing "Overlay top-right relative to base-frame"
(let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :top-right base-frame objects) (let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :top-right base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 75)) (t/is (= (:x overlay-pos) 75))
(t/is (= (:y overlay-pos) 5)))) (t/is (= (:y overlay-pos) 5))
(t/is (= snap-v :top))
(t/is (= snap-h :right))))
(t/testing "Overlay bottom-left relative to base-frame" (t/testing "Overlay bottom-left relative to base-frame"
(let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :bottom-left base-frame objects) (let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :bottom-left base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 5)) (t/is (= (:x overlay-pos) 5))
(t/is (= (:y overlay-pos) 85)))) (t/is (= (:y overlay-pos) 85))
(t/is (= snap-v :bottom))
(t/is (= snap-h :left))))
(t/testing "Overlay bottom-center relative to base-frame" (t/testing "Overlay bottom-center relative to base-frame"
(let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :bottom-center base-frame objects) (let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :bottom-center base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 40)) (t/is (= (:x overlay-pos) 40))
(t/is (= (:y overlay-pos) 85)))) (t/is (= (:y overlay-pos) 85))
(t/is (= snap-v :bottom))
(t/is (= snap-h :center))))
(t/testing "Overlay bottom-right relative to base-frame" (t/testing "Overlay bottom-right relative to base-frame"
(let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :bottom-right base-frame objects) (let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :bottom-right base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 75)) (t/is (= (:x overlay-pos) 75))
(t/is (= (:y overlay-pos) 85)))) (t/is (= (:y overlay-pos) 85))
(t/is (= snap-v :bottom))
(t/is (= snap-h :right))))
(t/testing "Overlay center relative to base-frame" (t/testing "Overlay center relative to base-frame"
(let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :center base-frame objects) (let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :center base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 40)) (t/is (= (:x overlay-pos) 40))
(t/is (= (:y overlay-pos) 45)))) (t/is (= (:y overlay-pos) 45))
(t/is (= snap-v :center))
(t/is (= snap-h :center))))
(t/testing "Overlay manual relative to base-frame" (t/testing "Overlay manual relative to base-frame"
(let [i2 (-> interaction-base-frame (let [i2 (-> interaction-base-frame
(ctsi/set-overlay-pos-type :manual base-frame objects) (ctsi/set-overlay-pos-type :manual base-frame objects)
(ctsi/set-overlay-position (gpt/point 12 62))) (ctsi/set-overlay-position (gpt/point 12 62)))
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 17)) (t/is (= (:x overlay-pos) 17))
(t/is (= (:y overlay-pos) 67)))) (t/is (= (:y overlay-pos) 67))
(t/is (= snap-v :top))
(t/is (= snap-h :left))))
(t/testing "Overlay top-left relative to popup" (t/testing "Overlay top-left relative to popup"
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :top-left base-frame objects) (let [i2 (ctsi/set-overlay-pos-type interaction-popup :top-left base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 15)) (t/is (= (:x overlay-pos) 15))
(t/is (= (:y overlay-pos) 15)))) (t/is (= (:y overlay-pos) 15))
(t/is (= snap-v :top))
(t/is (= snap-h :left))))
(t/testing "Overlay top-center relative to popup" (t/testing "Overlay top-center relative to popup"
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :top-center base-frame objects) (let [i2 (ctsi/set-overlay-pos-type interaction-popup :top-center base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 25)) (t/is (= (:x overlay-pos) 25))
(t/is (= (:y overlay-pos) 15)))) (t/is (= (:y overlay-pos) 15))
(t/is (= snap-v :top))
(t/is (= snap-h :center))))
(t/testing "Overlay top-right relative to popup" (t/testing "Overlay top-right relative to popup"
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :top-right base-frame objects) (let [i2 (ctsi/set-overlay-pos-type interaction-popup :top-right base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 35)) (t/is (= (:x overlay-pos) 35))
(t/is (= (:y overlay-pos) 15)))) (t/is (= (:y overlay-pos) 15))
(t/is (= snap-v :top))
(t/is (= snap-h :right))))
(t/testing "Overlay bottom-left relative to popup" (t/testing "Overlay bottom-left relative to popup"
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :bottom-left base-frame objects) (let [i2 (ctsi/set-overlay-pos-type interaction-popup :bottom-left base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 15)) (t/is (= (:x overlay-pos) 15))
(t/is (= (:y overlay-pos) 45)))) (t/is (= (:y overlay-pos) 45))
(t/is (= snap-v :bottom))
(t/is (= snap-h :left))))
(t/testing "Overlay bottom-center relative to popup" (t/testing "Overlay bottom-center relative to popup"
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :bottom-center base-frame objects) (let [i2 (ctsi/set-overlay-pos-type interaction-popup :bottom-center base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 25)) (t/is (= (:x overlay-pos) 25))
(t/is (= (:y overlay-pos) 45)))) (t/is (= (:y overlay-pos) 45))
(t/is (= snap-v :bottom))
(t/is (= snap-h :center))))
(t/testing "Overlay bottom-right relative to popup" (t/testing "Overlay bottom-right relative to popup"
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :bottom-right base-frame objects) (let [i2 (ctsi/set-overlay-pos-type interaction-popup :bottom-right base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 35)) (t/is (= (:x overlay-pos) 35))
(t/is (= (:y overlay-pos) 45)))) (t/is (= (:y overlay-pos) 45))
(t/is (= snap-v :bottom))
(t/is (= snap-h :right))))
(t/testing "Overlay center relative to popup" (t/testing "Overlay center relative to popup"
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :center base-frame objects) (let [i2 (ctsi/set-overlay-pos-type interaction-popup :center base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 25)) (t/is (= (:x overlay-pos) 25))
(t/is (= (:y overlay-pos) 30)))) (t/is (= (:y overlay-pos) 30))
(t/is (= snap-v :center))
(t/is (= snap-h :center))))
(t/testing "Overlay manual relative to popup" (t/testing "Overlay manual relative to popup"
(let [i2 (-> interaction-popup (let [i2 (-> interaction-popup
(ctsi/set-overlay-pos-type :manual base-frame objects) (ctsi/set-overlay-pos-type :manual base-frame objects)
(ctsi/set-overlay-position (gpt/point 12 62))) (ctsi/set-overlay-position (gpt/point 12 62)))
overlay-pos (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 27)) (t/is (= (:x overlay-pos) 27))
(t/is (= (:y overlay-pos) 77)))) (t/is (= (:y overlay-pos) 77))
(t/is (= snap-v :top))
(t/is (= snap-h :left))))
(t/testing "Overlay top-left relative to popup" (t/testing "Overlay top-left relative to popup"
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :top-left base-frame objects) (let [i2 (ctsi/set-overlay-pos-type interaction-popup :top-left base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 15)) (t/is (= (:x overlay-pos) 15))
(t/is (= (:y overlay-pos) 15)))) (t/is (= (:y overlay-pos) 15))
(t/is (= snap-v :top))
(t/is (= snap-h :left))))
(t/testing "Overlay top-center relative to rect" (t/testing "Overlay top-center relative to rect"
(let [i2 (ctsi/set-overlay-pos-type interaction-rect :top-center base-frame objects) (let [i2 (ctsi/set-overlay-pos-type interaction-rect :top-center base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 25)) (t/is (= (:x overlay-pos) 25))
(t/is (= (:y overlay-pos) 15)))) (t/is (= (:y overlay-pos) 15))
(t/is (= snap-v :top))
(t/is (= snap-h :center))))
(t/testing "Overlay top-right relative to rect" (t/testing "Overlay top-right relative to rect"
(let [i2 (ctsi/set-overlay-pos-type interaction-rect :top-right base-frame objects) (let [i2 (ctsi/set-overlay-pos-type interaction-rect :top-right base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 35)) (t/is (= (:x overlay-pos) 35))
(t/is (= (:y overlay-pos) 15)))) (t/is (= (:y overlay-pos) 15))
(t/is (= snap-v :top))
(t/is (= snap-h :right))))
(t/testing "Overlay bottom-left relative to rect" (t/testing "Overlay bottom-left relative to rect"
(let [i2 (ctsi/set-overlay-pos-type interaction-rect :bottom-left base-frame objects) (let [i2 (ctsi/set-overlay-pos-type interaction-rect :bottom-left base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 15)) (t/is (= (:x overlay-pos) 15))
(t/is (= (:y overlay-pos) 45)))) (t/is (= (:y overlay-pos) 45))
(t/is (= snap-v :bottom))
(t/is (= snap-h :left))))
(t/testing "Overlay bottom-center relative to rect" (t/testing "Overlay bottom-center relative to rect"
(let [i2 (ctsi/set-overlay-pos-type interaction-rect :bottom-center base-frame objects) (let [i2 (ctsi/set-overlay-pos-type interaction-rect :bottom-center base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 25)) (t/is (= (:x overlay-pos) 25))
(t/is (= (:y overlay-pos) 45)))) (t/is (= (:y overlay-pos) 45))
(t/is (= snap-v :bottom))
(t/is (= snap-h :center))))
(t/testing "Overlay bottom-right relative to rect" (t/testing "Overlay bottom-right relative to rect"
(let [i2 (ctsi/set-overlay-pos-type interaction-rect :bottom-right base-frame objects) (let [i2 (ctsi/set-overlay-pos-type interaction-rect :bottom-right base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 35)) (t/is (= (:x overlay-pos) 35))
(t/is (= (:y overlay-pos) 45)))) (t/is (= (:y overlay-pos) 45))
(t/is (= snap-v :bottom))
(t/is (= snap-h :right))))
(t/testing "Overlay center relative to rect" (t/testing "Overlay center relative to rect"
(let [i2 (ctsi/set-overlay-pos-type interaction-rect :center base-frame objects) (let [i2 (ctsi/set-overlay-pos-type interaction-rect :center base-frame objects)
overlay-pos (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 25)) (t/is (= (:x overlay-pos) 25))
(t/is (= (:y overlay-pos) 30)))) (t/is (= (:y overlay-pos) 30))
(t/is (= snap-v :center))
(t/is (= snap-h :center))))
(t/testing "Overlay manual relative to rect" (t/testing "Overlay manual relative to rect"
(let [i2 (-> interaction-rect (let [i2 (-> interaction-rect
(ctsi/set-overlay-pos-type :manual base-frame objects) (ctsi/set-overlay-pos-type :manual base-frame objects)
(ctsi/set-overlay-position (gpt/point 12 62))) (ctsi/set-overlay-position (gpt/point 12 62)))
overlay-pos (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)] [overlay-pos [snap-v snap-h]] (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)]
(t/is (= (:x overlay-pos) 17)) (t/is (= (:x overlay-pos) 17))
(t/is (= (:y overlay-pos) 67)))))) (t/is (= (:y overlay-pos) 67))
(t/is (= snap-v :top))
(t/is (= snap-h :left))))))
(t/deftest animation-checks (t/deftest animation-checks

View file

@ -290,6 +290,7 @@
border-radius: $br4; border-radius: $br4;
margin: 0.5rem; margin: 0.5rem;
cursor: pointer; cursor: pointer;
text-decoration: none;
&:hover { &:hover {
background-color: $color-primary; background-color: $color-primary;

View file

@ -378,6 +378,8 @@ span.element-name {
background-color: $color-gray-50; background-color: $color-gray-50;
color: $color-white; color: $color-white;
font-size: $fs12; font-size: $fs12;
flex-grow: 1;
margin: 0;
height: 16px; height: 16px;
&:focus { &:focus {
outline: none; outline: none;
@ -386,10 +388,16 @@ span.element-name {
div { div {
height: 16px; height: 16px;
overflow: hidden; overflow: hidden;
width: 100%;
display: flex;
align-items: center;
} }
.filter, .filter,
.clear { .clear {
width: 35px; width: 35px;
display: flex;
justify-content: center;
align-items: center;
&.active { &.active {
svg { svg {
fill: $color-primary; fill: $color-primary;

View file

@ -314,9 +314,18 @@
(ptk/reify ::retrieve-unread-comment-threads (ptk/reify ::retrieve-unread-comment-threads
ptk/WatchEvent ptk/WatchEvent
(watch [_ _ _] (watch [_ _ _]
(let [fetched #(assoc %2 :comment-threads (d/index-by :id %1))] (let [fetched-comments #(assoc %2 :comment-threads (d/index-by :id %1))
fetched-users #(assoc %2 :current-team-comments-users %1)]
(->> (rp/cmd! :get-unread-comment-threads {:team-id team-id}) (->> (rp/cmd! :get-unread-comment-threads {:team-id team-id})
(rx/map #(partial fetched %)) (rx/merge-map
(fn [comments]
(rx/concat
(rx/of (partial fetched-comments comments))
(->> (rx/from (map :file-id comments))
(rx/merge-map #(rp/cmd! :get-profiles-for-file-comments {:file-id %}))
(rx/reduce #(merge %1 (d/index-by :id %2)) {})
(rx/map #(partial fetched-users %))))))
(rx/catch #(rx/throw {:type :comment-error}))))))) (rx/catch #(rx/throw {:type :comment-error})))))))

View file

@ -542,13 +542,14 @@
;; --- Overlays ;; --- Overlays
(defn- open-overlay* (defn- open-overlay*
[state frame position close-click-outside background-overlay animation] [state frame position snap-to close-click-outside background-overlay animation]
(cond-> state (cond-> state
:always :always
(update :viewer-overlays conj (update :viewer-overlays conj
{:frame frame {:frame frame
:id (:id frame) :id (:id frame)
:position position :position position
:snap-to snap-to
:close-click-outside close-click-outside :close-click-outside close-click-outside
:background-overlay background-overlay :background-overlay background-overlay
:animation animation}) :animation animation})
@ -571,7 +572,7 @@
:animation animation}))) :animation animation})))
(defn open-overlay (defn open-overlay
[frame-id position close-click-outside background-overlay animation] [frame-id position snap-to close-click-outside background-overlay animation]
(dm/assert! (uuid? frame-id)) (dm/assert! (uuid? frame-id))
(dm/assert! (gpt/point? position)) (dm/assert! (gpt/point? position))
(dm/assert! (or (nil? close-click-outside) (dm/assert! (or (nil? close-click-outside)
@ -593,6 +594,7 @@
(open-overlay* state (open-overlay* state
frame frame
position position
snap-to
close-click-outside close-click-outside
background-overlay background-overlay
animation) animation)
@ -600,7 +602,7 @@
(defn toggle-overlay (defn toggle-overlay
[frame-id position close-click-outside background-overlay animation] [frame-id position snap-to close-click-outside background-overlay animation]
(dm/assert! (uuid? frame-id)) (dm/assert! (uuid? frame-id))
(dm/assert! (gpt/point? position)) (dm/assert! (gpt/point? position))
(dm/assert! (or (nil? close-click-outside) (dm/assert! (or (nil? close-click-outside)
@ -623,6 +625,7 @@
(open-overlay* state (open-overlay* state
frame frame
position position
snap-to
close-click-outside close-click-outside
background-overlay background-overlay
animation) animation)

View file

@ -23,6 +23,7 @@
[app.common.text :as txt] [app.common.text :as txt]
[app.common.transit :as t] [app.common.transit :as t]
[app.common.types.component :as ctk] [app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl]
[app.common.types.container :as ctn] [app.common.types.container :as ctn]
[app.common.types.file :as ctf] [app.common.types.file :as ctf]
[app.common.types.pages-list :as ctpl] [app.common.types.pages-list :as ctpl]
@ -450,18 +451,49 @@
(ptk/reify ::duplicate-page (ptk/reify ::duplicate-page
ptk/WatchEvent ptk/WatchEvent
(watch [it state _] (watch [it state _]
(let [id (uuid/next) (let [id (uuid/next)
pages (get-in state [:workspace-data :pages-index]) pages (get-in state [:workspace-data :pages-index])
unames (cfh/get-used-names pages) unames (cfh/get-used-names pages)
page (get-in state [:workspace-data :pages-index page-id]) page (get-in state [:workspace-data :pages-index page-id])
name (cfh/generate-unique-name unames (:name page)) name (cfh/generate-unique-name unames (:name page))
fdata (:workspace-data state)
components-v2 (dm/get-in fdata [:options :components-v2])
objects (->> (:objects page)
(d/mapm (fn [_ val] (dissoc val :use-for-thumbnail?))))
main-instances-ids (set (keep #(when (ctk/main-instance? (val %)) (key %)) objects))
ids-to-remove (set (apply concat (map #(cph/get-children-ids objects %) main-instances-ids)))
add-component-copy
(fn [objs id shape]
(let [component (ctkl/get-component fdata (:component-id shape))
[new-shape new-shapes]
(ctn/make-component-instance page
component
fdata
(gpt/point (:x shape) (:y shape))
components-v2
{:keep-ids? true})
children (into {} (map (fn [shape] [(:id shape) shape]) new-shapes))
objs (assoc objs id new-shape)]
(merge objs children)))
objects
(reduce
(fn [objs [id shape]]
(cond (contains? main-instances-ids id)
(add-component-copy objs id shape)
(contains? ids-to-remove id)
objs
:else
(assoc objs id shape)))
{}
objects)
page (-> page page (-> page
(assoc :name name) (assoc :name name)
(assoc :id id) (assoc :id id)
(assoc :objects (assoc :objects
(->> (:objects page) objects))
(d/mapm (fn [_ val] (dissoc val :use-for-thumbnail?))))))
changes (-> (pcb/empty-changes it) changes (-> (pcb/empty-changes it)
(pcb/add-page id page))] (pcb/add-page id page))]
@ -719,17 +751,18 @@
groups-to-delete groups-to-unmask shapes-to-detach groups-to-delete groups-to-unmask shapes-to-detach
shapes-to-reroot shapes-to-deroot shapes-to-unconstraint] shapes-to-reroot shapes-to-deroot shapes-to-unconstraint]
(let [ordered-indexes (cph/order-by-indexed-shapes objects ids) (let [ordered-indexes (cph/order-by-indexed-shapes objects ids)
shapes (map (d/getf objects) ordered-indexes)] shapes (map (d/getf objects) ordered-indexes)
parent (get objects parent-id)]
(-> (pcb/empty-changes it page-id) (-> (pcb/empty-changes it page-id)
(pcb/with-objects objects) (pcb/with-objects objects)
;; Remove layout-item properties when moving a shape outside a layout ;; Remove layout-item properties when moving a shape outside a layout
(cond-> (not (ctl/any-layout? objects parent-id)) (cond-> (not (ctl/any-layout? parent))
(pcb/update-shapes ordered-indexes ctl/remove-layout-item-data)) (pcb/update-shapes ordered-indexes ctl/remove-layout-item-data))
;; Remove the hide in viewer flag ;; Remove the hide in viewer flag
(cond-> (and (not= uuid/zero parent-id) (cph/frame-shape? objects parent-id)) (cond-> (and (not= uuid/zero parent-id) (cph/frame-shape? parent))
(pcb/update-shapes ordered-indexes #(cond-> % (cph/frame-shape? %) (assoc :hide-in-viewer true)))) (pcb/update-shapes ordered-indexes #(cond-> % (cph/frame-shape? %) (assoc :hide-in-viewer true))))
;; Move the shapes ;; Move the shapes
@ -761,8 +794,7 @@
;; Reset constraints depending on the new parent ;; Reset constraints depending on the new parent
(pcb/update-shapes shapes-to-unconstraint (pcb/update-shapes shapes-to-unconstraint
(fn [shape] (fn [shape]
(let [parent (get objects parent-id) (let [frame-id (if (= (:type parent) :frame)
frame-id (if (= (:type parent) :frame)
(:id parent) (:id parent)
(:frame-id parent)) (:frame-id parent))
moved-shape (assoc shape moved-shape (assoc shape
@ -797,6 +829,10 @@
(pcb/reorder-grid-children parents) (pcb/reorder-grid-children parents)
;; If parent locked, lock the added shapes
(cond-> (:blocked parent)
(pcb/update-shapes ordered-indexes #(assoc % :blocked true)))
;; Resize parent containers that need to ;; Resize parent containers that need to
(pcb/resize-parents parents)))) (pcb/resize-parents parents))))
@ -1706,7 +1742,8 @@
[(:frame-id base) parent-id delta index]) [(:frame-id base) parent-id delta index])
;; Paste inside selected frame otherwise ;; Paste inside selected frame otherwise
(let [origin-frame-id (:frame-id first-selected-obj) (let [selected-frame-obj (get page-objects (first page-selected))
origin-frame-id (:frame-id first-selected-obj)
origin-frame-object (get page-objects origin-frame-id) origin-frame-object (get page-objects origin-frame-id)
margin-x (-> (- (:width origin-frame-object) (+ (:x wrapper) (:width wrapper))) margin-x (-> (- (:width origin-frame-object) (+ (:x wrapper) (:width wrapper)))
@ -1735,7 +1772,7 @@
;; - Align it to the limits on the x and y axis ;; - Align it to the limits on the x and y axis
;; - Respect the distance of the object to the right and bottom in the original frame ;; - Respect the distance of the object to the right and bottom in the original frame
(gpt/point paste-x paste-y))] (gpt/point paste-x paste-y))]
[frame-id frame-id delta])) [frame-id frame-id delta (dec (count (:shapes selected-frame-obj )))]))
(empty? page-selected) (empty? page-selected)
(let [frame-id (ctst/top-nested-frame page-objects mouse-pos) (let [frame-id (ctst/top-nested-frame page-objects mouse-pos)

View file

@ -181,7 +181,9 @@
(on-error error) (on-error error)
:else :else
(rx/throw error))))] (do
(.error js/console "ERROR" error)
(rx/of (msg/error (tr "errors.cannot-upload")))))))]
(ptk/reify ::process-media-objects (ptk/reify ::process-media-objects
ptk/WatchEvent ptk/WatchEvent

View file

@ -407,6 +407,32 @@
(assoc state :workspace-modifiers modif-tree)))))) (assoc state :workspace-modifiers modif-tree))))))
;; This function is similar to set-rotation-modifiers but:
;; - It consideres the center for everyshape instead of the center of the total selrect
;; - The angle param is the desired final value, not a delta
(defn set-delta-rotation-modifiers
([angle shapes]
(ptk/reify ::set-delta-rotation-modifiers
ptk/UpdateEvent
(update [_ state]
(let [objects (wsh/lookup-page-objects state)
ids
(->> shapes
(remove #(get % :blocked false))
(filter #(contains? (get editable-attrs (:type %)) :rotation))
(map :id))
get-modifier
(fn [shape]
(let [delta (- angle (:rotation shape))
center (gsh/shape->center shape)]
(ctm/rotation-modifiers shape center delta)))
modif-tree
(-> (build-modif-tree ids objects get-modifier)
(gsh/set-objects-modifiers objects))]
(assoc state :workspace-modifiers modif-tree))))))
(defn apply-modifiers (defn apply-modifiers
([] ([]

View file

@ -344,12 +344,10 @@
(let [page-id (:current-page-id state) (let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id) objects (wsh/lookup-page-objects state page-id)
rotate-shape (fn [shape] shapes (->> ids (map #(get objects %)))]
(let [delta (- rotation (:rotation shape))]
(dwm/set-rotation-modifiers delta [shape])))]
(rx/concat (rx/concat
(rx/from (->> ids (map #(get objects %)) (map rotate-shape))) (rx/of (dwm/set-delta-rotation-modifiers rotation shapes))
(rx/of (dwm/apply-modifiers))))))) (rx/of (dwm/apply-modifiers)))))))
;; -- Move ---------------------------------------------------------- ;; -- Move ----------------------------------------------------------

View file

@ -459,6 +459,9 @@
(def current-file-comments-users (def current-file-comments-users
(l/derived :current-file-comments-users st/state)) (l/derived :current-file-comments-users st/state))
(def current-team-comments-users
(l/derived :current-team-comments-users st/state))
(def viewer-fullscreen? (def viewer-fullscreen?
(l/derived (fn [state] (l/derived (fn [state]
(dm/get-in state [:viewer-local :fullscreen?])) (dm/get-in state [:viewer-local :fullscreen?]))

View file

@ -32,7 +32,7 @@
show-dropdown (mf/use-fn #(reset! show-dropdown? true)) show-dropdown (mf/use-fn #(reset! show-dropdown? true))
hide-dropdown (mf/use-fn #(reset! show-dropdown? false)) hide-dropdown (mf/use-fn #(reset! show-dropdown? false))
threads-map (mf/deref refs/comment-threads) threads-map (mf/deref refs/comment-threads)
users (mf/deref refs/current-file-comments-users) users (mf/deref refs/current-team-comments-users)
tgroups (->> (vals threads-map) tgroups (->> (vals threads-map)
(sort-by :modified-at) (sort-by :modified-at)

View file

@ -18,6 +18,7 @@
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as kbd] [app.util.keyboard :as kbd]
[app.util.router :as rt]
[app.util.webapi :as wapi] [app.util.webapi :as wapi]
[beicon.core :as rx] [beicon.core :as rx]
[cuerdas.core :as str] [cuerdas.core :as str]
@ -142,12 +143,22 @@
(sort-by :modified-at) (sort-by :modified-at)
(reverse))) (reverse)))
on-file-created
(mf/use-fn
(fn [data]
(let [pparams {:project-id (:project-id data)
:file-id (:id data)}
qparams {:page-id (get-in data [:data :pages 0])}]
(st/emit! (rt/nav :workspace pparams qparams)))))
create-file create-file
(mf/use-fn (mf/use-fn
(mf/deps project) (mf/deps project)
(fn [origin] (fn [origin]
(st/emit! (with-meta (dd/create-file {:project-id (:id project)}) (let [mdata {:on-success on-file-created}
{::ev/origin origin}))))] params {:project-id (:id project)}]
(st/emit! (-> (dd/create-file (with-meta params mdata))
(with-meta {::ev/origin origin}))))))]
(mf/with-effect [] (mf/with-effect []
(let [node (mf/ref-val rowref) (let [node (mf/ref-val rowref)

View file

@ -31,23 +31,27 @@
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(mf/defc header (mf/defc header
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]
[{:keys [section team] :as props}] ::mf/wrap-props false}
(let [go-members (mf/use-fn #(st/emit! (dd/go-to-team-members))) [{:keys [section team]}]
go-settings (mf/use-fn #(st/emit! (dd/go-to-team-settings))) (let [on-nav-members (mf/use-fn #(st/emit! (dd/go-to-team-members)))
go-invitations (mf/use-fn #(st/emit! (dd/go-to-team-invitations))) on-nav-settings (mf/use-fn #(st/emit! (dd/go-to-team-settings)))
go-webhooks (mf/use-fn #(st/emit! (dd/go-to-team-webhooks))) on-nav-invitations (mf/use-fn #(st/emit! (dd/go-to-team-invitations)))
invite-member (mf/use-fn on-nav-webhooks (mf/use-fn #(st/emit! (dd/go-to-team-webhooks)))
(mf/deps team)
#(st/emit! (modal/show {:type :invite-members
:team team
:origin :team})))
members-section? (= section :dashboard-team-members) members-section? (= section :dashboard-team-members)
settings-section? (= section :dashboard-team-settings) settings-section? (= section :dashboard-team-settings)
invitations-section? (= section :dashboard-team-invitations) invitations-section? (= section :dashboard-team-invitations)
webhooks-section? (= section :dashboard-team-webhooks) webhooks-section? (= section :dashboard-team-webhooks)
permissions (:permissions team)] permissions (:permissions team)
on-invite-member
(mf/use-fn
(mf/deps team)
(fn []
(st/emit! (modal/show {:type :invite-members
:team team
:origin :team}))))]
[:header.dashboard-header.team [:header.dashboard-header.team
[:div.dashboard-title [:div.dashboard-title
@ -60,17 +64,19 @@
[:nav.dashboard-header-menu [:nav.dashboard-header-menu
[:ul.dashboard-header-options [:ul.dashboard-header-options
[:li {:class (when members-section? "active")} [:li {:class (when members-section? "active")}
[:a {:on-click go-members} (tr "labels.members")]] [:a {:on-click on-nav-members} (tr "labels.members")]]
[:li {:class (when invitations-section? "active")} [:li {:class (when invitations-section? "active")}
[:a {:on-click go-invitations} (tr "labels.invitations")]] [:a {:on-click on-nav-invitations} (tr "labels.invitations")]]
(when (contains? cfg/flags :webhooks) (when (contains? cfg/flags :webhooks)
[:li {:class (when webhooks-section? "active")} [:li {:class (when webhooks-section? "active")}
[:a {:on-click go-webhooks} (tr "labels.webhooks")]]) [:a {:on-click on-nav-webhooks} (tr "labels.webhooks")]])
[:li {:class (when settings-section? "active")} [:li {:class (when settings-section? "active")}
[:a {:on-click go-settings} (tr "labels.settings")]]]] [:a {:on-click on-nav-settings} (tr "labels.settings")]]]]
[:div.dashboard-buttons [:div.dashboard-buttons
(if (and (or invitations-section? members-section?) (:is-admin permissions)) (if (and (or invitations-section? members-section?) (:is-admin permissions))
[:a.btn-secondary.btn-small {:on-click invite-member :data-test "invite-member"} [:a.btn-secondary.btn-small
{:on-click on-invite-member
:data-test "invite-member"}
(tr "dashboard.invite-profile")] (tr "dashboard.invite-profile")]
[:div.blank-space])]])) [:div.blank-space])]]))
@ -98,27 +104,29 @@
(mf/defc invite-members-modal (mf/defc invite-members-modal
{::mf/register modal/components {::mf/register modal/components
::mf/register-as :invite-members} ::mf/register-as :invite-members
::mf/wrap-props false}
[{:keys [team origin]}] [{:keys [team origin]}]
(let [members-map (mf/deref refs/dashboard-team-members) (let [members-map (mf/deref refs/dashboard-team-members)
perms (:permissions team)
perms (:permissions team) roles (mf/use-memo (mf/deps perms) #(get-available-roles perms))
initial (mf/use-memo (constantly {:role "editor" :team-id (:id team)}))
roles (mf/use-memo (mf/deps perms) #(get-available-roles perms)) form (fm/use-form :spec ::invite-member-form
initial (mf/use-memo (constantly {:role "editor" :team-id (:id team)})) :initial initial)
form (fm/use-form :spec ::invite-member-form error-text (mf/use-state "")
:initial initial)
error-text (mf/use-state "")
on-success
(fn []
(st/emit! (msg/success (tr "notifications.invitation-email-sent"))
(modal/hide)
(dd/fetch-team-invitations)))
current-data-emails (into #{} (dm/get-in @form [:clean-data :emails])) current-data-emails (into #{} (dm/get-in @form [:clean-data :emails]))
current-members-emails (into #{} (map (comp :email second)) members-map) current-members-emails (into #{} (map (comp :email second)) members-map)
on-success
(fn [_form {:keys [total]}]
(when (pos? total)
(st/emit! (msg/success (tr "notifications.invitation-email-sent"))))
(st/emit! (modal/hide)
(dd/fetch-team-invitations)))
on-error on-error
(fn [{:keys [type code] :as error}] (fn [{:keys [type code] :as error}]
(cond (cond
@ -185,7 +193,9 @@
;; MEMBERS SECTION ;; MEMBERS SECTION
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(mf/defc member-info [{:keys [member profile] :as props}] (mf/defc member-info
{::mf/wrap-props false}
[{:keys [member profile]}]
(let [is-you? (= (:id profile) (:id member))] (let [is-you? (= (:id profile) (:id member))]
[:* [:*
[:div.member-image [:div.member-image
@ -196,93 +206,97 @@
[:span.you (tr "labels.you")])] [:span.you (tr "labels.you")])]
[:div.member-email (:email member)]]])) [:div.member-email (:email member)]]]))
(mf/defc rol-info [{:keys [member team set-admin set-editor set-owner profile] :as props}] (mf/defc rol-info
{::mf/wrap-props false}
[{:keys [member team on-set-admin on-set-editor on-set-owner profile]}]
(let [member-is-owner? (:is-owner member) (let [member-is-owner? (:is-owner member)
member-is-admin? (and (:is-admin member) (not member-is-owner?)) member-is-admin? (and (:is-admin member) (not member-is-owner?))
member-is-editor? (and (:can-edit member) (and (not member-is-admin?) (not member-is-owner?))) member-is-editor? (and (:can-edit member) (and (not member-is-admin?) (not member-is-owner?)))
show? (mf/use-state false) show? (mf/use-state false)
you-owner? (get-in team [:permissions :is-owner])
you-admin? (get-in team [:permissions :is-admin]) you-owner? (dm/get-in team [:permissions :is-owner])
you-admin? (dm/get-in team [:permissions :is-admin])
is-you? (= (:id profile) (:id member))
can-change-rol? (or you-owner? you-admin?) can-change-rol? (or you-owner? you-admin?)
not-superior? (or you-owner? (and can-change-rol? (or member-is-admin? member-is-editor?))) not-superior? (or you-owner? (and can-change-rol? (or member-is-admin? member-is-editor?)))
role (cond role (cond
member-is-owner? "labels.owner" member-is-owner? "labels.owner"
member-is-admin? "labels.admin" member-is-admin? "labels.admin"
member-is-editor? "labels.editor" member-is-editor? "labels.editor"
:else "labels.viewer") :else "labels.viewer")
is-you? (= (:id profile) (:id member))]
on-show (mf/use-fn #(reset! show? true))
on-hide (mf/use-fn #(reset! show? false))]
[:* [:*
(if (and can-change-rol? not-superior? (not (and is-you? you-owner?))) (if (and can-change-rol? not-superior? (not (and is-you? you-owner?)))
[:div.rol-selector.has-priv {:on-click #(reset! show? true)} [:div.rol-selector.has-priv {:on-click on-show}
[:span.rol-label (tr role)] [:span.rol-label (tr role)]
[:span.icon i/arrow-down]] [:span.icon i/arrow-down]]
[:div.rol-selector [:div.rol-selector
[:span.rol-label (tr role)]]) [:span.rol-label (tr role)]])
[:& dropdown {:show @show? [:& dropdown {:show @show? :on-close on-hide}
:on-close #(reset! show? false)}
[:ul.dropdown.options-dropdown [:ul.dropdown.options-dropdown
[:li {:on-click set-admin} (tr "labels.admin")] [:li {:on-click on-set-admin} (tr "labels.admin")]
[:li {:on-click set-editor} (tr "labels.editor")] [:li {:on-click on-set-editor} (tr "labels.editor")]
;; Temporarily disabled viewer role ;; Temporarily disabled viewer role
;; https://tree.taiga.io/project/penpot/issue/1083 ;; https://tree.taiga.io/project/penpot/issue/1083
;; [:li {:on-click set-viewer} (tr "labels.viewer")] ;; [:li {:on-click set-viewer} (tr "labels.viewer")]
(when you-owner? (when you-owner?
[:li {:on-click (partial set-owner member)} (tr "labels.owner")])]]])) [:li {:on-click (partial on-set-owner member)} (tr "labels.owner")])]]]))
(mf/defc member-actions
{::mf/wrap-props false}
[{:keys [member team on-delete on-leave profile]}]
(let [is-owner? (:is-owner member)
owner? (dm/get-in team [:permissions :is-owner])
admin? (dm/get-in team [:permissions :is-admin])
show? (mf/use-state false)
is-you? (= (:id profile) (:id member))
can-delete? (or owner? admin?)
on-show (mf/use-fn #(reset! show? true))
on-hide (mf/use-fn #(reset! show? false))]
(mf/defc member-actions [{:keys [member team delete leave profile] :as props}]
(let [is-owner? (:is-owner member)
owner? (get-in team [:permissions :is-owner])
admin? (get-in team [:permissions :is-admin])
show? (mf/use-state false)
is-you? (= (:id profile) (:id member))
can-delete? (or owner? admin?)]
[:* [:*
(when (or is-you? (and can-delete? (not (and is-owner? (not owner?))))) (when (or is-you? (and can-delete? (not (and is-owner? (not owner?)))))
[:span.icon {:on-click #(reset! show? true)} [i/actions]]) [:span.icon {:on-click on-show} [i/actions]])
[:& dropdown {:show @show?
:on-close #(reset! show? false)} [:& dropdown {:show @show? :on-close on-hide}
[:ul.dropdown.actions-dropdown [:ul.dropdown.actions-dropdown
(when is-you? (when is-you?
[:li {:on-click leave} (tr "dashboard.leave-team")]) [:li {:on-click on-leave} (tr "dashboard.leave-team")])
(when (and can-delete? (not is-you?) (not (and is-owner? (not owner?)))) (when (and can-delete? (not is-you?) (not (and is-owner? (not owner?))))
[:li {:on-click delete} (tr "labels.remove-member")])]]])) [:li {:on-click on-delete} (tr "labels.remove-member")])]]]))
(defn- set-role! [member-id role]
(let [params {:member-id member-id :role role}]
(st/emit! (dd/update-team-member-role params))))
(mf/defc team-member (mf/defc team-member
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]
[{:keys [team member members profile] :as props}] ::mf/wrap-props false}
[{:keys [team member members profile]}]
(let [owner? (dm/get-in team [:permissions :is-owner]) (let [member-id (:id member)
set-role on-set-admin (mf/use-fn (mf/deps member-id) (partial set-role! member-id :admin))
on-set-editor (mf/use-fn (mf/deps member-id) (partial set-role! member-id :editor))
owner? (dm/get-in team [:permissions :is-owner])
on-set-owner
(mf/use-fn (mf/use-fn
(mf/deps member) (mf/deps member)
(fn [role] (fn [member _event]
(let [params {:member-id (:id member) :role role}] (let [params {:type :confirm
(st/emit! (dd/update-team-member-role params))))) :title (tr "modals.promote-owner-confirm.title")
:message (tr "modals.promote-owner-confirm.message" (:name member))
:scd-message (tr "modals.promote-owner-confirm.hint")
set-owner-fn (mf/use-fn (mf/deps set-role) (partial set-role :owner)) :accept-label (tr "modals.promote-owner-confirm.accept")
set-admin (mf/use-fn (mf/deps set-role) (partial set-role :admin)) :on-accept (partial set-role! member-id :owner)
set-editor (mf/use-fn (mf/deps set-role) (partial set-role :editor)) :accept-style :primary}]
;; set-viewer (partial set-role :viewer) (st/emit! (modal/show params)))))
set-owner
(mf/use-fn
(mf/deps set-owner-fn member)
(fn [member]
(st/emit! (modal/show
{:type :confirm
:title (tr "modals.promote-owner-confirm.title")
:message (tr "modals.promote-owner-confirm.message" (:name member))
:scd-message (tr "modals.promote-owner-confirm.hint")
:accept-label (tr "modals.promote-owner-confirm.accept")
:on-accept set-owner-fn
:accept-style :primary}))))
delete-member-fn
(mf/use-fn
(mf/deps member)
(fn [] (st/emit! (dd/delete-team-member {:member-id (:id member)}))))
on-success on-success
(mf/use-fn (mf/use-fn
@ -308,14 +322,14 @@
(rx/throw error)))) (rx/throw error))))
delete-fn on-delete-accepted
(mf/use-fn (mf/use-fn
(mf/deps team on-success on-error) (mf/deps team on-success on-error)
(fn [] (fn []
(st/emit! (dd/delete-team (with-meta team {:on-success on-success (st/emit! (dd/delete-team (with-meta team {:on-success on-success
:on-error on-error}))))) :on-error on-error})))))
leave-fn on-leave-accepted
(mf/use-fn (mf/use-fn
(mf/deps on-success on-error) (mf/deps on-success on-error)
(fn [member-id] (fn [member-id]
@ -324,9 +338,9 @@
{:on-success on-success {:on-success on-success
:on-error on-error})))))) :on-error on-error}))))))
leave-and-close on-leave-and-close
(mf/use-fn (mf/use-fn
(mf/deps delete-fn) (mf/deps on-delete-accepted)
(fn [] (fn []
(st/emit! (modal/show (st/emit! (modal/show
{:type :confirm {:type :confirm
@ -334,80 +348,100 @@
:message (tr "modals.leave-and-close-confirm.message" (:name team)) :message (tr "modals.leave-and-close-confirm.message" (:name team))
:scd-message (tr "modals.leave-and-close-confirm.hint") :scd-message (tr "modals.leave-and-close-confirm.hint")
:accept-label (tr "modals.leave-confirm.accept") :accept-label (tr "modals.leave-confirm.accept")
:on-accept delete-fn})))) :on-accept on-delete-accepted}))))
change-owner-and-leave on-change-owner-and-leave
(mf/use-fn (mf/use-fn
(mf/deps profile team leave-fn) (mf/deps profile team on-leave-accepted)
(fn [] (fn []
(st/emit! (dd/fetch-team-members) (st/emit! (dd/fetch-team-members)
(modal/show (modal/show
{:type :leave-and-reassign {:type :leave-and-reassign
:profile profile :profile profile
:team team :team team
:accept leave-fn})))) :accept on-leave-accepted}))))
leave on-leave
(mf/use-fn (mf/use-fn
(mf/deps leave-fn) (mf/deps on-leave-accepted)
(fn [] (fn []
(st/emit! (modal/show (st/emit! (modal/show
{:type :confirm {:type :confirm
:title (tr "modals.leave-confirm.title") :title (tr "modals.leave-confirm.title")
:message (tr "modals.leave-confirm.message") :message (tr "modals.leave-confirm.message")
:accept-label (tr "modals.leave-confirm.accept") :accept-label (tr "modals.leave-confirm.accept")
:on-accept leave-fn})))) :on-accept on-leave-accepted}))))
preset-leave (cond (= 1 (count members)) leave-and-close on-delete
(= true owner?) change-owner-and-leave
:else leave)
delete
(mf/use-fn (mf/use-fn
(mf/deps delete-member-fn) (mf/deps member-id)
(fn [] (fn []
(st/emit! (modal/show (let [on-accept #(st/emit! (dd/delete-team-member {:member-id member-id}))
{:type :confirm params {:type :confirm
:title (tr "modals.delete-team-member-confirm.title") :title (tr "modals.delete-team-member-confirm.title")
:message (tr "modals.delete-team-member-confirm.message") :message (tr "modals.delete-team-member-confirm.message")
:accept-label (tr "modals.delete-team-member-confirm.accept") :accept-label (tr "modals.delete-team-member-confirm.accept")
:on-accept delete-member-fn}))))] :on-accept on-accept}]
(st/emit! (modal/show params)))))
on-leave'
(cond (= 1 (count members)) on-leave-and-close
(= true owner?) on-change-owner-and-leave
:else on-leave)]
[:div.table-row [:div.table-row
[:div.table-field.name [:div.table-field.name
[:& member-info {:member member :profile profile}]] [:& member-info {:member member :profile profile}]]
[:div.table-field.roles [:div.table-field.roles
[:& rol-info {:member member [:& rol-info {:member member
:team team :team team
:set-admin set-admin :on-set-admin on-set-admin
:set-editor set-editor :on-set-editor on-set-editor
:set-owner set-owner :on-set-owner on-set-owner
:profile profile}]] :profile profile}]]
[:div.table-field.actions [:div.table-field.actions
[:& member-actions {:member member [:& member-actions {:member member
:profile profile :profile profile
:team team :team team
:delete delete :on-delete on-delete
:leave preset-leave}]]])) :on-leave on-leave'}]]]))
(mf/defc team-members (mf/defc team-members
[{:keys [members-map team profile] :as props}] {::mf/wrap-props false}
(let [members (->> (vals members-map) [{:keys [members-map team profile]}]
(sort-by :created-at) (let [members (mf/with-memo [members-map]
(remove :is-owner)) (->> (vals members-map)
owner (->> (vals members-map) (sort-by :created-at)
(d/seek :is-owner))] (remove :is-owner)))
owner (mf/with-memo [members-map]
(->> (vals members-map)
(d/seek :is-owner)))]
[:div.dashboard-table.team-members [:div.dashboard-table.team-members
[:div.table-header [:div.table-header
[:div.table-field.name (tr "labels.member")] [:div.table-field.name (tr "labels.member")]
[:div.table-field.role (tr "labels.role")]] [:div.table-field.role (tr "labels.role")]]
[:div.table-rows [:div.table-rows
[:& team-member {:member owner :team team :profile profile :members members-map}] [:& team-member
{:member owner
:team team
:profile profile
:members members-map}]
(for [item members] (for [item members]
[:& team-member {:member item :team team :profile profile :key (:id item) :members members-map}])]])) [:& team-member
{:member item
:team team
:profile profile
:key (:id item)
:members members-map}])]]))
(mf/defc team-members-page (mf/defc team-members-page
[{:keys [team profile] :as props}] {::mf/wrap-props false}
[{:keys [team profile]}]
(let [members-map (mf/deref refs/dashboard-team-members)] (let [members-map (mf/deref refs/dashboard-team-members)]
(mf/with-effect [team] (mf/with-effect [team]
@ -417,74 +451,76 @@
(tr "dashboard.your-penpot") (tr "dashboard.your-penpot")
(:name team))))) (:name team)))))
(mf/with-effect (mf/with-effect []
(st/emit! (dd/fetch-team-members))) (st/emit! (dd/fetch-team-members)))
[:* [:*
[:& header {:section :dashboard-team-members [:& header {:section :dashboard-team-members :team team}]
:team team}]
[:section.dashboard-container.dashboard-team-members [:section.dashboard-container.dashboard-team-members
[:& team-members {:profile profile [:& team-members
:team team {:profile profile
:members-map members-map}]]])) :team team
:members-map members-map}]]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; INVITATIONS SECTION ;; INVITATIONS SECTION
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(mf/defc invitation-role-selector (mf/defc invitation-role-selector
[{:keys [can-invite? role status change-to-admin change-to-editor] :as props}] {::mf/wrap-props false}
(let [show? (mf/use-state false) [{:keys [can-invite? role status on-change]}]
role-label (cond (let [show? (mf/use-state false)
(= role :owner) "labels.owner" label (cond
(= role :admin) "labels.admin" (= role :owner) (tr "labels.owner")
(= role :editor) "labels.editor" (= role :admin) (tr "labels.admin")
:else "labels.viewer")] (= role :editor) (tr "labels.editor")
:else (tr "labels.viewer"))
on-hide (mf/use-fn #(reset! show? false))
on-show (mf/use-fn #(reset! show? true))
on-change'
(mf/use-fn
(mf/deps on-change)
(fn [event]
(let [role (-> (dom/get-current-target event)
(dom/get-data "role")
(keyword))]
(on-change role event))))]
[:* [:*
(if (and can-invite? (= status :pending)) (if (and can-invite? (= status :pending))
[:div.rol-selector.has-priv {:on-click #(reset! show? true)} [:div.rol-selector.has-priv {:on-click on-show}
[:span.rol-label (tr role-label)] [:span.rol-label label]
[:span.icon i/arrow-down]] [:span.icon i/arrow-down]]
[:div.rol-selector [:div.rol-selector
[:span.rol-label (tr role-label)]]) [:span.rol-label label]])
[:& dropdown {:show @show? [:& dropdown {:show @show? :on-close on-hide}
:on-close #(reset! show? false)}
[:ul.dropdown.options-dropdown [:ul.dropdown.options-dropdown
[:li {:on-click change-to-admin} (tr "labels.admin")] [:li {:data-role "admin" :on-click on-change'} (tr "labels.admin")]
[:li {:on-click change-to-editor} (tr "labels.editor")]]]])) [:li {:data-role "editor" :on-click on-change'} (tr "labels.editor")]]]]))
(mf/defc invitation-status-badge (mf/defc invitation-status-badge
[{:keys [status] :as props}] {::mf/wrap-props false}
(let [status-label (if (= status :expired) [{:keys [status]}]
(tr "labels.expired-invitation") [:div.status-badge
(tr "labels.pending-invitation"))] {:class (dom/classnames
[:div.status-badge {:class (dom/classnames :expired (= status :expired)
:expired (= status :expired) :pending (= status :pending))}
:pending (= status :pending))} [:span.status-label
[:span.status-label (tr status-label)]])) (if (= status :expired)
(tr "labels.expired-invitation")
(tr "labels.pending-invitation"))]])
(mf/defc invitation-actions (mf/defc invitation-actions
[{:keys [invitation team] :as props}] {::mf/wrap-props false}
[{:keys [invitation team-id]}]
(let [show? (mf/use-state false) (let [show? (mf/use-state false)
team-id (:id team)
email (:email invitation) email (:email invitation)
role (:role invitation) role (:role invitation)
on-resend-success
(mf/use-fn
(fn []
(st/emit! (msg/success (tr "notifications.invitation-email-sent"))
(modal/hide)
(dd/fetch-team-invitations))))
on-copy-success
(mf/use-fn
(fn []
(st/emit! (msg/success (tr "notifications.invitation-link-copied"))
(modal/hide))))
on-error on-error
(mf/use-fn (mf/use-fn
(mf/deps email) (mf/deps email)
@ -505,7 +541,7 @@
:else :else
(rx/throw error)))) (rx/throw error))))
delete-fn on-delete
(mf/use-fn (mf/use-fn
(mf/deps email team-id) (mf/deps email team-id)
(fn [] (fn []
@ -513,7 +549,15 @@
mdata {:on-success #(st/emit! (dd/fetch-team-invitations))}] mdata {:on-success #(st/emit! (dd/fetch-team-invitations))}]
(st/emit! (dd/delete-team-invitation (with-meta params mdata)))))) (st/emit! (dd/delete-team-invitation (with-meta params mdata))))))
resend-fn
on-resend-success
(mf/use-fn
(fn []
(st/emit! (msg/success (tr "notifications.invitation-email-sent"))
(modal/hide)
(dd/fetch-team-invitations))))
on-resend
(mf/use-fn (mf/use-fn
(mf/deps email team-id) (mf/deps email team-id)
(fn [] (fn []
@ -527,7 +571,13 @@
(-> (dd/invite-team-members params) (-> (dd/invite-team-members params)
(with-meta {::ev/origin :team})))))) (with-meta {::ev/origin :team}))))))
copy-fn on-copy-success
(mf/use-fn
(fn []
(st/emit! (msg/success (tr "notifications.invitation-link-copied"))
(modal/hide))))
on-copy
(mf/use-fn (mf/use-fn
(mf/deps email team-id) (mf/deps email team-id)
(fn [] (fn []
@ -536,52 +586,55 @@
:on-error on-error})] :on-error on-error})]
(st/emit! (st/emit!
(-> (dd/copy-invitation-link params) (-> (dd/copy-invitation-link params)
(with-meta {::ev/origin :team}))))))] (with-meta {::ev/origin :team}))))))
on-hide (mf/use-fn #(reset! show? false))
on-show (mf/use-fn #(reset! show? true))]
[:* [:*
[:span.icon {:on-click #(reset! show? true)} [i/actions]] [:span.icon {:on-click on-show} [i/actions]]
[:& dropdown {:show @show? [:& dropdown {:show @show? :on-close on-hide}
:on-close #(reset! show? false)}
[:ul.dropdown.actions-dropdown [:ul.dropdown.actions-dropdown
[:li {:on-click copy-fn} (tr "labels.copy-invitation-link")] [:li {:on-click on-copy} (tr "labels.copy-invitation-link")]
[:li {:on-click resend-fn} (tr "labels.resend-invitation")] [:li {:on-click on-resend} (tr "labels.resend-invitation")]
[:li {:on-click delete-fn} (tr "labels.delete-invitation")]]]])) [:li {:on-click on-delete} (tr "labels.delete-invitation")]]]]))
(mf/defc invitation-row (mf/defc invitation-row
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]
[{:keys [invitation can-invite? team] :as props}] ::mf/wrap-props false}
[{:keys [invitation can-invite? team-id] :as props}]
(let [expired? (:expired invitation) (let [expired? (:expired invitation)
email (:email invitation) email (:email invitation)
role (:role invitation) role (:role invitation)
status (if expired? :expired :pending) status (if expired? :expired :pending)
change-rol on-change-role
(mf/use-fn (mf/use-fn
(mf/deps team email) (mf/deps email team-id)
(fn [role] (fn [role _event]
(let [params {:email email :team-id (:id team) :role role} (let [params {:email email :team-id team-id :role role}
mdata {:on-success #(st/emit! (dd/fetch-team-invitations))}] mdata {:on-success #(st/emit! (dd/fetch-team-invitations))}]
(st/emit! (dd/update-team-invitation-role (with-meta params mdata))))))] (st/emit! (dd/update-team-invitation-role (with-meta params mdata))))))]
[:div.table-row [:div.table-row
[:div.table-field.mail email] [:div.table-field.mail email]
[:div.table-field.roles [:div.table-field.roles
[:& invitation-role-selector [:& invitation-role-selector
{:can-invite? can-invite? {:can-invite? can-invite?
:role role :role role
:status status :status status
:change-to-editor (partial change-rol :editor) :on-change on-change-role}]]
:change-to-admin (partial change-rol :admin)}]]
[:div.table-field.status [:div.table-field.status
[:& invitation-status-badge {:status status}]] [:& invitation-status-badge {:status status}]]
[:div.table-field.actions [:div.table-field.actions
(when can-invite? (when can-invite?
[:& invitation-actions [:& invitation-actions
{:invitation invitation {:invitation invitation
:team team}])]])) :team-id team-id}])]]))
(mf/defc empty-invitation-table (mf/defc empty-invitation-table
[{:keys [can-invite?] :as props}] [{:keys [can-invite?] :as props}]
@ -595,7 +648,8 @@
[{:keys [team invitations] :as props}] [{:keys [team invitations] :as props}]
(let [owner? (dm/get-in team [:permissions :is-owner]) (let [owner? (dm/get-in team [:permissions :is-owner])
admin? (dm/get-in team [:permissions :is-admin]) admin? (dm/get-in team [:permissions :is-admin])
can-invite? (or owner? admin?)] can-invite? (or owner? admin?)
team-id (:id team)]
[:div.dashboard-table.invitations [:div.dashboard-table.invitations
[:div.table-header [:div.table-header
@ -610,7 +664,7 @@
{:key (:email invitation) {:key (:email invitation)
:invitation invitation :invitation invitation
:can-invite? can-invite? :can-invite? can-invite?
:team team}])])])) :team-id team-id}])])]))
(mf/defc team-invitations-page (mf/defc team-invitations-page
[{:keys [team] :as props}] [{:keys [team] :as props}]
@ -767,6 +821,7 @@
(mf/defc webhooks-hero (mf/defc webhooks-hero
{::mf/wrap-props false}
[] []
[:div.banner [:div.banner
[:div.title (tr "labels.webhooks") [:div.title (tr "labels.webhooks")
@ -785,18 +840,22 @@
[:span (tr "dashboard.webhooks.create")]]]]) [:span (tr "dashboard.webhooks.create")]]]])
(mf/defc webhook-actions (mf/defc webhook-actions
[{:keys [on-edit on-delete] :as props}] {::mf/wrap-props false}
(let [show? (mf/use-state false)] [{:keys [on-edit on-delete]}]
(let [show? (mf/use-state false)
on-show (mf/use-fn #(reset! show? true))
on-hide (mf/use-fn #(reset! show? false))]
[:* [:*
[:span.icon {:on-click #(reset! show? true)} [i/actions]] [:span.icon {:on-click on-show} [i/actions]]
[:& dropdown {:show @show? [:& dropdown {:show @show? :on-close on-hide}
:on-close #(reset! show? false)}
[:ul.dropdown.actions-dropdown [:ul.dropdown.actions-dropdown
[:li {:on-click on-edit} (tr "labels.edit")] [:li {:on-click on-edit} (tr "labels.edit")]
[:li {:on-click on-delete} (tr "labels.delete")]]]])) [:li {:on-click on-delete} (tr "labels.delete")]]]]))
(mf/defc last-delivery-icon (mf/defc last-delivery-icon
[{:keys [success? text] :as props}] {::mf/wrap-props false}
[{:keys [success? text]}]
[:div.last-delivery-icon [:div.last-delivery-icon
[:div.tooltip [:div.tooltip
[:div.label text] [:div.label text]
@ -808,34 +867,44 @@
(mf/defc webhook-item (mf/defc webhook-item
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]}
[{:keys [webhook] :as props}] [{:keys [webhook] :as props}]
(let [on-edit #(st/emit! (modal/show :webhook {:webhook webhook})) (let [error-code (:error-code webhook)
error-code (:error-code webhook) id (:id webhook)
delete-fn on-edit
(fn [] (mf/use-fn
(let [params {:id (:id webhook)} (mf/deps webhook)
mdata {:on-success #(st/emit! (dd/fetch-team-webhooks))}] (fn []
(st/emit! (dd/delete-team-webhook (with-meta params mdata))))) (st/emit! (modal/show :webhook {:webhook webhook}))))
on-delete-accepted
(mf/use-fn
(mf/deps id)
(fn []
(let [params {:id id}
mdata {:on-success #(st/emit! (dd/fetch-team-webhooks))}]
(st/emit! (dd/delete-team-webhook (with-meta params mdata))))))
on-delete on-delete
(fn [] (mf/use-fn
(st/emit! (modal/show (mf/deps on-delete-accepted)
{:type :confirm (fn []
:title (tr "modals.delete-webhook.title") (let [params {:type :confirm
:message (tr "modals.delete-webhook.message") :title (tr "modals.delete-webhook.title")
:accept-label (tr "modals.delete-webhook.accept") :message (tr "modals.delete-webhook.message")
:on-accept delete-fn}))) :accept-label (tr "modals.delete-webhook.accept")
:on-accept on-delete-accepted}]
(st/emit! (modal/show params)))))
last-delivery-text last-delivery-text
(if (nil? error-code) (if (nil? error-code)
(tr "webhooks.last-delivery.success") (tr "webhooks.last-delivery.success")
(str (tr "errors.webhooks.last-delivery") (dm/str (tr "errors.webhooks.last-delivery")
(cond (cond
(= error-code "ssl-validation-error") (= error-code "ssl-validation-error")
(dm/str " " (tr "errors.webhooks.ssl-validation")) (dm/str " " (tr "errors.webhooks.ssl-validation"))
(str/starts-with? error-code "unexpected-status") (str/starts-with? error-code "unexpected-status")
(dm/str " " (tr "errors.webhooks.unexpected-status" (extract-status error-code))))))] (dm/str " " (tr "errors.webhooks.unexpected-status" (extract-status error-code))))))]
[:div.table-row [:div.table-row
[:div.table-field.last-delivery [:div.table-field.last-delivery
@ -855,14 +924,16 @@
:on-delete on-delete}]]])) :on-delete on-delete}]]]))
(mf/defc webhooks-list (mf/defc webhooks-list
[{:keys [webhooks] :as props}] {::mf/wrap-props false}
[{:keys [webhooks]}]
[:div.dashboard-table [:div.dashboard-table
[:div.table-rows [:div.table-rows
(for [webhook webhooks] (for [webhook webhooks]
[:& webhook-item {:webhook webhook :key (:id webhook)}])]]) [:& webhook-item {:webhook webhook :key (:id webhook)}])]])
(mf/defc team-webhooks-page (mf/defc team-webhooks-page
[{:keys [team] :as props}] {::mf/wrap-props false}
[{:keys [team]}]
(let [webhooks (mf/deref refs/dashboard-team-webhooks)] (let [webhooks (mf/deref refs/dashboard-team-webhooks)]
(mf/with-effect [team] (mf/with-effect [team]
@ -891,7 +962,8 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(mf/defc team-settings-page (mf/defc team-settings-page
[{:keys [team] :as props}] {::mf/wrap-props false}
[{:keys [team]}]
(let [finput (mf/use-ref) (let [finput (mf/use-ref)
members-map (mf/deref refs/dashboard-team-members) members-map (mf/deref refs/dashboard-team-members)
@ -912,22 +984,19 @@
(st/emit! (dd/update-team-photo file)))] (st/emit! (dd/update-team-photo file)))]
(mf/use-effect (mf/with-effect [team]
(mf/deps team) (dom/set-html-title (tr "title.team-settings"
(fn [] (if (:is-default team)
(dom/set-html-title (tr "title.team-settings" (tr "dashboard.your-penpot")
(if (:is-default team) (:name team)))))
(tr "dashboard.your-penpot")
(:name team))))))
(mf/use-effect (mf/with-effect []
#(st/emit! (dd/fetch-team-members) (st/emit! (dd/fetch-team-members)
(dd/fetch-team-stats))) (dd/fetch-team-stats)))
[:* [:*
[:& header {:section :dashboard-team-settings [:& header {:section :dashboard-team-settings :team team}]
:team team}]
[:section.dashboard-container.dashboard-team-settings [:section.dashboard-container.dashboard-team-settings
[:div.team-settings [:div.team-settings
[:div.horizontal-blocks [:div.horizontal-blocks

View file

@ -146,8 +146,9 @@
is-component? (mf/use-ctx muc/is-component?)] is-component? (mf/use-ctx muc/is-component?)]
[:> frame-container props [:> frame-container props
[:g.frame-children {:opacity (:opacity shape)} [:g.frame-children {:opacity (:opacity shape)}
(for [item childs] (for [{:keys [id] :as item} childs]
[:& shape-wrapper {:key (dm/str (:id item)) :shape item}])] (when (some? id)
[:& shape-wrapper {:key (dm/str (:id item)) :shape item}]))]
(when (and is-component? (empty? childs)) (when (and is-component? (empty? childs))
[:& grid-layout-viewer {:shape shape :childs childs}])]))) [:& grid-layout-viewer {:shape shape :childs childs}])])))

View file

@ -50,6 +50,8 @@
(l/derived :viewer-overlays st/state)) (l/derived :viewer-overlays st/state))
(defn- calculate-size (defn- calculate-size
"Calculate the total size we must reserve for the frame, including possible paddings
added because shadows or blur."
[objects frame zoom] [objects frame zoom]
(let [{:keys [x y width height]} (gsb/get-object-bounds objects frame)] (let [{:keys [x y width height]} (gsb/get-object-bounds objects frame)]
{:base-width width {:base-width width
@ -60,6 +62,23 @@
:height (* height zoom) :height (* height zoom)
:vbox (dm/fmt "% % % %" 0 0 width height)})) :vbox (dm/fmt "% % % %" 0 0 width height)}))
(defn calculate-delta
"Calculate the displacement we need to apply so that the original selrect appears in the
same position as if it had no extra paddings, depending on the side the frame will
be snapped to."
[size selrect [snap-v snap-h] zoom]
(let [delta-x (case snap-h
:left (- (:x1 selrect) (:x size))
:right (- (:x2 selrect) (+ (:x size) (/ (:width size) zoom)))
:center (- (/ (- (:width selrect) (/ (:width size) zoom)) 2)
(- (:x size) (:x1 selrect))))
delta-y (case snap-v
:top (- (:y1 selrect) (:y size))
:bottom (- (:y2 selrect) (+ (:y size) (/ (:height size) zoom)))
:center (- (/ (- (:height selrect) (/ (:height size) zoom)) 2)
(- (:y size) (:y1 selrect))))]
(gpt/point (* delta-x zoom) (* delta-y zoom))))
(defn- calculate-wrapper (defn- calculate-wrapper
[size1 size2 zoom] [size1 size2 zoom]
(cond (cond
@ -113,6 +132,10 @@
(mf/with-memo [page overlay zoom] (mf/with-memo [page overlay zoom]
(calculate-size (:objects page) (:frame overlay) zoom)) (calculate-size (:objects page) (:frame overlay) zoom))
delta
(mf/with-memo [size overlay-frame overlay zoom]
(calculate-delta size (:selrect overlay-frame) (:snap-to overlay) zoom))
on-click on-click
(mf/use-fn (mf/use-fn
(mf/deps overlay close-click-outside?) (mf/deps overlay close-click-outside?)
@ -145,6 +168,7 @@
:base-frame frame :base-frame frame
:frame-offset overlay-position :frame-offset overlay-position
:size size :size size
:delta delta
:page page :page page
:interactions-mode interactions-mode}]]])) :interactions-mode interactions-mode}]]]))

View file

@ -7,6 +7,7 @@
(ns app.main.ui.viewer.inspect.render (ns app.main.ui.viewer.inspect.render
"The main container for a frame in inspect mode" "The main container for a frame in inspect mode"
(:require (:require
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.main.data.viewer :as dv] [app.main.data.viewer :as dv]
@ -186,7 +187,7 @@
(mf/defc render-frame-svg (mf/defc render-frame-svg
[{:keys [page frame local size]}] [{:keys [page frame local size]}]
(let [objects (mf/with-memo [page frame size] (let [objects (mf/with-memo [page frame size]
(prepare-objects frame size (:objects page))) (prepare-objects frame size (gpt/point 0 0) (:objects page)))
;; Retrieve frame again with correct modifier ;; Retrieve frame again with correct modifier
frame (get objects (:id frame)) frame (get objects (:id frame))

View file

@ -28,9 +28,10 @@
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(defn prepare-objects (defn prepare-objects
[frame size objects] [frame size delta objects]
(let [frame-id (:id frame) (let [frame-id (:id frame)
vector (-> (gpt/point (:x size) (:y size)) vector (-> (gpt/point (:x size) (:y size))
(gpt/add delta)
(gpt/negate)) (gpt/negate))
update-fn #(d/update-when %1 %2 gsh/transform-shape (ctm/move-modifiers vector))] update-fn #(d/update-when %1 %2 gsh/transform-shape (ctm/move-modifiers vector))]
(->> (cph/get-children-ids objects frame-id) (->> (cph/get-children-ids objects frame-id)
@ -46,6 +47,7 @@
base (unchecked-get props "base") base (unchecked-get props "base")
offset (unchecked-get props "offset") offset (unchecked-get props "offset")
size (unchecked-get props "size") size (unchecked-get props "size")
delta (or (unchecked-get props "delta") (gpt/point 0 0))
vbox (:vbox size) vbox (:vbox size)
@ -67,20 +69,26 @@
(map (d/getf (:objects page))) (map (d/getf (:objects page)))
(concat [frame]) (concat [frame])
(d/index-by :id) (d/index-by :id)
(prepare-objects frame size))) (prepare-objects frame size delta)))
wrapper-fixed (mf/with-memo [page frame size] objects-fixed (mf/with-memo [fixed-ids page frame size delta]
(shapes/frame-container-factory (calculate-objects fixed-ids))) (calculate-objects fixed-ids))
objects-not-fixed (mf/with-memo [page frame size] objects-not-fixed (mf/with-memo [not-fixed-ids page frame size delta]
(calculate-objects not-fixed-ids)) (calculate-objects not-fixed-ids))
all-objects (mf/with-memo [objects-fixed objects-not-fixed]
(merge objects-fixed objects-not-fixed))
wrapper-fixed (mf/with-memo [page frame size]
(shapes/frame-container-factory objects-fixed all-objects))
wrapper-not-fixed (mf/with-memo [objects-not-fixed] wrapper-not-fixed (mf/with-memo [objects-not-fixed]
(shapes/frame-container-factory objects-not-fixed)) (shapes/frame-container-factory objects-not-fixed all-objects))
;; Retrieve frames again with correct modifier ;; Retrieve frames again with correct modifier
frame (get objects-not-fixed (:id frame)) frame (get all-objects (:id frame))
base (get objects-not-fixed (:id base)) base (get all-objects (:id base))
non-delay-interactions (->> (:interactions frame) non-delay-interactions (->> (:interactions frame)
(filterv #(not= (:event-type %) :after-delay))) (filterv #(not= (:event-type %) :after-delay)))
@ -121,6 +129,7 @@
mode (h/use-equal-memo (unchecked-get props "interactions-mode")) mode (h/use-equal-memo (unchecked-get props "interactions-mode"))
offset (h/use-equal-memo (unchecked-get props "frame-offset")) offset (h/use-equal-memo (unchecked-get props "frame-offset"))
size (h/use-equal-memo (unchecked-get props "size")) size (h/use-equal-memo (unchecked-get props "size"))
delta (unchecked-get props "delta")
page (unchecked-get props "page") page (unchecked-get props "page")
frame (unchecked-get props "frame") frame (unchecked-get props "frame")
@ -163,7 +172,8 @@
:frame frame :frame frame
:base base :base base
:offset offset :offset offset
:size size}])) :size size
:delta delta}]))
(mf/defc flows-menu (mf/defc flows-menu
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]}

View file

@ -59,19 +59,20 @@
:open-overlay :open-overlay
(let [dest-frame-id (:destination interaction) (let [dest-frame-id (:destination interaction)
viewer-objects (deref (refs/get-viewer-objects)) dest-frame (get objects dest-frame-id)
dest-frame (get viewer-objects dest-frame-id)
relative-to-id (if (= :manual (:overlay-pos-type interaction)) relative-to-id (if (= :manual (:overlay-pos-type interaction))
(:id shape) ;; manual interactions are allways from "self" (if (= (:type shape) :frame) ;; manual interactions are always from "self"
(:frame-id shape)
(:id shape))
(:position-relative-to interaction)) (:position-relative-to interaction))
relative-to-shape (or (get objects relative-to-id) base-frame) relative-to-shape (or (get objects relative-to-id) base-frame)
close-click-outside (:close-click-outside interaction) close-click-outside (:close-click-outside interaction)
background-overlay (:background-overlay interaction) background-overlay (:background-overlay interaction)
overlays-ids (set (map :id overlays)) overlays-ids (set (map :id overlays))
relative-to-base-frame (find-relative-to-base-frame relative-to-shape objects overlays-ids base-frame) relative-to-base-frame (find-relative-to-base-frame relative-to-shape objects overlays-ids base-frame)
position (ctsi/calc-overlay-position interaction [position snap-to] (ctsi/calc-overlay-position interaction
shape shape
viewer-objects objects
relative-to-shape relative-to-shape
relative-to-base-frame relative-to-base-frame
dest-frame dest-frame
@ -79,20 +80,23 @@
(when dest-frame-id (when dest-frame-id
(st/emit! (dv/open-overlay dest-frame-id (st/emit! (dv/open-overlay dest-frame-id
position position
snap-to
close-click-outside close-click-outside
background-overlay background-overlay
(:animation interaction))))) (:animation interaction)))))
:toggle-overlay :toggle-overlay
(let [frame-id (:destination interaction) (let [dest-frame-id (:destination interaction)
dest-frame (get objects frame-id) dest-frame (get objects dest-frame-id)
relative-to-id (if (= :manual (:overlay-pos-type interaction)) relative-to-id (if (= :manual (:overlay-pos-type interaction))
(:id shape) ;; manual interactions are allways from "self" (if (= (:type shape) :frame) ;; manual interactions are always from "self"
(:frame-id shape)
(:id shape))
(:position-relative-to interaction)) (:position-relative-to interaction))
relative-to-shape (or (get objects relative-to-id) base-frame) relative-to-shape (or (get objects relative-to-id) base-frame)
overlays-ids (set (map :id overlays)) overlays-ids (set (map :id overlays))
relative-to-base-frame (find-relative-to-base-frame relative-to-shape objects overlays-ids base-frame) relative-to-base-frame (find-relative-to-base-frame relative-to-shape objects overlays-ids base-frame)
position (ctsi/calc-overlay-position interaction [position snap-to] (ctsi/calc-overlay-position interaction
shape shape
objects objects
relative-to-shape relative-to-shape
@ -102,19 +106,21 @@
close-click-outside (:close-click-outside interaction) close-click-outside (:close-click-outside interaction)
background-overlay (:background-overlay interaction)] background-overlay (:background-overlay interaction)]
(when frame-id (when dest-frame-id
(st/emit! (dv/toggle-overlay frame-id (st/emit! (dv/toggle-overlay dest-frame-id
position position
snap-to
close-click-outside close-click-outside
background-overlay background-overlay
(:animation interaction))))) (:animation interaction)))))
:close-overlay :close-overlay
(let [frame-id (or (:destination interaction) (let [dest-frame-id (or (:destination interaction)
(if (= (:type shape) :frame) (if (and (= (:type shape) :frame)
(:id shape) (some #(= (:id %) (:id shape)) overlays))
(:frame-id shape)))] (:id shape)
(st/emit! (dv/close-overlay frame-id (:animation interaction)))) (:frame-id shape)))]
(st/emit! (dv/close-overlay dest-frame-id (:animation interaction))))
:prev-screen :prev-screen
(st/emit! (rt/nav-back-local)) (st/emit! (rt/nav-back-local))
@ -136,29 +142,49 @@
(st/emit! (dv/close-overlay frame-id))) (st/emit! (dv/close-overlay frame-id)))
:toggle-overlay :toggle-overlay
(let [frame-id (:destination interaction) (let [dest-frame-id (:destination interaction)
position (:overlay-position interaction) dest-frame (get objects dest-frame-id)
close-click-outside (:close-click-outside interaction) relative-to-id (if (= :manual (:overlay-pos-type interaction))
background-overlay (:background-overlay interaction)] (if (= (:type shape) :frame) ;; manual interactions are always from "self"
(when frame-id (:frame-id shape)
(st/emit! (dv/toggle-overlay frame-id (:id shape))
(:position-relative-to interaction))
relative-to-shape (or (get objects relative-to-id) base-frame)
overlays-ids (set (map :id overlays))
relative-to-base-frame (find-relative-to-base-frame relative-to-shape objects overlays-ids base-frame)
[position snap-to] (ctsi/calc-overlay-position interaction
shape
objects
relative-to-shape
relative-to-base-frame
dest-frame
frame-offset)
close-click-outside (:close-click-outside interaction)
background-overlay (:background-overlay interaction)]
(when dest-frame-id
(st/emit! (dv/toggle-overlay dest-frame-id
position position
snap-to
close-click-outside close-click-outside
background-overlay background-overlay
(:animation interaction))))) (:animation interaction)))))
:close-overlay :close-overlay
(let [dest-frame-id (:destination interaction) (let [dest-frame-id (:destination interaction)
dest-frame (get objects dest-frame-id) dest-frame (get objects dest-frame-id)
relative-to-id (if (= :manual (:overlay-pos-type interaction)) relative-to-id (if (= :manual (:overlay-pos-type interaction))
(:id shape) ;; manual interactions are allways from "self" (if (= (:type shape) :frame) ;; manual interactions are always from "self"
(:frame-id shape)
(:id shape))
(:position-relative-to interaction)) (:position-relative-to interaction))
relative-to-shape (or (get objects relative-to-id) base-frame) relative-to-shape (or (get objects relative-to-id) base-frame)
close-click-outside (:close-click-outside interaction) close-click-outside (:close-click-outside interaction)
background-overlay (:background-overlay interaction) background-overlay (:background-overlay interaction)
overlays-ids (set (map :id overlays)) overlays-ids (set (map :id overlays))
relative-to-base-frame (find-relative-to-base-frame relative-to-shape objects overlays-ids base-frame) relative-to-base-frame (find-relative-to-base-frame relative-to-shape objects overlays-ids base-frame)
position (ctsi/calc-overlay-position interaction [position snap-to] (ctsi/calc-overlay-position interaction
shape shape
objects objects
relative-to-shape relative-to-shape
@ -168,6 +194,7 @@
(when dest-frame-id (when dest-frame-id
(st/emit! (dv/open-overlay dest-frame-id (st/emit! (dv/open-overlay dest-frame-id
position position
snap-to
close-click-outside close-click-outside
background-overlay background-overlay
(:animation interaction))))) (:animation interaction)))))
@ -258,6 +285,7 @@
childs (unchecked-get props "childs") childs (unchecked-get props "childs")
frame (unchecked-get props "frame") frame (unchecked-get props "frame")
objects (unchecked-get props "objects") objects (unchecked-get props "objects")
all-objects (or (unchecked-get props "all-objects") objects)
base-frame (mf/use-ctx base-frame-ctx) base-frame (mf/use-ctx base-frame-ctx)
frame-offset (mf/use-ctx frame-offset-ctx) frame-offset (mf/use-ctx frame-offset-ctx)
interactions-show? (mf/deref viewer-interactions-show?) interactions-show? (mf/deref viewer-interactions-show?)
@ -266,22 +294,25 @@
svg-element? (and (= :svg-raw (:type shape)) svg-element? (and (= :svg-raw (:type shape))
(not= :svg (get-in shape [:content :tag]))) (not= :svg (get-in shape [:content :tag])))
;; The objects parameter has the shapes that we must draw. It may be a subset of
;; all-objects in some cases (e.g. if there are fixed elements). But for interactions
;; handling we need access to all objects inside the page.
on-pointer-down on-pointer-down
(mf/use-fn (mf/deps shape base-frame frame-offset objects) (mf/use-fn (mf/deps shape base-frame frame-offset all-objects)
#(on-pointer-down % shape base-frame frame-offset objects overlays)) #(on-pointer-down % shape base-frame frame-offset all-objects overlays))
on-pointer-up on-pointer-up
(mf/use-fn (mf/deps shape base-frame frame-offset objects) (mf/use-fn (mf/deps shape base-frame frame-offset all-objects)
#(on-pointer-up % shape base-frame frame-offset objects overlays)) #(on-pointer-up % shape base-frame frame-offset all-objects overlays))
on-pointer-enter on-pointer-enter
(mf/use-fn (mf/deps shape base-frame frame-offset objects) (mf/use-fn (mf/deps shape base-frame frame-offset all-objects)
#(on-pointer-enter % shape base-frame frame-offset objects overlays)) #(on-pointer-enter % shape base-frame frame-offset all-objects overlays))
on-pointer-leave on-pointer-leave
(mf/use-fn (mf/deps shape base-frame frame-offset objects) (mf/use-fn (mf/deps shape base-frame frame-offset all-objects)
#(on-pointer-leave % shape base-frame frame-offset objects overlays))] #(on-pointer-leave % shape base-frame frame-offset all-objects overlays))]
(mf/with-effect [] (mf/with-effect []
(let [sems (on-load shape base-frame frame-offset objects overlays)] (let [sems (on-load shape base-frame frame-offset objects overlays)]
@ -350,8 +381,8 @@
(declare shape-container-factory) (declare shape-container-factory)
(defn frame-container-factory (defn frame-container-factory
[objects] [objects all-objects]
(let [shape-container (shape-container-factory objects) (let [shape-container (shape-container-factory objects all-objects)
frame-wrapper (frame-wrapper shape-container)] frame-wrapper (frame-wrapper shape-container)]
(mf/fnc frame-container (mf/fnc frame-container
{::mf/wrap-props false} {::mf/wrap-props false}
@ -361,13 +392,14 @@
props (obj/merge! #js {} props props (obj/merge! #js {} props
#js {:shape shape #js {:shape shape
:childs childs :childs childs
:objects objects})] :objects objects
:all-objects all-objects})]
[:> frame-wrapper props])))) [:> frame-wrapper props]))))
(defn group-container-factory (defn group-container-factory
[objects] [objects all-objects]
(let [shape-container (shape-container-factory objects) (let [shape-container (shape-container-factory objects all-objects)
group-wrapper (group-wrapper shape-container)] group-wrapper (group-wrapper shape-container)]
(mf/fnc group-container (mf/fnc group-container
{::mf/wrap-props false} {::mf/wrap-props false}
@ -380,8 +412,8 @@
[:> group-wrapper props]))))) [:> group-wrapper props])))))
(defn bool-container-factory (defn bool-container-factory
[objects] [objects all-objects]
(let [shape-container (shape-container-factory objects) (let [shape-container (shape-container-factory objects all-objects)
bool-wrapper (bool-wrapper shape-container)] bool-wrapper (bool-wrapper shape-container)]
(mf/fnc bool-container (mf/fnc bool-container
{::mf/wrap-props false} {::mf/wrap-props false}
@ -394,8 +426,8 @@
[:> bool-wrapper props])))) [:> bool-wrapper props]))))
(defn svg-raw-container-factory (defn svg-raw-container-factory
[objects] [objects all-objects]
(let [shape-container (shape-container-factory objects) (let [shape-container (shape-container-factory objects all-objects)
svg-raw-wrapper (svg-raw-wrapper shape-container)] svg-raw-wrapper (svg-raw-wrapper shape-container)]
(mf/fnc svg-raw-container (mf/fnc svg-raw-container
{::mf/wrap-props false} {::mf/wrap-props false}
@ -407,7 +439,7 @@
[:> svg-raw-wrapper props])))) [:> svg-raw-wrapper props]))))
(defn shape-container-factory (defn shape-container-factory
[objects] [objects all-objects]
(let [path-wrapper (path-wrapper) (let [path-wrapper (path-wrapper)
text-wrapper (text-wrapper) text-wrapper (text-wrapper)
rect-wrapper (rect-wrapper) rect-wrapper (rect-wrapper)
@ -422,26 +454,27 @@
group-container group-container
(mf/with-memo [objects] (mf/with-memo [objects]
(group-container-factory objects)) (group-container-factory objects all-objects))
frame-container frame-container
(mf/with-memo [objects] (mf/with-memo [objects]
(frame-container-factory objects)) (frame-container-factory objects all-objects))
bool-container bool-container
(mf/with-memo [objects] (mf/with-memo [objects]
(bool-container-factory objects)) (bool-container-factory objects all-objects))
svg-raw-container svg-raw-container
(mf/with-memo [objects] (mf/with-memo [objects]
(svg-raw-container-factory objects))] (svg-raw-container-factory objects all-objects))]
(when (and shape (not (:hidden shape))) (when (and shape (not (:hidden shape)))
(let [shape (-> shape (let [shape (-> shape
#_(gsh/transform-shape) #_(gsh/transform-shape)
(gsh/translate-to-frame frame)) (gsh/translate-to-frame frame))
opts #js {:shape shape opts #js {:shape shape
:objects objects}] :objects objects
:all-objects all-objects}]
(case (:type shape) (case (:type shape)
:frame [:> frame-container opts] :frame [:> frame-container opts]
:text [:> text-wrapper opts] :text [:> text-wrapper opts]

View file

@ -87,7 +87,7 @@
dw/clear-edition-mode) dw/clear-edition-mode)
;; Delay so anything that launched :interrupt can finish ;; Delay so anything that launched :interrupt can finish
(st/emit! 100 (dw/select-for-drawing tool))))) (ts/schedule 100 #(st/emit! (dw/select-for-drawing tool))))))
toggle-text-palette toggle-text-palette
(mf/use-fn (mf/use-fn

View file

@ -75,7 +75,7 @@
(d/without-nils {:color (str/lower (dm/get-in shadow [:color :color])) (d/without-nils {:color (str/lower (dm/get-in shadow [:color :color]))
:opacity (dm/get-in shadow [:color :opacity]) :opacity (dm/get-in shadow [:color :opacity])
:gradient (dm/get-in shadow [:color :gradient])}))] :gradient (dm/get-in shadow [:color :gradient])}))]
{:attrs attrs {:attrs attrs
:prop :shadow :prop :shadow
@ -157,41 +157,33 @@
expand-color (mf/use-state false) expand-color (mf/use-state false)
grouped-colors* (mf/use-var nil) grouped-colors* (mf/use-var nil)
prev-color* (mf/use-var nil) prev-colors* (mf/use-var [])
on-change on-change
(mf/use-fn (mf/use-fn
(fn [new-color old-color from-picker?] (fn [new-color old-color from-picker?]
(let [old-color (-> old-color (let [old-color (-> old-color (dissoc :name :path) d/without-nils)
(dissoc :name)
(dissoc :path)
(d/without-nils))
prev-color (when @prev-color*
(-> @prev-color*
(dissoc :name)
(dissoc :path)
(d/without-nils)))
;; When dragging on the color picker sometimes all the shapes hasn't updated the color to the prev value so we need this extra calculation ;; When dragging on the color picker sometimes all the shapes hasn't updated the color to the prev value so we need this extra calculation
shapes-by-old-color (get @grouped-colors* old-color) shapes-by-old-color (get @grouped-colors* old-color)
prev-color (d/seek #(get @grouped-colors* %) @prev-colors*)
shapes-by-prev-color (get @grouped-colors* prev-color) shapes-by-prev-color (get @grouped-colors* prev-color)
shapes-by-color (or shapes-by-prev-color shapes-by-old-color)] shapes-by-color (or shapes-by-prev-color shapes-by-old-color)]
(when from-picker? (when from-picker?
(reset! prev-color* new-color)) (swap! prev-colors* conj (-> new-color (dissoc :name :path) d/without-nils)))
(st/emit! (dc/change-color-in-selected new-color shapes-by-color (or prev-color old-color)))))) (st/emit! (dc/change-color-in-selected new-color shapes-by-color (or prev-color old-color))))))
on-open on-open
(mf/use-fn (mf/use-fn
(fn [] (fn []
(reset! prev-color* nil))) (reset! prev-colors* [])))
on-close on-close
(mf/use-fn (mf/use-fn
(fn [] (fn []
(reset! prev-color* nil))) (reset! prev-colors* [])))
on-detach on-detach
(mf/use-fn (mf/use-fn
@ -217,7 +209,7 @@
[:div.element-set-content [:div.element-set-content
[:div.selected-colors [:div.selected-colors
(for [[index color] (d/enumerate (take 3 library-colors))] (for [[index color] (d/enumerate (take 3 library-colors))]
[:& color-row {:key (dm/str "library-color-" index) [:& color-row {:key (dm/str "library-color-" (:color color))
:color color :color color
:index index :index index
:on-detach on-detach :on-detach on-detach
@ -231,7 +223,7 @@
[:span.text (tr "workspace.options.more-lib-colors")]]) [:span.text (tr "workspace.options.more-lib-colors")]])
(when @expand-lib-color (when @expand-lib-color
(for [[index color] (d/enumerate (drop 3 library-colors))] (for [[index color] (d/enumerate (drop 3 library-colors))]
[:& color-row {:key (dm/str "library-color-" index) [:& color-row {:key (dm/str "library-color-" (:color color))
:color color :color color
:index index :index index
:on-detach on-detach :on-detach on-detach
@ -255,7 +247,7 @@
[:span.text (tr "workspace.options.more-colors")]]) [:span.text (tr "workspace.options.more-colors")]])
(when @expand-color (when @expand-color
(for [[index color] (d/enumerate (drop 3 colors))] (for [[index color] (d/enumerate (drop 3 colors))]
[:& color-row {:key (dm/str "color-" index) [:& color-row {:key (dm/str "color-" (:color color))
:color color :color color
:index index :index index
:select-only select-only :select-only select-only

View file

@ -315,12 +315,12 @@
[measure-ids measure-values] (get-attrs shapes objects :measure) [measure-ids measure-values] (get-attrs shapes objects :measure)
[layer-ids layer-values [layer-ids layer-values
text-ids text-values
constraint-ids constraint-values constraint-ids constraint-values
fill-ids fill-values fill-ids fill-values
shadow-ids shadow-values shadow-ids shadow-values
blur-ids blur-values blur-ids blur-values
stroke-ids stroke-values stroke-ids stroke-values
text-ids text-values
exports-ids exports-values exports-ids exports-values
layout-container-ids layout-container-values layout-container-ids layout-container-values
layout-item-ids layout-item-values] layout-item-ids layout-item-values]
@ -331,12 +331,12 @@
[] []
(mapcat identity) (mapcat identity)
[(get-attrs shapes objects-no-measures :layer) [(get-attrs shapes objects-no-measures :layer)
(get-attrs shapes objects-no-measures :text)
(get-attrs shapes objects-no-measures :constraint) (get-attrs shapes objects-no-measures :constraint)
(get-attrs shapes objects-no-measures :fill) (get-attrs shapes objects-no-measures :fill)
(get-attrs shapes objects-no-measures :shadow) (get-attrs shapes objects-no-measures :shadow)
(get-attrs shapes objects-no-measures :blur) (get-attrs shapes objects-no-measures :blur)
(get-attrs shapes objects-no-measures :stroke) (get-attrs shapes objects-no-measures :stroke)
(get-attrs shapes objects-no-measures :text)
(get-attrs shapes objects-no-measures :exports) (get-attrs shapes objects-no-measures :exports)
(get-attrs shapes objects-no-measures :layout-container) (get-attrs shapes objects-no-measures :layout-container)
(get-attrs shapes objects-no-measures :layout-item) (get-attrs shapes objects-no-measures :layout-item)
@ -364,6 +364,9 @@
(when-not (empty? layer-ids) (when-not (empty? layer-ids)
[:& layer-menu {:type type :ids layer-ids :values layer-values}]) [:& layer-menu {:type type :ids layer-ids :values layer-values}])
(when-not (empty? text-ids)
[:& ot/text-menu {:type type :ids text-ids :values text-values}])
(when-not (empty? fill-ids) (when-not (empty? fill-ids)
[:& fill-menu {:type type :ids fill-ids :values fill-values}]) [:& fill-menu {:type type :ids fill-ids :values fill-values}])
@ -380,8 +383,5 @@
(when-not (empty? blur-ids) (when-not (empty? blur-ids)
[:& blur-menu {:type type :ids blur-ids :values blur-values}]) [:& blur-menu {:type type :ids blur-ids :values blur-values}])
(when-not (empty? text-ids)
[:& ot/text-menu {:type type :ids text-ids :values text-values}])
(when-not (empty? exports-ids) (when-not (empty? exports-ids)
[:& exports-menu {:type type :ids exports-ids :values exports-values :page-id page-id :file-id file-id}])])) [:& exports-menu {:type type :ids exports-ids :values exports-values :page-id page-id :file-id file-id}])]))

View file

@ -2321,9 +2321,6 @@ msgstr "Přichytit k vodicím lištám"
msgid "shortcuts.toggle-textpalette" msgid "shortcuts.toggle-textpalette"
msgstr "Přepnout paletu textu" msgstr "Přepnout paletu textu"
msgid "shortcuts.toggle-visibility"
msgstr "Přepnout viditelnost"
msgid "shortcuts.toggle-zoom-style" msgid "shortcuts.toggle-zoom-style"
msgstr "Přepnout styl přiblížení" msgstr "Přepnout styl přiblížení"

View file

@ -1017,6 +1017,9 @@ msgstr "Email or password is incorrect."
msgid "errors.wrong-old-password" msgid "errors.wrong-old-password"
msgstr "Old password is incorrect" msgstr "Old password is incorrect"
msgid "errors.cannot-upload"
msgstr "Cannot upload the media file."
#: src/app/main/ui/settings/feedback.cljs #: src/app/main/ui/settings/feedback.cljs
msgid "feedback.description" msgid "feedback.description"
msgstr "Description" msgstr "Description"
@ -2528,7 +2531,7 @@ msgid "shortcuts.h-distribute"
msgstr "Distribute horizontally" msgstr "Distribute horizontally"
msgid "shortcuts.hide-ui" msgid "shortcuts.hide-ui"
msgstr "Show/hide UI" msgstr "Show / Hide UI"
msgid "shortcuts.increase-zoom" msgid "shortcuts.increase-zoom"
msgstr "Zoom in" msgstr "Zoom in"
@ -2687,10 +2690,10 @@ msgid "shortcuts.separate-nodes"
msgstr "Separate nodes" msgstr "Separate nodes"
msgid "shortcuts.show-pixel-grid" msgid "shortcuts.show-pixel-grid"
msgstr "Show/hide pixel grid" msgstr "Show / Hide pixel grid"
msgid "shortcuts.show-shortcuts" msgid "shortcuts.show-shortcuts"
msgstr "Show/hide shortcuts" msgstr "Show / Hide shortcuts"
msgid "shortcuts.snap-nodes" msgid "shortcuts.snap-nodes"
msgstr "Snap to nodes" msgstr "Snap to nodes"
@ -2742,7 +2745,7 @@ msgid "shortcuts.toggle-fullscreen"
msgstr "Toggle fullscreen" msgstr "Toggle fullscreen"
msgid "shortcuts.toggle-grid" msgid "shortcuts.toggle-grid"
msgstr "Show/hide grid" msgstr "Show / Hide grid"
msgid "shortcuts.toggle-history" msgid "shortcuts.toggle-history"
msgstr "Toggle history" msgstr "Toggle history"
@ -2751,16 +2754,16 @@ msgid "shortcuts.toggle-layers"
msgstr "Toggle layers" msgstr "Toggle layers"
msgid "shortcuts.toggle-layout-flex" msgid "shortcuts.toggle-layout-flex"
msgstr "Add/remove flex layout" msgstr "Add / Remove flex layout"
msgid "shortcuts.toggle-lock" msgid "shortcuts.toggle-lock"
msgstr "Lock selected" msgstr "Lock / Unlock"
msgid "shortcuts.toggle-lock-size" msgid "shortcuts.toggle-lock-size"
msgstr "Lock proportions" msgstr "Lock proportions"
msgid "shortcuts.toggle-rules" msgid "shortcuts.toggle-rules"
msgstr "Show/hide rulers" msgstr "Show / Hide rulers"
msgid "shortcuts.toggle-scale-text" msgid "shortcuts.toggle-scale-text"
msgstr "Toggle scale text" msgstr "Toggle scale text"
@ -2775,7 +2778,7 @@ msgid "shortcuts.toggle-textpalette"
msgstr "Toggle text palette" msgstr "Toggle text palette"
msgid "shortcuts.toggle-visibility" msgid "shortcuts.toggle-visibility"
msgstr "Toggle visibility" msgstr "Show / Hide"
msgid "shortcuts.toggle-zoom-style" msgid "shortcuts.toggle-zoom-style"
msgstr "Toggle zoom style" msgstr "Toggle zoom style"
@ -4475,7 +4478,7 @@ msgid "workspace.shape.menu.hide"
msgstr "Hide" msgstr "Hide"
msgid "workspace.shape.menu.hide-ui" msgid "workspace.shape.menu.hide-ui"
msgstr "Show/Hide UI" msgstr "Show / Hide UI"
msgid "workspace.shape.menu.intersection" msgid "workspace.shape.menu.intersection"
msgstr "Intersection" msgstr "Intersection"

View file

@ -1057,6 +1057,9 @@ msgstr "El email o la contraseña son incorrectos."
msgid "errors.wrong-old-password" msgid "errors.wrong-old-password"
msgstr "La contraseña anterior no es correcta" msgstr "La contraseña anterior no es correcta"
msgid "errors.cannot-upload"
msgstr "No se puede subir el fichero"
#: src/app/main/ui/settings/feedback.cljs #: src/app/main/ui/settings/feedback.cljs
msgid "feedback.description" msgid "feedback.description"
msgstr "Descripción" msgstr "Descripción"

View file

@ -2592,9 +2592,6 @@ msgstr "הצמדה לקווים מנחים"
msgid "shortcuts.toggle-textpalette" msgid "shortcuts.toggle-textpalette"
msgstr "החלפת לוח טקסט" msgstr "החלפת לוח טקסט"
msgid "shortcuts.toggle-visibility"
msgstr "החלפת מצב הצגה"
msgid "shortcuts.toggle-zoom-style" msgid "shortcuts.toggle-zoom-style"
msgstr "החלפת סגנון תקריב" msgstr "החלפת סגנון תקריב"

View file

@ -2405,9 +2405,6 @@ msgstr "Pričvrsti na guides"
msgid "shortcuts.toggle-textpalette" msgid "shortcuts.toggle-textpalette"
msgstr "Promijeni paletu teksta" msgstr "Promijeni paletu teksta"
msgid "shortcuts.toggle-visibility"
msgstr "Promijeni vidljivost"
msgid "shortcuts.toggle-zoom-style" msgid "shortcuts.toggle-zoom-style"
msgstr "Promijeni stil zooma" msgstr "Promijeni stil zooma"

View file

@ -2559,9 +2559,6 @@ msgstr "Tancap ke pemandu"
msgid "shortcuts.toggle-textpalette" msgid "shortcuts.toggle-textpalette"
msgstr "Alih palet teks" msgstr "Alih palet teks"
msgid "shortcuts.toggle-visibility"
msgstr "Alih keterlihatan"
msgid "shortcuts.toggle-zoom-style" msgid "shortcuts.toggle-zoom-style"
msgstr "Alih gaya zum" msgstr "Alih gaya zum"

View file

@ -2592,9 +2592,6 @@ msgstr "Pieķerties vadotnēm"
msgid "shortcuts.toggle-textpalette" msgid "shortcuts.toggle-textpalette"
msgstr "Pārslēgt teksta paleti" msgstr "Pārslēgt teksta paleti"
msgid "shortcuts.toggle-visibility"
msgstr "Pārslēgt redzamību"
msgid "shortcuts.toggle-zoom-style" msgid "shortcuts.toggle-zoom-style"
msgstr "Pārslēgt tālummaiņas stilu" msgstr "Pārslēgt tālummaiņas stilu"

View file

@ -2612,9 +2612,6 @@ msgstr "Przyciągaj do prowadnic"
msgid "shortcuts.toggle-textpalette" msgid "shortcuts.toggle-textpalette"
msgstr "Przełącz paletę tekstu" msgstr "Przełącz paletę tekstu"
msgid "shortcuts.toggle-visibility"
msgstr "Przełącz widoczność"
msgid "shortcuts.toggle-zoom-style" msgid "shortcuts.toggle-zoom-style"
msgstr "Przełącz sposób powiększania" msgstr "Przełącz sposób powiększania"

View file

@ -2567,9 +2567,6 @@ msgstr "Aderir as réguas"
msgid "shortcuts.toggle-textpalette" msgid "shortcuts.toggle-textpalette"
msgstr "Mostrar/Esconder paleta de tipografias" msgstr "Mostrar/Esconder paleta de tipografias"
msgid "shortcuts.toggle-visibility"
msgstr "Alternar visibilidade"
msgid "shortcuts.toggle-zoom-style" msgid "shortcuts.toggle-zoom-style"
msgstr "Alternar estilo de zoom" msgstr "Alternar estilo de zoom"

View file

@ -2424,9 +2424,6 @@ msgstr "Ajustar às guias"
msgid "shortcuts.toggle-textpalette" msgid "shortcuts.toggle-textpalette"
msgstr "Alternar paleta de texto" msgstr "Alternar paleta de texto"
msgid "shortcuts.toggle-visibility"
msgstr "Alternar visibilidade"
msgid "shortcuts.toggle-zoom-style" msgid "shortcuts.toggle-zoom-style"
msgstr "Alternar estilo de zoom" msgstr "Alternar estilo de zoom"

View file

@ -2589,9 +2589,6 @@ msgstr "Fixare la ghiduri"
msgid "shortcuts.toggle-textpalette" msgid "shortcuts.toggle-textpalette"
msgstr "Comutați paleta de text" msgstr "Comutați paleta de text"
msgid "shortcuts.toggle-visibility"
msgstr "Comutați vizibilitatea"
msgid "shortcuts.toggle-zoom-style" msgid "shortcuts.toggle-zoom-style"
msgstr "Comutați stilul zoomului" msgstr "Comutați stilul zoomului"

View file

@ -2691,9 +2691,6 @@ msgstr "Kılavuzlara tuttur"
msgid "shortcuts.toggle-textpalette" msgid "shortcuts.toggle-textpalette"
msgstr "Metin paletini değiştir" msgstr "Metin paletini değiştir"
msgid "shortcuts.toggle-visibility"
msgstr "Görünürlüğü değiştir"
msgid "shortcuts.toggle-zoom-style" msgid "shortcuts.toggle-zoom-style"
msgstr "Yakınlaştırma şeklini değiştir" msgstr "Yakınlaştırma şeklini değiştir"

View file

@ -2446,9 +2446,6 @@ msgstr "辅助线对齐"
msgid "shortcuts.toggle-textpalette" msgid "shortcuts.toggle-textpalette"
msgstr "切换文本调色板" msgstr "切换文本调色板"
msgid "shortcuts.toggle-visibility"
msgstr "切换可见度"
msgid "shortcuts.toggle-zoom-style" msgid "shortcuts.toggle-zoom-style"
msgstr "切换缩放样式" msgstr "切换缩放样式"