From 40f39681ad512ff69c2cfca14a7734d6a0c21ac3 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 4 Jun 2024 13:20:58 +0200 Subject: [PATCH 01/17] :sparkles: Add backward compatibility fixes for email whitelisting --- backend/src/app/email/whitelist.clj | 44 +++++++++++++++++------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/backend/src/app/email/whitelist.clj b/backend/src/app/email/whitelist.clj index d6fbd0c85..85c137bfb 100644 --- a/backend/src/app/email/whitelist.clj +++ b/backend/src/app/email/whitelist.clj @@ -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." From 25265cec708674085dc197260051cc856d0decbf Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 5 Jun 2024 10:39:55 +0200 Subject: [PATCH 02/17] :sparkles: Remove claims from token validation error report --- backend/src/app/tokens.clj | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/backend/src/app/tokens.clj b/backend/src/app/tokens.clj index 30ca32b3b..b1e36b939 100644 --- a/backend/src/app/tokens.clj +++ b/backend/src/app/tokens.clj @@ -39,15 +39,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)) From 046ef7eb6e659684a68b6f476798651f5e93572c Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 5 Jun 2024 10:42:13 +0200 Subject: [PATCH 03/17] :fire: Replace clojure.spec with simple assert on tokens ns --- backend/src/app/tokens.clj | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/backend/src/app/tokens.clj b/backend/src/app/tokens.clj index b1e36b939..60b0d50b2 100644 --- a/backend/src/app/tokens.clj +++ b/backend/src/app/tokens.clj @@ -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) From ae90d59b43fab0b2fe200de151001718c2fc563e Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 5 Jun 2024 11:10:28 +0200 Subject: [PATCH 04/17] :sparkles: Remove spec usage o teams rpc ns --- backend/src/app/rpc/commands/teams.clj | 116 ++++++++++++------------- 1 file changed, 57 insertions(+), 59 deletions(-) diff --git a/backend/src/app/rpc/commands/teams.clj b/backend/src/app/rpc/commands/teams.clj index 4cc75de5a..8eb41386f 100644 --- a/backend/src/app/rpc/commands/teams.clj +++ b/backend/src/app/rpc/commands/teams.clj @@ -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)] From 96b7fb7f12685c23b3446d135699e7077cd0e874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Fri, 7 Jun 2024 13:15:20 +0200 Subject: [PATCH 05/17] :bug: Fix viewer querystring not being updated with zoom type --- frontend/src/app/main/data/viewer.cljs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/main/data/viewer.cljs b/frontend/src/app/main/data/viewer.cljs index a45e75939..456413018 100644 --- a/frontend/src/app/main/data/viewer.cljs +++ b/frontend/src/app/main/data/viewer.cljs @@ -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 From 724bc24063d696b17c0e276814795f5a9af8f941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Fri, 7 Jun 2024 13:44:54 +0200 Subject: [PATCH 06/17] :sparkles: Add test for #7805 --- .../get-file-fragment-single-board.json | 186 ++++++++++++++++++ .../get-view-only-bundle-single-board.json | 86 ++++++++ frontend/playwright/ui/pages/ViewerPage.js | 6 +- .../playwright/ui/specs/viewer-header.spec.js | 25 +++ 4 files changed, 299 insertions(+), 4 deletions(-) create mode 100644 frontend/playwright/data/viewer/get-file-fragment-single-board.json create mode 100644 frontend/playwright/data/viewer/get-view-only-bundle-single-board.json diff --git a/frontend/playwright/data/viewer/get-file-fragment-single-board.json b/frontend/playwright/data/viewer/get-file-fragment-single-board.json new file mode 100644 index 000000000..8c1e62a15 --- /dev/null +++ b/frontend/playwright/data/viewer/get-file-fragment-single-board.json @@ -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" + } +} \ No newline at end of file diff --git a/frontend/playwright/data/viewer/get-view-only-bundle-single-board.json b/frontend/playwright/data/viewer/get-view-only-bundle-single-board.json new file mode 100644 index 000000000..9284de685 --- /dev/null +++ b/frontend/playwright/data/viewer/get-view-only-bundle-single-board.json @@ -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 + } +} \ No newline at end of file diff --git a/frontend/playwright/ui/pages/ViewerPage.js b/frontend/playwright/ui/pages/ViewerPage.js index cd328eb65..18c8cebe9 100644 --- a/frontend/playwright/ui/pages/ViewerPage.js +++ b/frontend/playwright/ui/pages/ViewerPage.js @@ -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(); diff --git a/frontend/playwright/ui/specs/viewer-header.spec.js b/frontend/playwright/ui/specs/viewer-header.spec.js index 01cfb8634..48f282965 100644 --- a/frontend/playwright/ui/specs/viewer-header.spec.js +++ b/frontend/playwright/ui/specs/viewer-header.spec.js @@ -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/); +}); From 2c21a049e1805cdb5e218c6572d75096c1edc160 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 7 Jun 2024 13:51:12 +0200 Subject: [PATCH 07/17] :bug: Fix problem with moving+selection not working properly --- CHANGES.md | 1 + .../app/main/data/workspace/transforms.cljs | 2 +- frontend/src/app/util/mouse.cljs | 26 ++++++++++++------- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d4e659d92..7a755a9e3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -23,6 +23,7 @@ - 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) ## 2.0.3 diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 7fe33314d..abb7a7c83 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -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 diff --git a/frontend/src/app/util/mouse.cljs b/frontend/src/app/util/mouse.cljs index 4576ed325..8f3e7652a 100644 --- a/frontend/src/app/util/mouse.cljs +++ b/frontend/src/app/util/mouse.cljs @@ -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))))) From 3ad91d1c9d6bc026af25c440bbbc20bdbe32b741 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 7 Jun 2024 15:25:57 +0200 Subject: [PATCH 08/17] :bug: Fix problem with flex layout fit to content not positioning correctly children --- CHANGES.md | 1 + common/src/app/common/geom/modifiers.cljc | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 7a755a9e3..a8b70d097 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -24,6 +24,7 @@ - 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) ## 2.0.3 diff --git a/common/src/app/common/geom/modifiers.cljc b/common/src/app/common/geom/modifiers.cljc index 813fd784a..ec4646f66 100644 --- a/common/src/app/common/geom/modifiers.cljc +++ b/common/src/app/common/geom/modifiers.cljc @@ -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)) From 1f7b5a0f7fe4d008b53105751cd8dca52511e0ce Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Fri, 7 Jun 2024 15:36:31 +0200 Subject: [PATCH 09/17] :bug: Fix black line is displaying after show main --- CHANGES.md | 1 + .../src/app/main/ui/workspace/sidebar/assets/common.cljs | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a8b70d097..2f552a0d4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -25,6 +25,7 @@ - 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) ## 2.0.3 diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs index d99e9dcac..e3a1cf372 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs @@ -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 From 96993a6ebdac62d31483d30ff3e87f1667096f8e Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Fri, 7 Jun 2024 16:09:10 +0200 Subject: [PATCH 10/17] :bug: Fix "Share prototypes" modal remains open --- CHANGES.md | 1 + frontend/src/app/main/ui/viewer/header.cljs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 2f552a0d4..09a21a091 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -26,6 +26,7 @@ - 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) ## 2.0.3 diff --git a/frontend/src/app/main/ui/viewer/header.cljs b/frontend/src/app/main/ui/viewer/header.cljs index 03dd06548..603c1cba1 100644 --- a/frontend/src/app/main/ui/viewer/header.cljs +++ b/frontend/src/app/main/ui/viewer/header.cljs @@ -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 From edfc47d3de64cc3f1c7c3ce2b0a21b922c64dd0b Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 6 Jun 2024 12:28:41 +0200 Subject: [PATCH 11/17] :sparkles: Add e2e tests for fix color palette default library --- .../data/workspace/get-file-library.json | 242 ++++++++++++++++++ .../get-team-shared-libraries-non-empty.json | 47 ++++ .../data/workspace/link-file-to-library.json | 1 + .../workspace/unlink-file-from-library.json | 1 + frontend/playwright/ui/pages/WorkspacePage.js | 31 +++ .../playwright/ui/specs/workspace.spec.js | 28 ++ .../app/main/ui/components/tab_container.cljs | 1 + .../src/app/main/ui/workspace/libraries.cljs | 11 +- .../src/app/main/ui/workspace/palette.cljs | 3 +- .../app/main/ui/workspace/sidebar/assets.cljs | 3 +- 10 files changed, 362 insertions(+), 6 deletions(-) create mode 100644 frontend/playwright/data/workspace/get-file-library.json create mode 100644 frontend/playwright/data/workspace/get-team-shared-libraries-non-empty.json create mode 100644 frontend/playwright/data/workspace/link-file-to-library.json create mode 100644 frontend/playwright/data/workspace/unlink-file-from-library.json diff --git a/frontend/playwright/data/workspace/get-file-library.json b/frontend/playwright/data/workspace/get-file-library.json new file mode 100644 index 000000000..de4775427 --- /dev/null +++ b/frontend/playwright/data/workspace/get-file-library.json @@ -0,0 +1,242 @@ +{ + "~:features":{ + "~#set":[ + "layout/grid", + "styles/v2", + "components/v2", + "fdata/shape-data-type" + ] + }, + "~:permissions":{ + "~:type":"~:membership", + "~:is-owner":true, + "~:is-admin":true, + "~:can-edit":true, + "~:can-read":true, + "~:is-logged":true + }, + "~:has-media-trimmed":false, + "~:comment-thread-seqn":0, + "~:name":"Testing library 1", + "~:revn":2, + "~:modified-at":"~m1717512948250", + "~:id":"~uc1249a66-fce0-8175-8004-7433fe4be8bc", + "~:is-shared":true, + "~:version":48, + "~:project-id": "~uc7ce0794-0992-8105-8004-38e630f7920b", + "~:created-at":"~m1717512934704", + "~:data":{ + "~:pages":[ + "~uc1249a66-fce0-8175-8004-7433fe4be8bd" + ], + "~:pages-index":{ + "~uc1249a66-fce0-8175-8004-7433fe4be8bd":{ + "~: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":[ + "~uc70224ec-c410-807b-8004-743400e00be8" + ] + } + }, + "~uc70224ec-c410-807b-8004-743400e00be8":{ + "~#shape":{ + "~:y":255, + "~:rx":0, + "~: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":"Rectangle", + "~:width":279.0000000000001, + "~:type":"~:rect", + "~:points":[ + { + "~#point":{ + "~:x":523, + "~:y":255 + } + }, + { + "~#point":{ + "~:x":802.0000000000001, + "~:y":255 + } + }, + { + "~#point":{ + "~:x":802.0000000000001, + "~:y":534 + } + }, + { + "~#point":{ + "~:x":523, + "~:y":534 + } + } + ], + "~: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":"~uc70224ec-c410-807b-8004-743400e00be8", + "~:parent-id":"~u00000000-0000-0000-0000-000000000000", + "~:frame-id":"~u00000000-0000-0000-0000-000000000000", + "~:strokes":[ + + ], + "~:x":523, + "~:proportion":1, + "~:selrect":{ + "~#rect":{ + "~:x":523, + "~:y":255, + "~:width":279.0000000000001, + "~:height":279, + "~:x1":523, + "~:y1":255, + "~:x2":802.0000000000001, + "~:y2":534 + } + }, + "~:fills":[ + { + "~:fill-color":"#B1B2B5", + "~:fill-opacity":1 + } + ], + "~:flip-x":null, + "~:ry":0, + "~:height":279, + "~:flip-y":null + } + } + }, + "~:id":"~uc1249a66-fce0-8175-8004-7433fe4be8bd", + "~:name":"Page 1" + } + }, + "~:id":"~uc1249a66-fce0-8175-8004-7433fe4be8bc", + "~:options":{ + "~:components-v2":true + }, + "~:recent-colors":[ + { + "~:color":"#187cd5", + "~:opacity":1 + } + ], + "~:colors":{ + "~uc70224ec-c410-807b-8004-74340616cffb":{ + "~:path":"", + "~:color":"#187cd5", + "~:name":"test-color-187cd5", + "~:modified-at":"~m1717512945259", + "~:opacity":1, + "~:id":"~uc70224ec-c410-807b-8004-74340616cffb" + } + } + } +} \ No newline at end of file diff --git a/frontend/playwright/data/workspace/get-team-shared-libraries-non-empty.json b/frontend/playwright/data/workspace/get-team-shared-libraries-non-empty.json new file mode 100644 index 000000000..05a5c8c3c --- /dev/null +++ b/frontend/playwright/data/workspace/get-team-shared-libraries-non-empty.json @@ -0,0 +1,47 @@ +{ + "~#set":[ + { + "~:name":"Testing library 1", + "~:revn":2, + "~:modified-at":"~m1717512948250", + "~:thumbnail-uri":"http://localhost:3000/assets/by-id/5ad7a7a7-c64e-4bb8-852d-15708d125905", + "~:id":"~uc1249a66-fce0-8175-8004-7433fe4be8bc", + "~:is-shared":true, + "~:project-id":"~uc7ce0794-0992-8105-8004-38e630f7920b", + "~:created-at":"~m1717512934704", + "~:library-summary":{ + "~:components":{ + "~:count":0, + "~:sample":[ + + ] + }, + "~:media":{ + "~:count":0, + "~:sample":[ + + ] + }, + "~:colors":{ + "~:count":1, + "~:sample":[ + { + "~:path":"", + "~:color":"#187cd5", + "~:name":"test-color", + "~:modified-at":"~m1717512945259", + "~:opacity":1, + "~:id":"~uc70224ec-c410-807b-8004-74340616cffb" + } + ] + }, + "~:typographies":{ + "~:count":0, + "~:sample":[ + + ] + } + } + } + ] +} \ No newline at end of file diff --git a/frontend/playwright/data/workspace/link-file-to-library.json b/frontend/playwright/data/workspace/link-file-to-library.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/frontend/playwright/data/workspace/link-file-to-library.json @@ -0,0 +1 @@ +{} diff --git a/frontend/playwright/data/workspace/unlink-file-from-library.json b/frontend/playwright/data/workspace/unlink-file-from-library.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/frontend/playwright/data/workspace/unlink-file-from-library.json @@ -0,0 +1 @@ +{} diff --git a/frontend/playwright/ui/pages/WorkspacePage.js b/frontend/playwright/ui/pages/WorkspacePage.js index d66447253..4c82e0155 100644 --- a/frontend/playwright/ui/pages/WorkspacePage.js +++ b/frontend/playwright/ui/pages/WorkspacePage.js @@ -46,6 +46,11 @@ export class WorkspacePage extends BaseWebSocketPage { this.rectShapeButton = page.getByRole("button", { name: "Rectangle (R)" }); this.colorpicker = page.getByTestId("colorpicker"); this.layers = page.getByTestId("layers"); + this.palette = page.getByTestId("palette"); + this.assets = page.getByTestId("assets"); + this.libraries = page.getByTestId("libraries"); + this.closeLibraries = page.getByTestId("close-libraries"); + this.librariesModal = page.getByTestId("libraries-modal"); } async goToWorkspace({ fileId = WorkspacePage.anyFileId, pageId = WorkspacePage.anyPageId } = {}) { @@ -106,4 +111,30 @@ 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); + } + + async clickLibraries(clickOptions = {}) { + await this.libraries.click(clickOptions); + } + + async clickLibrary(name, clickOptions = {}) { + await this.page + .getByTestId("library-item") + .filter({ hasText: name }) + .getByRole("button") + .click(clickOptions); + } + + async clickCloseLibraries(clickOptions = {}) { + await this.closeLibraries.click(clickOptions); + } + + async clickColorPalette(clickOptions = {}) { + await this.palette + .getByRole("button", { name: "Color Palette (Alt+P)" }) + .click(clickOptions); + } } diff --git a/frontend/playwright/ui/specs/workspace.spec.js b/frontend/playwright/ui/specs/workspace.spec.js index 58e9e5697..529965613 100644 --- a/frontend/playwright/ui/specs/workspace.spec.js +++ b/frontend/playwright/ui/specs/workspace.spec.js @@ -38,3 +38,31 @@ test("User draws a rect", async ({ page }) => { await expect(shape).toHaveAttribute("width", "200"); await expect(shape).toHaveAttribute("height", "100"); }); + +test("User adds a library and its automatically selected in the color palette", async ({ page }) => { + const workspacePage = new WorkspacePage(page); + await workspacePage.setupEmptyFile(); + await workspacePage.mockRPC("link-file-to-library", "workspace/link-file-to-library.json"); + await workspacePage.mockRPC("unlink-file-from-library", "workspace/unlink-file-from-library.json"); + await workspacePage.mockRPC("get-team-shared-files?team-id=*", "workspace/get-team-shared-libraries-non-empty.json"); + + await workspacePage.goToWorkspace(); + + // Add Testing library 1 + await workspacePage.clickColorPalette(); + await workspacePage.clickAssets(); + // Now the get-file call should return a library + await workspacePage.mockRPC(/get\-file\?/, "workspace/get-file-library.json"); + await workspacePage.clickLibraries(); + await workspacePage.clickLibrary("Testing library 1") + await workspacePage.clickCloseLibraries(); + + await expect(workspacePage.palette.getByRole("button", { name: "test-color-187cd5" })).toBeVisible(); + + // Remove Testing library 1 + await workspacePage.clickLibraries(); + await workspacePage.clickLibrary("Testing library 1") + await workspacePage.clickCloseLibraries(); + + await expect(workspacePage.palette.getByText('There are no color styles in your library yet')).toBeVisible(); +}); diff --git a/frontend/src/app/main/ui/components/tab_container.cljs b/frontend/src/app/main/ui/components/tab_container.cljs index 20c79a417..1e3b99079 100644 --- a/frontend/src/app/main/ui/components/tab_container.cljs +++ b/frontend/src/app/main/ui/components/tab_container.cljs @@ -59,6 +59,7 @@ [:div {:key (str/concat "tab-" sid) :title tooltip :data-id sid + :data-testid sid :on-click on-click :class (stl/css-case :tab-container-tab-title true diff --git a/frontend/src/app/main/ui/workspace/libraries.cljs b/frontend/src/app/main/ui/workspace/libraries.cljs index e28f28f96..d07517c7b 100644 --- a/frontend/src/app/main/ui/workspace/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/libraries.cljs @@ -230,7 +230,8 @@ (for [{:keys [id name] :as library} linked-libraries] [:div {:class (stl/css :section-list-item) - :key (dm/str id)} + :key (dm/str id) + :data-testid "library-item"} [:div {:class (stl/css :item-content)} [:div {:class (stl/css :item-name)} name] [:ul {:class (stl/css :item-contents)} @@ -263,7 +264,8 @@ [:div {:class (stl/css :section-list-shared)} (for [{:keys [id name] :as library} shared-libraries] [:div {:class (stl/css :section-list-item) - :key (dm/str id)} + :key (dm/str id) + :data-testid "library-item"} [:div {:class (stl/css :item-content)} [:div {:class (stl/css :item-name)} name] [:ul {:class (stl/css :item-contents)} @@ -513,10 +515,11 @@ (when team-id (st/emit! (dwl/fetch-shared-files {:team-id team-id})))) - [:div {:class (stl/css :modal-overlay) :on-click close-dialog-outside} + [:div {:class (stl/css :modal-overlay) :on-click close-dialog-outside :data-testid "libraries-modal"} [:div {:class (stl/css :modal-dialog)} [:button {:class (stl/css :close-btn) - :on-click close-dialog} + :on-click close-dialog + :data-testid "close-libraries"} close-icon] [:div {:class (stl/css :modal-title)} (tr "workspace.libraries.libraries")] diff --git a/frontend/src/app/main/ui/workspace/palette.cljs b/frontend/src/app/main/ui/workspace/palette.cljs index eedaeda61..b0d246a99 100644 --- a/frontend/src/app/main/ui/workspace/palette.cljs +++ b/frontend/src/app/main/ui/workspace/palette.cljs @@ -141,7 +141,8 @@ (swap! state* assoc :width width))) [:div {:class (stl/css :palette-wrapper) - :style (calculate-palette-padding rulers?)} + :style (calculate-palette-padding rulers?) + :data-testid "palette"} (when-not workspace-read-only? [:div {:ref parent-ref :class (dm/str size-classname " " (stl/css-case :palettes true diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index b3f755fc5..af12d40ab 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -157,7 +157,8 @@ [:div {:class (stl/css :assets-header)} (when-not ^boolean read-only? [:button {:class (stl/css :libraries-button) - :on-click show-libraries-dialog} + :on-click show-libraries-dialog + :data-testid "libraries"} [:span {:class (stl/css :libraries-icon)} i/library] (tr "workspace.assets.libraries")]) From 257dab4775234c6abaffe145d1a3908b5dd37952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Mon, 10 Jun 2024 11:06:18 +0200 Subject: [PATCH 12/17] :bug: Fix export hidden shapes --- frontend/src/app/main/render.cljs | 2 +- frontend/src/app/main/ui/shapes/shape.cljs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/main/render.cljs b/frontend/src/app/main/render.cljs index 16c961b5d..bbf85e4ad 100644 --- a/frontend/src/app/main/render.cljs +++ b/frontend/src/app/main/render.cljs @@ -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? diff --git a/frontend/src/app/main/ui/shapes/shape.cljs b/frontend/src/app/main/ui/shapes/shape.cljs index e0ab37c45..e4082b89c 100644 --- a/frontend/src/app/main/ui/shapes/shape.cljs +++ b/frontend/src/app/main/ui/shapes/shape.cljs @@ -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)))) From 9a4c45c8a3023a82e5e772ecbcac71184f1b7b97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Mon, 10 Jun 2024 16:19:29 +0200 Subject: [PATCH 13/17] :bug: Fix export touched attributes --- CHANGES.md | 2 + common/src/app/common/data.cljc | 1 - common/src/app/common/types/component.cljc | 27 ++++++++++-- .../types/types_component_test.cljc | 43 +++++++++++++++++++ frontend/src/app/main/ui/shapes/export.cljs | 7 ++- frontend/src/app/worker/import/parser.cljs | 18 +++++++- 6 files changed, 89 insertions(+), 9 deletions(-) create mode 100644 common/test/common_tests/types/types_component_test.cljc diff --git a/CHANGES.md b/CHANGES.md index 09a21a091..4419c2575 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -27,6 +27,8 @@ - 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 diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index 736803631..645091fd0 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -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`." diff --git a/common/src/app/common/types/component.cljc b/common/src/app/common/types/component.cljc index 95bf3016a..bcd6ef3b3 100644 --- a/common/src/app/common/types/component.cljc +++ b/common/src/app/common/types/component.cljc @@ -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-, 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))) \ No newline at end of file diff --git a/common/test/common_tests/types/types_component_test.cljc b/common/test/common_tests/types/types_component_test.cljc new file mode 100644 index 000000000..cff174329 --- /dev/null +++ b/common/test/common_tests/types/types_component_test.cljc @@ -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)))))) diff --git a/frontend/src/app/main/ui/shapes/export.cljs b/frontend/src/app/main/ui/shapes/export.cljs index 8ac2387e8..35895b777 100644 --- a/frontend/src/app/main/ui/shapes/export.cljs +++ b/frontend/src/app/main/ui/shapes/export.cljs @@ -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]] diff --git a/frontend/src/app/worker/import/parser.cljs b/frontend/src/app/worker/import/parser.cljs index fab4075ca..5eda72daa 100644 --- a/frontend/src/app/worker/import/parser.cljs +++ b/frontend/src/app/worker/import/parser.cljs @@ -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] From b635427f91964817509f2753f6509bae07c22579 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 10 Jun 2024 15:13:19 +0200 Subject: [PATCH 14/17] :bug: Fix incorrect order of update-index operations --- frontend/src/app/main/data/persistence.cljs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/main/data/persistence.cljs b/frontend/src/app/main/data/persistence.cljs index 8fbcc372a..452340176 100644 --- a/frontend/src/app/main/data/persistence.cljs +++ b/frontend/src/app/main/data/persistence.cljs @@ -205,15 +205,21 @@ (update-status :pending))) (rx/take-until stoper-s)) + (->> 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 (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")))) From 3c1086dfccfde0aac60c3c93898021474bea5b1f Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 10 Jun 2024 18:50:50 +0200 Subject: [PATCH 15/17] :bug: Fix race condition between shape modifiation and persistence --- frontend/src/app/main/data/changes.cljs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/main/data/changes.cljs b/frontend/src/app/main/data/changes.cljs index 4da8d30da..6c0b3d753 100644 --- a/frontend/src/app/main/data/changes.cljs +++ b/frontend/src/app/main/data/changes.cljs @@ -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 From e4e56828f6488cc96dd1caee0514fb91d186904c Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 10 Jun 2024 18:56:59 +0200 Subject: [PATCH 16/17] :lipstick: Fix internal naming for make code more self-explanatory --- frontend/src/app/main/data/persistence.cljs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/main/data/persistence.cljs b/frontend/src/app/main/data/persistence.cljs index 452340176..08a0719b3 100644 --- a/frontend/src/app/main/data/persistence.cljs +++ b/frontend/src/app/main/data/persistence.cljs @@ -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,20 +192,20 @@ 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)) - (->> commits-s + (->> local-commits-s (rx/buffer-time 200) (rx/mapcat merge-commit) (rx/map dch/update-indexes) @@ -216,7 +216,7 @@ ;; 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/map append-commit) From d35569dc55b53be53885f91653b47babec48487d Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 10 Jun 2024 18:59:39 +0200 Subject: [PATCH 17/17] :sparkles: Simplify transducer definition for proces redo changes --- frontend/src/app/main/data/changes.cljs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend/src/app/main/data/changes.cljs b/frontend/src/app/main/data/changes.cljs index 6c0b3d753..d16cfafc5 100644 --- a/frontend/src/app/main/data/changes.cljs +++ b/frontend/src/app/main/data/changes.cljs @@ -111,9 +111,7 @@ redo-changes (if pending (into redo-changes - (comp - (map :redo-changes) - (mapcat identity)) + (mapcat :redo-changes) pending) redo-changes)]