mirror of
https://github.com/penpot/penpot.git
synced 2025-03-27 23:21:47 -05:00
Merge remote-tracking branch 'origin/staging' into develop
This commit is contained in:
commit
8eb64de062
43 changed files with 827 additions and 532 deletions
20
CHANGES.md
20
CHANGES.md
|
@ -70,7 +70,25 @@
|
|||
- 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 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
|
||||
|
||||
- Update google fonts catalog (at 2023/07/06) [Taiga #5592](https://tree.taiga.io/project/penpot/issue/5592)
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
[app.common.data.macros :as dm]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.logging :as l]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.spec :as us]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
|
@ -719,29 +720,22 @@
|
|||
|
||||
itoken))))
|
||||
|
||||
(s/def ::email ::us/email)
|
||||
(s/def ::emails ::us/set-of-valid-emails)
|
||||
(s/def ::create-team-invitations
|
||||
(s/keys :req [::rpc/profile-id]
|
||||
:req-un [::team-id ::role]
|
||||
:opt-un [::email ::emails]))
|
||||
(def ^:private schema:create-team-invitations
|
||||
[:map {:title "create-team-invitations"}
|
||||
[:team-id ::sm/uuid]
|
||||
[:role [::sm/one-of #{:owner :admin :editor}]]
|
||||
[:emails ::sm/set-of-emails]])
|
||||
|
||||
(sv/defmethod ::create-team-invitations
|
||||
"A rpc call that allow to send a single or multiple invitations to
|
||||
join the team."
|
||||
{::doc/added "1.17"}
|
||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id email emails role] :as params}]
|
||||
{::doc/added "1.17"
|
||||
::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]
|
||||
(let [perms (get-permissions conn profile-id team-id)
|
||||
profile (db/get-by-id conn :profile profile-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))]
|
||||
team (db/get-by-id conn :team team-id)]
|
||||
|
||||
(run! (partial quotes/check-quote! conn)
|
||||
(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"))
|
||||
|
||||
(let [cfg (assoc cfg ::db/conn conn)
|
||||
invitations (into []
|
||||
members (->> (db/exec! conn [sql:team-members team-id])
|
||||
(into #{} (map :email)))
|
||||
|
||||
invitations (into #{}
|
||||
(comp
|
||||
(remove member?)
|
||||
;; We don't re-send inviation to already existing members
|
||||
(remove (partial contains? members))
|
||||
(map (fn [email]
|
||||
{:email (str/lower email)
|
||||
:team team
|
||||
|
@ -774,7 +772,8 @@
|
|||
:role role}))
|
||||
(keep (partial create-invitation cfg)))
|
||||
emails)]
|
||||
(with-meta invitations
|
||||
(with-meta {:total (count invitations)
|
||||
:invitations invitations}
|
||||
{::audit/props {:invitations (count invitations)}})))))
|
||||
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
:role :editor}]
|
||||
|
||||
;; 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)
|
||||
;; retrieve the value from the database and check its content
|
||||
invitation (db/exec-one!
|
||||
|
@ -52,7 +52,7 @@
|
|||
|
||||
;; invite internal user without complaints
|
||||
(th/reset-mock! mock)
|
||||
(let [data (assoc data :email (:email profile2))
|
||||
(let [data (assoc data :emails [(:email profile2)])
|
||||
out (th/command! data)]
|
||||
(t/is (th/success? out))
|
||||
(t/is (= 1 (:call-count (deref mock)))))
|
||||
|
@ -60,7 +60,7 @@
|
|||
;; invite user with complaint
|
||||
(th/create-global-complaint-for pool {:type :complaint :email "foo@bar.com"})
|
||||
(th/reset-mock! mock)
|
||||
(let [data (assoc data :email "foo@bar.com")
|
||||
(let [data (assoc data :emails ["foo@bar.com"])
|
||||
out (th/command! data)]
|
||||
(t/is (th/success? out))
|
||||
(t/is (= 1 (:call-count (deref mock)))))
|
||||
|
@ -79,7 +79,7 @@
|
|||
(th/reset-mock! mock)
|
||||
|
||||
(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)]
|
||||
|
||||
(t/is (not (th/success? out)))
|
||||
|
@ -92,7 +92,7 @@
|
|||
;; invite internal user that is muted
|
||||
(th/reset-mock! mock)
|
||||
|
||||
(let [data (assoc data :email (:email profile3))
|
||||
(let [data (assoc data :emails [(:email profile3)])
|
||||
out (th/command! data)]
|
||||
|
||||
(t/is (not (th/success? out)))
|
||||
|
@ -118,7 +118,7 @@
|
|||
;; Try to invite a not existing user
|
||||
(let [data {::th/type :create-team-invitations
|
||||
::rpc/profile-id (:id profile1)
|
||||
:email "notexisting@example.com"
|
||||
:emails ["notexisting@example.com"]
|
||||
:team-id (:id team)
|
||||
:role :editor}
|
||||
out (th/command! data)]
|
||||
|
@ -126,15 +126,15 @@
|
|||
;; (th/print-result! out)
|
||||
(t/is (th/success? out))
|
||||
(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)]
|
||||
(t/is (= :team-invitation (:iss claims)))
|
||||
(t/is (= (:id profile1) (:profile-id claims)))
|
||||
(t/is (= :editor (:role 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)))))
|
||||
|
||||
(th/reset-mock! mock)
|
||||
|
@ -142,7 +142,7 @@
|
|||
;; Try to invite existing user
|
||||
(let [data {::th/type :create-team-invitations
|
||||
::rpc/profile-id (:id profile1)
|
||||
:email (:email profile2)
|
||||
:emails [(:email profile2)]
|
||||
:team-id (:id team)
|
||||
:role :editor}
|
||||
out (th/command! data)]
|
||||
|
@ -150,15 +150,15 @@
|
|||
;; (th/print-result! out)
|
||||
(t/is (th/success? out))
|
||||
(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)]
|
||||
(t/is (= :team-invitation (:iss claims)))
|
||||
(t/is (= (:id profile1) (:profile-id claims)))
|
||||
(t/is (= :editor (:role 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)))))
|
||||
|
||||
)))
|
||||
|
@ -264,7 +264,7 @@
|
|||
;; invite internal user without complaints
|
||||
(with-redefs [app.config/flags #{}]
|
||||
(th/reset-mock! mock)
|
||||
(let [data (assoc data :email (:email profile2))
|
||||
(let [data (assoc data :emails [(:email profile2)])
|
||||
out (th/command! data)]
|
||||
(t/is (th/success? out))
|
||||
(t/is (= 0 (:call-count (deref mock)))))
|
||||
|
|
|
@ -280,13 +280,15 @@
|
|||
|
||||
:else
|
||||
(let [objects (lookup-objects file)
|
||||
bool-content (gsh/calc-bool-content bool objects)
|
||||
bool' (gsh/update-bool-selrect bool children objects)]
|
||||
(commit-change
|
||||
file
|
||||
{:type :mod-obj
|
||||
:id bool-id
|
||||
: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 :x :val (-> bool' :selrect :x) :ignore-touched true}
|
||||
{:type :set :attr :y :val (-> bool' :selrect :y) :ignore-touched true}
|
||||
|
|
|
@ -6,4 +6,4 @@
|
|||
|
||||
(ns app.common.files.defaults)
|
||||
|
||||
(def version 23)
|
||||
(def version 24)
|
||||
|
|
|
@ -436,32 +436,6 @@
|
|||
(update :pages-index 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
|
||||
[data]
|
||||
(letfn [(update-object [object]
|
||||
|
@ -517,3 +491,29 @@
|
|||
(-> data
|
||||
(update :pages-index 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))))
|
||||
|
|
|
@ -163,6 +163,7 @@
|
|||
:else
|
||||
(cph/reduce-objects
|
||||
objects
|
||||
|
||||
(fn [shape]
|
||||
(and (d/not-empty? (:shapes shape))
|
||||
(or (not (cph/frame-shape? shape))
|
||||
|
|
|
@ -406,7 +406,7 @@
|
|||
(update page :objects update-vals root-to-board))]
|
||||
|
||||
(-> 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)
|
||||
(assoc-in [:options :components-v2] true))))))))
|
||||
|
||||
|
|
|
@ -495,8 +495,7 @@
|
|||
"expected compatible interaction map"
|
||||
(has-overlay-opts interaction))
|
||||
|
||||
(let [
|
||||
;; When the interactive item is inside a nested frame we need to add to the offset the position
|
||||
(let [;; 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
|
||||
shape-frame (cph/get-frame objects shape)
|
||||
|
||||
|
@ -505,10 +504,10 @@
|
|||
(cph/root-frame? shape-frame)
|
||||
(cph/root? shape-frame))
|
||||
frame-offset
|
||||
(gpt/add frame-offset (gpt/point shape-frame)))
|
||||
]
|
||||
(gpt/add frame-offset (gpt/point shape-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)
|
||||
base-frame-size (:selrect base-frame)
|
||||
relative-to-shape-size (:selrect relative-to-shape)
|
||||
|
@ -526,37 +525,46 @@
|
|||
overlay-position
|
||||
{:x (- (:x overlay-position) (:x relative-to-adjusted-to-base-frame))
|
||||
:y (- (:y overlay-position) (:y relative-to-adjusted-to-base-frame))})]
|
||||
|
||||
(case (:overlay-pos-type interaction)
|
||||
:center
|
||||
(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)))
|
||||
[(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)))
|
||||
[:center :center]]
|
||||
|
||||
:top-left
|
||||
(gpt/point (:x base-position) (:y base-position))
|
||||
[(gpt/point (:x base-position) (:y base-position))
|
||||
[:top :left]]
|
||||
|
||||
:top-right
|
||||
(gpt/point (+ (:x base-position) (- (:width relative-to-shape-size) (:width overlay-size)))
|
||||
(:y base-position))
|
||||
[(gpt/point (+ (:x base-position) (- (:width relative-to-shape-size) (:width overlay-size)))
|
||||
(:y base-position))
|
||||
[:top :right]]
|
||||
|
||||
:top-center
|
||||
(gpt/point (+ (:x base-position) (/ (- (:width relative-to-shape-size) (:width overlay-size)) 2))
|
||||
(:y base-position))
|
||||
[(gpt/point (+ (:x base-position) (/ (- (:width relative-to-shape-size) (:width overlay-size)) 2))
|
||||
(:y base-position))
|
||||
[:top :center]]
|
||||
|
||||
:bottom-left
|
||||
(gpt/point (:x base-position)
|
||||
(+ (:y base-position) (- (:height relative-to-shape-size) (:height overlay-size))))
|
||||
[(gpt/point (:x base-position)
|
||||
(+ (:y base-position) (- (:height relative-to-shape-size) (:height overlay-size))))
|
||||
[:bottom :left]]
|
||||
|
||||
:bottom-right
|
||||
(gpt/point (+ (:x base-position) (- (:width relative-to-shape-size) (:width overlay-size)))
|
||||
(+ (:y base-position) (- (:height relative-to-shape-size) (:height 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))))
|
||||
[:bottom :right]]
|
||||
|
||||
:bottom-center
|
||||
(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))))
|
||||
[(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))))
|
||||
[:bottom :center]]
|
||||
|
||||
:manual
|
||||
(gpt/point (+ (:x base-position) (:x overlay-position))
|
||||
(+ (:y base-position) (:y overlay-position))))))))
|
||||
[(gpt/point (+ (:x base-position) (:x overlay-position))
|
||||
(+ (:y base-position) (:y overlay-position)))
|
||||
[:top :left]])))))
|
||||
|
||||
(defn has-animation?
|
||||
[interaction]
|
||||
|
|
|
@ -323,209 +323,275 @@
|
|||
interaction-rect (ctsi/set-position-relative-to interaction (:id rect))]
|
||||
(t/testing "Overlay top-left relative to auto"
|
||||
(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 (= (: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"
|
||||
(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 (= (: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"
|
||||
(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 (= (: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"
|
||||
(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 (= (: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"
|
||||
(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 (= (: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"
|
||||
(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 (= (: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"
|
||||
(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 (= (: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"
|
||||
(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 (= (: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"
|
||||
(let [i2 (-> interaction-auto
|
||||
(ctsi/set-overlay-pos-type :manual base-frame objects)
|
||||
(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 (= (: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"
|
||||
(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 (= (: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"
|
||||
(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 (= (: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"
|
||||
(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 (= (: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"
|
||||
(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 (= (: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"
|
||||
(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 (= (: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"
|
||||
(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 (= (: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"
|
||||
(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 (= (: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"
|
||||
(let [i2 (-> interaction-base-frame
|
||||
(ctsi/set-overlay-pos-type :manual base-frame objects)
|
||||
(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 (= (: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"
|
||||
(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 (= (: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"
|
||||
(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 (= (: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"
|
||||
(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 (= (: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"
|
||||
(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 (= (: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"
|
||||
(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 (= (: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"
|
||||
(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 (= (: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"
|
||||
(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 (= (: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"
|
||||
(let [i2 (-> interaction-popup
|
||||
(ctsi/set-overlay-pos-type :manual base-frame objects)
|
||||
(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 (= (: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"
|
||||
(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 (= (: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"
|
||||
(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 (= (: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"
|
||||
(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 (= (: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"
|
||||
(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 (= (: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"
|
||||
(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 (= (: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"
|
||||
(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 (= (: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"
|
||||
(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 (= (: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"
|
||||
(let [i2 (-> interaction-rect
|
||||
(ctsi/set-overlay-pos-type :manual base-frame objects)
|
||||
(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 (= (:y overlay-pos) 67))))))
|
||||
(t/is (= (:y overlay-pos) 67))
|
||||
(t/is (= snap-v :top))
|
||||
(t/is (= snap-h :left))))))
|
||||
|
||||
|
||||
(t/deftest animation-checks
|
||||
|
|
|
@ -290,6 +290,7 @@
|
|||
border-radius: $br4;
|
||||
margin: 0.5rem;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
background-color: $color-primary;
|
||||
|
|
|
@ -378,6 +378,8 @@ span.element-name {
|
|||
background-color: $color-gray-50;
|
||||
color: $color-white;
|
||||
font-size: $fs12;
|
||||
flex-grow: 1;
|
||||
margin: 0;
|
||||
height: 16px;
|
||||
&:focus {
|
||||
outline: none;
|
||||
|
@ -386,10 +388,16 @@ span.element-name {
|
|||
div {
|
||||
height: 16px;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.filter,
|
||||
.clear {
|
||||
width: 35px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
&.active {
|
||||
svg {
|
||||
fill: $color-primary;
|
||||
|
|
|
@ -314,9 +314,18 @@
|
|||
(ptk/reify ::retrieve-unread-comment-threads
|
||||
ptk/WatchEvent
|
||||
(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})
|
||||
(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})))))))
|
||||
|
||||
|
||||
|
|
|
@ -542,13 +542,14 @@
|
|||
;; --- Overlays
|
||||
|
||||
(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
|
||||
:always
|
||||
(update :viewer-overlays conj
|
||||
{:frame frame
|
||||
:id (:id frame)
|
||||
:position position
|
||||
:snap-to snap-to
|
||||
:close-click-outside close-click-outside
|
||||
:background-overlay background-overlay
|
||||
:animation animation})
|
||||
|
@ -571,7 +572,7 @@
|
|||
:animation animation})))
|
||||
|
||||
(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! (gpt/point? position))
|
||||
(dm/assert! (or (nil? close-click-outside)
|
||||
|
@ -593,6 +594,7 @@
|
|||
(open-overlay* state
|
||||
frame
|
||||
position
|
||||
snap-to
|
||||
close-click-outside
|
||||
background-overlay
|
||||
animation)
|
||||
|
@ -600,7 +602,7 @@
|
|||
|
||||
|
||||
(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! (gpt/point? position))
|
||||
(dm/assert! (or (nil? close-click-outside)
|
||||
|
@ -623,6 +625,7 @@
|
|||
(open-overlay* state
|
||||
frame
|
||||
position
|
||||
snap-to
|
||||
close-click-outside
|
||||
background-overlay
|
||||
animation)
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
[app.common.text :as txt]
|
||||
[app.common.transit :as t]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.pages-list :as ctpl]
|
||||
|
@ -450,18 +451,49 @@
|
|||
(ptk/reify ::duplicate-page
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [id (uuid/next)
|
||||
pages (get-in state [:workspace-data :pages-index])
|
||||
unames (cfh/get-used-names pages)
|
||||
page (get-in state [:workspace-data :pages-index page-id])
|
||||
name (cfh/generate-unique-name unames (:name page))
|
||||
(let [id (uuid/next)
|
||||
pages (get-in state [:workspace-data :pages-index])
|
||||
unames (cfh/get-used-names pages)
|
||||
page (get-in state [:workspace-data :pages-index page-id])
|
||||
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
|
||||
(assoc :name name)
|
||||
(assoc :id id)
|
||||
(assoc :objects
|
||||
(->> (:objects page)
|
||||
(d/mapm (fn [_ val] (dissoc val :use-for-thumbnail?))))))
|
||||
objects))
|
||||
|
||||
changes (-> (pcb/empty-changes it)
|
||||
(pcb/add-page id page))]
|
||||
|
@ -719,17 +751,18 @@
|
|||
groups-to-delete groups-to-unmask shapes-to-detach
|
||||
shapes-to-reroot shapes-to-deroot shapes-to-unconstraint]
|
||||
(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/with-objects objects)
|
||||
|
||||
;; 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))
|
||||
|
||||
;; 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))))
|
||||
|
||||
;; Move the shapes
|
||||
|
@ -761,8 +794,7 @@
|
|||
;; Reset constraints depending on the new parent
|
||||
(pcb/update-shapes shapes-to-unconstraint
|
||||
(fn [shape]
|
||||
(let [parent (get objects parent-id)
|
||||
frame-id (if (= (:type parent) :frame)
|
||||
(let [frame-id (if (= (:type parent) :frame)
|
||||
(:id parent)
|
||||
(:frame-id parent))
|
||||
moved-shape (assoc shape
|
||||
|
@ -797,6 +829,10 @@
|
|||
|
||||
(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
|
||||
(pcb/resize-parents parents))))
|
||||
|
||||
|
@ -1706,7 +1742,8 @@
|
|||
[(:frame-id base) parent-id delta index])
|
||||
|
||||
;; 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)
|
||||
|
||||
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
|
||||
;; - Respect the distance of the object to the right and bottom in the original frame
|
||||
(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)
|
||||
(let [frame-id (ctst/top-nested-frame page-objects mouse-pos)
|
||||
|
|
|
@ -181,7 +181,9 @@
|
|||
(on-error error)
|
||||
|
||||
:else
|
||||
(rx/throw error))))]
|
||||
(do
|
||||
(.error js/console "ERROR" error)
|
||||
(rx/of (msg/error (tr "errors.cannot-upload")))))))]
|
||||
|
||||
(ptk/reify ::process-media-objects
|
||||
ptk/WatchEvent
|
||||
|
|
|
@ -407,6 +407,32 @@
|
|||
|
||||
(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
|
||||
([]
|
||||
|
|
|
@ -344,12 +344,10 @@
|
|||
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (wsh/lookup-page-objects state page-id)
|
||||
rotate-shape (fn [shape]
|
||||
(let [delta (- rotation (:rotation shape))]
|
||||
(dwm/set-rotation-modifiers delta [shape])))]
|
||||
shapes (->> ids (map #(get objects %)))]
|
||||
(rx/concat
|
||||
(rx/from (->> ids (map #(get objects %)) (map rotate-shape)))
|
||||
(rx/of (dwm/apply-modifiers)))))))
|
||||
(rx/of (dwm/set-delta-rotation-modifiers rotation shapes))
|
||||
(rx/of (dwm/apply-modifiers)))))))
|
||||
|
||||
|
||||
;; -- Move ----------------------------------------------------------
|
||||
|
|
|
@ -459,6 +459,9 @@
|
|||
(def current-file-comments-users
|
||||
(l/derived :current-file-comments-users st/state))
|
||||
|
||||
(def current-team-comments-users
|
||||
(l/derived :current-team-comments-users st/state))
|
||||
|
||||
(def viewer-fullscreen?
|
||||
(l/derived (fn [state]
|
||||
(dm/get-in state [:viewer-local :fullscreen?]))
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
show-dropdown (mf/use-fn #(reset! show-dropdown? true))
|
||||
hide-dropdown (mf/use-fn #(reset! show-dropdown? false))
|
||||
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)
|
||||
(sort-by :modified-at)
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.keyboard :as kbd]
|
||||
[app.util.router :as rt]
|
||||
[app.util.webapi :as wapi]
|
||||
[beicon.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
|
@ -142,12 +143,22 @@
|
|||
(sort-by :modified-at)
|
||||
(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
|
||||
(mf/use-fn
|
||||
(mf/deps project)
|
||||
(fn [origin]
|
||||
(st/emit! (with-meta (dd/create-file {:project-id (:id project)})
|
||||
{::ev/origin origin}))))]
|
||||
(let [mdata {:on-success on-file-created}
|
||||
params {:project-id (:id project)}]
|
||||
(st/emit! (-> (dd/create-file (with-meta params mdata))
|
||||
(with-meta {::ev/origin origin}))))))]
|
||||
|
||||
(mf/with-effect []
|
||||
(let [node (mf/ref-val rowref)
|
||||
|
|
|
@ -31,23 +31,27 @@
|
|||
[rumext.v2 :as mf]))
|
||||
|
||||
(mf/defc header
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [section team] :as props}]
|
||||
(let [go-members (mf/use-fn #(st/emit! (dd/go-to-team-members)))
|
||||
go-settings (mf/use-fn #(st/emit! (dd/go-to-team-settings)))
|
||||
go-invitations (mf/use-fn #(st/emit! (dd/go-to-team-invitations)))
|
||||
go-webhooks (mf/use-fn #(st/emit! (dd/go-to-team-webhooks)))
|
||||
invite-member (mf/use-fn
|
||||
(mf/deps team)
|
||||
#(st/emit! (modal/show {:type :invite-members
|
||||
:team team
|
||||
:origin :team})))
|
||||
{::mf/wrap [mf/memo]
|
||||
::mf/wrap-props false}
|
||||
[{:keys [section team]}]
|
||||
(let [on-nav-members (mf/use-fn #(st/emit! (dd/go-to-team-members)))
|
||||
on-nav-settings (mf/use-fn #(st/emit! (dd/go-to-team-settings)))
|
||||
on-nav-invitations (mf/use-fn #(st/emit! (dd/go-to-team-invitations)))
|
||||
on-nav-webhooks (mf/use-fn #(st/emit! (dd/go-to-team-webhooks)))
|
||||
|
||||
members-section? (= section :dashboard-team-members)
|
||||
settings-section? (= section :dashboard-team-settings)
|
||||
invitations-section? (= section :dashboard-team-invitations)
|
||||
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
|
||||
[:div.dashboard-title
|
||||
|
@ -60,17 +64,19 @@
|
|||
[:nav.dashboard-header-menu
|
||||
[:ul.dashboard-header-options
|
||||
[: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")}
|
||||
[:a {:on-click go-invitations} (tr "labels.invitations")]]
|
||||
[:a {:on-click on-nav-invitations} (tr "labels.invitations")]]
|
||||
(when (contains? cfg/flags :webhooks)
|
||||
[: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")}
|
||||
[:a {:on-click go-settings} (tr "labels.settings")]]]]
|
||||
[:a {:on-click on-nav-settings} (tr "labels.settings")]]]]
|
||||
[:div.dashboard-buttons
|
||||
(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")]
|
||||
[:div.blank-space])]]))
|
||||
|
||||
|
@ -98,27 +104,29 @@
|
|||
|
||||
(mf/defc invite-members-modal
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :invite-members}
|
||||
::mf/register-as :invite-members
|
||||
::mf/wrap-props false}
|
||||
[{:keys [team origin]}]
|
||||
(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)}))
|
||||
form (fm/use-form :spec ::invite-member-form
|
||||
: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)))
|
||||
roles (mf/use-memo (mf/deps perms) #(get-available-roles perms))
|
||||
initial (mf/use-memo (constantly {:role "editor" :team-id (:id team)}))
|
||||
form (fm/use-form :spec ::invite-member-form
|
||||
:initial initial)
|
||||
error-text (mf/use-state "")
|
||||
|
||||
current-data-emails (into #{} (dm/get-in @form [:clean-data :emails]))
|
||||
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
|
||||
(fn [{:keys [type code] :as error}]
|
||||
(cond
|
||||
|
@ -185,7 +193,9 @@
|
|||
;; 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))]
|
||||
[:*
|
||||
[:div.member-image
|
||||
|
@ -196,93 +206,97 @@
|
|||
[:span.you (tr "labels.you")])]
|
||||
[: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)
|
||||
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?)))
|
||||
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?)
|
||||
not-superior? (or you-owner? (and can-change-rol? (or member-is-admin? member-is-editor?)))
|
||||
|
||||
role (cond
|
||||
member-is-owner? "labels.owner"
|
||||
member-is-admin? "labels.admin"
|
||||
member-is-owner? "labels.owner"
|
||||
member-is-admin? "labels.admin"
|
||||
member-is-editor? "labels.editor"
|
||||
:else "labels.viewer")
|
||||
is-you? (= (:id profile) (:id member))]
|
||||
:else "labels.viewer")
|
||||
|
||||
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?)))
|
||||
[: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.icon i/arrow-down]]
|
||||
[:div.rol-selector
|
||||
[:span.rol-label (tr role)]])
|
||||
|
||||
[:& dropdown {:show @show?
|
||||
:on-close #(reset! show? false)}
|
||||
[:& dropdown {:show @show? :on-close on-hide}
|
||||
[:ul.dropdown.options-dropdown
|
||||
[:li {:on-click set-admin} (tr "labels.admin")]
|
||||
[:li {:on-click set-editor} (tr "labels.editor")]
|
||||
[:li {:on-click on-set-admin} (tr "labels.admin")]
|
||||
[:li {:on-click on-set-editor} (tr "labels.editor")]
|
||||
;; Temporarily disabled viewer role
|
||||
;; https://tree.taiga.io/project/penpot/issue/1083
|
||||
;; [:li {:on-click set-viewer} (tr "labels.viewer")]
|
||||
(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?)))))
|
||||
[:span.icon {:on-click #(reset! show? true)} [i/actions]])
|
||||
[:& dropdown {:show @show?
|
||||
:on-close #(reset! show? false)}
|
||||
[:span.icon {:on-click on-show} [i/actions]])
|
||||
|
||||
[:& dropdown {:show @show? :on-close on-hide}
|
||||
[:ul.dropdown.actions-dropdown
|
||||
(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?))))
|
||||
[: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/wrap [mf/memo]}
|
||||
[{:keys [team member members profile] :as props}]
|
||||
{::mf/wrap [mf/memo]
|
||||
::mf/wrap-props false}
|
||||
[{:keys [team member members profile]}]
|
||||
|
||||
(let [owner? (dm/get-in team [:permissions :is-owner])
|
||||
set-role
|
||||
(let [member-id (:id member)
|
||||
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/deps member)
|
||||
(fn [role]
|
||||
(let [params {:member-id (:id member) :role role}]
|
||||
(st/emit! (dd/update-team-member-role params)))))
|
||||
|
||||
|
||||
set-owner-fn (mf/use-fn (mf/deps set-role) (partial set-role :owner))
|
||||
set-admin (mf/use-fn (mf/deps set-role) (partial set-role :admin))
|
||||
set-editor (mf/use-fn (mf/deps set-role) (partial set-role :editor))
|
||||
;; set-viewer (partial set-role :viewer)
|
||||
|
||||
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)}))))
|
||||
(fn [member _event]
|
||||
(let [params {: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 (partial set-role! member-id :owner)
|
||||
:accept-style :primary}]
|
||||
(st/emit! (modal/show params)))))
|
||||
|
||||
on-success
|
||||
(mf/use-fn
|
||||
|
@ -308,14 +322,14 @@
|
|||
|
||||
(rx/throw error))))
|
||||
|
||||
delete-fn
|
||||
on-delete-accepted
|
||||
(mf/use-fn
|
||||
(mf/deps team on-success on-error)
|
||||
(fn []
|
||||
(st/emit! (dd/delete-team (with-meta team {:on-success on-success
|
||||
:on-error on-error})))))
|
||||
|
||||
leave-fn
|
||||
on-leave-accepted
|
||||
(mf/use-fn
|
||||
(mf/deps on-success on-error)
|
||||
(fn [member-id]
|
||||
|
@ -324,9 +338,9 @@
|
|||
{:on-success on-success
|
||||
:on-error on-error}))))))
|
||||
|
||||
leave-and-close
|
||||
on-leave-and-close
|
||||
(mf/use-fn
|
||||
(mf/deps delete-fn)
|
||||
(mf/deps on-delete-accepted)
|
||||
(fn []
|
||||
(st/emit! (modal/show
|
||||
{:type :confirm
|
||||
|
@ -334,80 +348,100 @@
|
|||
:message (tr "modals.leave-and-close-confirm.message" (:name team))
|
||||
:scd-message (tr "modals.leave-and-close-confirm.hint")
|
||||
: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/deps profile team leave-fn)
|
||||
(mf/deps profile team on-leave-accepted)
|
||||
(fn []
|
||||
(st/emit! (dd/fetch-team-members)
|
||||
(modal/show
|
||||
{:type :leave-and-reassign
|
||||
:profile profile
|
||||
:team team
|
||||
:accept leave-fn}))))
|
||||
:accept on-leave-accepted}))))
|
||||
|
||||
leave
|
||||
on-leave
|
||||
(mf/use-fn
|
||||
(mf/deps leave-fn)
|
||||
(mf/deps on-leave-accepted)
|
||||
(fn []
|
||||
(st/emit! (modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.leave-confirm.title")
|
||||
:message (tr "modals.leave-confirm.message")
|
||||
: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
|
||||
(= true owner?) change-owner-and-leave
|
||||
:else leave)
|
||||
|
||||
delete
|
||||
on-delete
|
||||
(mf/use-fn
|
||||
(mf/deps delete-member-fn)
|
||||
(mf/deps member-id)
|
||||
(fn []
|
||||
(st/emit! (modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.delete-team-member-confirm.title")
|
||||
:message (tr "modals.delete-team-member-confirm.message")
|
||||
:accept-label (tr "modals.delete-team-member-confirm.accept")
|
||||
:on-accept delete-member-fn}))))]
|
||||
(let [on-accept #(st/emit! (dd/delete-team-member {:member-id member-id}))
|
||||
params {:type :confirm
|
||||
:title (tr "modals.delete-team-member-confirm.title")
|
||||
:message (tr "modals.delete-team-member-confirm.message")
|
||||
:accept-label (tr "modals.delete-team-member-confirm.accept")
|
||||
: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-field.name
|
||||
[:& member-info {:member member :profile profile}]]
|
||||
|
||||
[:div.table-field.roles
|
||||
[:& rol-info {:member member
|
||||
:team team
|
||||
:set-admin set-admin
|
||||
:set-editor set-editor
|
||||
:set-owner set-owner
|
||||
:on-set-admin on-set-admin
|
||||
:on-set-editor on-set-editor
|
||||
:on-set-owner on-set-owner
|
||||
:profile profile}]]
|
||||
|
||||
[:div.table-field.actions
|
||||
[:& member-actions {:member member
|
||||
:profile profile
|
||||
:team team
|
||||
:delete delete
|
||||
:leave preset-leave}]]]))
|
||||
:on-delete on-delete
|
||||
:on-leave on-leave'}]]]))
|
||||
|
||||
(mf/defc team-members
|
||||
[{:keys [members-map team profile] :as props}]
|
||||
(let [members (->> (vals members-map)
|
||||
(sort-by :created-at)
|
||||
(remove :is-owner))
|
||||
owner (->> (vals members-map)
|
||||
(d/seek :is-owner))]
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [members-map team profile]}]
|
||||
(let [members (mf/with-memo [members-map]
|
||||
(->> (vals members-map)
|
||||
(sort-by :created-at)
|
||||
(remove :is-owner)))
|
||||
owner (mf/with-memo [members-map]
|
||||
(->> (vals members-map)
|
||||
(d/seek :is-owner)))]
|
||||
|
||||
[:div.dashboard-table.team-members
|
||||
[:div.table-header
|
||||
[:div.table-field.name (tr "labels.member")]
|
||||
[:div.table-field.role (tr "labels.role")]]
|
||||
|
||||
[: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]
|
||||
[:& 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
|
||||
[{:keys [team profile] :as props}]
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [team profile]}]
|
||||
(let [members-map (mf/deref refs/dashboard-team-members)]
|
||||
|
||||
(mf/with-effect [team]
|
||||
|
@ -417,74 +451,76 @@
|
|||
(tr "dashboard.your-penpot")
|
||||
(:name team)))))
|
||||
|
||||
(mf/with-effect
|
||||
(mf/with-effect []
|
||||
(st/emit! (dd/fetch-team-members)))
|
||||
|
||||
[:*
|
||||
[:& header {:section :dashboard-team-members
|
||||
:team team}]
|
||||
[:& header {:section :dashboard-team-members :team team}]
|
||||
[:section.dashboard-container.dashboard-team-members
|
||||
[:& team-members {:profile profile
|
||||
:team team
|
||||
:members-map members-map}]]]))
|
||||
[:& team-members
|
||||
{:profile profile
|
||||
:team team
|
||||
:members-map members-map}]]]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; INVITATIONS SECTION
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(mf/defc invitation-role-selector
|
||||
[{:keys [can-invite? role status change-to-admin change-to-editor] :as props}]
|
||||
(let [show? (mf/use-state false)
|
||||
role-label (cond
|
||||
(= role :owner) "labels.owner"
|
||||
(= role :admin) "labels.admin"
|
||||
(= role :editor) "labels.editor"
|
||||
:else "labels.viewer")]
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [can-invite? role status on-change]}]
|
||||
(let [show? (mf/use-state false)
|
||||
label (cond
|
||||
(= role :owner) (tr "labels.owner")
|
||||
(= role :admin) (tr "labels.admin")
|
||||
(= 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))
|
||||
[:div.rol-selector.has-priv {:on-click #(reset! show? true)}
|
||||
[:span.rol-label (tr role-label)]
|
||||
[:div.rol-selector.has-priv {:on-click on-show}
|
||||
[:span.rol-label label]
|
||||
[:span.icon i/arrow-down]]
|
||||
[:div.rol-selector
|
||||
[:span.rol-label (tr role-label)]])
|
||||
[:span.rol-label label]])
|
||||
|
||||
[:& dropdown {:show @show?
|
||||
:on-close #(reset! show? false)}
|
||||
[:& dropdown {:show @show? :on-close on-hide}
|
||||
[:ul.dropdown.options-dropdown
|
||||
[:li {:on-click change-to-admin} (tr "labels.admin")]
|
||||
[:li {:on-click change-to-editor} (tr "labels.editor")]]]]))
|
||||
[:li {:data-role "admin" :on-click on-change'} (tr "labels.admin")]
|
||||
[:li {:data-role "editor" :on-click on-change'} (tr "labels.editor")]]]]))
|
||||
|
||||
(mf/defc invitation-status-badge
|
||||
[{:keys [status] :as props}]
|
||||
(let [status-label (if (= status :expired)
|
||||
(tr "labels.expired-invitation")
|
||||
(tr "labels.pending-invitation"))]
|
||||
[:div.status-badge {:class (dom/classnames
|
||||
:expired (= status :expired)
|
||||
:pending (= status :pending))}
|
||||
[:span.status-label (tr status-label)]]))
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [status]}]
|
||||
[:div.status-badge
|
||||
{:class (dom/classnames
|
||||
:expired (= status :expired)
|
||||
:pending (= status :pending))}
|
||||
[:span.status-label
|
||||
(if (= status :expired)
|
||||
(tr "labels.expired-invitation")
|
||||
(tr "labels.pending-invitation"))]])
|
||||
|
||||
(mf/defc invitation-actions
|
||||
[{:keys [invitation team] :as props}]
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [invitation team-id]}]
|
||||
(let [show? (mf/use-state false)
|
||||
|
||||
team-id (:id team)
|
||||
email (:email 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
|
||||
(mf/use-fn
|
||||
(mf/deps email)
|
||||
|
@ -505,7 +541,7 @@
|
|||
:else
|
||||
(rx/throw error))))
|
||||
|
||||
delete-fn
|
||||
on-delete
|
||||
(mf/use-fn
|
||||
(mf/deps email team-id)
|
||||
(fn []
|
||||
|
@ -513,7 +549,15 @@
|
|||
mdata {:on-success #(st/emit! (dd/fetch-team-invitations))}]
|
||||
(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/deps email team-id)
|
||||
(fn []
|
||||
|
@ -527,7 +571,13 @@
|
|||
(-> (dd/invite-team-members params)
|
||||
(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/deps email team-id)
|
||||
(fn []
|
||||
|
@ -536,52 +586,55 @@
|
|||
:on-error on-error})]
|
||||
(st/emit!
|
||||
(-> (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]]
|
||||
[:& dropdown {:show @show?
|
||||
:on-close #(reset! show? false)}
|
||||
[:span.icon {:on-click on-show} [i/actions]]
|
||||
[:& dropdown {:show @show? :on-close on-hide}
|
||||
[:ul.dropdown.actions-dropdown
|
||||
[:li {:on-click copy-fn} (tr "labels.copy-invitation-link")]
|
||||
[:li {:on-click resend-fn} (tr "labels.resend-invitation")]
|
||||
[:li {:on-click delete-fn} (tr "labels.delete-invitation")]]]]))
|
||||
[:li {:on-click on-copy} (tr "labels.copy-invitation-link")]
|
||||
[:li {:on-click on-resend} (tr "labels.resend-invitation")]
|
||||
[:li {:on-click on-delete} (tr "labels.delete-invitation")]]]]))
|
||||
|
||||
(mf/defc invitation-row
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [invitation can-invite? team] :as props}]
|
||||
{::mf/wrap [mf/memo]
|
||||
::mf/wrap-props false}
|
||||
[{:keys [invitation can-invite? team-id] :as props}]
|
||||
|
||||
(let [expired? (:expired invitation)
|
||||
email (:email invitation)
|
||||
role (:role invitation)
|
||||
status (if expired? :expired :pending)
|
||||
|
||||
change-rol
|
||||
on-change-role
|
||||
(mf/use-fn
|
||||
(mf/deps team email)
|
||||
(fn [role]
|
||||
(let [params {:email email :team-id (:id team) :role role}
|
||||
(mf/deps email team-id)
|
||||
(fn [role _event]
|
||||
(let [params {:email email :team-id team-id :role role}
|
||||
mdata {:on-success #(st/emit! (dd/fetch-team-invitations))}]
|
||||
(st/emit! (dd/update-team-invitation-role (with-meta params mdata))))))]
|
||||
|
||||
[:div.table-row
|
||||
[:div.table-field.mail email]
|
||||
|
||||
[:div.table-field.roles
|
||||
[:& invitation-role-selector
|
||||
{:can-invite? can-invite?
|
||||
:role role
|
||||
:status status
|
||||
:change-to-editor (partial change-rol :editor)
|
||||
:change-to-admin (partial change-rol :admin)}]]
|
||||
:on-change on-change-role}]]
|
||||
|
||||
[:div.table-field.status
|
||||
[:& invitation-status-badge {:status status}]]
|
||||
|
||||
[:div.table-field.actions
|
||||
(when can-invite?
|
||||
[:& invitation-actions
|
||||
{:invitation invitation
|
||||
:team team}])]]))
|
||||
:team-id team-id}])]]))
|
||||
|
||||
(mf/defc empty-invitation-table
|
||||
[{:keys [can-invite?] :as props}]
|
||||
|
@ -595,7 +648,8 @@
|
|||
[{:keys [team invitations] :as props}]
|
||||
(let [owner? (dm/get-in team [:permissions :is-owner])
|
||||
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.table-header
|
||||
|
@ -610,7 +664,7 @@
|
|||
{:key (:email invitation)
|
||||
:invitation invitation
|
||||
:can-invite? can-invite?
|
||||
:team team}])])]))
|
||||
:team-id team-id}])])]))
|
||||
|
||||
(mf/defc team-invitations-page
|
||||
[{:keys [team] :as props}]
|
||||
|
@ -767,6 +821,7 @@
|
|||
|
||||
|
||||
(mf/defc webhooks-hero
|
||||
{::mf/wrap-props false}
|
||||
[]
|
||||
[:div.banner
|
||||
[:div.title (tr "labels.webhooks")
|
||||
|
@ -785,18 +840,22 @@
|
|||
[:span (tr "dashboard.webhooks.create")]]]])
|
||||
|
||||
(mf/defc webhook-actions
|
||||
[{:keys [on-edit on-delete] :as props}]
|
||||
(let [show? (mf/use-state false)]
|
||||
{::mf/wrap-props 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]]
|
||||
[:& dropdown {:show @show?
|
||||
:on-close #(reset! show? false)}
|
||||
[:span.icon {:on-click on-show} [i/actions]]
|
||||
[:& dropdown {:show @show? :on-close on-hide}
|
||||
[:ul.dropdown.actions-dropdown
|
||||
[:li {:on-click on-edit} (tr "labels.edit")]
|
||||
[:li {:on-click on-delete} (tr "labels.delete")]]]]))
|
||||
|
||||
(mf/defc last-delivery-icon
|
||||
[{:keys [success? text] :as props}]
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [success? text]}]
|
||||
[:div.last-delivery-icon
|
||||
[:div.tooltip
|
||||
[:div.label text]
|
||||
|
@ -808,34 +867,44 @@
|
|||
(mf/defc webhook-item
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [webhook] :as props}]
|
||||
(let [on-edit #(st/emit! (modal/show :webhook {:webhook webhook}))
|
||||
error-code (:error-code webhook)
|
||||
(let [error-code (:error-code webhook)
|
||||
id (:id webhook)
|
||||
|
||||
delete-fn
|
||||
(fn []
|
||||
(let [params {:id (:id webhook)}
|
||||
mdata {:on-success #(st/emit! (dd/fetch-team-webhooks))}]
|
||||
(st/emit! (dd/delete-team-webhook (with-meta params mdata)))))
|
||||
on-edit
|
||||
(mf/use-fn
|
||||
(mf/deps webhook)
|
||||
(fn []
|
||||
(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
|
||||
(fn []
|
||||
(st/emit! (modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.delete-webhook.title")
|
||||
:message (tr "modals.delete-webhook.message")
|
||||
:accept-label (tr "modals.delete-webhook.accept")
|
||||
:on-accept delete-fn})))
|
||||
(mf/use-fn
|
||||
(mf/deps on-delete-accepted)
|
||||
(fn []
|
||||
(let [params {:type :confirm
|
||||
:title (tr "modals.delete-webhook.title")
|
||||
:message (tr "modals.delete-webhook.message")
|
||||
:accept-label (tr "modals.delete-webhook.accept")
|
||||
:on-accept on-delete-accepted}]
|
||||
(st/emit! (modal/show params)))))
|
||||
|
||||
last-delivery-text
|
||||
(if (nil? error-code)
|
||||
(tr "webhooks.last-delivery.success")
|
||||
(str (tr "errors.webhooks.last-delivery")
|
||||
(cond
|
||||
(= error-code "ssl-validation-error")
|
||||
(dm/str " " (tr "errors.webhooks.ssl-validation"))
|
||||
(dm/str (tr "errors.webhooks.last-delivery")
|
||||
(cond
|
||||
(= error-code "ssl-validation-error")
|
||||
(dm/str " " (tr "errors.webhooks.ssl-validation"))
|
||||
|
||||
(str/starts-with? error-code "unexpected-status")
|
||||
(dm/str " " (tr "errors.webhooks.unexpected-status" (extract-status error-code))))))]
|
||||
(str/starts-with? error-code "unexpected-status")
|
||||
(dm/str " " (tr "errors.webhooks.unexpected-status" (extract-status error-code))))))]
|
||||
|
||||
[:div.table-row
|
||||
[:div.table-field.last-delivery
|
||||
|
@ -855,14 +924,16 @@
|
|||
:on-delete on-delete}]]]))
|
||||
|
||||
(mf/defc webhooks-list
|
||||
[{:keys [webhooks] :as props}]
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [webhooks]}]
|
||||
[:div.dashboard-table
|
||||
[:div.table-rows
|
||||
(for [webhook webhooks]
|
||||
[:& webhook-item {:webhook webhook :key (:id webhook)}])]])
|
||||
|
||||
(mf/defc team-webhooks-page
|
||||
[{:keys [team] :as props}]
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [team]}]
|
||||
(let [webhooks (mf/deref refs/dashboard-team-webhooks)]
|
||||
|
||||
(mf/with-effect [team]
|
||||
|
@ -891,7 +962,8 @@
|
|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(mf/defc team-settings-page
|
||||
[{:keys [team] :as props}]
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [team]}]
|
||||
(let [finput (mf/use-ref)
|
||||
|
||||
members-map (mf/deref refs/dashboard-team-members)
|
||||
|
@ -912,22 +984,19 @@
|
|||
(st/emit! (dd/update-team-photo file)))]
|
||||
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps team)
|
||||
(fn []
|
||||
(dom/set-html-title (tr "title.team-settings"
|
||||
(if (:is-default team)
|
||||
(tr "dashboard.your-penpot")
|
||||
(:name team))))))
|
||||
(mf/with-effect [team]
|
||||
(dom/set-html-title (tr "title.team-settings"
|
||||
(if (:is-default team)
|
||||
(tr "dashboard.your-penpot")
|
||||
(:name team)))))
|
||||
|
||||
|
||||
(mf/use-effect
|
||||
#(st/emit! (dd/fetch-team-members)
|
||||
(mf/with-effect []
|
||||
(st/emit! (dd/fetch-team-members)
|
||||
(dd/fetch-team-stats)))
|
||||
|
||||
[:*
|
||||
[:& header {:section :dashboard-team-settings
|
||||
:team team}]
|
||||
[:& header {:section :dashboard-team-settings :team team}]
|
||||
[:section.dashboard-container.dashboard-team-settings
|
||||
[:div.team-settings
|
||||
[:div.horizontal-blocks
|
||||
|
|
|
@ -146,8 +146,9 @@
|
|||
is-component? (mf/use-ctx muc/is-component?)]
|
||||
[:> frame-container props
|
||||
[:g.frame-children {:opacity (:opacity shape)}
|
||||
(for [item childs]
|
||||
[:& shape-wrapper {:key (dm/str (:id item)) :shape item}])]
|
||||
(for [{:keys [id] :as item} childs]
|
||||
(when (some? id)
|
||||
[:& shape-wrapper {:key (dm/str (:id item)) :shape item}]))]
|
||||
(when (and is-component? (empty? childs))
|
||||
[:& grid-layout-viewer {:shape shape :childs childs}])])))
|
||||
|
||||
|
|
|
@ -50,6 +50,8 @@
|
|||
(l/derived :viewer-overlays st/state))
|
||||
|
||||
(defn- calculate-size
|
||||
"Calculate the total size we must reserve for the frame, including possible paddings
|
||||
added because shadows or blur."
|
||||
[objects frame zoom]
|
||||
(let [{:keys [x y width height]} (gsb/get-object-bounds objects frame)]
|
||||
{:base-width width
|
||||
|
@ -60,6 +62,23 @@
|
|||
:height (* height zoom)
|
||||
: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
|
||||
[size1 size2 zoom]
|
||||
(cond
|
||||
|
@ -113,6 +132,10 @@
|
|||
(mf/with-memo [page 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
|
||||
(mf/use-fn
|
||||
(mf/deps overlay close-click-outside?)
|
||||
|
@ -145,6 +168,7 @@
|
|||
:base-frame frame
|
||||
:frame-offset overlay-position
|
||||
:size size
|
||||
:delta delta
|
||||
:page page
|
||||
:interactions-mode interactions-mode}]]]))
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
(ns app.main.ui.viewer.inspect.render
|
||||
"The main container for a frame in inspect mode"
|
||||
(:require
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.main.data.viewer :as dv]
|
||||
|
@ -186,7 +187,7 @@
|
|||
(mf/defc render-frame-svg
|
||||
[{:keys [page frame local 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
|
||||
frame (get objects (:id frame))
|
||||
|
|
|
@ -28,9 +28,10 @@
|
|||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn prepare-objects
|
||||
[frame size objects]
|
||||
[frame size delta objects]
|
||||
(let [frame-id (:id frame)
|
||||
vector (-> (gpt/point (:x size) (:y size))
|
||||
(gpt/add delta)
|
||||
(gpt/negate))
|
||||
update-fn #(d/update-when %1 %2 gsh/transform-shape (ctm/move-modifiers vector))]
|
||||
(->> (cph/get-children-ids objects frame-id)
|
||||
|
@ -46,6 +47,7 @@
|
|||
base (unchecked-get props "base")
|
||||
offset (unchecked-get props "offset")
|
||||
size (unchecked-get props "size")
|
||||
delta (or (unchecked-get props "delta") (gpt/point 0 0))
|
||||
|
||||
vbox (:vbox size)
|
||||
|
||||
|
@ -67,20 +69,26 @@
|
|||
(map (d/getf (:objects page)))
|
||||
(concat [frame])
|
||||
(d/index-by :id)
|
||||
(prepare-objects frame size)))
|
||||
(prepare-objects frame size delta)))
|
||||
|
||||
wrapper-fixed (mf/with-memo [page frame size]
|
||||
(shapes/frame-container-factory (calculate-objects fixed-ids)))
|
||||
objects-fixed (mf/with-memo [fixed-ids page frame size delta]
|
||||
(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))
|
||||
|
||||
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]
|
||||
(shapes/frame-container-factory objects-not-fixed))
|
||||
(shapes/frame-container-factory objects-not-fixed all-objects))
|
||||
|
||||
;; Retrieve frames again with correct modifier
|
||||
frame (get objects-not-fixed (:id frame))
|
||||
base (get objects-not-fixed (:id base))
|
||||
frame (get all-objects (:id frame))
|
||||
base (get all-objects (:id base))
|
||||
|
||||
non-delay-interactions (->> (:interactions frame)
|
||||
(filterv #(not= (:event-type %) :after-delay)))
|
||||
|
@ -121,6 +129,7 @@
|
|||
mode (h/use-equal-memo (unchecked-get props "interactions-mode"))
|
||||
offset (h/use-equal-memo (unchecked-get props "frame-offset"))
|
||||
size (h/use-equal-memo (unchecked-get props "size"))
|
||||
delta (unchecked-get props "delta")
|
||||
|
||||
page (unchecked-get props "page")
|
||||
frame (unchecked-get props "frame")
|
||||
|
@ -163,7 +172,8 @@
|
|||
:frame frame
|
||||
:base base
|
||||
:offset offset
|
||||
:size size}]))
|
||||
:size size
|
||||
:delta delta}]))
|
||||
|
||||
(mf/defc flows-menu
|
||||
{::mf/wrap [mf/memo]}
|
||||
|
|
|
@ -59,19 +59,20 @@
|
|||
|
||||
:open-overlay
|
||||
(let [dest-frame-id (:destination interaction)
|
||||
viewer-objects (deref (refs/get-viewer-objects))
|
||||
dest-frame (get viewer-objects dest-frame-id)
|
||||
dest-frame (get objects dest-frame-id)
|
||||
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))
|
||||
relative-to-shape (or (get objects relative-to-id) base-frame)
|
||||
close-click-outside (:close-click-outside interaction)
|
||||
background-overlay (:background-overlay interaction)
|
||||
overlays-ids (set (map :id overlays))
|
||||
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
|
||||
viewer-objects
|
||||
objects
|
||||
relative-to-shape
|
||||
relative-to-base-frame
|
||||
dest-frame
|
||||
|
@ -79,20 +80,23 @@
|
|||
(when dest-frame-id
|
||||
(st/emit! (dv/open-overlay dest-frame-id
|
||||
position
|
||||
snap-to
|
||||
close-click-outside
|
||||
background-overlay
|
||||
(:animation interaction)))))
|
||||
|
||||
:toggle-overlay
|
||||
(let [frame-id (:destination interaction)
|
||||
dest-frame (get objects frame-id)
|
||||
(let [dest-frame-id (:destination interaction)
|
||||
dest-frame (get objects dest-frame-id)
|
||||
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))
|
||||
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 (ctsi/calc-overlay-position interaction
|
||||
[position snap-to] (ctsi/calc-overlay-position interaction
|
||||
shape
|
||||
objects
|
||||
relative-to-shape
|
||||
|
@ -102,19 +106,21 @@
|
|||
|
||||
close-click-outside (:close-click-outside interaction)
|
||||
background-overlay (:background-overlay interaction)]
|
||||
(when frame-id
|
||||
(st/emit! (dv/toggle-overlay frame-id
|
||||
(when dest-frame-id
|
||||
(st/emit! (dv/toggle-overlay dest-frame-id
|
||||
position
|
||||
snap-to
|
||||
close-click-outside
|
||||
background-overlay
|
||||
(:animation interaction)))))
|
||||
|
||||
:close-overlay
|
||||
(let [frame-id (or (:destination interaction)
|
||||
(if (= (:type shape) :frame)
|
||||
(:id shape)
|
||||
(:frame-id shape)))]
|
||||
(st/emit! (dv/close-overlay frame-id (:animation interaction))))
|
||||
(let [dest-frame-id (or (:destination interaction)
|
||||
(if (and (= (:type shape) :frame)
|
||||
(some #(= (:id %) (:id shape)) overlays))
|
||||
(:id shape)
|
||||
(:frame-id shape)))]
|
||||
(st/emit! (dv/close-overlay dest-frame-id (:animation interaction))))
|
||||
|
||||
:prev-screen
|
||||
(st/emit! (rt/nav-back-local))
|
||||
|
@ -136,29 +142,49 @@
|
|||
(st/emit! (dv/close-overlay frame-id)))
|
||||
|
||||
:toggle-overlay
|
||||
(let [frame-id (:destination interaction)
|
||||
position (:overlay-position interaction)
|
||||
close-click-outside (:close-click-outside interaction)
|
||||
background-overlay (:background-overlay interaction)]
|
||||
(when frame-id
|
||||
(st/emit! (dv/toggle-overlay frame-id
|
||||
(let [dest-frame-id (:destination interaction)
|
||||
dest-frame (get objects dest-frame-id)
|
||||
relative-to-id (if (= :manual (:overlay-pos-type interaction))
|
||||
(if (= (:type shape) :frame) ;; manual interactions are always from "self"
|
||||
(:frame-id shape)
|
||||
(: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
|
||||
snap-to
|
||||
close-click-outside
|
||||
background-overlay
|
||||
(:animation interaction)))))
|
||||
|
||||
|
||||
:close-overlay
|
||||
(let [dest-frame-id (:destination interaction)
|
||||
dest-frame (get objects dest-frame-id)
|
||||
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))
|
||||
relative-to-shape (or (get objects relative-to-id) base-frame)
|
||||
close-click-outside (:close-click-outside interaction)
|
||||
background-overlay (:background-overlay interaction)
|
||||
overlays-ids (set (map :id overlays))
|
||||
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
|
||||
objects
|
||||
relative-to-shape
|
||||
|
@ -168,6 +194,7 @@
|
|||
(when dest-frame-id
|
||||
(st/emit! (dv/open-overlay dest-frame-id
|
||||
position
|
||||
snap-to
|
||||
close-click-outside
|
||||
background-overlay
|
||||
(:animation interaction)))))
|
||||
|
@ -258,6 +285,7 @@
|
|||
childs (unchecked-get props "childs")
|
||||
frame (unchecked-get props "frame")
|
||||
objects (unchecked-get props "objects")
|
||||
all-objects (or (unchecked-get props "all-objects") objects)
|
||||
base-frame (mf/use-ctx base-frame-ctx)
|
||||
frame-offset (mf/use-ctx frame-offset-ctx)
|
||||
interactions-show? (mf/deref viewer-interactions-show?)
|
||||
|
@ -266,22 +294,25 @@
|
|||
svg-element? (and (= :svg-raw (:type shape))
|
||||
(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
|
||||
(mf/use-fn (mf/deps shape base-frame frame-offset objects)
|
||||
#(on-pointer-down % shape base-frame frame-offset objects overlays))
|
||||
(mf/use-fn (mf/deps shape base-frame frame-offset all-objects)
|
||||
#(on-pointer-down % shape base-frame frame-offset all-objects overlays))
|
||||
|
||||
on-pointer-up
|
||||
(mf/use-fn (mf/deps shape base-frame frame-offset objects)
|
||||
#(on-pointer-up % shape base-frame frame-offset objects overlays))
|
||||
(mf/use-fn (mf/deps shape base-frame frame-offset all-objects)
|
||||
#(on-pointer-up % shape base-frame frame-offset all-objects overlays))
|
||||
|
||||
on-pointer-enter
|
||||
(mf/use-fn (mf/deps shape base-frame frame-offset objects)
|
||||
#(on-pointer-enter % shape base-frame frame-offset objects overlays))
|
||||
(mf/use-fn (mf/deps shape base-frame frame-offset all-objects)
|
||||
#(on-pointer-enter % shape base-frame frame-offset all-objects overlays))
|
||||
|
||||
on-pointer-leave
|
||||
(mf/use-fn (mf/deps shape base-frame frame-offset objects)
|
||||
#(on-pointer-leave % shape base-frame frame-offset objects overlays))]
|
||||
|
||||
(mf/use-fn (mf/deps shape base-frame frame-offset all-objects)
|
||||
#(on-pointer-leave % shape base-frame frame-offset all-objects overlays))]
|
||||
|
||||
(mf/with-effect []
|
||||
(let [sems (on-load shape base-frame frame-offset objects overlays)]
|
||||
|
@ -350,8 +381,8 @@
|
|||
(declare shape-container-factory)
|
||||
|
||||
(defn frame-container-factory
|
||||
[objects]
|
||||
(let [shape-container (shape-container-factory objects)
|
||||
[objects all-objects]
|
||||
(let [shape-container (shape-container-factory objects all-objects)
|
||||
frame-wrapper (frame-wrapper shape-container)]
|
||||
(mf/fnc frame-container
|
||||
{::mf/wrap-props false}
|
||||
|
@ -361,13 +392,14 @@
|
|||
props (obj/merge! #js {} props
|
||||
#js {:shape shape
|
||||
:childs childs
|
||||
:objects objects})]
|
||||
:objects objects
|
||||
:all-objects all-objects})]
|
||||
|
||||
[:> frame-wrapper props]))))
|
||||
|
||||
(defn group-container-factory
|
||||
[objects]
|
||||
(let [shape-container (shape-container-factory objects)
|
||||
[objects all-objects]
|
||||
(let [shape-container (shape-container-factory objects all-objects)
|
||||
group-wrapper (group-wrapper shape-container)]
|
||||
(mf/fnc group-container
|
||||
{::mf/wrap-props false}
|
||||
|
@ -380,8 +412,8 @@
|
|||
[:> group-wrapper props])))))
|
||||
|
||||
(defn bool-container-factory
|
||||
[objects]
|
||||
(let [shape-container (shape-container-factory objects)
|
||||
[objects all-objects]
|
||||
(let [shape-container (shape-container-factory objects all-objects)
|
||||
bool-wrapper (bool-wrapper shape-container)]
|
||||
(mf/fnc bool-container
|
||||
{::mf/wrap-props false}
|
||||
|
@ -394,8 +426,8 @@
|
|||
[:> bool-wrapper props]))))
|
||||
|
||||
(defn svg-raw-container-factory
|
||||
[objects]
|
||||
(let [shape-container (shape-container-factory objects)
|
||||
[objects all-objects]
|
||||
(let [shape-container (shape-container-factory objects all-objects)
|
||||
svg-raw-wrapper (svg-raw-wrapper shape-container)]
|
||||
(mf/fnc svg-raw-container
|
||||
{::mf/wrap-props false}
|
||||
|
@ -407,7 +439,7 @@
|
|||
[:> svg-raw-wrapper props]))))
|
||||
|
||||
(defn shape-container-factory
|
||||
[objects]
|
||||
[objects all-objects]
|
||||
(let [path-wrapper (path-wrapper)
|
||||
text-wrapper (text-wrapper)
|
||||
rect-wrapper (rect-wrapper)
|
||||
|
@ -422,26 +454,27 @@
|
|||
|
||||
group-container
|
||||
(mf/with-memo [objects]
|
||||
(group-container-factory objects))
|
||||
(group-container-factory objects all-objects))
|
||||
|
||||
frame-container
|
||||
(mf/with-memo [objects]
|
||||
(frame-container-factory objects))
|
||||
(frame-container-factory objects all-objects))
|
||||
|
||||
bool-container
|
||||
(mf/with-memo [objects]
|
||||
(bool-container-factory objects))
|
||||
(bool-container-factory objects all-objects))
|
||||
|
||||
svg-raw-container
|
||||
(mf/with-memo [objects]
|
||||
(svg-raw-container-factory objects))]
|
||||
(svg-raw-container-factory objects all-objects))]
|
||||
(when (and shape (not (:hidden shape)))
|
||||
(let [shape (-> shape
|
||||
#_(gsh/transform-shape)
|
||||
(gsh/translate-to-frame frame))
|
||||
|
||||
opts #js {:shape shape
|
||||
:objects objects}]
|
||||
:objects objects
|
||||
:all-objects all-objects}]
|
||||
(case (:type shape)
|
||||
:frame [:> frame-container opts]
|
||||
:text [:> text-wrapper opts]
|
||||
|
|
|
@ -87,7 +87,7 @@
|
|||
dw/clear-edition-mode)
|
||||
|
||||
;; 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
|
||||
(mf/use-fn
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
(d/without-nils {:color (str/lower (dm/get-in shadow [:color :color]))
|
||||
:opacity (dm/get-in shadow [:color :opacity])
|
||||
:gradient (dm/get-in shadow [:color :gradient])}))]
|
||||
|
||||
|
||||
|
||||
{:attrs attrs
|
||||
:prop :shadow
|
||||
|
@ -157,41 +157,33 @@
|
|||
expand-color (mf/use-state false)
|
||||
|
||||
grouped-colors* (mf/use-var nil)
|
||||
prev-color* (mf/use-var nil)
|
||||
prev-colors* (mf/use-var [])
|
||||
|
||||
on-change
|
||||
(mf/use-fn
|
||||
(fn [new-color old-color from-picker?]
|
||||
(let [old-color (-> old-color
|
||||
(dissoc :name)
|
||||
(dissoc :path)
|
||||
(d/without-nils))
|
||||
|
||||
prev-color (when @prev-color*
|
||||
(-> @prev-color*
|
||||
(dissoc :name)
|
||||
(dissoc :path)
|
||||
(d/without-nils)))
|
||||
(let [old-color (-> old-color (dissoc :name :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
|
||||
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-color (or shapes-by-prev-color shapes-by-old-color)]
|
||||
|
||||
(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))))))
|
||||
|
||||
on-open
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(reset! prev-color* nil)))
|
||||
(reset! prev-colors* [])))
|
||||
|
||||
on-close
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(reset! prev-color* nil)))
|
||||
(reset! prev-colors* [])))
|
||||
|
||||
on-detach
|
||||
(mf/use-fn
|
||||
|
@ -217,7 +209,7 @@
|
|||
[:div.element-set-content
|
||||
[:div.selected-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
|
||||
:index index
|
||||
:on-detach on-detach
|
||||
|
@ -231,7 +223,7 @@
|
|||
[:span.text (tr "workspace.options.more-lib-colors")]])
|
||||
(when @expand-lib-color
|
||||
(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
|
||||
:index index
|
||||
:on-detach on-detach
|
||||
|
@ -255,7 +247,7 @@
|
|||
[:span.text (tr "workspace.options.more-colors")]])
|
||||
(when @expand-color
|
||||
(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
|
||||
:index index
|
||||
:select-only select-only
|
||||
|
|
|
@ -315,12 +315,12 @@
|
|||
[measure-ids measure-values] (get-attrs shapes objects :measure)
|
||||
|
||||
[layer-ids layer-values
|
||||
text-ids text-values
|
||||
constraint-ids constraint-values
|
||||
fill-ids fill-values
|
||||
shadow-ids shadow-values
|
||||
blur-ids blur-values
|
||||
stroke-ids stroke-values
|
||||
text-ids text-values
|
||||
exports-ids exports-values
|
||||
layout-container-ids layout-container-values
|
||||
layout-item-ids layout-item-values]
|
||||
|
@ -331,12 +331,12 @@
|
|||
[]
|
||||
(mapcat identity)
|
||||
[(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 :fill)
|
||||
(get-attrs shapes objects-no-measures :shadow)
|
||||
(get-attrs shapes objects-no-measures :blur)
|
||||
(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 :layout-container)
|
||||
(get-attrs shapes objects-no-measures :layout-item)
|
||||
|
@ -364,6 +364,9 @@
|
|||
(when-not (empty? layer-ids)
|
||||
[:& 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)
|
||||
[:& fill-menu {:type type :ids fill-ids :values fill-values}])
|
||||
|
||||
|
@ -380,8 +383,5 @@
|
|||
(when-not (empty? blur-ids)
|
||||
[:& 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)
|
||||
[:& exports-menu {:type type :ids exports-ids :values exports-values :page-id page-id :file-id file-id}])]))
|
||||
|
|
|
@ -2321,9 +2321,6 @@ msgstr "Přichytit k vodicím lištám"
|
|||
msgid "shortcuts.toggle-textpalette"
|
||||
msgstr "Přepnout paletu textu"
|
||||
|
||||
msgid "shortcuts.toggle-visibility"
|
||||
msgstr "Přepnout viditelnost"
|
||||
|
||||
msgid "shortcuts.toggle-zoom-style"
|
||||
msgstr "Přepnout styl přiblížení"
|
||||
|
||||
|
|
|
@ -1017,6 +1017,9 @@ msgstr "Email or password is incorrect."
|
|||
msgid "errors.wrong-old-password"
|
||||
msgstr "Old password is incorrect"
|
||||
|
||||
msgid "errors.cannot-upload"
|
||||
msgstr "Cannot upload the media file."
|
||||
|
||||
#: src/app/main/ui/settings/feedback.cljs
|
||||
msgid "feedback.description"
|
||||
msgstr "Description"
|
||||
|
@ -2528,7 +2531,7 @@ msgid "shortcuts.h-distribute"
|
|||
msgstr "Distribute horizontally"
|
||||
|
||||
msgid "shortcuts.hide-ui"
|
||||
msgstr "Show/hide UI"
|
||||
msgstr "Show / Hide UI"
|
||||
|
||||
msgid "shortcuts.increase-zoom"
|
||||
msgstr "Zoom in"
|
||||
|
@ -2687,10 +2690,10 @@ msgid "shortcuts.separate-nodes"
|
|||
msgstr "Separate nodes"
|
||||
|
||||
msgid "shortcuts.show-pixel-grid"
|
||||
msgstr "Show/hide pixel grid"
|
||||
msgstr "Show / Hide pixel grid"
|
||||
|
||||
msgid "shortcuts.show-shortcuts"
|
||||
msgstr "Show/hide shortcuts"
|
||||
msgstr "Show / Hide shortcuts"
|
||||
|
||||
msgid "shortcuts.snap-nodes"
|
||||
msgstr "Snap to nodes"
|
||||
|
@ -2742,7 +2745,7 @@ msgid "shortcuts.toggle-fullscreen"
|
|||
msgstr "Toggle fullscreen"
|
||||
|
||||
msgid "shortcuts.toggle-grid"
|
||||
msgstr "Show/hide grid"
|
||||
msgstr "Show / Hide grid"
|
||||
|
||||
msgid "shortcuts.toggle-history"
|
||||
msgstr "Toggle history"
|
||||
|
@ -2751,16 +2754,16 @@ msgid "shortcuts.toggle-layers"
|
|||
msgstr "Toggle layers"
|
||||
|
||||
msgid "shortcuts.toggle-layout-flex"
|
||||
msgstr "Add/remove flex layout"
|
||||
msgstr "Add / Remove flex layout"
|
||||
|
||||
msgid "shortcuts.toggle-lock"
|
||||
msgstr "Lock selected"
|
||||
msgstr "Lock / Unlock"
|
||||
|
||||
msgid "shortcuts.toggle-lock-size"
|
||||
msgstr "Lock proportions"
|
||||
|
||||
msgid "shortcuts.toggle-rules"
|
||||
msgstr "Show/hide rulers"
|
||||
msgstr "Show / Hide rulers"
|
||||
|
||||
msgid "shortcuts.toggle-scale-text"
|
||||
msgstr "Toggle scale text"
|
||||
|
@ -2775,7 +2778,7 @@ msgid "shortcuts.toggle-textpalette"
|
|||
msgstr "Toggle text palette"
|
||||
|
||||
msgid "shortcuts.toggle-visibility"
|
||||
msgstr "Toggle visibility"
|
||||
msgstr "Show / Hide"
|
||||
|
||||
msgid "shortcuts.toggle-zoom-style"
|
||||
msgstr "Toggle zoom style"
|
||||
|
@ -4475,7 +4478,7 @@ msgid "workspace.shape.menu.hide"
|
|||
msgstr "Hide"
|
||||
|
||||
msgid "workspace.shape.menu.hide-ui"
|
||||
msgstr "Show/Hide UI"
|
||||
msgstr "Show / Hide UI"
|
||||
|
||||
msgid "workspace.shape.menu.intersection"
|
||||
msgstr "Intersection"
|
||||
|
|
|
@ -1057,6 +1057,9 @@ msgstr "El email o la contraseña son incorrectos."
|
|||
msgid "errors.wrong-old-password"
|
||||
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
|
||||
msgid "feedback.description"
|
||||
msgstr "Descripción"
|
||||
|
|
|
@ -2592,9 +2592,6 @@ msgstr "הצמדה לקווים מנחים"
|
|||
msgid "shortcuts.toggle-textpalette"
|
||||
msgstr "החלפת לוח טקסט"
|
||||
|
||||
msgid "shortcuts.toggle-visibility"
|
||||
msgstr "החלפת מצב הצגה"
|
||||
|
||||
msgid "shortcuts.toggle-zoom-style"
|
||||
msgstr "החלפת סגנון תקריב"
|
||||
|
||||
|
|
|
@ -2405,9 +2405,6 @@ msgstr "Pričvrsti na guides"
|
|||
msgid "shortcuts.toggle-textpalette"
|
||||
msgstr "Promijeni paletu teksta"
|
||||
|
||||
msgid "shortcuts.toggle-visibility"
|
||||
msgstr "Promijeni vidljivost"
|
||||
|
||||
msgid "shortcuts.toggle-zoom-style"
|
||||
msgstr "Promijeni stil zooma"
|
||||
|
||||
|
|
|
@ -2559,9 +2559,6 @@ msgstr "Tancap ke pemandu"
|
|||
msgid "shortcuts.toggle-textpalette"
|
||||
msgstr "Alih palet teks"
|
||||
|
||||
msgid "shortcuts.toggle-visibility"
|
||||
msgstr "Alih keterlihatan"
|
||||
|
||||
msgid "shortcuts.toggle-zoom-style"
|
||||
msgstr "Alih gaya zum"
|
||||
|
||||
|
|
|
@ -2592,9 +2592,6 @@ msgstr "Pieķerties vadotnēm"
|
|||
msgid "shortcuts.toggle-textpalette"
|
||||
msgstr "Pārslēgt teksta paleti"
|
||||
|
||||
msgid "shortcuts.toggle-visibility"
|
||||
msgstr "Pārslēgt redzamību"
|
||||
|
||||
msgid "shortcuts.toggle-zoom-style"
|
||||
msgstr "Pārslēgt tālummaiņas stilu"
|
||||
|
||||
|
|
|
@ -2612,9 +2612,6 @@ msgstr "Przyciągaj do prowadnic"
|
|||
msgid "shortcuts.toggle-textpalette"
|
||||
msgstr "Przełącz paletę tekstu"
|
||||
|
||||
msgid "shortcuts.toggle-visibility"
|
||||
msgstr "Przełącz widoczność"
|
||||
|
||||
msgid "shortcuts.toggle-zoom-style"
|
||||
msgstr "Przełącz sposób powiększania"
|
||||
|
||||
|
|
|
@ -2567,9 +2567,6 @@ msgstr "Aderir as réguas"
|
|||
msgid "shortcuts.toggle-textpalette"
|
||||
msgstr "Mostrar/Esconder paleta de tipografias"
|
||||
|
||||
msgid "shortcuts.toggle-visibility"
|
||||
msgstr "Alternar visibilidade"
|
||||
|
||||
msgid "shortcuts.toggle-zoom-style"
|
||||
msgstr "Alternar estilo de zoom"
|
||||
|
||||
|
|
|
@ -2424,9 +2424,6 @@ msgstr "Ajustar às guias"
|
|||
msgid "shortcuts.toggle-textpalette"
|
||||
msgstr "Alternar paleta de texto"
|
||||
|
||||
msgid "shortcuts.toggle-visibility"
|
||||
msgstr "Alternar visibilidade"
|
||||
|
||||
msgid "shortcuts.toggle-zoom-style"
|
||||
msgstr "Alternar estilo de zoom"
|
||||
|
||||
|
|
|
@ -2589,9 +2589,6 @@ msgstr "Fixare la ghiduri"
|
|||
msgid "shortcuts.toggle-textpalette"
|
||||
msgstr "Comutați paleta de text"
|
||||
|
||||
msgid "shortcuts.toggle-visibility"
|
||||
msgstr "Comutați vizibilitatea"
|
||||
|
||||
msgid "shortcuts.toggle-zoom-style"
|
||||
msgstr "Comutați stilul zoomului"
|
||||
|
||||
|
|
|
@ -2691,9 +2691,6 @@ msgstr "Kılavuzlara tuttur"
|
|||
msgid "shortcuts.toggle-textpalette"
|
||||
msgstr "Metin paletini değiştir"
|
||||
|
||||
msgid "shortcuts.toggle-visibility"
|
||||
msgstr "Görünürlüğü değiştir"
|
||||
|
||||
msgid "shortcuts.toggle-zoom-style"
|
||||
msgstr "Yakınlaştırma şeklini değiştir"
|
||||
|
||||
|
|
|
@ -2446,9 +2446,6 @@ msgstr "辅助线对齐"
|
|||
msgid "shortcuts.toggle-textpalette"
|
||||
msgstr "切换文本调色板"
|
||||
|
||||
msgid "shortcuts.toggle-visibility"
|
||||
msgstr "切换可见度"
|
||||
|
||||
msgid "shortcuts.toggle-zoom-style"
|
||||
msgstr "切换缩放样式"
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue