mirror of
https://github.com/penpot/penpot.git
synced 2026-09-10 22:19:19 +00:00
Merge remote-tracking branch 'origin/staging' into develop
This commit is contained in:
commit
a4fbc050cc
10
CHANGES.md
10
CHANGES.md
@ -11,6 +11,9 @@
|
|||||||
## 1.13.0-beta
|
## 1.13.0-beta
|
||||||
|
|
||||||
### :boom: Breaking changes
|
### :boom: Breaking changes
|
||||||
|
|
||||||
|
- We've changed the behaviour of the border-radius so it works as CSS that [has some limits](https://www.w3.org/TR/css-backgrounds-3/#corner-overlap).
|
||||||
|
|
||||||
### :sparkles: New features
|
### :sparkles: New features
|
||||||
|
|
||||||
- Exporting big files flow [Taiga #2218](https://tree.taiga.io/project/penpot/us/2218)
|
- Exporting big files flow [Taiga #2218](https://tree.taiga.io/project/penpot/us/2218)
|
||||||
@ -34,6 +37,7 @@
|
|||||||
- Add the ability to specify the attr for retrieve the email on OIDC integration [#1460](https://github.com/penpot/penpot/issues/1460)
|
- Add the ability to specify the attr for retrieve the email on OIDC integration [#1460](https://github.com/penpot/penpot/issues/1460)
|
||||||
- Allow registration with invitation token when registration is disabled
|
- Allow registration with invitation token when registration is disabled
|
||||||
- Add the ability to disable standard, password login [Taiga #2999](https://tree.taiga.io/project/penpot/us/2999)
|
- Add the ability to disable standard, password login [Taiga #2999](https://tree.taiga.io/project/penpot/us/2999)
|
||||||
|
- Don't stop SVG import when an image cannot be imported [#1531](https://github.com/penpot/penpot/issues/1531)
|
||||||
|
|
||||||
### :bug: Bugs fixed
|
### :bug: Bugs fixed
|
||||||
|
|
||||||
@ -45,6 +49,12 @@
|
|||||||
- Fix ellipsis in long page names [Taiga #2962](https://tree.taiga.io/project/penpot/issue/2962)
|
- Fix ellipsis in long page names [Taiga #2962](https://tree.taiga.io/project/penpot/issue/2962)
|
||||||
- Fix color palette animation [Taiga #2852](https://tree.taiga.io/project/penpot/issue/2852)
|
- Fix color palette animation [Taiga #2852](https://tree.taiga.io/project/penpot/issue/2852)
|
||||||
- Fix display code icon on preview hover [Taiga #2838](https://tree.taiga.io/project/penpot/us/2838)
|
- Fix display code icon on preview hover [Taiga #2838](https://tree.taiga.io/project/penpot/us/2838)
|
||||||
|
- Fix crash on iOS when displaying viewer [#1522](https://github.com/penpot/penpot/issues/1522)
|
||||||
|
- Fix problem when importing a SVG with text [#1532](https://github.com/penpot/penpot/issues/1532)
|
||||||
|
- Fix problem when adding shadows to imported text [#Taiga 3057](https://tree.taiga.io/project/penpot/issue/3057)
|
||||||
|
- Fix problem when importing SVG's with uses with overriding properties [#Taiga 2884](https://tree.taiga.io/project/penpot/issue/2884)
|
||||||
|
- Fix inconsistency with radius in SVG an CSS [#1587](https://github.com/penpot/penpot/issues/1587)
|
||||||
|
- Fix clickable area in layers [#1680](https://github.com/penpot/penpot/issues/1680)
|
||||||
|
|
||||||
### :arrow_up: Deps updates
|
### :arrow_up: Deps updates
|
||||||
### :heart: Community contributions by (Thank you!)
|
### :heart: Community contributions by (Thank you!)
|
||||||
|
|||||||
@ -180,17 +180,6 @@
|
|||||||
|
|
||||||
;; --- HTTP HANDLERS
|
;; --- HTTP HANDLERS
|
||||||
|
|
||||||
(defn extract-utm-props
|
|
||||||
"Extracts additional data from user params."
|
|
||||||
[params]
|
|
||||||
(reduce-kv (fn [params k v]
|
|
||||||
(let [sk (name k)]
|
|
||||||
(cond-> params
|
|
||||||
(str/starts-with? sk "utm_")
|
|
||||||
(assoc (->> sk str/kebab (keyword "penpot")) v))))
|
|
||||||
{}
|
|
||||||
params))
|
|
||||||
|
|
||||||
(defn- retrieve-profile
|
(defn- retrieve-profile
|
||||||
[{:keys [pool executor] :as cfg} info]
|
[{:keys [pool executor] :as cfg} info]
|
||||||
(px/with-dispatch executor
|
(px/with-dispatch executor
|
||||||
@ -252,7 +241,7 @@
|
|||||||
(defn- auth-handler
|
(defn- auth-handler
|
||||||
[{:keys [tokens] :as cfg} {:keys [params] :as request} respond raise]
|
[{:keys [tokens] :as cfg} {:keys [params] :as request} respond raise]
|
||||||
(try
|
(try
|
||||||
(let [props (extract-utm-props params)
|
(let [props (audit/extract-utm-params params)
|
||||||
state (tokens :generate
|
state (tokens :generate
|
||||||
{:iss :oauth
|
{:iss :oauth
|
||||||
:invitation-token (:invitation-token params)
|
:invitation-token (:invitation-token params)
|
||||||
|
|||||||
@ -34,6 +34,20 @@
|
|||||||
(yrq/get-header request "x-real-ip")
|
(yrq/get-header request "x-real-ip")
|
||||||
(yrq/remote-addr request)))
|
(yrq/remote-addr request)))
|
||||||
|
|
||||||
|
(defn extract-utm-params
|
||||||
|
"Extracts additional data from params and namespace them under
|
||||||
|
`penpot` ns."
|
||||||
|
[params]
|
||||||
|
(letfn [(process-param [params k v]
|
||||||
|
(let [sk (d/name k)]
|
||||||
|
(cond-> params
|
||||||
|
(str/starts-with? sk "utm_")
|
||||||
|
(assoc (->> sk str/kebab (keyword "penpot")) v)
|
||||||
|
|
||||||
|
(str/starts-with? sk "mtm_")
|
||||||
|
(assoc (->> sk str/kebab (keyword "penpot")) v))))]
|
||||||
|
(reduce-kv process-param {} params)))
|
||||||
|
|
||||||
(defn profile->props
|
(defn profile->props
|
||||||
[profile]
|
[profile]
|
||||||
(-> profile
|
(-> profile
|
||||||
|
|||||||
@ -12,7 +12,6 @@
|
|||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.emails :as eml]
|
[app.emails :as eml]
|
||||||
[app.http.oauth :refer [extract-utm-props]]
|
|
||||||
[app.loggers.audit :as audit]
|
[app.loggers.audit :as audit]
|
||||||
[app.media :as media]
|
[app.media :as media]
|
||||||
[app.rpc.mutations.teams :as teams]
|
[app.rpc.mutations.teams :as teams]
|
||||||
@ -223,7 +222,7 @@
|
|||||||
[conn params]
|
[conn params]
|
||||||
(let [id (or (:id params) (uuid/next))
|
(let [id (or (:id params) (uuid/next))
|
||||||
|
|
||||||
props (-> (extract-utm-props params)
|
props (-> (audit/extract-utm-params params)
|
||||||
(merge (:props params))
|
(merge (:props params))
|
||||||
(db/tjson))
|
(db/tjson))
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.emails :as eml]
|
[app.emails :as eml]
|
||||||
|
[app.loggers.audit :as audit]
|
||||||
[app.media :as media]
|
[app.media :as media]
|
||||||
[app.rpc.mutations.projects :as projects]
|
[app.rpc.mutations.projects :as projects]
|
||||||
[app.rpc.permissions :as perms]
|
[app.rpc.permissions :as perms]
|
||||||
@ -357,14 +358,14 @@
|
|||||||
:opt-un [::email ::emails]))
|
:opt-un [::email ::emails]))
|
||||||
|
|
||||||
(sv/defmethod ::invite-team-member
|
(sv/defmethod ::invite-team-member
|
||||||
|
"A rpc call that allow to send a single or multiple invitations to
|
||||||
|
join the team."
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id team-id email emails role] :as params}]
|
[{:keys [pool] :as cfg} {:keys [profile-id team-id email emails role] :as params}]
|
||||||
(db/with-atomic [conn pool]
|
(db/with-atomic [conn pool]
|
||||||
(let [perms (teams/get-permissions conn profile-id team-id)
|
(let [perms (teams/get-permissions conn profile-id team-id)
|
||||||
profile (db/get-by-id conn :profile profile-id)
|
profile (db/get-by-id conn :profile profile-id)
|
||||||
team (db/get-by-id conn :team team-id)
|
team (db/get-by-id conn :team team-id)
|
||||||
emails (or emails #{})
|
emails (cond-> (or emails #{}) (string? email) (conj email))]
|
||||||
emails (if email (conj emails email) emails)
|
|
||||||
]
|
|
||||||
|
|
||||||
(when-not (:is-admin perms)
|
(when-not (:is-admin perms)
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
@ -385,7 +386,9 @@
|
|||||||
:profile profile
|
:profile profile
|
||||||
:role role))
|
:role role))
|
||||||
)
|
)
|
||||||
nil)))
|
|
||||||
|
(with-meta {}
|
||||||
|
{::audit/props {:invitations (count emails)}}))))
|
||||||
|
|
||||||
(def sql:upsert-team-invitation
|
(def sql:upsert-team-invitation
|
||||||
"insert into team_invitation(team_id, email_to, role, valid_until)
|
"insert into team_invitation(team_id, email_to, role, valid_until)
|
||||||
@ -395,19 +398,19 @@
|
|||||||
|
|
||||||
(defn- create-team-invitation
|
(defn- create-team-invitation
|
||||||
[{:keys [conn tokens team profile role email] :as cfg}]
|
[{:keys [conn tokens team profile role email] :as cfg}]
|
||||||
(let [member (profile/retrieve-profile-data-by-email conn email)
|
(let [member (profile/retrieve-profile-data-by-email conn email)
|
||||||
token-exp (dt/in-future "48h")
|
token-exp (dt/in-future "48h")
|
||||||
itoken (tokens :generate
|
itoken (tokens :generate
|
||||||
{:iss :team-invitation
|
{:iss :team-invitation
|
||||||
:exp token-exp
|
:exp token-exp
|
||||||
:profile-id (:id profile)
|
:profile-id (:id profile)
|
||||||
:role role
|
:role role
|
||||||
:team-id (:id team)
|
:team-id (:id team)
|
||||||
:member-email (:email member email)
|
:member-email (:email member email)
|
||||||
:member-id (:id member)})
|
:member-id (:id member)})
|
||||||
ptoken (tokens :generate-predefined
|
ptoken (tokens :generate-predefined
|
||||||
{:iss :profile-identity
|
{:iss :profile-identity
|
||||||
:profile-id (:id profile)})]
|
:profile-id (:id profile)})]
|
||||||
|
|
||||||
(when (and member (not (eml/allow-send-emails? conn member)))
|
(when (and member (not (eml/allow-send-emails? conn member)))
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
@ -443,21 +446,14 @@
|
|||||||
(s/and ::create-team (s/keys :req-un [::emails ::role])))
|
(s/and ::create-team (s/keys :req-un [::emails ::role])))
|
||||||
|
|
||||||
(sv/defmethod ::create-team-and-invite-members
|
(sv/defmethod ::create-team-and-invite-members
|
||||||
[{:keys [pool audit] :as cfg} {:keys [profile-id emails role] :as params}]
|
[{:keys [pool] :as cfg} {:keys [profile-id emails role] :as params}]
|
||||||
(db/with-atomic [conn pool]
|
(db/with-atomic [conn pool]
|
||||||
(let [team (create-team conn params)
|
(let [team (create-team conn params)
|
||||||
profile (db/get-by-id conn :profile profile-id)]
|
audit-fn (:audit cfg)
|
||||||
|
profile (db/get-by-id conn :profile profile-id)]
|
||||||
|
|
||||||
;; Create invitations for all provided emails.
|
;; Create invitations for all provided emails.
|
||||||
(doseq [email emails]
|
(doseq [email emails]
|
||||||
(audit :cmd :submit
|
|
||||||
:type "mutation"
|
|
||||||
:name "create-team-invitation"
|
|
||||||
:profile-id profile-id
|
|
||||||
:props {:email email
|
|
||||||
:role role
|
|
||||||
:profile-id profile-id})
|
|
||||||
|
|
||||||
(create-team-invitation
|
(create-team-invitation
|
||||||
(assoc cfg
|
(assoc cfg
|
||||||
:conn conn
|
:conn conn
|
||||||
@ -465,8 +461,17 @@
|
|||||||
:profile profile
|
:profile profile
|
||||||
:email email
|
:email email
|
||||||
:role role)))
|
:role role)))
|
||||||
team)))
|
|
||||||
|
|
||||||
|
(with-meta team
|
||||||
|
{:before-complete
|
||||||
|
#(audit-fn :cmd :submit
|
||||||
|
:type "mutation"
|
||||||
|
:name "invite-team-member"
|
||||||
|
:profile-id profile-id
|
||||||
|
:props {:emails emails
|
||||||
|
:role role
|
||||||
|
:profile-id profile-id
|
||||||
|
:invitations (count emails)})}))))
|
||||||
|
|
||||||
;; --- Mutation: Update invitation role
|
;; --- Mutation: Update invitation role
|
||||||
|
|
||||||
|
|||||||
@ -44,16 +44,15 @@
|
|||||||
|
|
||||||
;; (th/print-result! out)
|
;; (th/print-result! out)
|
||||||
|
|
||||||
(t/is (nil? (:result out)))
|
(t/is (= {} (:result out)))
|
||||||
(t/is (= 1 (:call-count (deref mock))))
|
(t/is (= 1 (:call-count (deref mock))))
|
||||||
(t/is (= 1 (:num invitation))))
|
(t/is (= 1 (:num invitation))))
|
||||||
|
|
||||||
|
|
||||||
;; invite internal user without complaints
|
;; invite internal user without complaints
|
||||||
(th/reset-mock! mock)
|
(th/reset-mock! mock)
|
||||||
(let [data (assoc data :email (:email profile2))
|
(let [data (assoc data :email (:email profile2))
|
||||||
out (th/mutation! data)]
|
out (th/mutation! data)]
|
||||||
(t/is (nil? (:result out)))
|
(t/is (= {} (:result out)))
|
||||||
(t/is (= 1 (:call-count (deref mock)))))
|
(t/is (= 1 (:call-count (deref mock)))))
|
||||||
|
|
||||||
;; invite user with complaint
|
;; invite user with complaint
|
||||||
@ -61,7 +60,7 @@
|
|||||||
(th/reset-mock! mock)
|
(th/reset-mock! mock)
|
||||||
(let [data (assoc data :email "foo@bar.com")
|
(let [data (assoc data :email "foo@bar.com")
|
||||||
out (th/mutation! data)]
|
out (th/mutation! data)]
|
||||||
(t/is (nil? (:result out)))
|
(t/is (= {} (:result out)))
|
||||||
(t/is (= 1 (:call-count (deref mock)))))
|
(t/is (= 1 (:call-count (deref mock)))))
|
||||||
|
|
||||||
;; invite user with bounce
|
;; invite user with bounce
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
[app.common.geom.shapes.bool :as gsb]
|
[app.common.geom.shapes.bool :as gsb]
|
||||||
[app.common.geom.shapes.common :as gco]
|
[app.common.geom.shapes.common :as gco]
|
||||||
[app.common.geom.shapes.constraints :as gct]
|
[app.common.geom.shapes.constraints :as gct]
|
||||||
|
[app.common.geom.shapes.corners :as gsc]
|
||||||
[app.common.geom.shapes.intersect :as gin]
|
[app.common.geom.shapes.intersect :as gin]
|
||||||
[app.common.geom.shapes.path :as gsp]
|
[app.common.geom.shapes.path :as gsp]
|
||||||
[app.common.geom.shapes.rect :as gpr]
|
[app.common.geom.shapes.rect :as gpr]
|
||||||
@ -153,3 +154,7 @@
|
|||||||
;; Constraints
|
;; Constraints
|
||||||
(dm/export gct/default-constraints-h)
|
(dm/export gct/default-constraints-h)
|
||||||
(dm/export gct/default-constraints-v)
|
(dm/export gct/default-constraints-v)
|
||||||
|
|
||||||
|
;; Corners
|
||||||
|
(dm/export gsc/shape-corners-1)
|
||||||
|
(dm/export gsc/shape-corners-4)
|
||||||
|
|||||||
46
common/src/app/common/geom/shapes/corners.cljc
Normal file
46
common/src/app/common/geom/shapes/corners.cljc
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) UXBOX Labs SL
|
||||||
|
|
||||||
|
(ns app.common.geom.shapes.corners)
|
||||||
|
|
||||||
|
(defn fix-radius
|
||||||
|
;; https://www.w3.org/TR/css-backgrounds-3/#corner-overlap
|
||||||
|
;;
|
||||||
|
;; > Corner curves must not overlap: When the sum of any two adjacent border radii exceeds the size of the border box,
|
||||||
|
;; > UAs must proportionally reduce the used values of all border radii until none of them overlap.
|
||||||
|
;;
|
||||||
|
;; > The algorithm for reducing radii is as follows: Let f = min(Li/Si), where i ∈ {top, right, bottom, left}, Si is
|
||||||
|
;; > the sum of the two corresponding radii of the corners on side i, and Ltop = Lbottom = the width of the box, and
|
||||||
|
;; > Lleft = Lright = the height of the box. If f < 1, then all corner radii are reduced by multiplying them by f.
|
||||||
|
([width height r]
|
||||||
|
(let [f (min (/ width (* 2 r))
|
||||||
|
(/ height (* 2 r)))]
|
||||||
|
(if (< f 1)
|
||||||
|
(* r f)
|
||||||
|
r)))
|
||||||
|
|
||||||
|
([width height r1 r2 r3 r4]
|
||||||
|
(let [f (min (/ width (+ r1 r2))
|
||||||
|
(/ height (+ r2 r3))
|
||||||
|
(/ width (+ r3 r4))
|
||||||
|
(/ height (+ r4 r1)))]
|
||||||
|
(if (< f 1)
|
||||||
|
[(* r1 f) (* r2 f) (* r3 f) (* r4 f)]
|
||||||
|
[r1 r2 r3 r4]))))
|
||||||
|
|
||||||
|
(defn shape-corners-1
|
||||||
|
"Retrieve the effective value for the corner given a single value for corner."
|
||||||
|
[{:keys [width height rx] :as shape}]
|
||||||
|
(if (some? rx)
|
||||||
|
(fix-radius width height rx)
|
||||||
|
0))
|
||||||
|
|
||||||
|
(defn shape-corners-4
|
||||||
|
"Retrieve the effective value for the corner given four values for the corners."
|
||||||
|
[{:keys [width height r1 r2 r3 r4]}]
|
||||||
|
(if (and (some? r1) (some? r2) (some? r3) (some? r4))
|
||||||
|
(fix-radius width height r1 r2 r3 r4)
|
||||||
|
[r1 r2 r3 r4]))
|
||||||
@ -11,9 +11,11 @@
|
|||||||
[app.common.geom.matrix :as gmt]
|
[app.common.geom.matrix :as gmt]
|
||||||
[app.common.geom.point :as gpt]
|
[app.common.geom.point :as gpt]
|
||||||
[app.common.geom.shapes.common :as gsc]
|
[app.common.geom.shapes.common :as gsc]
|
||||||
|
[app.common.geom.shapes.corners :as gso]
|
||||||
[app.common.geom.shapes.path :as gsp]
|
[app.common.geom.shapes.path :as gsp]
|
||||||
[app.common.path.bool :as pb]
|
[app.common.path.bool :as pb]
|
||||||
[app.common.path.commands :as pc]))
|
[app.common.path.commands :as pc]
|
||||||
|
[app.common.spec.radius :as ctr]))
|
||||||
|
|
||||||
(def ^:const bezier-circle-c 0.551915024494)
|
(def ^:const bezier-circle-c 0.551915024494)
|
||||||
|
|
||||||
@ -93,7 +95,7 @@
|
|||||||
|
|
||||||
(defn circle->path
|
(defn circle->path
|
||||||
"Creates the bezier curves to approximate a circle shape"
|
"Creates the bezier curves to approximate a circle shape"
|
||||||
[x y width height]
|
[{:keys [x y width height]}]
|
||||||
(let [mx (+ x (/ width 2))
|
(let [mx (+ x (/ width 2))
|
||||||
my (+ y (/ height 2))
|
my (+ y (/ height 2))
|
||||||
ex (+ x width)
|
ex (+ x width)
|
||||||
@ -116,35 +118,50 @@
|
|||||||
(pc/make-curve-to p4 (assoc p3 :x c1x) (assoc p4 :y c2y))
|
(pc/make-curve-to p4 (assoc p3 :x c1x) (assoc p4 :y c2y))
|
||||||
(pc/make-curve-to p1 (assoc p4 :y c1y) (assoc p1 :x c1x))]))
|
(pc/make-curve-to p1 (assoc p4 :y c1y) (assoc p1 :x c1x))]))
|
||||||
|
|
||||||
|
(defn draw-rounded-rect-path
|
||||||
|
([x y width height r]
|
||||||
|
(draw-rounded-rect-path x y width height r r r r))
|
||||||
|
|
||||||
|
([x y width height r1 r2 r3 r4]
|
||||||
|
(let [p1 (gpt/point x (+ y r1))
|
||||||
|
p2 (gpt/point (+ x r1) y)
|
||||||
|
|
||||||
|
p3 (gpt/point (+ width x (- r2)) y)
|
||||||
|
p4 (gpt/point (+ width x) (+ y r2))
|
||||||
|
|
||||||
|
p5 (gpt/point (+ width x) (+ height y (- r3)))
|
||||||
|
p6 (gpt/point (+ width x (- r3)) (+ height y))
|
||||||
|
|
||||||
|
p7 (gpt/point (+ x r4) (+ height y))
|
||||||
|
p8 (gpt/point x (+ height y (- r4)))]
|
||||||
|
(-> []
|
||||||
|
(conj (pc/make-move-to p1))
|
||||||
|
(cond-> (not= p1 p2)
|
||||||
|
(conj (make-corner-arc p1 p2 :top-left r1)))
|
||||||
|
(conj (pc/make-line-to p3))
|
||||||
|
(cond-> (not= p3 p4)
|
||||||
|
(conj (make-corner-arc p3 p4 :top-right r2)))
|
||||||
|
(conj (pc/make-line-to p5))
|
||||||
|
(cond-> (not= p5 p6)
|
||||||
|
(conj (make-corner-arc p5 p6 :bottom-right r3)))
|
||||||
|
(conj (pc/make-line-to p7))
|
||||||
|
(cond-> (not= p7 p8)
|
||||||
|
(conj (make-corner-arc p7 p8 :bottom-left r4)))
|
||||||
|
(conj (pc/make-line-to p1))))))
|
||||||
|
|
||||||
(defn rect->path
|
(defn rect->path
|
||||||
"Creates a bezier curve that approximates a rounded corner rectangle"
|
"Creates a bezier curve that approximates a rounded corner rectangle"
|
||||||
[x y width height r1 r2 r3 r4 rx]
|
[{:keys [x y width height] :as shape}]
|
||||||
(let [[r1 r2 r3 r4] (->> [r1 r2 r3 r4] (mapv #(or % rx 0)))
|
(case (ctr/radius-mode shape)
|
||||||
p1 (gpt/point x (+ y r1))
|
:radius-1
|
||||||
p2 (gpt/point (+ x r1) y)
|
(let [radius (gso/shape-corners-1 shape)]
|
||||||
|
(draw-rounded-rect-path x y width height radius))
|
||||||
|
|
||||||
p3 (gpt/point (+ width x (- r2)) y)
|
:radius-4
|
||||||
p4 (gpt/point (+ width x) (+ y r2))
|
(let [[r1 r2 r3 r4] (gso/shape-corners-4 shape)]
|
||||||
|
(draw-rounded-rect-path x y width height r1 r2 r3 r4))
|
||||||
|
|
||||||
p5 (gpt/point (+ width x) (+ height y (- r3)))
|
[]))
|
||||||
p6 (gpt/point (+ width x (- r3)) (+ height y))
|
|
||||||
|
|
||||||
p7 (gpt/point (+ x r4) (+ height y))
|
|
||||||
p8 (gpt/point x (+ height y (- r4)))]
|
|
||||||
(-> []
|
|
||||||
(conj (pc/make-move-to p1))
|
|
||||||
(cond-> (not= p1 p2)
|
|
||||||
(conj (make-corner-arc p1 p2 :top-left r1)))
|
|
||||||
(conj (pc/make-line-to p3))
|
|
||||||
(cond-> (not= p3 p4)
|
|
||||||
(conj (make-corner-arc p3 p4 :top-right r2)))
|
|
||||||
(conj (pc/make-line-to p5))
|
|
||||||
(cond-> (not= p5 p6)
|
|
||||||
(conj (make-corner-arc p5 p6 :bottom-right r3)))
|
|
||||||
(conj (pc/make-line-to p7))
|
|
||||||
(cond-> (not= p7 p8)
|
|
||||||
(conj (make-corner-arc p7 p8 :bottom-left r4)))
|
|
||||||
(conj (pc/make-line-to p1)))))
|
|
||||||
|
|
||||||
(declare convert-to-path)
|
(declare convert-to-path)
|
||||||
|
|
||||||
@ -192,9 +209,9 @@
|
|||||||
"Transforms the given shape to a path"
|
"Transforms the given shape to a path"
|
||||||
([shape]
|
([shape]
|
||||||
(convert-to-path shape {}))
|
(convert-to-path shape {}))
|
||||||
([{:keys [type x y width height r1 r2 r3 r4 rx metadata] :as shape} objects]
|
([{:keys [type metadata] :as shape} objects]
|
||||||
(assert (map? objects))
|
(assert (map? objects))
|
||||||
(case (:type shape)
|
(case type
|
||||||
:group
|
:group
|
||||||
(group-to-path shape objects)
|
(group-to-path shape objects)
|
||||||
|
|
||||||
@ -204,8 +221,8 @@
|
|||||||
(:rect :circle :image :text)
|
(:rect :circle :image :text)
|
||||||
(let [new-content
|
(let [new-content
|
||||||
(case type
|
(case type
|
||||||
:circle (circle->path x y width height)
|
:circle (circle->path shape)
|
||||||
#_:else (rect->path x y width height r1 r2 r3 r4 rx))
|
#_:else (rect->path shape))
|
||||||
|
|
||||||
;; Apply the transforms that had the shape
|
;; Apply the transforms that had the shape
|
||||||
transform (:transform shape)
|
transform (:transform shape)
|
||||||
|
|||||||
@ -26,6 +26,7 @@
|
|||||||
|
|
||||||
(defn- get-mtype
|
(defn- get-mtype
|
||||||
[type]
|
[type]
|
||||||
|
|
||||||
(case (d/name type)
|
(case (d/name type)
|
||||||
"zip" "application/zip"
|
"zip" "application/zip"
|
||||||
"pdf" "application/pdf"
|
"pdf" "application/pdf"
|
||||||
|
|||||||
@ -327,6 +327,7 @@
|
|||||||
:page-id page-id
|
:page-id page-id
|
||||||
:object-id object-id
|
:object-id object-id
|
||||||
:render-texts true
|
:render-texts true
|
||||||
|
:embed true
|
||||||
:route "render-object"}
|
:route "render-object"}
|
||||||
|
|
||||||
uri (-> (or uri (cf/get :public-uri))
|
uri (-> (or uri (cf/get :public-uri))
|
||||||
|
|||||||
@ -215,6 +215,7 @@ span.element-name {
|
|||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.element-actions {
|
.element-actions {
|
||||||
|
|||||||
@ -426,21 +426,21 @@
|
|||||||
(rx/tap #(tm/schedule on-success))
|
(rx/tap #(tm/schedule on-success))
|
||||||
(rx/catch on-error))))))
|
(rx/catch on-error))))))
|
||||||
|
|
||||||
(defn invite-team-member
|
(defn invite-team-members
|
||||||
[{:keys [emails role] :as params}]
|
[{:keys [emails role team-id resend?] :as params}]
|
||||||
(us/assert ::us/set-of-emails emails)
|
(us/assert ::us/set-of-emails emails)
|
||||||
(us/assert ::us/keyword role)
|
(us/assert ::us/keyword role)
|
||||||
(ptk/reify ::invite-team-member
|
(us/assert ::us/uuid team-id)
|
||||||
|
(ptk/reify ::invite-team-members
|
||||||
IDeref
|
IDeref
|
||||||
(-deref [_] {:role role})
|
(-deref [_] {:role role :team-id team-id :resend? resend?})
|
||||||
|
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state _]
|
(watch [_ _ _]
|
||||||
(let [{:keys [on-success on-error]
|
(let [{:keys [on-success on-error]
|
||||||
:or {on-success identity
|
:or {on-success identity
|
||||||
on-error rx/throw}} (meta params)
|
on-error rx/throw}} (meta params)
|
||||||
team-id (:current-team-id state)
|
params (dissoc params :resend?)]
|
||||||
params (assoc params :team-id team-id)]
|
|
||||||
(->> (rp/mutation! :invite-team-member params)
|
(->> (rp/mutation! :invite-team-member params)
|
||||||
(rx/tap on-success)
|
(rx/tap on-success)
|
||||||
(rx/catch on-error))))))
|
(rx/catch on-error))))))
|
||||||
|
|||||||
@ -85,7 +85,7 @@
|
|||||||
(derive :app.main.data.dashboard/delete-team-member ::generic-action)
|
(derive :app.main.data.dashboard/delete-team-member ::generic-action)
|
||||||
(derive :app.main.data.dashboard/duplicate-project ::generic-action)
|
(derive :app.main.data.dashboard/duplicate-project ::generic-action)
|
||||||
(derive :app.main.data.dashboard/file-created ::generic-action)
|
(derive :app.main.data.dashboard/file-created ::generic-action)
|
||||||
(derive :app.main.data.dashboard/invite-team-member ::generic-action)
|
(derive :app.main.data.dashboard/invite-team-members ::generic-action)
|
||||||
(derive :app.main.data.dashboard/leave-team ::generic-action)
|
(derive :app.main.data.dashboard/leave-team ::generic-action)
|
||||||
(derive :app.main.data.dashboard/move-files ::generic-action)
|
(derive :app.main.data.dashboard/move-files ::generic-action)
|
||||||
(derive :app.main.data.dashboard/move-project ::generic-action)
|
(derive :app.main.data.dashboard/move-project ::generic-action)
|
||||||
@ -113,6 +113,7 @@
|
|||||||
(derive :app.main.data.workspace.persistence/attach-library ::generic-action)
|
(derive :app.main.data.workspace.persistence/attach-library ::generic-action)
|
||||||
(derive :app.main.data.workspace.persistence/detach-library ::generic-action)
|
(derive :app.main.data.workspace.persistence/detach-library ::generic-action)
|
||||||
(derive :app.main.data.workspace.persistence/set-file-shard ::generic-action)
|
(derive :app.main.data.workspace.persistence/set-file-shard ::generic-action)
|
||||||
|
(derive :app.main.data.workspace.selection/toggle-focus-mode ::generic-action)
|
||||||
(derive :app.main.data.workspace/create-page ::generic-action)
|
(derive :app.main.data.workspace/create-page ::generic-action)
|
||||||
(derive :app.main.data.workspace/set-workspace-layout ::generic-action)
|
(derive :app.main.data.workspace/set-workspace-layout ::generic-action)
|
||||||
(derive :app.main.data.workspace/toggle-layout-flag ::generic-action)
|
(derive :app.main.data.workspace/toggle-layout-flag ::generic-action)
|
||||||
|
|||||||
@ -293,29 +293,6 @@
|
|||||||
(rx/catch (constantly (rx/of 1)))
|
(rx/catch (constantly (rx/of 1)))
|
||||||
(rx/map #(logged-out params)))))))
|
(rx/map #(logged-out params)))))))
|
||||||
|
|
||||||
;; --- EVENT: register
|
|
||||||
|
|
||||||
(s/def ::register
|
|
||||||
(s/keys :req-un [::fullname ::password ::email]))
|
|
||||||
|
|
||||||
(defn register
|
|
||||||
"Create a register event instance."
|
|
||||||
[data]
|
|
||||||
(s/assert ::register data)
|
|
||||||
(ptk/reify ::register
|
|
||||||
ptk/WatchEvent
|
|
||||||
(watch [_ _ _]
|
|
||||||
(let [{:keys [on-error on-success]
|
|
||||||
:or {on-error identity
|
|
||||||
on-success identity}} (meta data)]
|
|
||||||
(->> (rp/mutation :register-profile data)
|
|
||||||
(rx/tap on-success)
|
|
||||||
(rx/catch on-error))))
|
|
||||||
|
|
||||||
ptk/EffectEvent
|
|
||||||
(effect [_ _ _]
|
|
||||||
(swap! storage dissoc ::redirect-to))))
|
|
||||||
|
|
||||||
;; --- Update Profile
|
;; --- Update Profile
|
||||||
|
|
||||||
(defn update-profile
|
(defn update-profile
|
||||||
|
|||||||
@ -70,30 +70,32 @@
|
|||||||
:else (str tag))))
|
:else (str tag))))
|
||||||
|
|
||||||
(defn setup-fill [shape]
|
(defn setup-fill [shape]
|
||||||
(cond-> shape
|
(if (some? (:fills shape))
|
||||||
;; Color present as attribute
|
shape
|
||||||
(uc/color? (str/trim (get-in shape [:svg-attrs :fill])))
|
(cond-> shape
|
||||||
(-> (update :svg-attrs dissoc :fill)
|
;; Color present as attribute
|
||||||
(assoc-in [:fills 0 :fill-color] (-> (get-in shape [:svg-attrs :fill])
|
(uc/color? (str/trim (get-in shape [:svg-attrs :fill])))
|
||||||
(str/trim)
|
(-> (update :svg-attrs dissoc :fill)
|
||||||
(uc/parse-color))))
|
(assoc-in [:fills 0 :fill-color] (-> (get-in shape [:svg-attrs :fill])
|
||||||
|
(str/trim)
|
||||||
|
(uc/parse-color))))
|
||||||
|
|
||||||
;; Color present as style
|
;; Color present as style
|
||||||
(uc/color? (str/trim (get-in shape [:svg-attrs :style :fill])))
|
(uc/color? (str/trim (get-in shape [:svg-attrs :style :fill])))
|
||||||
(-> (update-in [:svg-attrs :style] dissoc :fill)
|
(-> (update-in [:svg-attrs :style] dissoc :fill)
|
||||||
(assoc-in [:fills 0 :fill-color] (-> (get-in shape [:svg-attrs :style :fill])
|
(assoc-in [:fills 0 :fill-color] (-> (get-in shape [:svg-attrs :style :fill])
|
||||||
(str/trim)
|
(str/trim)
|
||||||
(uc/parse-color))))
|
(uc/parse-color))))
|
||||||
|
|
||||||
(get-in shape [:svg-attrs :fill-opacity])
|
(get-in shape [:svg-attrs :fill-opacity])
|
||||||
(-> (update :svg-attrs dissoc :fill-opacity)
|
(-> (update :svg-attrs dissoc :fill-opacity)
|
||||||
(assoc-in [:fills 0 :fill-opacity] (-> (get-in shape [:svg-attrs :fill-opacity])
|
(assoc-in [:fills 0 :fill-opacity] (-> (get-in shape [:svg-attrs :fill-opacity])
|
||||||
(d/parse-double))))
|
(d/parse-double))))
|
||||||
|
|
||||||
(get-in shape [:svg-attrs :style :fill-opacity])
|
(get-in shape [:svg-attrs :style :fill-opacity])
|
||||||
(-> (update-in [:svg-attrs :style] dissoc :fill-opacity)
|
(-> (update-in [:svg-attrs :style] dissoc :fill-opacity)
|
||||||
(assoc-in [:fills 0 :fill-opacity] (-> (get-in shape [:svg-attrs :style :fill-opacity])
|
(assoc-in [:fills 0 :fill-opacity] (-> (get-in shape [:svg-attrs :style :fill-opacity])
|
||||||
(d/parse-double))))))
|
(d/parse-double)))))))
|
||||||
|
|
||||||
(defn setup-stroke [shape]
|
(defn setup-stroke [shape]
|
||||||
(let [stroke-linecap (-> (or (get-in shape [:svg-attrs :stroke-linecap])
|
(let [stroke-linecap (-> (or (get-in shape [:svg-attrs :stroke-linecap])
|
||||||
@ -105,11 +107,25 @@
|
|||||||
(cond-> shape
|
(cond-> shape
|
||||||
(uc/color? (str/trim (get-in shape [:svg-attrs :stroke])))
|
(uc/color? (str/trim (get-in shape [:svg-attrs :stroke])))
|
||||||
(-> (update :svg-attrs dissoc :stroke)
|
(-> (update :svg-attrs dissoc :stroke)
|
||||||
(assoc-in [:strokes 0 :stroke-color] (get-in shape [:svg-attrs :stroke])))
|
(assoc-in [:strokes 0 :stroke-color] (-> (get-in shape [:svg-attrs :stroke])
|
||||||
|
(str/trim)
|
||||||
|
(uc/parse-color))))
|
||||||
|
|
||||||
(uc/color? (str/trim (get-in shape [:svg-attrs :style :stroke])))
|
(uc/color? (str/trim (get-in shape [:svg-attrs :style :stroke])))
|
||||||
(-> (update-in [:svg-attrs :style] dissoc :stroke)
|
(-> (update-in [:svg-attrs :style] dissoc :stroke)
|
||||||
(assoc-in [:strokes 0 :stroke-color] (get-in shape [:svg-attrs :style :stroke])))
|
(assoc-in [:strokes 0 :stroke-color] (-> (get-in shape [:svg-attrs :style :stroke])
|
||||||
|
(str/trim)
|
||||||
|
(uc/parse-color))))
|
||||||
|
|
||||||
|
(get-in shape [:svg-attrs :stroke-opacity])
|
||||||
|
(-> (update :svg-attrs dissoc :stroke-opacity)
|
||||||
|
(assoc-in [:strokes 0 :stroke-opacity] (-> (get-in shape [:svg-attrs :stroke-opacity])
|
||||||
|
(d/parse-double))))
|
||||||
|
|
||||||
|
(get-in shape [:svg-attrs :style :stroke-opacity])
|
||||||
|
(-> (update-in [:svg-attrs :style] dissoc :stroke-opacity)
|
||||||
|
(assoc-in [:fills 0 :stroke-opacity] (-> (get-in shape [:svg-attrs :style :stroke-opacity])
|
||||||
|
(d/parse-double))))
|
||||||
|
|
||||||
(get-in shape [:svg-attrs :stroke-width])
|
(get-in shape [:svg-attrs :stroke-width])
|
||||||
(-> (update :svg-attrs dissoc :stroke-width)
|
(-> (update :svg-attrs dissoc :stroke-width)
|
||||||
@ -123,14 +139,13 @@
|
|||||||
|
|
||||||
(and stroke-linecap (= (:type shape) :path))
|
(and stroke-linecap (= (:type shape) :path))
|
||||||
(-> (update-in [:svg-attrs :style] dissoc :stroke-linecap)
|
(-> (update-in [:svg-attrs :style] dissoc :stroke-linecap)
|
||||||
(cond->
|
(cond-> (#{:round :square} stroke-linecap)
|
||||||
(#{:round :square} stroke-linecap)
|
|
||||||
(assoc :stroke-cap-start stroke-linecap
|
(assoc :stroke-cap-start stroke-linecap
|
||||||
:stroke-cap-end stroke-linecap))))]
|
:stroke-cap-end stroke-linecap))))]
|
||||||
|
|
||||||
(if (d/any-key? (get-in [:strokes 0] shape) :stroke-color :stroke-opacity :stroke-width :stroke-cap-start :stroke-cap-end)
|
(cond-> shape
|
||||||
(assoc-in shape [:strokes 0 :stroke-style] :svg)
|
(d/any-key? (get-in shape [:strokes 0]) :stroke-color :stroke-opacity :stroke-width :stroke-cap-start :stroke-cap-end)
|
||||||
shape)))
|
(assoc-in [:strokes 0 :stroke-style] :svg))))
|
||||||
|
|
||||||
(defn setup-opacity [shape]
|
(defn setup-opacity [shape]
|
||||||
(cond-> shape
|
(cond-> shape
|
||||||
@ -324,18 +339,20 @@
|
|||||||
(update :y - (:y origin)))
|
(update :y - (:y origin)))
|
||||||
|
|
||||||
rect-metadata (calculate-rect-metadata rect-data transform)]
|
rect-metadata (calculate-rect-metadata rect-data transform)]
|
||||||
(-> {:id (uuid/next)
|
|
||||||
:type :image
|
|
||||||
:name name
|
|
||||||
:frame-id frame-id
|
|
||||||
:metadata {:width (:width image-data)
|
|
||||||
:height (:height image-data)
|
|
||||||
:mtype (:mtype image-data)
|
|
||||||
:id (:id image-data)}}
|
|
||||||
|
|
||||||
(merge rect-metadata)
|
(when (some? image-data)
|
||||||
(assoc :svg-viewbox (select-keys rect [:x :y :width :height]))
|
(-> {:id (uuid/next)
|
||||||
(assoc :svg-attrs (dissoc attrs :x :y :width :height :xlink:href)))))
|
:type :image
|
||||||
|
:name name
|
||||||
|
:frame-id frame-id
|
||||||
|
:metadata {:width (:width image-data)
|
||||||
|
:height (:height image-data)
|
||||||
|
:mtype (:mtype image-data)
|
||||||
|
:id (:id image-data)}}
|
||||||
|
|
||||||
|
(merge rect-metadata)
|
||||||
|
(assoc :svg-viewbox (select-keys rect [:x :y :width :height]))
|
||||||
|
(assoc :svg-attrs (dissoc attrs :x :y :width :height :xlink:href))))))
|
||||||
|
|
||||||
(defn parse-svg-element [frame-id svg-data element-data unames]
|
(defn parse-svg-element [frame-id svg-data element-data unames]
|
||||||
(let [{:keys [tag attrs]} element-data
|
(let [{:keys [tag attrs]} element-data
|
||||||
@ -352,8 +369,9 @@
|
|||||||
use-tag? (and (= :use tag) (contains? defs href-id))]
|
use-tag? (and (= :use tag) (contains? defs href-id))]
|
||||||
|
|
||||||
(if use-tag?
|
(if use-tag?
|
||||||
(let [use-data (get defs href-id)
|
(let [;; Merge the data of the use definition with the properties passed as attributes
|
||||||
|
use-data (-> (get defs href-id)
|
||||||
|
(update :attrs #(d/deep-merge % (dissoc attrs :xlink:href :href))))
|
||||||
displacement (gpt/point (d/parse-double (:x attrs "0")) (d/parse-double (:y attrs "0")))
|
displacement (gpt/point (d/parse-double (:x attrs "0")) (d/parse-double (:y attrs "0")))
|
||||||
disp-matrix (str (gmt/translate-matrix displacement))
|
disp-matrix (str (gmt/translate-matrix displacement))
|
||||||
element-data (-> element-data
|
element-data (-> element-data
|
||||||
@ -375,21 +393,21 @@
|
|||||||
:polygon (create-path-shape name frame-id svg-data (-> element-data usvg/polygon->path))
|
:polygon (create-path-shape name frame-id svg-data (-> element-data usvg/polygon->path))
|
||||||
:line (create-path-shape name frame-id svg-data (-> element-data usvg/line->path))
|
:line (create-path-shape name frame-id svg-data (-> element-data usvg/line->path))
|
||||||
:image (create-image-shape name frame-id svg-data element-data)
|
:image (create-image-shape name frame-id svg-data element-data)
|
||||||
#_other (create-raw-svg name frame-id svg-data element-data)))
|
#_other (create-raw-svg name frame-id svg-data element-data)))]
|
||||||
|
(when (some? shape)
|
||||||
|
(let [shape (assoc shape :fills [])
|
||||||
|
shape (assoc shape :strokes [])
|
||||||
|
|
||||||
shape (assoc shape :fills [])
|
shape (when (some? shape)
|
||||||
shape (assoc shape :strokes [])
|
(-> shape
|
||||||
|
(assoc :svg-defs (select-keys (:defs svg-data) references))
|
||||||
|
(setup-fill)
|
||||||
|
(setup-stroke)))
|
||||||
|
|
||||||
shape (when (some? shape)
|
children (cond->> (:content element-data)
|
||||||
(-> shape
|
(or (= tag :g) (= tag :svg))
|
||||||
(assoc :svg-defs (select-keys (:defs svg-data) references))
|
(mapv #(usvg/inherit-attributes attrs %)))]
|
||||||
(setup-fill)
|
[shape children]))))))
|
||||||
(setup-stroke)))
|
|
||||||
|
|
||||||
children (cond->> (:content element-data)
|
|
||||||
(or (= tag :g) (= tag :svg))
|
|
||||||
(mapv #(usvg/inherit-attributes attrs %)))]
|
|
||||||
[shape children]))))
|
|
||||||
|
|
||||||
(defn add-svg-child-changes [page-id objects selected frame-id parent-id svg-data [unames changes] [index data]]
|
(defn add-svg-child-changes [page-id objects selected frame-id parent-id svg-data [unames changes] [index data]]
|
||||||
(let [[shape children] (parse-svg-element frame-id svg-data data unames)]
|
(let [[shape children] (parse-svg-element frame-id svg-data data unames)]
|
||||||
@ -434,6 +452,9 @@
|
|||||||
(->> (rp/mutation! (if (contains? uri-data :content)
|
(->> (rp/mutation! (if (contains? uri-data :content)
|
||||||
:upload-file-media-object
|
:upload-file-media-object
|
||||||
:create-file-media-object-from-url) uri-data)
|
:create-file-media-object-from-url) uri-data)
|
||||||
|
;; When the image uploaded fail we skip the shape
|
||||||
|
;; returning `nil` will afterward not create the shape.
|
||||||
|
(rx/catch #(rx/of nil))
|
||||||
(rx/map #(vector (:url uri-data) %)))))
|
(rx/map #(vector (:url uri-data) %)))))
|
||||||
(rx/reduce (fn [acc [url image]] (assoc acc url image)) {})
|
(rx/reduce (fn [acc [url image]] (assoc acc url image)) {})
|
||||||
(rx/map #(create-svg-shapes (assoc svg-data :image-data %) position))))))
|
(rx/map #(create-svg-shapes (assoc svg-data :image-data %) position))))))
|
||||||
|
|||||||
@ -226,7 +226,7 @@
|
|||||||
font-style: %(style)s;
|
font-style: %(style)s;
|
||||||
font-weight: %(weight)s;
|
font-weight: %(weight)s;
|
||||||
font-display: block;
|
font-display: block;
|
||||||
src: url(/fonts/%(family)s-%(suffix)s.woff) format('woff');
|
src: url(%(baseurl)sfonts/%(family)s-%(suffix)s.woff) format('woff');
|
||||||
}
|
}
|
||||||
")
|
")
|
||||||
|
|
||||||
@ -262,7 +262,8 @@
|
|||||||
:else
|
:else
|
||||||
(let [{:keys [weight style suffix] :as variant}
|
(let [{:keys [weight style suffix] :as variant}
|
||||||
(d/seek #(= (:id %) font-variant-id) variants)
|
(d/seek #(= (:id %) font-variant-id) variants)
|
||||||
font-data {:family family
|
font-data {:baseurl (str cf/public-uri)
|
||||||
|
:family family
|
||||||
:style style
|
:style style
|
||||||
:suffix (or suffix font-variant-id)
|
:suffix (or suffix font-variant-id)
|
||||||
:weight weight}]
|
:weight weight}]
|
||||||
|
|||||||
@ -115,10 +115,12 @@
|
|||||||
(let [file-id (uuid (get-in route [:path-params :file-id]))
|
(let [file-id (uuid (get-in route [:path-params :file-id]))
|
||||||
page-id (uuid (get-in route [:path-params :page-id]))
|
page-id (uuid (get-in route [:path-params :page-id]))
|
||||||
object-id (uuid (get-in route [:path-params :object-id]))
|
object-id (uuid (get-in route [:path-params :object-id]))
|
||||||
|
embed? (= (get-in route [:query-params :embed]) "true")
|
||||||
render-texts (get-in route [:query-params :render-texts])]
|
render-texts (get-in route [:query-params :render-texts])]
|
||||||
[:& render/render-object {:file-id file-id
|
[:& render/render-object {:file-id file-id
|
||||||
:page-id page-id
|
:page-id page-id
|
||||||
:object-id object-id
|
:object-id object-id
|
||||||
|
:embed? embed?
|
||||||
:render-texts? (and (some? render-texts) (= render-texts "true"))}]))
|
:render-texts? (and (some? render-texts) (= render-texts "true"))}]))
|
||||||
|
|
||||||
:render-sprite
|
:render-sprite
|
||||||
|
|||||||
@ -68,8 +68,8 @@
|
|||||||
(st/emit! (dm/error (tr "errors.generic")))))
|
(st/emit! (dm/error (tr "errors.generic")))))
|
||||||
|
|
||||||
(defn- handle-prepare-register-success
|
(defn- handle-prepare-register-success
|
||||||
[_form {:keys [token] :as result}]
|
[_ params]
|
||||||
(st/emit! (rt/nav :auth-register-validate {} {:token token})))
|
(st/emit! (rt/nav :auth-register-validate {} params)))
|
||||||
|
|
||||||
(mf/defc register-form
|
(mf/defc register-form
|
||||||
[{:keys [params] :as props}]
|
[{:keys [params] :as props}]
|
||||||
@ -83,8 +83,9 @@
|
|||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
(fn [form _event]
|
(fn [form _event]
|
||||||
(reset! submitted? true)
|
(reset! submitted? true)
|
||||||
(let [params (:clean-data @form)]
|
(let [cdata (:clean-data @form)]
|
||||||
(->> (rp/mutation :prepare-register-profile params)
|
(->> (rp/mutation :prepare-register-profile cdata)
|
||||||
|
(rx/map #(merge % params))
|
||||||
(rx/finalize #(reset! submitted? false))
|
(rx/finalize #(reset! submitted? false))
|
||||||
(rx/subs (partial handle-prepare-register-success form)
|
(rx/subs (partial handle-prepare-register-success form)
|
||||||
(partial handle-prepare-register-error form))))))
|
(partial handle-prepare-register-error form))))))
|
||||||
@ -160,13 +161,6 @@
|
|||||||
(defn- handle-register-error
|
(defn- handle-register-error
|
||||||
[form error]
|
[form error]
|
||||||
(case (:code error)
|
(case (:code error)
|
||||||
:registration-disabled
|
|
||||||
(st/emit! (dm/error (tr "errors.registration-disabled")))
|
|
||||||
|
|
||||||
:email-has-permanent-bounces
|
|
||||||
(let [email (get @form [:data :email])]
|
|
||||||
(st/emit! (dm/error (tr "errors.email-has-permanent-bounces" email))))
|
|
||||||
|
|
||||||
:email-already-exists
|
:email-already-exists
|
||||||
(swap! form assoc-in [:errors :email]
|
(swap! form assoc-in [:errors :email]
|
||||||
{:message "errors.email-already-exists"})
|
{:message "errors.email-already-exists"})
|
||||||
|
|||||||
@ -7,10 +7,11 @@
|
|||||||
(ns app.main.ui.dashboard.team
|
(ns app.main.ui.dashboard.team
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
|
[app.common.data.macros :as dm]
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.config :as cfg]
|
[app.config :as cfg]
|
||||||
[app.main.data.dashboard :as dd]
|
[app.main.data.dashboard :as dd]
|
||||||
[app.main.data.messages :as dm]
|
[app.main.data.messages :as msg]
|
||||||
[app.main.data.modal :as modal]
|
[app.main.data.modal :as modal]
|
||||||
[app.main.data.users :as du]
|
[app.main.data.users :as du]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
@ -27,15 +28,14 @@
|
|||||||
[cljs.spec.alpha :as s]
|
[cljs.spec.alpha :as s]
|
||||||
[rumext.alpha :as mf]))
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
;; TEAM SECTION
|
|
||||||
|
|
||||||
(mf/defc header
|
(mf/defc header
|
||||||
{::mf/wrap [mf/memo]}
|
{::mf/wrap [mf/memo]}
|
||||||
[{:keys [section team] :as props}]
|
[{:keys [section team] :as props}]
|
||||||
(let [go-members (st/emitf (dd/go-to-team-members))
|
(let [go-members (mf/use-fn #(st/emit! (dd/go-to-team-members)))
|
||||||
go-settings (st/emitf (dd/go-to-team-settings))
|
go-settings (mf/use-fn #(st/emit! (dd/go-to-team-settings)))
|
||||||
go-invitations (st/emitf (dd/go-to-team-invitations))
|
go-invitations (mf/use-fn #(st/emit! (dd/go-to-team-invitations)))
|
||||||
invite-member (st/emitf (modal/show {:type ::invite-member :team team}))
|
invite-member (mf/use-fn #(st/emit! (modal/show {:type :invite-members :team team})))
|
||||||
|
|
||||||
members-section? (= section :dashboard-team-members)
|
members-section? (= section :dashboard-team-members)
|
||||||
settings-section? (= section :dashboard-team-settings)
|
settings-section? (= section :dashboard-team-settings)
|
||||||
invitations-section? (= section :dashboard-team-invitations)
|
invitations-section? (= section :dashboard-team-invitations)
|
||||||
@ -62,12 +62,16 @@
|
|||||||
(tr "dashboard.invite-profile")]
|
(tr "dashboard.invite-profile")]
|
||||||
[:div.blank-space])]]))
|
[:div.blank-space])]]))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; INVITATIONS MODAL
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
(defn get-available-roles
|
(defn get-available-roles
|
||||||
[permissions]
|
[permissions]
|
||||||
(->> [{:value "editor" :label (tr "labels.editor")}
|
(->> [{:value "editor" :label (tr "labels.editor")}
|
||||||
(when (:is-admin permissions)
|
(when (:is-admin permissions)
|
||||||
{:value "admin" :label (tr "labels.admin")})
|
{:value "admin" :label (tr "labels.admin")})
|
||||||
;; Temporarily disabled viewer role
|
;; Temporarily disabled viewer roles
|
||||||
;; https://tree.taiga.io/project/uxboxproject/issue/1083
|
;; https://tree.taiga.io/project/uxboxproject/issue/1083
|
||||||
;; {:value "viewer" :label (tr "labels.viewer")}
|
;; {:value "viewer" :label (tr "labels.viewer")}
|
||||||
]
|
]
|
||||||
@ -75,31 +79,34 @@
|
|||||||
|
|
||||||
(s/def ::emails (s/and ::us/set-of-emails d/not-empty?))
|
(s/def ::emails (s/and ::us/set-of-emails d/not-empty?))
|
||||||
(s/def ::role ::us/keyword)
|
(s/def ::role ::us/keyword)
|
||||||
(s/def ::invite-member-form
|
(s/def ::team-id ::us/uuid)
|
||||||
(s/keys :req-un [::role ::emails]))
|
|
||||||
|
|
||||||
(mf/defc invite-member-modal
|
(s/def ::invite-member-form
|
||||||
|
(s/keys :req-un [::role ::emails ::team-id]))
|
||||||
|
|
||||||
|
(mf/defc invite-members-modal
|
||||||
{::mf/register modal/components
|
{::mf/register modal/components
|
||||||
::mf/register-as ::invite-member}
|
::mf/register-as :invite-members}
|
||||||
[{:keys [team]}]
|
[{:keys [team]}]
|
||||||
(let [perms (:permissions team)
|
(let [perms (:permissions team)
|
||||||
roles (mf/use-memo (mf/deps perms) #(get-available-roles perms))
|
roles (mf/use-memo (mf/deps perms) #(get-available-roles perms))
|
||||||
initial (mf/use-memo (constantly {:role "editor"}))
|
initial (mf/use-memo (constantly {:role "editor" :team-id (:id team)}))
|
||||||
form (fm/use-form :spec ::invite-member-form
|
form (fm/use-form :spec ::invite-member-form
|
||||||
:initial initial)
|
:initial initial)
|
||||||
error-text (mf/use-state "")
|
error-text (mf/use-state "")
|
||||||
|
|
||||||
on-success
|
on-success
|
||||||
(st/emitf (dm/success (tr "notifications.invitation-email-sent"))
|
(fn []
|
||||||
(modal/hide)
|
(st/emit! (msg/success (tr "notifications.invitation-email-sent"))
|
||||||
(dd/fetch-team-invitations))
|
(modal/hide)
|
||||||
|
(dd/fetch-team-invitations)))
|
||||||
|
|
||||||
on-error
|
on-error
|
||||||
(fn [{:keys [type code] :as error}]
|
(fn [{:keys [type code] :as error}]
|
||||||
(cond
|
(cond
|
||||||
(and (= :validation type)
|
(and (= :validation type)
|
||||||
(= :profile-is-muted code))
|
(= :profile-is-muted code))
|
||||||
(st/emit! (dm/error (tr "errors.profile-is-muted"))
|
(st/emit! (msg/error (tr "errors.profile-is-muted"))
|
||||||
(modal/hide))
|
(modal/hide))
|
||||||
|
|
||||||
(and (= :validation type)
|
(and (= :validation type)
|
||||||
@ -108,7 +115,7 @@
|
|||||||
(swap! error-text (tr "errors.email-spam-or-permanent-bounces" (:email error)))
|
(swap! error-text (tr "errors.email-spam-or-permanent-bounces" (:email error)))
|
||||||
|
|
||||||
:else
|
:else
|
||||||
(st/emit! (dm/error (tr "errors.generic"))
|
(st/emit! (msg/error (tr "errors.generic"))
|
||||||
(modal/hide))))
|
(modal/hide))))
|
||||||
|
|
||||||
on-submit
|
on-submit
|
||||||
@ -116,9 +123,9 @@
|
|||||||
(let [params (:clean-data @form)
|
(let [params (:clean-data @form)
|
||||||
mdata {:on-success (partial on-success form)
|
mdata {:on-success (partial on-success form)
|
||||||
:on-error (partial on-error form)}]
|
:on-error (partial on-error form)}]
|
||||||
(st/emit! (dd/invite-team-member (with-meta params mdata))
|
(st/emit! (dd/invite-team-members (with-meta params mdata))
|
||||||
(dd/fetch-team-invitations))))]
|
(dd/fetch-team-invitations))))]
|
||||||
|
|
||||||
[:div.modal.dashboard-invite-modal.form-container
|
[:div.modal.dashboard-invite-modal.form-container
|
||||||
[:& fm/form {:on-submit on-submit :form form}
|
[:& fm/form {:on-submit on-submit :form form}
|
||||||
[:div.title
|
[:div.title
|
||||||
@ -141,7 +148,9 @@
|
|||||||
[:div.action-buttons
|
[:div.action-buttons
|
||||||
[:& fm/submit-button {:label (tr "modals.invite-member-confirm.accept")}]]]]))
|
[:& fm/submit-button {:label (tr "modals.invite-member-confirm.accept")}]]]]))
|
||||||
|
|
||||||
;; TEAM MEMBERS SECTION
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; MEMBERS SECTION
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
(mf/defc member-info [{:keys [member profile] :as props}]
|
(mf/defc member-info [{:keys [member profile] :as props}]
|
||||||
(let [is-you? (= (:id profile) (:id member))]
|
(let [is-you? (= (:id profile) (:id member))]
|
||||||
@ -210,101 +219,126 @@
|
|||||||
{::mf/wrap [mf/memo]}
|
{::mf/wrap [mf/memo]}
|
||||||
[{:keys [team member members profile] :as props}]
|
[{:keys [team member members profile] :as props}]
|
||||||
|
|
||||||
(let [set-role
|
(let [owner? (dm/get-in team [:permissions :is-owner])
|
||||||
(fn [role]
|
set-role
|
||||||
(let [params {:member-id (:id member) :role role}]
|
(mf/use-fn
|
||||||
(st/emit! (dd/update-team-member-role params))))
|
(mf/deps member)
|
||||||
owner? (get-in team [:permissions :is-owner])
|
(fn [role]
|
||||||
|
(let [params {:member-id (:id member) :role role}]
|
||||||
|
(st/emit! (dd/update-team-member-role params)))))
|
||||||
|
|
||||||
set-owner-fn (partial set-role :owner)
|
|
||||||
set-admin (partial set-role :admin)
|
set-owner-fn (mf/use-fn (mf/deps set-role) (partial set-role :owner))
|
||||||
set-editor (partial set-role :editor)
|
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-viewer (partial set-role :viewer)
|
||||||
|
|
||||||
set-owner
|
set-owner
|
||||||
(fn [member]
|
(mf/use-fn
|
||||||
(st/emit! (modal/show
|
(mf/deps set-owner-fn member)
|
||||||
{:type :confirm
|
(fn [member]
|
||||||
:title (tr "modals.promote-owner-confirm.title")
|
(st/emit! (modal/show
|
||||||
:message (tr "modals.promote-owner-confirm.message" (:name member))
|
{:type :confirm
|
||||||
:scd-message (tr "modals.promote-owner-confirm.hint")
|
:title (tr "modals.promote-owner-confirm.title")
|
||||||
:accept-label (tr "modals.promote-owner-confirm.accept")
|
:message (tr "modals.promote-owner-confirm.message" (:name member))
|
||||||
:on-accept set-owner-fn
|
:scd-message (tr "modals.promote-owner-confirm.hint")
|
||||||
:accept-style :primary})))
|
:accept-label (tr "modals.promote-owner-confirm.accept")
|
||||||
|
:on-accept set-owner-fn
|
||||||
|
:accept-style :primary}))))
|
||||||
|
|
||||||
delete-member-fn
|
delete-member-fn
|
||||||
(st/emitf (dd/delete-team-member {:member-id (:id member)}))
|
(mf/use-fn
|
||||||
|
(mf/deps member)
|
||||||
|
(fn [] (st/emit! (dd/delete-team-member {:member-id (:id member)}))))
|
||||||
|
|
||||||
on-success
|
on-success
|
||||||
(fn []
|
(mf/use-fn
|
||||||
(st/emit! (dd/go-to-projects (:default-team-id profile))
|
(mf/deps profile)
|
||||||
(modal/hide)
|
(fn []
|
||||||
(du/fetch-teams)))
|
(st/emit! (dd/go-to-projects (:default-team-id profile))
|
||||||
|
(modal/hide)
|
||||||
|
(du/fetch-teams))))
|
||||||
|
|
||||||
on-error
|
on-error
|
||||||
(fn [{:keys [code] :as error}]
|
(mf/use-fn
|
||||||
(condp = code
|
(fn [{:keys [code] :as error}]
|
||||||
|
(condp = code
|
||||||
|
|
||||||
:no-enough-members-for-leave
|
:no-enough-members-for-leave
|
||||||
(rx/of (dm/error (tr "errors.team-leave.insufficient-members")))
|
(rx/of (msg/error (tr "errors.team-leave.insufficient-members")))
|
||||||
|
|
||||||
:member-does-not-exist
|
:member-does-not-exist
|
||||||
(rx/of (dm/error (tr "errors.team-leave.member-does-not-exists")))
|
(rx/of (msg/error (tr "errors.team-leave.member-does-not-exists")))
|
||||||
|
|
||||||
:owner-cant-leave-team
|
:owner-cant-leave-team
|
||||||
(rx/of (dm/error (tr "errors.team-leave.owner-cant-leave")))
|
(rx/of (msg/error (tr "errors.team-leave.owner-cant-leave")))
|
||||||
|
|
||||||
(rx/throw error)))
|
(rx/throw error))))
|
||||||
|
|
||||||
delete-fn
|
delete-fn
|
||||||
(fn []
|
(mf/use-fn
|
||||||
(st/emit! (dd/delete-team (with-meta team {:on-success on-success
|
(mf/deps team on-success on-error)
|
||||||
:on-error on-error}))))
|
(fn []
|
||||||
|
(st/emit! (dd/delete-team (with-meta team {:on-success on-success
|
||||||
|
:on-error on-error})))))
|
||||||
|
|
||||||
leave-fn
|
leave-fn
|
||||||
(fn [member-id]
|
(mf/use-fn
|
||||||
(let [params (cond-> {} (uuid? member-id) (assoc :reassign-to member-id))]
|
(mf/deps on-success on-error)
|
||||||
(st/emit! (dd/leave-team (with-meta params
|
(fn [member-id]
|
||||||
{:on-success on-success
|
(let [params (cond-> {} (uuid? member-id) (assoc :reassign-to member-id))]
|
||||||
:on-error on-error})))))
|
(st/emit! (dd/leave-team (with-meta params
|
||||||
|
{:on-success on-success
|
||||||
|
:on-error on-error}))))))
|
||||||
|
|
||||||
leave-and-close
|
leave-and-close
|
||||||
(st/emitf (modal/show
|
(mf/use-fn
|
||||||
{:type :confirm
|
(mf/deps delete-fn)
|
||||||
:title (tr "modals.leave-confirm.title")
|
(fn []
|
||||||
:message (tr "modals.leave-and-close-confirm.message" (:name team))
|
(st/emit! (modal/show
|
||||||
:scd-message (tr "modals.leave-and-close-confirm.hint")
|
{:type :confirm
|
||||||
:accept-label (tr "modals.leave-confirm.accept")
|
:title (tr "modals.leave-confirm.title")
|
||||||
:on-accept delete-fn}))
|
: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}))))
|
||||||
|
|
||||||
change-owner-and-leave
|
change-owner-and-leave
|
||||||
(fn []
|
(mf/use-fn
|
||||||
(st/emit! (dd/fetch-team-members)
|
(mf/deps profile team leave-fn)
|
||||||
(modal/show
|
(fn []
|
||||||
{:type :leave-and-reassign
|
(st/emit! (dd/fetch-team-members)
|
||||||
:profile profile
|
(modal/show
|
||||||
:team team
|
{:type :leave-and-reassign
|
||||||
:accept leave-fn})))
|
:profile profile
|
||||||
|
:team team
|
||||||
|
:accept leave-fn}))))
|
||||||
|
|
||||||
leave
|
leave
|
||||||
(st/emitf (modal/show
|
(mf/use-fn
|
||||||
{:type :confirm
|
(mf/deps leave-fn)
|
||||||
:title (tr "modals.leave-confirm.title")
|
(fn []
|
||||||
:message (tr "modals.leave-confirm.message")
|
(st/emit! (modal/show
|
||||||
:accept-label (tr "modals.leave-confirm.accept")
|
{:type :confirm
|
||||||
:on-accept leave-fn}))
|
:title (tr "modals.leave-confirm.title")
|
||||||
|
:message (tr "modals.leave-confirm.message")
|
||||||
|
:accept-label (tr "modals.leave-confirm.accept")
|
||||||
|
:on-accept leave-fn}))))
|
||||||
|
|
||||||
preset-leave (cond (= 1 (count members)) leave-and-close
|
preset-leave (cond (= 1 (count members)) leave-and-close
|
||||||
(= true owner?) change-owner-and-leave
|
(= true owner?) change-owner-and-leave
|
||||||
:else leave)
|
:else leave)
|
||||||
|
|
||||||
delete
|
delete
|
||||||
(st/emitf (modal/show
|
(mf/use-fn
|
||||||
{:type :confirm
|
(mf/deps delete-member-fn)
|
||||||
:title (tr "modals.delete-team-member-confirm.title")
|
(fn []
|
||||||
:message (tr "modals.delete-team-member-confirm.message")
|
(st/emit! (modal/show
|
||||||
:accept-label (tr "modals.delete-team-member-confirm.accept")
|
{:type :confirm
|
||||||
:on-accept delete-member-fn}))]
|
: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}))))]
|
||||||
|
|
||||||
[:div.table-row
|
[:div.table-row
|
||||||
[:div.table-field.name
|
[:div.table-field.name
|
||||||
@ -361,7 +395,9 @@
|
|||||||
:team team
|
:team team
|
||||||
:members-map members-map}]]]))
|
:members-map members-map}]]]))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; INVITATIONS SECTION
|
;; INVITATIONS SECTION
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
(mf/defc invitation-role-selector
|
(mf/defc invitation-role-selector
|
||||||
[{:keys [can-invite? role status change-to-admin change-to-editor] :as props}]
|
[{:keys [can-invite? role status change-to-admin change-to-editor] :as props}]
|
||||||
@ -418,7 +454,7 @@
|
|||||||
:pending)
|
:pending)
|
||||||
|
|
||||||
on-success
|
on-success
|
||||||
#(st/emit! (dm/success (tr "notifications.invitation-email-sent"))
|
#(st/emit! (msg/success (tr "notifications.invitation-email-sent"))
|
||||||
(modal/hide)
|
(modal/hide)
|
||||||
(dd/fetch-team-invitations))
|
(dd/fetch-team-invitations))
|
||||||
|
|
||||||
@ -428,18 +464,18 @@
|
|||||||
(cond
|
(cond
|
||||||
(and (= :validation type)
|
(and (= :validation type)
|
||||||
(= :profile-is-muted code))
|
(= :profile-is-muted code))
|
||||||
(dm/error (tr "errors.profile-is-muted"))
|
(msg/error (tr "errors.profile-is-muted"))
|
||||||
|
|
||||||
(and (= :validation type)
|
(and (= :validation type)
|
||||||
(= :member-is-muted code))
|
(= :member-is-muted code))
|
||||||
(dm/error (tr "errors.member-is-muted"))
|
(msg/error (tr "errors.member-is-muted"))
|
||||||
|
|
||||||
(and (= :validation type)
|
(and (= :validation type)
|
||||||
(= :email-has-permanent-bounces code))
|
(= :email-has-permanent-bounces code))
|
||||||
(dm/error (tr "errors.email-has-permanent-bounces" email))
|
(msg/error (tr "errors.email-has-permanent-bounces" email))
|
||||||
|
|
||||||
:else
|
:else
|
||||||
(dm/error (tr "errors.generic"))))
|
(msg/error (tr "errors.generic"))))
|
||||||
|
|
||||||
change-rol
|
change-rol
|
||||||
(fn [role]
|
(fn [role]
|
||||||
@ -455,20 +491,31 @@
|
|||||||
|
|
||||||
resend-invitation
|
resend-invitation
|
||||||
(fn []
|
(fn []
|
||||||
(let [params {:email email :team-id (:id team) :role invitation-role}
|
(let [params {:email email
|
||||||
|
:team-id (:id team)
|
||||||
|
:resend? true
|
||||||
|
:role invitation-role}
|
||||||
mdata {:on-success on-success
|
mdata {:on-success on-success
|
||||||
:on-error (partial on-error email)}]
|
:on-error (partial on-error email)}]
|
||||||
(st/emit! (dd/invite-team-member (with-meta params mdata)))
|
(st/emit! (dd/invite-team-members (with-meta params mdata))
|
||||||
(st/emit! (dd/fetch-team-invitations))))]
|
(dd/fetch-team-invitations))))]
|
||||||
[:div.table-row
|
[:div.table-row
|
||||||
[:div.table-field.mail email]
|
[:div.table-field.mail email]
|
||||||
[:div.table-field.roles [:& invitation-role-selector {:can-invite? can-invite?
|
[:div.table-field.roles
|
||||||
:role invitation-role
|
[:& invitation-role-selector
|
||||||
:status status
|
{:can-invite? can-invite?
|
||||||
:change-to-editor (partial change-rol :editor)
|
:role invitation-role
|
||||||
:change-to-admin (partial change-rol :admin)}]]
|
:status status
|
||||||
[:div.table-field.status [:& invitation-status-badge {:status status}]]
|
:change-to-editor (partial change-rol :editor)
|
||||||
[:div.table-field.actions [:& invitation-actions {:can-modify? can-invite? :delete delete-invitation :resend resend-invitation}]]]))
|
:change-to-admin (partial change-rol :admin)}]]
|
||||||
|
|
||||||
|
[:div.table-field.status
|
||||||
|
[:& invitation-status-badge {:status status}]]
|
||||||
|
[:div.table-field.actions
|
||||||
|
[:& invitation-actions
|
||||||
|
{:can-modify? can-invite?
|
||||||
|
:delete delete-invitation
|
||||||
|
:resend resend-invitation}]]]))
|
||||||
|
|
||||||
(mf/defc empty-invitation-table [can-invite?]
|
(mf/defc empty-invitation-table [can-invite?]
|
||||||
[:div.empty-invitations
|
[:div.empty-invitations
|
||||||
@ -513,7 +560,9 @@
|
|||||||
[:& invitation-section {:team team
|
[:& invitation-section {:team team
|
||||||
:invitations invitations}]]]))
|
:invitations invitations}]]]))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; SETTINGS SECTION
|
;; SETTINGS SECTION
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
(mf/defc team-settings-page
|
(mf/defc team-settings-page
|
||||||
[{:keys [team] :as props}]
|
[{:keys [team] :as props}]
|
||||||
|
|||||||
@ -52,8 +52,8 @@
|
|||||||
|
|
||||||
(mf/defc object-svg
|
(mf/defc object-svg
|
||||||
{::mf/wrap [mf/memo]}
|
{::mf/wrap [mf/memo]}
|
||||||
[{:keys [objects object-id zoom render-texts?]
|
[{:keys [objects object-id zoom render-texts? embed?]
|
||||||
:or {zoom 1}
|
:or {zoom 1 embed? false}
|
||||||
:as props}]
|
:as props}]
|
||||||
(let [object (get objects object-id)
|
(let [object (get objects object-id)
|
||||||
frame-id (if (= :frame (:type object))
|
frame-id (if (= :frame (:type object))
|
||||||
@ -106,7 +106,7 @@
|
|||||||
{:size (str (mth/ceil width) "px "
|
{:size (str (mth/ceil width) "px "
|
||||||
(mth/ceil height) "px")}))
|
(mth/ceil height) "px")}))
|
||||||
|
|
||||||
[:& (mf/provider embed/context) {:value false}
|
[:& (mf/provider embed/context) {:value embed?}
|
||||||
[:svg {:id "screenshot"
|
[:svg {:id "screenshot"
|
||||||
:view-box vbox
|
:view-box vbox
|
||||||
:width width
|
:width width
|
||||||
@ -152,7 +152,7 @@
|
|||||||
objects))
|
objects))
|
||||||
|
|
||||||
(mf/defc render-object
|
(mf/defc render-object
|
||||||
[{:keys [file-id page-id object-id render-texts?] :as props}]
|
[{:keys [file-id page-id object-id render-texts? embed?] :as props}]
|
||||||
(let [objects (mf/use-state nil)]
|
(let [objects (mf/use-state nil)]
|
||||||
|
|
||||||
(mf/with-effect [file-id page-id object-id]
|
(mf/with-effect [file-id page-id object-id]
|
||||||
@ -171,6 +171,7 @@
|
|||||||
(when @objects
|
(when @objects
|
||||||
[:& object-svg {:objects @objects
|
[:& object-svg {:objects @objects
|
||||||
:object-id object-id
|
:object-id object-id
|
||||||
|
:embed? embed?
|
||||||
:render-texts? render-texts?
|
:render-texts? render-texts?
|
||||||
:zoom 1}])))
|
:zoom 1}])))
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,8 @@
|
|||||||
(ns app.main.ui.shapes.attrs
|
(ns app.main.ui.shapes.attrs
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
|
[app.common.data.macros :as dm]
|
||||||
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.common.spec.radius :as ctr]
|
[app.common.spec.radius :as ctr]
|
||||||
[app.common.spec.shape :refer [stroke-caps-line stroke-caps-marker]]
|
[app.common.spec.shape :refer [stroke-caps-line stroke-caps-marker]]
|
||||||
[app.main.ui.context :as muc]
|
[app.main.ui.context :as muc]
|
||||||
@ -26,58 +28,30 @@
|
|||||||
|
|
||||||
(->> values (map #(+ % width)) (str/join ","))))
|
(->> values (map #(+ % width)) (str/join ","))))
|
||||||
|
|
||||||
(defn- truncate-side
|
|
||||||
[shape ra-attr rb-attr dimension-attr]
|
|
||||||
(let [ra (ra-attr shape)
|
|
||||||
rb (rb-attr shape)
|
|
||||||
dimension (dimension-attr shape)]
|
|
||||||
(if (<= (+ ra rb) dimension)
|
|
||||||
[ra rb]
|
|
||||||
[(/ (* ra dimension) (+ ra rb))
|
|
||||||
(/ (* rb dimension) (+ ra rb))])))
|
|
||||||
|
|
||||||
(defn- truncate-radius
|
(defn add-border-radius [attrs {:keys [x y width height] :as shape}]
|
||||||
[shape]
|
|
||||||
(let [[r-top-left r-top-right]
|
|
||||||
(truncate-side shape :r1 :r2 :width)
|
|
||||||
|
|
||||||
[r-right-top r-right-bottom]
|
|
||||||
(truncate-side shape :r2 :r3 :height)
|
|
||||||
|
|
||||||
[r-bottom-right r-bottom-left]
|
|
||||||
(truncate-side shape :r3 :r4 :width)
|
|
||||||
|
|
||||||
[r-left-bottom r-left-top]
|
|
||||||
(truncate-side shape :r4 :r1 :height)]
|
|
||||||
|
|
||||||
[(min r-top-left r-left-top)
|
|
||||||
(min r-top-right r-right-top)
|
|
||||||
(min r-right-bottom r-bottom-right)
|
|
||||||
(min r-bottom-left r-left-bottom)]))
|
|
||||||
|
|
||||||
(defn add-border-radius [attrs shape]
|
|
||||||
(case (ctr/radius-mode shape)
|
(case (ctr/radius-mode shape)
|
||||||
|
|
||||||
:radius-1
|
:radius-1
|
||||||
(obj/merge! attrs #js {:rx (:rx shape 0)
|
(let [radius (gsh/shape-corners-1 shape)]
|
||||||
:ry (:ry shape 0)})
|
(obj/merge! attrs #js {:rx radius :ry radius}))
|
||||||
|
|
||||||
:radius-4
|
:radius-4
|
||||||
(let [[r1 r2 r3 r4] (truncate-radius shape)
|
(let [[r1 r2 r3 r4] (gsh/shape-corners-4 shape)
|
||||||
top (- (:width shape) r1 r2)
|
top (- width r1 r2)
|
||||||
right (- (:height shape) r2 r3)
|
right (- height r2 r3)
|
||||||
bottom (- (:width shape) r3 r4)
|
bottom (- width r3 r4)
|
||||||
left (- (:height shape) r4 r1)]
|
left (- height r4 r1)]
|
||||||
(obj/merge! attrs #js {:d (str "M" (+ (:x shape) r1) "," (:y shape) " "
|
(obj/merge! attrs #js {:d (dm/str
|
||||||
"h" top " "
|
"M" (+ x r1) "," y " "
|
||||||
"a" r2 "," r2 " 0 0 1 " r2 "," r2 " "
|
"h" top " "
|
||||||
"v" right " "
|
"a" r2 "," r2 " 0 0 1 " r2 "," r2 " "
|
||||||
"a" r3 "," r3 " 0 0 1 " (- r3) "," r3 " "
|
"v" right " "
|
||||||
"h" (- bottom) " "
|
"a" r3 "," r3 " 0 0 1 " (- r3) "," r3 " "
|
||||||
"a" r4 "," r4 " 0 0 1 " (- r4) "," (- r4) " "
|
"h" (- bottom) " "
|
||||||
"v" (- left) " "
|
"a" r4 "," r4 " 0 0 1 " (- r4) "," (- r4) " "
|
||||||
"a" r1 "," r1 " 0 0 1 " r1 "," (- r1) " "
|
"v" (- left) " "
|
||||||
"z")}))
|
"a" r1 "," r1 " 0 0 1 " r1 "," (- r1) " "
|
||||||
|
"z")}))
|
||||||
attrs))
|
attrs))
|
||||||
|
|
||||||
(defn add-fill
|
(defn add-fill
|
||||||
@ -98,14 +72,8 @@
|
|||||||
(contains? shape :fill-color)
|
(contains? shape :fill-color)
|
||||||
{:fill (:fill-color shape)}
|
{:fill (:fill-color shape)}
|
||||||
|
|
||||||
;; If contains svg-attrs the origin is svg. If it's not svg origin
|
|
||||||
;; we setup the default fill as transparent (instead of black)
|
|
||||||
(and (not (contains? shape :svg-attrs))
|
|
||||||
(not (#{:svg-raw :group} (:type shape))))
|
|
||||||
{:fill "none"}
|
|
||||||
|
|
||||||
:else
|
:else
|
||||||
{})
|
{:fill "none"})
|
||||||
|
|
||||||
fill-attrs (cond-> fill-attrs
|
fill-attrs (cond-> fill-attrs
|
||||||
(contains? shape :fill-opacity)
|
(contains? shape :fill-opacity)
|
||||||
@ -212,8 +180,23 @@
|
|||||||
(obj/set! "fill" (obj/get svg-styles "fill"))
|
(obj/set! "fill" (obj/get svg-styles "fill"))
|
||||||
(obj/set! "fillOpacity" (obj/get svg-styles "fillOpacity")))
|
(obj/set! "fillOpacity" (obj/get svg-styles "fillOpacity")))
|
||||||
|
|
||||||
|
(obj/contains? svg-attrs "fill")
|
||||||
|
(-> styles
|
||||||
|
(obj/set! "fill" (obj/get svg-attrs "fill"))
|
||||||
|
(obj/set! "fillOpacity" (obj/get svg-attrs "fillOpacity")))
|
||||||
|
|
||||||
|
;; If contains svg-attrs the origin is svg. If it's not svg origin
|
||||||
|
;; we setup the default fill as transparent (instead of black)
|
||||||
|
(and (contains? shape :svg-attrs)
|
||||||
|
(#{:svg-raw :group} (:type shape))
|
||||||
|
(empty? (:fills shape)))
|
||||||
|
styles
|
||||||
|
|
||||||
|
(d/not-empty? (:fills shape))
|
||||||
|
(add-fill styles (d/without-nils (get-in shape [:fills 0])) render-id 0)
|
||||||
|
|
||||||
:else
|
:else
|
||||||
(add-fill styles (d/without-nils (get-in shape [:fills 0])) render-id 0))]
|
styles)]
|
||||||
|
|
||||||
(-> props
|
(-> props
|
||||||
(obj/merge! svg-attrs)
|
(obj/merge! svg-attrs)
|
||||||
|
|||||||
@ -328,7 +328,13 @@
|
|||||||
|
|
||||||
props (cond-> props
|
props (cond-> props
|
||||||
(d/not-empty? (:shadow shape))
|
(d/not-empty? (:shadow shape))
|
||||||
(obj/set! "filter" (dm/fmt "url(#filter_%)" render-id)))]
|
(obj/set! "filter" (dm/fmt "url(#filter_%)" render-id)))
|
||||||
|
|
||||||
|
svg-defs (:svg-defs shape {})
|
||||||
|
svg-attrs (:svg-attrs shape {})
|
||||||
|
|
||||||
|
[svg-attrs svg-styles]
|
||||||
|
(attrs/extract-svg-attrs render-id svg-defs svg-attrs)]
|
||||||
|
|
||||||
(cond
|
(cond
|
||||||
url-fill?
|
url-fill?
|
||||||
@ -339,6 +345,25 @@
|
|||||||
(obj/without ["fill" "fillOpacity"])))]
|
(obj/without ["fill" "fillOpacity"])))]
|
||||||
(obj/set! props "fill" (dm/fmt "url(#fill-0-%)" render-id)))
|
(obj/set! props "fill" (dm/fmt "url(#fill-0-%)" render-id)))
|
||||||
|
|
||||||
|
(obj/contains? svg-styles "fill")
|
||||||
|
(let [style
|
||||||
|
(-> (obj/get props "style")
|
||||||
|
(obj/clone)
|
||||||
|
(obj/set! "fill" (obj/get svg-styles "fill"))
|
||||||
|
(obj/set! "fillOpacity" (obj/get svg-styles "fillOpacity")))]
|
||||||
|
(-> props
|
||||||
|
(obj/set! "style" style)))
|
||||||
|
|
||||||
|
(obj/contains? svg-attrs "fill")
|
||||||
|
(let [style
|
||||||
|
(-> (obj/get props "style")
|
||||||
|
(obj/clone)
|
||||||
|
(obj/set! "fill" (obj/get svg-attrs "fill"))
|
||||||
|
(obj/set! "fillOpacity" (obj/get svg-attrs "fillOpacity")))]
|
||||||
|
(-> props
|
||||||
|
(obj/set! "style" style)))
|
||||||
|
|
||||||
|
|
||||||
(d/not-empty? (:fills shape))
|
(d/not-empty? (:fills shape))
|
||||||
(let [fill-props
|
(let [fill-props
|
||||||
(attrs/extract-fill-attrs (get-in shape [:fills 0]) render-id 0)
|
(attrs/extract-fill-attrs (get-in shape [:fills 0]) render-id 0)
|
||||||
|
|||||||
@ -175,7 +175,7 @@
|
|||||||
(if svg-root?
|
(if svg-root?
|
||||||
;; When is a raw-svg but not the root we use the whole svg as bound for the filter. Is the maximum
|
;; When is a raw-svg but not the root we use the whole svg as bound for the filter. Is the maximum
|
||||||
;; we're allowed to display
|
;; we're allowed to display
|
||||||
{:x 0 :y 0 :width width :height height}
|
{:x x :y y :width width :height height}
|
||||||
|
|
||||||
;; Otherwise we calculate the bound
|
;; Otherwise we calculate the bound
|
||||||
(let [filter-bounds (->> filters
|
(let [filter-bounds (->> filters
|
||||||
@ -224,15 +224,14 @@
|
|||||||
filter-y (/ (- (:y bounds) (:y selrect) padding) (:height selrect))
|
filter-y (/ (- (:y bounds) (:y selrect) padding) (:height selrect))
|
||||||
filter-width (/ (+ (:width bounds) (* 2 padding)) (:width selrect))
|
filter-width (/ (+ (:width bounds) (* 2 padding)) (:width selrect))
|
||||||
filter-height (/ (+ (:height bounds) (* 2 padding)) (:height selrect))]
|
filter-height (/ (+ (:height bounds) (* 2 padding)) (:height selrect))]
|
||||||
[:*
|
(when (> (count filters) 2)
|
||||||
(when (> (count filters) 2)
|
[:filter {:id filter-id
|
||||||
[:filter {:id filter-id
|
:x filter-x
|
||||||
:x filter-x
|
:y filter-y
|
||||||
:y filter-y
|
:width filter-width
|
||||||
:width filter-width
|
:height filter-height
|
||||||
:height filter-height
|
:filterUnits "objectBoundingBox"
|
||||||
:filterUnits "objectBoundingBox"
|
:color-interpolation-filters "sRGB"}
|
||||||
:color-interpolation-filters "sRGB"}
|
(for [entry filters]
|
||||||
(for [entry filters]
|
[:& filter-entry {:entry entry}])])))
|
||||||
[:& filter-entry {:entry entry}])])]))
|
|
||||||
|
|
||||||
|
|||||||
@ -152,7 +152,7 @@
|
|||||||
(mf/deps fullscreen?)
|
(mf/deps fullscreen?)
|
||||||
(fn []
|
(fn []
|
||||||
;; Trigger dom fullscreen depending on our state
|
;; Trigger dom fullscreen depending on our state
|
||||||
(let [wrapper (dom/get-element "viewer-layout")
|
(let [wrapper (dom/get-element "viewer-layout")
|
||||||
fullscreen-dom? (dom/fullscreen?)]
|
fullscreen-dom? (dom/fullscreen?)]
|
||||||
(when (not= fullscreen? fullscreen-dom?)
|
(when (not= fullscreen? fullscreen-dom?)
|
||||||
(if fullscreen?
|
(if fullscreen?
|
||||||
|
|||||||
@ -69,31 +69,24 @@
|
|||||||
::mf/wrap-props false}
|
::mf/wrap-props false}
|
||||||
[props]
|
[props]
|
||||||
(let [shape (obj/get props "shape")
|
(let [shape (obj/get props "shape")
|
||||||
opts #js {:shape shape}
|
opts #js {:shape shape}]
|
||||||
|
|
||||||
svg-element? (and (= (:type shape) :svg-raw)
|
|
||||||
(not= :svg (get-in shape [:content :tag])))]
|
|
||||||
|
|
||||||
(when (and (some? shape) (not (:hidden shape)))
|
(when (and (some? shape) (not (:hidden shape)))
|
||||||
[:*
|
[:*
|
||||||
(if-not svg-element?
|
(case (:type shape)
|
||||||
(case (:type shape)
|
:path [:> path/path-wrapper opts]
|
||||||
:path [:> path/path-wrapper opts]
|
:text [:> text/text-wrapper opts]
|
||||||
:text [:> text/text-wrapper opts]
|
:group [:> group-wrapper opts]
|
||||||
:group [:> group-wrapper opts]
|
:rect [:> rect-wrapper opts]
|
||||||
:rect [:> rect-wrapper opts]
|
:image [:> image-wrapper opts]
|
||||||
:image [:> image-wrapper opts]
|
:circle [:> circle-wrapper opts]
|
||||||
:circle [:> circle-wrapper opts]
|
:svg-raw [:> svg-raw-wrapper opts]
|
||||||
:svg-raw [:> svg-raw-wrapper opts]
|
:bool [:> bool-wrapper opts]
|
||||||
:bool [:> bool-wrapper opts]
|
|
||||||
|
|
||||||
;; Only used when drawing a new frame.
|
;; Only used when drawing a new frame.
|
||||||
:frame [:> frame-wrapper opts]
|
:frame [:> frame-wrapper opts]
|
||||||
|
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
;; Don't wrap svg elements inside a <g> otherwise some can break
|
|
||||||
[:> svg-raw-wrapper opts])
|
|
||||||
|
|
||||||
(when (debug? :bounding-boxes)
|
(when (debug? :bounding-boxes)
|
||||||
[:> bounding-box opts])])))
|
[:> bounding-box opts])])))
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.ui.shapes.shape :refer [shape-container]]
|
[app.main.ui.shapes.shape :refer [shape-container]]
|
||||||
[app.main.ui.shapes.svg-raw :as svg-raw]
|
[app.main.ui.shapes.svg-raw :as svg-raw]
|
||||||
|
[app.util.svg :as usvg]
|
||||||
[rumext.alpha :as mf]))
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
(defn svg-raw-wrapper-factory
|
(defn svg-raw-wrapper-factory
|
||||||
@ -20,11 +21,9 @@
|
|||||||
[props]
|
[props]
|
||||||
(let [shape (unchecked-get props "shape")
|
(let [shape (unchecked-get props "shape")
|
||||||
childs-ref (mf/use-memo (mf/deps shape) #(refs/objects-by-id (:shapes shape)))
|
childs-ref (mf/use-memo (mf/deps shape) #(refs/objects-by-id (:shapes shape)))
|
||||||
childs (mf/deref childs-ref)]
|
childs (mf/deref childs-ref)
|
||||||
|
svg-tag (get-in shape [:content :tag])]
|
||||||
|
(if (contains? usvg/svg-group-safe-tags svg-tag)
|
||||||
(if (or (= (get-in shape [:content :tag]) :svg)
|
|
||||||
(and (contains? shape :svg-attrs) (map? (:content shape))))
|
|
||||||
[:> shape-container {:shape shape}
|
[:> shape-container {:shape shape}
|
||||||
[:& svg-raw-shape {:shape shape
|
[:& svg-raw-shape {:shape shape
|
||||||
:childs childs}]]
|
:childs childs}]]
|
||||||
|
|||||||
@ -235,11 +235,12 @@
|
|||||||
(defn translate-point-from-viewport
|
(defn translate-point-from-viewport
|
||||||
"Translate a point in the viewport into client coordinates"
|
"Translate a point in the viewport into client coordinates"
|
||||||
[pt viewport zoom]
|
[pt viewport zoom]
|
||||||
(let [vbox (.. ^js viewport -viewBox -baseVal)
|
(when (some? viewport)
|
||||||
box (gpt/point (.-x vbox) (.-y vbox))
|
(let [vbox (.. ^js viewport -viewBox -baseVal)
|
||||||
zoom (gpt/point zoom)]
|
box (gpt/point (.-x vbox) (.-y vbox))
|
||||||
(-> (gpt/subtract pt box)
|
zoom (gpt/point zoom)]
|
||||||
(gpt/multiply zoom))))
|
(-> (gpt/subtract pt box)
|
||||||
|
(gpt/multiply zoom)))))
|
||||||
|
|
||||||
(mf/defc text-editor-viewport
|
(mf/defc text-editor-viewport
|
||||||
{::mf/wrap-props false}
|
{::mf/wrap-props false}
|
||||||
|
|||||||
@ -60,17 +60,19 @@
|
|||||||
(s/def ::file-id ::us/uuid)
|
(s/def ::file-id ::us/uuid)
|
||||||
(s/def ::object-id ::us/uuid)
|
(s/def ::object-id ::us/uuid)
|
||||||
(s/def ::render-text ::us/boolean)
|
(s/def ::render-text ::us/boolean)
|
||||||
|
(s/def ::embed ::us/boolean)
|
||||||
|
|
||||||
(s/def ::render-object-params
|
(s/def ::render-object-params
|
||||||
(s/keys :req-un [::file-id ::page-id ::object-id]
|
(s/keys :req-un [::file-id ::page-id ::object-id]
|
||||||
:opt-un [::render-text]))
|
:opt-un [::render-text ::embed]))
|
||||||
|
|
||||||
(defn- render-object
|
(defn- render-object
|
||||||
[params]
|
[params]
|
||||||
(let [{:keys [page-id file-id object-id render-texts]} (us/conform ::render-object-params params)]
|
(let [{:keys [page-id file-id object-id render-texts embed]} (us/conform ::render-object-params params)]
|
||||||
(mf/html
|
(mf/html
|
||||||
[:& render/render-object
|
[:& render/render-object
|
||||||
{:file-id file-id
|
{:file-id file-id
|
||||||
:page-id page-id
|
:page-id page-id
|
||||||
:object-id object-id
|
:object-id object-id
|
||||||
:render-texts? (and (some? render-texts) (= render-texts "true"))}])))
|
:embed? embed
|
||||||
|
:render-texts? render-texts}])))
|
||||||
|
|||||||
@ -7,14 +7,16 @@
|
|||||||
(ns app.util.dom
|
(ns app.util.dom
|
||||||
(:require
|
(:require
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.exceptions :as ex]
|
|
||||||
[app.common.geom.point :as gpt]
|
[app.common.geom.point :as gpt]
|
||||||
|
[app.common.logging :as log]
|
||||||
[app.util.globals :as globals]
|
[app.util.globals :as globals]
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[goog.dom :as dom]
|
[goog.dom :as dom]
|
||||||
[promesa.core :as p]))
|
[promesa.core :as p]))
|
||||||
|
|
||||||
|
(log/set-level! :warn)
|
||||||
|
|
||||||
;; --- Deprecated methods
|
;; --- Deprecated methods
|
||||||
|
|
||||||
(defn event->inner-text
|
(defn event->inner-text
|
||||||
@ -306,8 +308,9 @@
|
|||||||
(boolean (.-fullscreenElement globals/document))
|
(boolean (.-fullscreenElement globals/document))
|
||||||
|
|
||||||
:else
|
:else
|
||||||
(ex/raise :type :not-supported
|
(do
|
||||||
:hint "seems like the current browser does not support fullscreen api.")))
|
(log/error :msg "Seems like the current browser does not support fullscreen api.")
|
||||||
|
false)))
|
||||||
|
|
||||||
(defn ^boolean blob?
|
(defn ^boolean blob?
|
||||||
[^js v]
|
[^js v]
|
||||||
|
|||||||
@ -458,6 +458,49 @@
|
|||||||
:feTile
|
:feTile
|
||||||
:feTurbulence})
|
:feTurbulence})
|
||||||
|
|
||||||
|
;; By spec: https://www.w3.org/TR/SVG11/single-page.html#struct-GElement
|
||||||
|
(defonce svg-group-safe-tags
|
||||||
|
#{:animate
|
||||||
|
:animateColor
|
||||||
|
:animateMotion
|
||||||
|
:animateTransform
|
||||||
|
:set
|
||||||
|
:desc
|
||||||
|
:metadata
|
||||||
|
:title
|
||||||
|
:circle
|
||||||
|
:ellipse
|
||||||
|
:line
|
||||||
|
:path
|
||||||
|
:polygon
|
||||||
|
:polyline
|
||||||
|
:rect
|
||||||
|
:defs
|
||||||
|
:g
|
||||||
|
:svg
|
||||||
|
:symbol
|
||||||
|
:use
|
||||||
|
:linearGradient
|
||||||
|
:radialGradient
|
||||||
|
:a
|
||||||
|
:altGlyphDef
|
||||||
|
:clipPath
|
||||||
|
:color-profile
|
||||||
|
:cursor
|
||||||
|
:filter
|
||||||
|
:font
|
||||||
|
:font-face
|
||||||
|
:foreignObject
|
||||||
|
:image
|
||||||
|
:marker
|
||||||
|
:mask
|
||||||
|
:pattern
|
||||||
|
:script
|
||||||
|
:style
|
||||||
|
:switch
|
||||||
|
:text
|
||||||
|
:view})
|
||||||
|
|
||||||
;; Props not supported by react we need to keep them lowercase
|
;; Props not supported by react we need to keep them lowercase
|
||||||
(defonce non-react-props
|
(defonce non-react-props
|
||||||
#{:mask-type})
|
#{:mask-type})
|
||||||
|
|||||||
@ -8,11 +8,13 @@
|
|||||||
"HTML5 web api helpers."
|
"HTML5 web api helpers."
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.exceptions :as ex]
|
[app.common.logging :as log]
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
[cuerdas.core :as str]))
|
[cuerdas.core :as str]))
|
||||||
|
|
||||||
|
(log/set-level! :warn)
|
||||||
|
|
||||||
(defn- file-reader
|
(defn- file-reader
|
||||||
[f]
|
[f]
|
||||||
(rx/create
|
(rx/create
|
||||||
@ -114,8 +116,9 @@
|
|||||||
(.webkitRequestFullscreen el)
|
(.webkitRequestFullscreen el)
|
||||||
|
|
||||||
:else
|
:else
|
||||||
(ex/raise :type :not-supported
|
(do
|
||||||
:hint "seems like the current browser does not support fullscreen api.")))
|
(log/error :msg "Seems like the current browser does not support fullscreen api.")
|
||||||
|
false)))
|
||||||
|
|
||||||
(defn exit-fullscreen
|
(defn exit-fullscreen
|
||||||
[]
|
[]
|
||||||
@ -127,8 +130,9 @@
|
|||||||
(.webkitExitFullscreen js/document)
|
(.webkitExitFullscreen js/document)
|
||||||
|
|
||||||
:else
|
:else
|
||||||
(ex/raise :type :not-supported
|
(do
|
||||||
:hint "seems like the current browser does not support fullscreen api.")))
|
(log/error :msg "Seems like the current browser does not support fullscreen api.")
|
||||||
|
false)))
|
||||||
|
|
||||||
(defn observe-resize
|
(defn observe-resize
|
||||||
[node]
|
[node]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user