mirror of
https://github.com/penpot/penpot.git
synced 2025-02-10 00:58:26 -05:00
Merge remote-tracking branch 'origin/staging' into develop
This commit is contained in:
commit
3bb5db6490
24 changed files with 554 additions and 129 deletions
|
@ -33,6 +33,12 @@
|
|||
- Fix expand libraries when search results are present [Taiga #7876](https://tree.taiga.io/project/penpot/issue/7876)
|
||||
- Fix color palette default library [Taiga #8029](https://tree.taiga.io/project/penpot/issue/8029)
|
||||
- Component Library is lost after exporting/importing in .zip format [Github #4672](https://github.com/penpot/penpot/issues/4672)
|
||||
- Fix problem with moving+selection not working properly [Taiga #7943](https://tree.taiga.io/project/penpot/issue/7943)
|
||||
- Fix problem with flex layout fit to content not positioning correctly children [Taiga #7537](https://tree.taiga.io/project/penpot/issue/7537)
|
||||
- Fix black line is displaying after show main [Taiga #7653](https://tree.taiga.io/project/penpot/issue/7653)
|
||||
- Fix "Share prototypes" modal remains open [Taiga #7442](https://tree.taiga.io/project/penpot/issue/7442)
|
||||
- Fix "Components visibility and opacity" [#4694](https://github.com/penpot/penpot/issues/4694)
|
||||
- Fix "Attribute overrides in copies are not exported in zip file" [Taiga #8072](https://tree.taiga.io/project/penpot/issue/8072)
|
||||
|
||||
## 2.0.3
|
||||
|
||||
|
|
|
@ -14,30 +14,38 @@
|
|||
[clojure.core :as c]
|
||||
[clojure.java.io :as io]
|
||||
[cuerdas.core :as str]
|
||||
[datoteka.fs :as fs]
|
||||
[integrant.core :as ig]))
|
||||
|
||||
(defn- read-whitelist
|
||||
[path]
|
||||
(when (and path (fs/exists? path))
|
||||
(try
|
||||
(with-open [reader (io/reader path)]
|
||||
(reduce (fn [result line]
|
||||
(if (str/starts-with? line "#")
|
||||
result
|
||||
(conj result (-> line str/trim str/lower))))
|
||||
#{}
|
||||
(line-seq reader)))
|
||||
|
||||
(catch Throwable cause
|
||||
(l/wrn :hint "unexpected exception on reading email whitelist"
|
||||
:cause cause)))))
|
||||
|
||||
(defmethod ig/init-key ::email/whitelist
|
||||
[_ _]
|
||||
(when (c/contains? cf/flags :email-whitelist)
|
||||
(try
|
||||
(let [path (cf/get :email-domain-whitelist)
|
||||
result (with-open [reader (io/reader path)]
|
||||
(reduce (fn [result line]
|
||||
(if (str/starts-with? line "#")
|
||||
result
|
||||
(conj result (-> line str/trim str/lower))))
|
||||
#{}
|
||||
(line-seq reader)))
|
||||
(let [whitelist (or (cf/get :registration-domain-whitelist) #{})
|
||||
whitelist (if (c/contains? cf/flags :email-whitelist)
|
||||
(into whitelist (read-whitelist (cf/get :email-domain-whitelist)))
|
||||
whitelist)
|
||||
whitelist (not-empty whitelist)]
|
||||
|
||||
;; backward comapatibility with previous way to set a
|
||||
;; whitelist for email domains
|
||||
result (into result (cf/get :registration-domain-whitelist))]
|
||||
|
||||
(l/inf :hint "initializing email whitelist" :domains (count result))
|
||||
(not-empty result))
|
||||
(catch Throwable cause
|
||||
(l/wrn :hint "unexpected exception on initializing email whitelist"
|
||||
:cause cause)))))
|
||||
(when whitelist
|
||||
(l/inf :hint "initializing email whitelist" :domains (count whitelist)))
|
||||
|
||||
whitelist))
|
||||
|
||||
(defn contains?
|
||||
"Check if email is in the whitelist."
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
[app.common.features :as cfeat]
|
||||
[app.common.logging :as l]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.spec :as us]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.db :as db]
|
||||
|
@ -32,16 +31,10 @@
|
|||
[app.util.services :as sv]
|
||||
[app.util.time :as dt]
|
||||
[app.worker :as wrk]
|
||||
[clojure.spec.alpha :as s]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
;; --- Helpers & Specs
|
||||
|
||||
(s/def ::id ::us/uuid)
|
||||
(s/def ::name ::us/string)
|
||||
(s/def ::file-id ::us/uuid)
|
||||
(s/def ::team-id ::us/uuid)
|
||||
|
||||
(def ^:private sql:team-permissions
|
||||
"select tpr.is_owner,
|
||||
tpr.is_admin,
|
||||
|
@ -351,7 +344,7 @@
|
|||
|
||||
(def ^:private schema:create-team
|
||||
[:map {:title "create-team"}
|
||||
[:name :string]
|
||||
[:name [:string {:max 250}]]
|
||||
[:features {:optional true} ::cfeat/features]
|
||||
[:id {:optional true} ::sm/uuid]])
|
||||
|
||||
|
@ -438,12 +431,14 @@
|
|||
|
||||
;; --- Mutation: Update Team
|
||||
|
||||
(s/def ::update-team
|
||||
(s/keys :req [::rpc/profile-id]
|
||||
:req-un [::name ::id]))
|
||||
(def ^:private schema:update-team
|
||||
[:map {:title "update-team"}
|
||||
[:name [:string {:max 250}]]
|
||||
[:id ::sm/uuid]])
|
||||
|
||||
(sv/defmethod ::update-team
|
||||
{::doc/added "1.17"}
|
||||
{::doc/added "1.17"
|
||||
::sm/params schema:update-team}
|
||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id id name] :as params}]
|
||||
(db/with-atomic [conn pool]
|
||||
(check-edition-permissions! conn profile-id id)
|
||||
|
@ -503,14 +498,14 @@
|
|||
|
||||
nil))
|
||||
|
||||
(s/def ::reassign-to ::us/uuid)
|
||||
(s/def ::leave-team
|
||||
(s/keys :req [::rpc/profile-id]
|
||||
:req-un [::id]
|
||||
:opt-un [::reassign-to]))
|
||||
(def ^:private schema:leave-team
|
||||
[:map {:title "leave-team"}
|
||||
[:id ::sm/uuid]
|
||||
[:reassign-to {:optional true} ::sm/uuid]])
|
||||
|
||||
(sv/defmethod ::leave-team
|
||||
{::doc/added "1.17"}
|
||||
{::doc/added "1.17"
|
||||
::sm/params schema:leave-team}
|
||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id] :as params}]
|
||||
(db/with-atomic [conn pool]
|
||||
(leave-team conn (assoc params :profile-id profile-id))))
|
||||
|
@ -539,12 +534,13 @@
|
|||
:id team-id})
|
||||
team))
|
||||
|
||||
(s/def ::delete-team
|
||||
(s/keys :req [::rpc/profile-id]
|
||||
:req-un [::id]))
|
||||
(def ^:private schema:delete-team
|
||||
[:map {:title "delete-team"}
|
||||
[:id ::sm/uuid]])
|
||||
|
||||
(sv/defmethod ::delete-team
|
||||
{::doc/added "1.17"}
|
||||
{::doc/added "1.17"
|
||||
::sm/params schema:delete-team}
|
||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id id] :as params}]
|
||||
(db/with-atomic [conn pool]
|
||||
(let [perms (get-permissions conn profile-id id)]
|
||||
|
@ -557,10 +553,6 @@
|
|||
|
||||
;; --- Mutation: Team Update Role
|
||||
|
||||
(s/def ::team-id ::us/uuid)
|
||||
(s/def ::member-id ::us/uuid)
|
||||
(s/def ::role #{:owner :admin :editor})
|
||||
|
||||
;; Temporarily disabled viewer role
|
||||
;; https://tree.taiga.io/project/penpot/issue/1083
|
||||
(def valid-roles
|
||||
|
@ -624,25 +616,29 @@
|
|||
:profile-id member-id})
|
||||
nil)))
|
||||
|
||||
(s/def ::update-team-member-role
|
||||
(s/keys :req [::rpc/profile-id]
|
||||
:req-un [::team-id ::member-id ::role]))
|
||||
(def ^:private schema:update-team-member-role
|
||||
[:map {:title "update-team-member-role"}
|
||||
[:team-id ::sm/uuid]
|
||||
[:member-id ::sm/uuid]
|
||||
[:role schema:role]])
|
||||
|
||||
(sv/defmethod ::update-team-member-role
|
||||
{::doc/added "1.17"}
|
||||
{::doc/added "1.17"
|
||||
::sm/params schema:update-team-member-role}
|
||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id] :as params}]
|
||||
(db/with-atomic [conn pool]
|
||||
(update-team-member-role conn (assoc params :profile-id profile-id))))
|
||||
|
||||
|
||||
;; --- Mutation: Delete Team Member
|
||||
|
||||
(s/def ::delete-team-member
|
||||
(s/keys :req [::rpc/profile-id]
|
||||
:req-un [::team-id ::member-id]))
|
||||
(def ^:private schema:delete-team-member
|
||||
[:map {:title "delete-team-member"}
|
||||
[:team-id ::sm/uuid]
|
||||
[:member-id ::sm/uuid]])
|
||||
|
||||
(sv/defmethod ::delete-team-member
|
||||
{::doc/added "1.17"}
|
||||
{::doc/added "1.17"
|
||||
::sm/params schema:delete-team-member}
|
||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id member-id] :as params}]
|
||||
(db/with-atomic [conn pool]
|
||||
(let [perms (get-permissions conn profile-id team-id)]
|
||||
|
@ -665,13 +661,14 @@
|
|||
(declare upload-photo)
|
||||
(declare ^:private update-team-photo)
|
||||
|
||||
(s/def ::file ::media/upload)
|
||||
(s/def ::update-team-photo
|
||||
(s/keys :req [::rpc/profile-id]
|
||||
:req-un [::team-id ::file]))
|
||||
(def ^:private schema:update-team-photo
|
||||
[:map {:title "update-team-photo"}
|
||||
[:team-id ::sm/uuid]
|
||||
[:file ::media/upload]])
|
||||
|
||||
(sv/defmethod ::update-team-photo
|
||||
{::doc/added "1.17"}
|
||||
{::doc/added "1.17"
|
||||
::sm/params schema:update-team-photo}
|
||||
[cfg {:keys [::rpc/profile-id file] :as params}]
|
||||
;; Validate incoming mime type
|
||||
(media/validate-media-type! file #{"image/jpeg" "image/png" "image/webp"})
|
||||
|
@ -809,7 +806,7 @@
|
|||
(def ^:private schema:create-team-invitations
|
||||
[:map {:title "create-team-invitations"}
|
||||
[:team-id ::sm/uuid]
|
||||
[:role [::sm/one-of #{:owner :admin :editor}]]
|
||||
[:role schema:role]
|
||||
[:emails ::sm/set-of-emails]])
|
||||
|
||||
(sv/defmethod ::create-team-invitations
|
||||
|
@ -866,12 +863,6 @@
|
|||
|
||||
;; --- Mutation: Create Team & Invite Members
|
||||
|
||||
(s/def ::emails ::us/set-of-valid-emails)
|
||||
(s/def ::create-team-with-invitations
|
||||
(s/merge ::create-team
|
||||
(s/keys :req-un [::emails ::role])))
|
||||
|
||||
|
||||
(def ^:private schema:create-team-with-invitations
|
||||
[:map {:title "create-team-with-invitations"}
|
||||
[:name :string]
|
||||
|
@ -930,12 +921,14 @@
|
|||
|
||||
;; --- Query: get-team-invitation-token
|
||||
|
||||
(s/def ::get-team-invitation-token
|
||||
(s/keys :req [::rpc/profile-id]
|
||||
:req-un [::team-id ::email]))
|
||||
(def ^:private schema:get-team-invitation-token
|
||||
[:map {:title "get-team-invitation-token"}
|
||||
[:team-id ::sm/uuid]
|
||||
[:email ::sm/email]])
|
||||
|
||||
(sv/defmethod ::get-team-invitation-token
|
||||
{::doc/added "1.17"}
|
||||
{::doc/added "1.17"
|
||||
::sm/params schema:get-team-invitation-token}
|
||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id email] :as params}]
|
||||
(check-read-permissions! pool profile-id team-id)
|
||||
(let [email (profile/clean-email email)
|
||||
|
@ -956,12 +949,15 @@
|
|||
|
||||
;; --- Mutation: Update invitation role
|
||||
|
||||
(s/def ::update-team-invitation-role
|
||||
(s/keys :req [::rpc/profile-id]
|
||||
:req-un [::team-id ::email ::role]))
|
||||
(def ^:private schema:update-team-invitation-role
|
||||
[:map {:title "update-team-invitation-role"}
|
||||
[:team-id ::sm/uuid]
|
||||
[:email ::sm/email]
|
||||
[:role schema:role]])
|
||||
|
||||
(sv/defmethod ::update-team-invitation-role
|
||||
{::doc/added "1.17"}
|
||||
{::doc/added "1.17"
|
||||
::sm/params schema:update-team-invitation-role}
|
||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id email role] :as params}]
|
||||
(db/with-atomic [conn pool]
|
||||
(let [perms (get-permissions conn profile-id team-id)]
|
||||
|
@ -977,12 +973,14 @@
|
|||
|
||||
;; --- Mutation: Delete invitation
|
||||
|
||||
(s/def ::delete-team-invitation
|
||||
(s/keys :req [::rpc/profile-id]
|
||||
:req-un [::team-id ::email]))
|
||||
(def ^:private schema:delete-team-invition
|
||||
[:map {:title "delete-team-invitation"}
|
||||
[:team-id ::sm/uuid]
|
||||
[:email ::sm/email]])
|
||||
|
||||
(sv/defmethod ::delete-team-invitation
|
||||
{::doc/added "1.17"}
|
||||
{::doc/added "1.17"
|
||||
::sm/params schema:delete-team-invition}
|
||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id email] :as params}]
|
||||
(db/with-atomic [conn pool]
|
||||
(let [perms (get-permissions conn profile-id team-id)]
|
||||
|
|
|
@ -8,18 +8,19 @@
|
|||
"Tokens generation API."
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.spec :as us]
|
||||
[app.common.transit :as t]
|
||||
[app.util.time :as dt]
|
||||
[buddy.sign.jwe :as jwe]
|
||||
[clojure.spec.alpha :as s]))
|
||||
|
||||
(s/def ::tokens-key bytes?)
|
||||
[buddy.sign.jwe :as jwe]))
|
||||
|
||||
(defn generate
|
||||
[{:keys [tokens-key]} claims]
|
||||
(us/assert! ::tokens-key tokens-key)
|
||||
|
||||
(dm/assert!
|
||||
"expexted token-key to be bytes instance"
|
||||
(bytes? tokens-key))
|
||||
|
||||
(let [payload (-> claims
|
||||
(assoc :iat (dt/now))
|
||||
(d/without-nils)
|
||||
|
@ -39,15 +40,13 @@
|
|||
(ex/raise :type :validation
|
||||
:code :invalid-token
|
||||
:reason :token-expired
|
||||
:params params
|
||||
:claims claims))
|
||||
:params params))
|
||||
(when (and (contains? params :iss)
|
||||
(not= (:iss claims)
|
||||
(:iss params)))
|
||||
(ex/raise :type :validation
|
||||
:code :invalid-token
|
||||
:reason :invalid-issuer
|
||||
:claims claims
|
||||
:params params))
|
||||
claims))
|
||||
|
||||
|
|
|
@ -224,7 +224,6 @@
|
|||
[coll]
|
||||
(into [] (remove nil?) coll))
|
||||
|
||||
|
||||
(defn without-nils
|
||||
"Given a map, return a map removing key-value
|
||||
pairs when value is `nil`."
|
||||
|
|
|
@ -269,6 +269,13 @@
|
|||
(keep (mk-check-auto-layout objects))
|
||||
shapes)))
|
||||
|
||||
(defn full-tree?
|
||||
"Checks if we need to calculate the full tree or we can calculate just a partial tree. Partial
|
||||
trees are more efficient but cannot be done when the layout is centered."
|
||||
[objects layout-id]
|
||||
(let [layout-justify-content (get-in objects [layout-id :layout-justify-content])]
|
||||
(contains? #{:center :end :space-around :space-evenly :stretch} layout-justify-content)))
|
||||
|
||||
(defn sizing-auto-modifiers
|
||||
"Recalculates the layouts to adjust the sizing: auto new sizes"
|
||||
[modif-tree sizing-auto-layouts objects bounds ignore-constraints]
|
||||
|
@ -286,7 +293,7 @@
|
|||
(d/seek sizing-auto-layouts))
|
||||
|
||||
shapes
|
||||
(if from-layout
|
||||
(if (and from-layout (not (full-tree? objects from-layout)))
|
||||
(cgst/resolve-subtree from-layout layout-id objects)
|
||||
(cgst/resolve-tree #{layout-id} objects))
|
||||
|
||||
|
|
|
@ -189,14 +189,20 @@
|
|||
(when swap-slot
|
||||
(keyword (str "swap-slot-" swap-slot))))
|
||||
|
||||
(defn swap-slot?
|
||||
[group]
|
||||
(str/starts-with? (name group) "swap-slot-"))
|
||||
|
||||
(defn group->swap-slot
|
||||
[group]
|
||||
(uuid/uuid (subs (name group) 10)))
|
||||
|
||||
(defn get-swap-slot
|
||||
"If the shape has a :touched group in the form :swap-slot-<uuid>, get the id."
|
||||
[shape]
|
||||
(let [group (->> (:touched shape)
|
||||
(map name)
|
||||
(d/seek #(str/starts-with? % "swap-slot-")))]
|
||||
(let [group (d/seek swap-slot? (:touched shape))]
|
||||
(when group
|
||||
(uuid/uuid (subs group 10)))))
|
||||
(group->swap-slot group))))
|
||||
|
||||
(defn match-swap-slot?
|
||||
[shape-main shape-inst]
|
||||
|
@ -264,3 +270,16 @@
|
|||
;; Non instance, non copy. We allow
|
||||
(or (not (instance-head? shape))
|
||||
(not (in-component-copy? parent))))))
|
||||
|
||||
(defn all-touched-groups
|
||||
[]
|
||||
(into #{} (vals sync-attrs)))
|
||||
|
||||
(defn valid-touched-group?
|
||||
[group]
|
||||
(try
|
||||
(or ((all-touched-groups) group)
|
||||
(and (swap-slot? group)
|
||||
(some? (group->swap-slot group))))
|
||||
(catch #?(:clj Throwable :cljs :default) _
|
||||
false)))
|
43
common/test/common_tests/types/types_component_test.cljc
Normal file
43
common/test/common_tests/types/types_component_test.cljc
Normal file
|
@ -0,0 +1,43 @@
|
|||
;; 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) KALEIDOS INC
|
||||
|
||||
(ns common-tests.types.types-component-test
|
||||
(:require
|
||||
[app.common.test-helpers.ids-map :as thi]
|
||||
[app.common.test-helpers.shapes :as ths]
|
||||
[app.common.types.component :as ctk]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(t/use-fixtures :each thi/test-fixture)
|
||||
|
||||
(t/deftest test-valid-touched-group
|
||||
(t/is (ctk/valid-touched-group? :name-group))
|
||||
(t/is (ctk/valid-touched-group? :geometry-group))
|
||||
(t/is (ctk/valid-touched-group? :swap-slot-9cc181fa-5eef-8084-8004-7bb2ab45fd1f))
|
||||
(t/is (not (ctk/valid-touched-group? :this-is-not-a-group)))
|
||||
(t/is (not (ctk/valid-touched-group? :swap-slot-)))
|
||||
(t/is (not (ctk/valid-touched-group? :swap-slot-xxxxxx)))
|
||||
(t/is (not (ctk/valid-touched-group? :swap-slot-9cc181fa-5eef-8084-8004)))
|
||||
(t/is (not (ctk/valid-touched-group? nil))))
|
||||
|
||||
(t/deftest test-get-swap-slot
|
||||
(let [s1 (ths/sample-shape :s1)
|
||||
s2 (ths/sample-shape :s2 :touched #{:visibility-group})
|
||||
s3 (ths/sample-shape :s3 :touched #{:swap-slot-9cc181fa-5eef-8084-8004-7bb2ab45fd1f})
|
||||
s4 (ths/sample-shape :s4 :touched #{:fill-group
|
||||
:swap-slot-9cc181fa-5eef-8084-8004-7bb2ab45fd1f})
|
||||
s5 (ths/sample-shape :s5 :touched #{:swap-slot-9cc181fa-5eef-8084-8004-7bb2ab45fd1f
|
||||
:content-group
|
||||
:geometry-group})
|
||||
s6 (ths/sample-shape :s6 :touched #{:swap-slot-9cc181fa})]
|
||||
(t/is (nil? (ctk/get-swap-slot s1)))
|
||||
(t/is (nil? (ctk/get-swap-slot s2)))
|
||||
(t/is (= (ctk/get-swap-slot s3) #uuid "9cc181fa-5eef-8084-8004-7bb2ab45fd1f"))
|
||||
(t/is (= (ctk/get-swap-slot s4) #uuid "9cc181fa-5eef-8084-8004-7bb2ab45fd1f"))
|
||||
(t/is (= (ctk/get-swap-slot s5) #uuid "9cc181fa-5eef-8084-8004-7bb2ab45fd1f"))
|
||||
#?(:clj
|
||||
(t/is (thrown-with-msg? IllegalArgumentException #"Invalid UUID string"
|
||||
(ctk/get-swap-slot s6))))))
|
|
@ -0,0 +1,186 @@
|
|||
{
|
||||
"~:id": "~udd5cc0bb-91ff-81b9-8004-77dfae2d9e7c",
|
||||
"~:file-id": "~udd5cc0bb-91ff-81b9-8004-77df9cd3edb1",
|
||||
"~:created-at": "~m1717759268004",
|
||||
"~:content": {
|
||||
"~:options": {},
|
||||
"~:objects": {
|
||||
"~u00000000-0000-0000-0000-000000000000": {
|
||||
"~#shape": {
|
||||
"~:y": 0,
|
||||
"~:hide-fill-on-export": false,
|
||||
"~:transform": {
|
||||
"~#matrix": {
|
||||
"~:a": 1.0,
|
||||
"~:b": 0.0,
|
||||
"~:c": 0.0,
|
||||
"~:d": 1.0,
|
||||
"~:e": 0.0,
|
||||
"~:f": 0.0
|
||||
}
|
||||
},
|
||||
"~:rotation": 0,
|
||||
"~:name": "Root Frame",
|
||||
"~:width": 0.01,
|
||||
"~:type": "~:frame",
|
||||
"~:points": [
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 0,
|
||||
"~:y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 0.01,
|
||||
"~:y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 0.01,
|
||||
"~:y": 0.01
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 0,
|
||||
"~:y": 0.01
|
||||
}
|
||||
}
|
||||
],
|
||||
"~:proportion-lock": false,
|
||||
"~:transform-inverse": {
|
||||
"~#matrix": {
|
||||
"~:a": 1.0,
|
||||
"~:b": 0.0,
|
||||
"~:c": 0.0,
|
||||
"~:d": 1.0,
|
||||
"~:e": 0.0,
|
||||
"~:f": 0.0
|
||||
}
|
||||
},
|
||||
"~:id": "~u00000000-0000-0000-0000-000000000000",
|
||||
"~:parent-id": "~u00000000-0000-0000-0000-000000000000",
|
||||
"~:frame-id": "~u00000000-0000-0000-0000-000000000000",
|
||||
"~:strokes": [],
|
||||
"~:x": 0,
|
||||
"~:proportion": 1.0,
|
||||
"~:selrect": {
|
||||
"~#rect": {
|
||||
"~:x": 0,
|
||||
"~:y": 0,
|
||||
"~:width": 0.01,
|
||||
"~:height": 0.01,
|
||||
"~:x1": 0,
|
||||
"~:y1": 0,
|
||||
"~:x2": 0.01,
|
||||
"~:y2": 0.01
|
||||
}
|
||||
},
|
||||
"~:fills": [
|
||||
{
|
||||
"~:fill-color": "#FFFFFF",
|
||||
"~:fill-opacity": 1
|
||||
}
|
||||
],
|
||||
"~:flip-x": null,
|
||||
"~:height": 0.01,
|
||||
"~:flip-y": null,
|
||||
"~:shapes": [
|
||||
"~uec508673-9e3b-80bf-8004-77dfa30a2b13"
|
||||
]
|
||||
}
|
||||
},
|
||||
"~uec508673-9e3b-80bf-8004-77dfa30a2b13": {
|
||||
"~#shape": {
|
||||
"~:y": 0,
|
||||
"~:hide-fill-on-export": false,
|
||||
"~:transform": {
|
||||
"~#matrix": {
|
||||
"~:a": 1.0,
|
||||
"~:b": 0.0,
|
||||
"~:c": 0.0,
|
||||
"~:d": 1.0,
|
||||
"~:e": 0.0,
|
||||
"~:f": 0.0
|
||||
}
|
||||
},
|
||||
"~:rotation": 0,
|
||||
"~:grow-type": "~:fixed",
|
||||
"~:hide-in-viewer": false,
|
||||
"~:name": "Board",
|
||||
"~:width": 256.00000000000006,
|
||||
"~:type": "~:frame",
|
||||
"~:points": [
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 0,
|
||||
"~:y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 256.00000000000006,
|
||||
"~:y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 256.00000000000006,
|
||||
"~:y": 256
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 0,
|
||||
"~:y": 256
|
||||
}
|
||||
}
|
||||
],
|
||||
"~:proportion-lock": false,
|
||||
"~:transform-inverse": {
|
||||
"~#matrix": {
|
||||
"~:a": 1.0,
|
||||
"~:b": 0.0,
|
||||
"~:c": 0.0,
|
||||
"~:d": 1.0,
|
||||
"~:e": 0.0,
|
||||
"~:f": 0.0
|
||||
}
|
||||
},
|
||||
"~:id": "~uec508673-9e3b-80bf-8004-77dfa30a2b13",
|
||||
"~:parent-id": "~u00000000-0000-0000-0000-000000000000",
|
||||
"~:frame-id": "~u00000000-0000-0000-0000-000000000000",
|
||||
"~:strokes": [],
|
||||
"~:x": 0,
|
||||
"~:proportion": 1,
|
||||
"~:selrect": {
|
||||
"~#rect": {
|
||||
"~:x": 0,
|
||||
"~:y": 0,
|
||||
"~:width": 256.00000000000006,
|
||||
"~:height": 256,
|
||||
"~:x1": 0,
|
||||
"~:y1": 0,
|
||||
"~:x2": 256.00000000000006,
|
||||
"~:y2": 256
|
||||
}
|
||||
},
|
||||
"~:fills": [
|
||||
{
|
||||
"~:fill-color": "#FFFFFF",
|
||||
"~:fill-opacity": 1
|
||||
}
|
||||
],
|
||||
"~:flip-x": null,
|
||||
"~:height": 256,
|
||||
"~:flip-y": null,
|
||||
"~:shapes": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"~:id": "~udd5cc0bb-91ff-81b9-8004-77df9cd3edb2",
|
||||
"~:name": "Page 1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
{
|
||||
"~:users": [
|
||||
{
|
||||
"~:id": "~u0515a066-e303-8169-8004-73eb4018f4e0",
|
||||
"~:email": "leia@example.com",
|
||||
"~:name": "Princesa Leia",
|
||||
"~:fullname": "Princesa Leia",
|
||||
"~:is-active": true
|
||||
}
|
||||
],
|
||||
"~:fonts": [],
|
||||
"~:project": {
|
||||
"~:id": "~u0515a066-e303-8169-8004-73eb401b5d55",
|
||||
"~:name": "Drafts",
|
||||
"~:team-id": "~u0515a066-e303-8169-8004-73eb401977a6"
|
||||
},
|
||||
"~:share-links": [],
|
||||
"~:libraries": [],
|
||||
"~:file": {
|
||||
"~:features": {
|
||||
"~#set": [
|
||||
"layout/grid",
|
||||
"styles/v2",
|
||||
"fdata/pointer-map",
|
||||
"fdata/objects-map",
|
||||
"components/v2",
|
||||
"fdata/shape-data-type"
|
||||
]
|
||||
},
|
||||
"~:has-media-trimmed": false,
|
||||
"~:comment-thread-seqn": 0,
|
||||
"~:name": "New File 3",
|
||||
"~:revn": 1,
|
||||
"~:modified-at": "~m1717759268010",
|
||||
"~:id": "~udd5cc0bb-91ff-81b9-8004-77df9cd3edb1",
|
||||
"~:is-shared": false,
|
||||
"~:version": 48,
|
||||
"~:project-id": "~u0515a066-e303-8169-8004-73eb401b5d55",
|
||||
"~:created-at": "~m1717759250257",
|
||||
"~:data": {
|
||||
"~:id": "~udd5cc0bb-91ff-81b9-8004-77df9cd3edb1",
|
||||
"~:options": {
|
||||
"~:components-v2": true
|
||||
},
|
||||
"~:pages": [
|
||||
"~udd5cc0bb-91ff-81b9-8004-77df9cd3edb2"
|
||||
],
|
||||
"~:pages-index": {
|
||||
"~udd5cc0bb-91ff-81b9-8004-77df9cd3edb2": {
|
||||
"~#penpot/pointer": [
|
||||
"~udd5cc0bb-91ff-81b9-8004-77dfae2d9e7c",
|
||||
{
|
||||
"~:created-at": "~m1717759268024"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"~:team": {
|
||||
"~:id": "~u0515a066-e303-8169-8004-73eb401977a6",
|
||||
"~:created-at": "~m1717493865581",
|
||||
"~:modified-at": "~m1717493865581",
|
||||
"~:name": "Default",
|
||||
"~:is-default": true,
|
||||
"~:features": {
|
||||
"~#set": [
|
||||
"layout/grid",
|
||||
"styles/v2",
|
||||
"fdata/pointer-map",
|
||||
"fdata/objects-map",
|
||||
"components/v2",
|
||||
"fdata/shape-data-type"
|
||||
]
|
||||
}
|
||||
},
|
||||
"~:permissions": {
|
||||
"~:type": "~:membership",
|
||||
"~:is-owner": true,
|
||||
"~:is-admin": true,
|
||||
"~:can-edit": true,
|
||||
"~:can-read": true,
|
||||
"~:is-logged": true,
|
||||
"~:in-team": true
|
||||
}
|
||||
}
|
|
@ -33,10 +33,8 @@ export class ViewerPage extends BaseWebSocketPage {
|
|||
super(page);
|
||||
}
|
||||
|
||||
async goToViewer() {
|
||||
await this.page.goto(
|
||||
`/#/view/${ViewerPage.anyFileId}?page-id=${ViewerPage.anyPageId}§ion=interactions&index=0`,
|
||||
);
|
||||
async goToViewer({ fileId = ViewerPage.anyFileId, pageId = ViewerPage.anyPageId } = {}) {
|
||||
await this.page.goto(`/#/view/${fileId}?page-id=${pageId}§ion=interactions&index=0`);
|
||||
|
||||
this.#ws = await this.waitForNotificationsWebSocket();
|
||||
await this.#ws.mockOpen();
|
||||
|
|
|
@ -111,7 +111,7 @@ export class WorkspacePage extends BaseWebSocketPage {
|
|||
const layer = this.layers.getByTestId("layer-item").filter({ has: this.page.getByText(name) });
|
||||
await layer.getByRole("button").click(clickOptions);
|
||||
}
|
||||
|
||||
|
||||
async clickAssets(clickOptions = {}) {
|
||||
await this.assets.click(clickOptions);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,18 @@ test.beforeEach(async ({ page }) => {
|
|||
await ViewerPage.init(page);
|
||||
});
|
||||
|
||||
const singleBoardFileId = "dd5cc0bb-91ff-81b9-8004-77df9cd3edb1";
|
||||
const singleBoardPageId = "dd5cc0bb-91ff-81b9-8004-77df9cd3edb2";
|
||||
|
||||
const setupFileWithSingleBoard = async (viewer) => {
|
||||
await viewer.mockRPC(/get\-view\-only\-bundle\?/, "viewer/get-view-only-bundle-single-board.json");
|
||||
await viewer.mockRPC("get-comment-threads?file-id=*", "workspace/get-comment-threads-empty.json");
|
||||
await viewer.mockRPC(
|
||||
"get-file-fragment?file-id=*&fragment-id=*",
|
||||
"viewer/get-file-fragment-single-board.json",
|
||||
);
|
||||
};
|
||||
|
||||
test("Clips link area of the logo", async ({ page }) => {
|
||||
const viewerPage = new ViewerPage(page);
|
||||
await viewerPage.setupLoggedInUser();
|
||||
|
@ -21,3 +33,16 @@ test("Clips link area of the logo", async ({ page }) => {
|
|||
await viewerPage.page.mouse.click(x, y + 100);
|
||||
await expect(page.url()).toBe(viewerUrl);
|
||||
});
|
||||
|
||||
test("Updates URL with zoom type", async ({ page }) => {
|
||||
const viewer = new ViewerPage(page);
|
||||
await viewer.setupLoggedInUser();
|
||||
await setupFileWithSingleBoard(viewer);
|
||||
|
||||
await viewer.goToViewer({ fileId: singleBoardFileId, pageId: singleBoardPageId });
|
||||
|
||||
await viewer.page.getByTitle("Zoom").click();
|
||||
await viewer.page.getByText(/Fit/).click();
|
||||
|
||||
await expect(viewer.page).toHaveURL(/&zoom=fit/);
|
||||
});
|
||||
|
|
|
@ -69,9 +69,10 @@
|
|||
(cpc/check-changes! undo-changes)))
|
||||
|
||||
(let [commit-id (or commit-id (uuid/next))
|
||||
source (d/nilv source :local)
|
||||
commit {:id commit-id
|
||||
:created-at (dt/now)
|
||||
:source (d/nilv source :local)
|
||||
:source source
|
||||
:origin (ptk/type origin)
|
||||
:features features
|
||||
:file-id file-id
|
||||
|
@ -110,9 +111,7 @@
|
|||
|
||||
redo-changes (if pending
|
||||
(into redo-changes
|
||||
(comp
|
||||
(map :redo-changes)
|
||||
(mapcat identity))
|
||||
(mapcat :redo-changes)
|
||||
pending)
|
||||
redo-changes)]
|
||||
|
||||
|
|
|
@ -182,7 +182,7 @@
|
|||
(log/debug :hint "initialize persistence")
|
||||
(let [stoper-s (rx/filter (ptk/type? ::initialize-persistence) stream)
|
||||
|
||||
commits-s
|
||||
local-commits-s
|
||||
(->> stream
|
||||
(rx/filter dch/commit?)
|
||||
(rx/map deref)
|
||||
|
@ -192,28 +192,34 @@
|
|||
|
||||
notifier-s
|
||||
(rx/merge
|
||||
(->> commits-s
|
||||
(->> local-commits-s
|
||||
(rx/debounce 3000)
|
||||
(rx/tap #(log/trc :hint "persistence beat")))
|
||||
(->> stream
|
||||
(rx/filter #(= % ::force-persist))))]
|
||||
|
||||
(rx/merge
|
||||
(->> commits-s
|
||||
(->> local-commits-s
|
||||
(rx/debounce 200)
|
||||
(rx/map (fn [_]
|
||||
(update-status :pending)))
|
||||
(rx/take-until stoper-s))
|
||||
|
||||
(->> local-commits-s
|
||||
(rx/buffer-time 200)
|
||||
(rx/mapcat merge-commit)
|
||||
(rx/map dch/update-indexes)
|
||||
(rx/take-until stoper-s)
|
||||
(rx/finalize (fn []
|
||||
(log/debug :hint "finalize persistence: changes watcher [index]"))))
|
||||
|
||||
;; Here we watch for local commits, buffer them in a small
|
||||
;; chunks (very near in time commits) and append them to the
|
||||
;; persistence queue
|
||||
(->> commits-s
|
||||
(->> local-commits-s
|
||||
(rx/buffer-until notifier-s)
|
||||
(rx/mapcat merge-commit)
|
||||
(rx/mapcat (fn [commit]
|
||||
(rx/of (append-commit commit)
|
||||
(dch/update-indexes commit))))
|
||||
(rx/map append-commit)
|
||||
(rx/take-until (rx/delay 100 stoper-s))
|
||||
(rx/finalize (fn []
|
||||
(log/debug :hint "finalize persistence: changes watcher"))))
|
||||
|
|
|
@ -253,6 +253,18 @@
|
|||
|
||||
;; --- Zoom Management
|
||||
|
||||
(def update-zoom-querystring
|
||||
(ptk/reify ::update-zoom-querystring
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [zoom-type (get-in state [:viewer-local :zoom-type])
|
||||
route (:route state)
|
||||
screen (-> route :data :name keyword)
|
||||
qparams (:query-params route)
|
||||
pparams (:path-params route)]
|
||||
|
||||
(rx/of (rt/nav screen pparams (assoc qparams :zoom zoom-type)))))))
|
||||
|
||||
(def increase-zoom
|
||||
(ptk/reify ::increase-zoom
|
||||
ptk/UpdateEvent
|
||||
|
@ -293,7 +305,10 @@
|
|||
minzoom (min wdiff hdiff)]
|
||||
(-> state
|
||||
(assoc-in [:viewer-local :zoom] minzoom)
|
||||
(assoc-in [:viewer-local :zoom-type] :fit))))))
|
||||
(assoc-in [:viewer-local :zoom-type] :fit))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _] (rx/of update-zoom-querystring))))
|
||||
|
||||
(def zoom-to-fill
|
||||
(ptk/reify ::zoom-to-fill
|
||||
|
@ -309,7 +324,9 @@
|
|||
maxzoom (max wdiff hdiff)]
|
||||
(-> state
|
||||
(assoc-in [:viewer-local :zoom] maxzoom)
|
||||
(assoc-in [:viewer-local :zoom-type] :fill))))))
|
||||
(assoc-in [:viewer-local :zoom-type] :fill))))
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _] (rx/of update-zoom-querystring))))
|
||||
|
||||
(def toggle-zoom-style
|
||||
(ptk/reify ::toggle-zoom-style
|
||||
|
|
|
@ -431,7 +431,7 @@
|
|||
(watch [_ state stream]
|
||||
(let [initial (deref ms/mouse-position)
|
||||
|
||||
stopper (mse/drag-stopper stream)
|
||||
stopper (mse/drag-stopper stream {:interrupt? false})
|
||||
zoom (get-in state [:workspace-local :zoom] 1)
|
||||
|
||||
;; We toggle the selection so we don't have to wait for the event
|
||||
|
|
|
@ -149,7 +149,7 @@
|
|||
svg-raw-wrapper (mf/use-memo (mf/deps objects) #(svg-raw-wrapper-factory objects))
|
||||
bool-wrapper (mf/use-memo (mf/deps objects) #(bool-wrapper-factory objects))
|
||||
frame-wrapper (mf/use-memo (mf/deps objects) #(frame-wrapper-factory objects))]
|
||||
(when (and shape (not (:hidden shape)))
|
||||
(when shape
|
||||
(let [opts #js {:shape shape}
|
||||
svg-raw? (= :svg-raw (:type shape))]
|
||||
(if-not svg-raw?
|
||||
|
|
|
@ -50,6 +50,9 @@
|
|||
(defn bool->str [val]
|
||||
(when (some? val) (str val)))
|
||||
|
||||
(defn touched->str [val]
|
||||
(str/join " " (map str val)))
|
||||
|
||||
(defn add-factory [shape]
|
||||
(fn add!
|
||||
([props attr]
|
||||
|
@ -136,7 +139,6 @@
|
|||
(cond-> bool?
|
||||
(add! :bool-type)))))
|
||||
|
||||
|
||||
(defn add-library-refs [props shape]
|
||||
(let [add! (add-factory shape)]
|
||||
(-> props
|
||||
|
@ -150,7 +152,8 @@
|
|||
(add! :component-id)
|
||||
(add! :component-root)
|
||||
(add! :main-instance)
|
||||
(add! :shape-ref))))
|
||||
(add! :shape-ref)
|
||||
(add! :touched touched->str))))
|
||||
|
||||
(defn prefix-keys [m]
|
||||
(letfn [(prefix-entry [[k v]]
|
||||
|
|
|
@ -72,6 +72,8 @@
|
|||
(obj/set! "pointerEvents" pointer-events)
|
||||
(cond-> (not (cfh/frame-shape? shape))
|
||||
(obj/set! "opacity" (:opacity shape)))
|
||||
(cond-> (:hidden shape)
|
||||
(obj/set! "display" "none"))
|
||||
(cond-> (and blend-mode (not= blend-mode :normal))
|
||||
(obj/set! "mixBlendMode" (d/name blend-mode))))
|
||||
|
||||
|
|
|
@ -138,7 +138,7 @@
|
|||
(mf/deps page)
|
||||
(fn []
|
||||
(modal/show! :share-link {:page page :file file})
|
||||
(modal/allow-click-outside!)))
|
||||
(modal/disallow-click-outside!)))
|
||||
|
||||
handle-increase
|
||||
(mf/use-fn
|
||||
|
|
|
@ -402,9 +402,11 @@
|
|||
(st/emit! (dwl/nav-to-component-file library-id comp))))
|
||||
|
||||
do-show-component
|
||||
#(if local-component?
|
||||
(do-show-local-component)
|
||||
(do-show-remote-component))
|
||||
(fn []
|
||||
(st/emit! dw/hide-context-menu)
|
||||
(if local-component?
|
||||
(do-show-local-component)
|
||||
(do-show-remote-component)))
|
||||
|
||||
do-restore-component
|
||||
#(let [;; Extract a map of component-id -> component-file in order to avoid duplicates
|
||||
|
|
|
@ -72,12 +72,20 @@
|
|||
(defn drag-stopper
|
||||
"Creates a stream to stop drag events. Takes into account the mouse and also
|
||||
if the window loses focus or the esc key is pressed."
|
||||
[stream]
|
||||
(rx/merge
|
||||
(->> stream
|
||||
(rx/filter blur-event?))
|
||||
(->> stream
|
||||
(rx/filter mouse-event?)
|
||||
(rx/filter mouse-up-event?))
|
||||
(->> stream
|
||||
(rx/filter #(= % :interrupt)))))
|
||||
([stream]
|
||||
(drag-stopper stream nil))
|
||||
([stream {:keys [blur? up-mouse? interrupt?] :or {blur? true up-mouse? true interrupt? true}}]
|
||||
(rx/merge
|
||||
(if blur?
|
||||
(->> stream
|
||||
(rx/filter blur-event?))
|
||||
(rx/empty))
|
||||
(if up-mouse?
|
||||
(->> stream
|
||||
(rx/filter mouse-event?)
|
||||
(rx/filter mouse-up-event?))
|
||||
(rx/empty))
|
||||
(if interrupt?
|
||||
(->> stream
|
||||
(rx/filter #(= % :interrupt)))
|
||||
(rx/empty)))))
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.svg.path :as svg.path]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.shape.interactions :as ctsi]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.util.json :as json]
|
||||
|
@ -129,6 +130,15 @@
|
|||
(into {}))
|
||||
style-str))
|
||||
|
||||
(defn parse-touched
|
||||
"Transform a string of :touched-groups into a set"
|
||||
[touched-str]
|
||||
(let [touched (->> (str/split touched-str " ")
|
||||
(map #(keyword (subs % 1)))
|
||||
(filter ctk/valid-touched-group?)
|
||||
(into #{}))]
|
||||
touched))
|
||||
|
||||
(defn add-attrs
|
||||
[m attrs]
|
||||
(reduce-kv
|
||||
|
@ -424,7 +434,8 @@
|
|||
component-file (get-meta node :component-file uuid/uuid)
|
||||
shape-ref (get-meta node :shape-ref uuid/uuid)
|
||||
component-root? (get-meta node :component-root str->bool)
|
||||
main-instance? (get-meta node :main-instance str->bool)]
|
||||
main-instance? (get-meta node :main-instance str->bool)
|
||||
touched (get-meta node :touched parse-touched)]
|
||||
|
||||
(cond-> props
|
||||
(some? stroke-color-ref-id)
|
||||
|
@ -442,7 +453,10 @@
|
|||
(assoc :main-instance main-instance?)
|
||||
|
||||
(some? shape-ref)
|
||||
(assoc :shape-ref shape-ref))))
|
||||
(assoc :shape-ref shape-ref)
|
||||
|
||||
(seq touched)
|
||||
(assoc :touched touched))))
|
||||
|
||||
(defn add-fill
|
||||
[props node svg-data]
|
||||
|
|
Loading…
Add table
Reference in a new issue