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

This commit is contained in:
Andrey Antukh 2022-03-22 15:01:43 +01:00
commit a4fbc050cc
32 changed files with 575 additions and 390 deletions

View File

@ -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!)

View File

@ -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)

View File

@ -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

View File

@ -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))

View File

@ -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

View File

@ -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

View File

@ -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)

View 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]))

View File

@ -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)

View File

@ -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"

View File

@ -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))

View File

@ -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 {

View File

@ -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))))))

View File

@ -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)

View File

@ -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

View File

@ -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))))))

View File

@ -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}]

View File

@ -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

View File

@ -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"})

View File

@ -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}]

View File

@ -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}])))

View File

@ -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)

View File

@ -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)

View File

@ -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}])])]))

View File

@ -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?

View File

@ -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])])))

View File

@ -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}]]

View File

@ -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}

View File

@ -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}])))

View File

@ -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]

View File

@ -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})

View File

@ -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]