0
Fork 0
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:
Alejandro Alonso 2024-06-11 07:34:48 +02:00
commit 3bb5db6490
24 changed files with 554 additions and 129 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

@ -33,10 +33,8 @@ export class ViewerPage extends BaseWebSocketPage {
super(page);
}
async goToViewer() {
await this.page.goto(
`/#/view/${ViewerPage.anyFileId}?page-id=${ViewerPage.anyPageId}&section=interactions&index=0`,
);
async goToViewer({ fileId = ViewerPage.anyFileId, pageId = ViewerPage.anyPageId } = {}) {
await this.page.goto(`/#/view/${fileId}?page-id=${pageId}&section=interactions&index=0`);
this.#ws = await this.waitForNotificationsWebSocket();
await this.#ws.mockOpen();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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