From 017aad6454536da45f709e1300e94e6254e7311a Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 12 Sep 2024 12:20:04 +0200 Subject: [PATCH 01/19] :bug: Fix export failed error when exporting multiple shapes --- backend/src/app/rpc/commands/files.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/app/rpc/commands/files.clj b/backend/src/app/rpc/commands/files.clj index 79381d34f..cfcd85df7 100644 --- a/backend/src/app/rpc/commands/files.clj +++ b/backend/src/app/rpc/commands/files.clj @@ -487,7 +487,7 @@ [:file-id ::sm/uuid] [:page-id {:optional true} ::sm/uuid] [:share-id {:optional true} ::sm/uuid] - [:object-id {:optional true} [:or ::sm/uuid ::sm/coll-of-uuid]] + [:object-id {:optional true} [:or ::sm/uuid [::sm/set ::sm/uuid]]] [:features {:optional true} ::cfeat/features]]) (sv/defmethod ::get-page From d03788af939060fb88309a9f464541df977dde10 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 12 Sep 2024 15:20:16 +0200 Subject: [PATCH 02/19] :bug: Ignore object thumbnail requests if file is already marked as deleted --- backend/src/app/rpc/commands/files.clj | 2 +- .../src/app/rpc/commands/files_thumbnails.clj | 22 ++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/backend/src/app/rpc/commands/files.clj b/backend/src/app/rpc/commands/files.clj index cfcd85df7..8c80a93bd 100644 --- a/backend/src/app/rpc/commands/files.clj +++ b/backend/src/app/rpc/commands/files.clj @@ -273,7 +273,7 @@ (defn get-minimal-file [cfg id & {:as opts}] - (let [opts (assoc opts ::sql/columns [:id :modified-at :revn :data-ref-id :data-backend])] + (let [opts (assoc opts ::sql/columns [:id :modified-at :deleted-at :revn :data-ref-id :data-backend])] (db/get cfg :file {:id id} opts))) (defn get-file-etag diff --git a/backend/src/app/rpc/commands/files_thumbnails.clj b/backend/src/app/rpc/commands/files_thumbnails.clj index 8e9c2da1c..7afa8752b 100644 --- a/backend/src/app/rpc/commands/files_thumbnails.clj +++ b/backend/src/app/rpc/commands/files_thumbnails.clj @@ -233,7 +233,7 @@ "INSERT INTO file_tagged_object_thumbnail (file_id, object_id, tag, media_id) VALUES (?, ?, ?, ?) ON CONFLICT (file_id, object_id, tag) - DO UPDATE SET updated_at=?, media_id=?, deleted_at=null + DO UPDATE SET updated_at=?, media_id=?, deleted_at=? RETURNING *") (defn- persist-thumbnail! @@ -251,17 +251,19 @@ :content-type mtype :bucket "file-object-thumbnail"}))) - - (defn- create-file-object-thumbnail! - [{:keys [::sto/storage] :as cfg} file-id object-id media tag] - (let [tsnow (dt/now) - media (persist-thumbnail! storage media tsnow) + [{:keys [::sto/storage] :as cfg} file object-id media tag] + (let [file-id (:id file) + timestamp (dt/now) + media (persist-thumbnail! storage media timestamp) [th1 th2] (db/tx-run! cfg (fn [{:keys [::db/conn]}] (let [th1 (db/exec-one! conn [sql:get-file-object-thumbnail file-id object-id tag]) th2 (db/exec-one! conn [sql:create-file-object-thumbnail - file-id object-id tag (:id media) - tsnow (:id media)])] + file-id object-id tag + (:id media) + timestamp + (:id media) + (:deleted-at file)])] [th1 th2])))] (when (and (some? th1) @@ -294,8 +296,8 @@ (media/validate-media-size! media) (db/run! cfg files/check-edition-permissions! profile-id file-id) - - (create-file-object-thumbnail! cfg file-id object-id media (or tag "frame"))) + (when-let [file (files/get-minimal-file cfg file-id {::db/check-deleted false})] + (create-file-object-thumbnail! cfg file object-id media (or tag "frame")))) ;; --- MUTATION COMMAND: delete-file-object-thumbnail From 229eeae6dbcae4d8d0da94ac87c4c9ecb5a1b893 Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Thu, 12 Sep 2024 16:11:15 +0200 Subject: [PATCH 03/19] :bug: Fix bad redirect on new oops page with social login --- frontend/src/app/main/data/users.cljs | 22 ++-- frontend/src/app/main/ui/auth/login.cljs | 14 ++- frontend/src/app/main/ui/static.cljs | 3 +- frontend/src/app/util/router.cljs | 14 ++- frontend/src/app/util/storage.cljs | 151 +++++++++++++++++------ 5 files changed, 154 insertions(+), 50 deletions(-) diff --git a/frontend/src/app/main/data/users.cljs b/frontend/src/app/main/data/users.cljs index e8369a2f9..9cf0f9966 100644 --- a/frontend/src/app/main/data/users.cljs +++ b/frontend/src/app/main/data/users.cljs @@ -168,13 +168,21 @@ accepting invitation, or third party auth signup or singin." [profile] (letfn [(get-redirect-events [] - (let [team-id (get-current-team-id profile) - welcome-file-id (get-in profile [:props :welcome-file-id])] - (if (some? welcome-file-id) - (rx/of - (rt/nav' :workspace {:project-id (:default-project-id profile) - :file-id welcome-file-id}) - (update-profile-props {:welcome-file-id nil})) + (let [team-id (get-current-team-id profile) + welcome-file-id (dm/get-in profile [:props :welcome-file-id]) + redirect-href (:login-redirect @s/session)] + (cond + (some? redirect-href) + (binding [s/*sync* true] + (swap! s/session dissoc :login-redirect) + (rx/of (rt/nav-raw :href redirect-href))) + + (some? welcome-file-id) + (rx/of (rt/nav' :workspace {:project-id (:default-project-id profile) + :file-id welcome-file-id}) + (update-profile-props {:welcome-file-id nil})) + + :else (rx/of (rt/nav' :dashboard-projects {:team-id team-id})))))] (ptk/reify ::logged-in diff --git a/frontend/src/app/main/ui/auth/login.cljs b/frontend/src/app/main/ui/auth/login.cljs index 0d0b587a9..5533a9171 100644 --- a/frontend/src/app/main/ui/auth/login.cljs +++ b/frontend/src/app/main/ui/auth/login.cljs @@ -23,6 +23,7 @@ [app.util.i18n :refer [tr]] [app.util.keyboard :as k] [app.util.router :as rt] + [app.util.storage :as s] [beicon.v2.core :as rx] [rumext.v2 :as mf])) @@ -47,10 +48,21 @@ (defn- login-with-oidc [event provider params] (dom/prevent-default event) + + (binding [s/*sync* true] + (if (some? (:save-login-redirect params)) + ;; Save the current login raw uri for later redirect user back to + ;; the same page, we need it to be synchronous because the user is + ;; going to be redirected instantly to the oidc provider uri + (swap! s/session assoc :login-redirect (rt/get-current-href)) + ;; Clean the login redirect + (swap! s/session dissoc :login-redirect))) + + ;; FIXME: this code should be probably moved outside of the UI (->> (rp/cmd! :login-with-oidc (assoc params :provider provider)) (rx/subs! (fn [{:keys [redirect-uri] :as rsp}] (if redirect-uri - (.replace js/location redirect-uri) + (st/emit! (rt/nav-raw :uri redirect-uri)) (log/error :hint "unexpected response from OIDC method" :resp (pr-str rsp)))) (fn [cause] diff --git a/frontend/src/app/main/ui/static.cljs b/frontend/src/app/main/ui/static.cljs index 06333087c..08c6ec06b 100644 --- a/frontend/src/app/main/ui/static.cljs +++ b/frontend/src/app/main/ui/static.cljs @@ -118,7 +118,8 @@ [:div {:class (stl/css :logo-title)} (tr "labels.login")] [:div {:class (stl/css :logo-subtitle)} (tr "not-found.login.free")] [:& login-methods {:on-recovery-request set-section-recovery - :on-success-callback success-login}] + :on-success-callback success-login + :params {:save-login-redirect true}}] [:hr {:class (stl/css :separator)}] [:div {:class (stl/css :change-section)} (tr "auth.register") diff --git a/frontend/src/app/util/router.cljs b/frontend/src/app/util/router.cljs index b556bd84b..9180d5d77 100644 --- a/frontend/src/app/util/router.cljs +++ b/frontend/src/app/util/router.cljs @@ -151,11 +151,20 @@ (set! (.-href globals/location) "/")) (defn nav-raw - [href] + [& {:keys [href uri]}] (ptk/reify ::nav-raw ptk/EffectEvent (effect [_ _ _] - (set! (.-href globals/location) href)))) + (cond + (string? uri) + (.replace globals/location uri) + + (string? href) + (set! (.-href globals/location) href))))) + +(defn get-current-href + [] + (.-href globals/location)) (defn get-current-path [] @@ -164,6 +173,7 @@ (subs hash 1) hash))) + ;; --- History API (defn initialize-history diff --git a/frontend/src/app/util/storage.cljs b/frontend/src/app/util/storage.cljs index df08e0eac..2be417230 100644 --- a/frontend/src/app/util/storage.cljs +++ b/frontend/src/app/util/storage.cljs @@ -10,75 +10,148 @@ [app.common.transit :as t] [app.util.functions :as fns] [app.util.globals :as g] - [cuerdas.core :as str])) + [cuerdas.core :as str] + [okulary.util :as ou])) ;; Using ex/ignoring because can receive a DOMException like this when ;; importing the code as a library: Failed to read the 'localStorage' ;; property from 'Window': Storage is disabled inside 'data:' URLs. -(defonce ^:private local-storage +(defonce ^:private local-storage-backend (ex/ignoring (unchecked-get g/global "localStorage"))) +(defonce ^:private session-storage-backend + (ex/ignoring (unchecked-get g/global "sessionStorage"))) + +(def ^:dynamic *sync* + "Dynamic variable which determines the mode of operation of the + storage mutatio actions. By default is asynchronous." + false) + (defn- encode-key - [k] + [prefix k] (assert (keyword? k) "key must be keyword") (let [kns (namespace k) kn (name k)] - (str "penpot:" kns "/" kn))) + (str prefix ":" kns "/" kn))) (defn- decode-key - [k] - (when (str/starts-with? k "penpot:") - (let [k (subs k 7)] + [prefix k] + (when (str/starts-with? k prefix) + (let [l (+ (count prefix) 1) + k (subs k l)] (if (str/starts-with? k "/") (keyword (subs k 1)) (let [[kns kn] (str/split k "/" 2)] (keyword kns kn)))))) (defn- lookup-by-index - [result index] + [backend prefix result index] (try - (let [key (.key ^js local-storage index) - key' (decode-key key)] + (let [key (.key ^js backend index) + key' (decode-key prefix key)] (if key' - (let [val (.getItem ^js local-storage key)] + (let [val (.getItem ^js backend key)] (assoc! result key' (t/decode-str val))) result)) (catch :default _ result))) -(defn- load - [] - (when (some? local-storage) - (let [length (.-length ^js local-storage)] +(defn- load-data + [backend prefix] + (if (some? backend) + (let [length (.-length ^js backend)] (loop [index 0 result (transient {})] (if (< index length) (recur (inc index) - (lookup-by-index result index)) - (persistent! result)))))) + (lookup-by-index backend prefix result index)) + (persistent! result)))) + {})) -(defonce ^:private latest-state (load)) +(defn create-storage + [backend prefix] + (let [initial (load-data backend prefix) + curr-data #js {:content initial} + last-data #js {:content initial} + watches (js/Map.) -(defn- on-change* - [curr-state] - (let [prev-state latest-state] - (try - (run! (fn [key] - (let [prev-val (get prev-state key) - curr-val (get curr-state key)] - (when-not (identical? curr-val prev-val) - (if (some? curr-val) - (.setItem ^js local-storage (encode-key key) (t/encode-str curr-val)) - (.removeItem ^js local-storage (encode-key key)))))) - (into #{} (concat (keys curr-state) - (keys prev-state)))) - (finally - (set! latest-state curr-state))))) + update-key + (fn [key val] + (when (some? backend) + (if (some? val) + (.setItem ^js backend (encode-key prefix key) (t/encode-str val)) + (.removeItem ^js backend (encode-key prefix key))))) -(defonce on-change - (fns/debounce on-change* 2000)) + on-change* + (fn [curr-state] + (let [prev-state (unchecked-get last-data "content")] + (try + (run! (fn [key] + (let [prev-val (get prev-state key) + curr-val (get curr-state key)] + (when-not (identical? curr-val prev-val) + (update-key key curr-val)))) + (into #{} (concat (keys curr-state) + (keys prev-state)))) + (finally + (unchecked-set last-data "content" curr-state))))) -(defonce storage (atom latest-state)) -(add-watch storage :persistence - (fn [_ _ _ curr-state] - (on-change curr-state))) + on-change + (fns/debounce on-change* 2000)] + + (reify + IAtom + + IDeref + (-deref [_] (unchecked-get curr-data "content")) + + ILookup + (-lookup [coll k] + (-lookup coll k nil)) + (-lookup [_ k not-found] + (let [state (unchecked-get curr-data "content")] + (-lookup state k not-found))) + + IReset + (-reset! [self newval] + (let [oldval (unchecked-get curr-data "content")] + (unchecked-set curr-data "content" newval) + (if *sync* + (on-change* newval) + (on-change newval)) + (when (> (.-size watches) 0) + (-notify-watches self oldval newval)) + newval)) + + ISwap + (-swap! [self f] + (let [state (unchecked-get curr-data "content")] + (-reset! self (f state)))) + (-swap! [self f x] + (let [state (unchecked-get curr-data "content")] + (-reset! self (f state x)))) + (-swap! [self f x y] + (let [state (unchecked-get curr-data "content")] + (-reset! self (f state x y)))) + (-swap! [self f x y more] + (let [state (unchecked-get curr-data "content")] + (-reset! self (apply f state x y more)))) + + IWatchable + (-notify-watches [self oldval newval] + (ou/doiter + (.entries watches) + (fn [n] + (let [f (aget n 1) + k (aget n 0)] + (f k self oldval newval))))) + + (-add-watch [self key f] + (.set watches key f) + self) + + (-remove-watch [_ key] + (.delete watches key))))) + +(defonce storage (create-storage local-storage-backend "penpot")) +(defonce session (create-storage session-storage-backend "penpot")) From a8814dcabaebaa034358dbdf6a2b0f883e8f1c76 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 12 Sep 2024 21:07:19 +0200 Subject: [PATCH 04/19] :bug: Add missing fields on file-gc libraries fetching sql --- backend/src/app/tasks/file_gc.clj | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/backend/src/app/tasks/file_gc.clj b/backend/src/app/tasks/file_gc.clj index 07bc7ffba..a903a6730 100644 --- a/backend/src/app/tasks/file_gc.clj +++ b/backend/src/app/tasks/file_gc.clj @@ -133,7 +133,13 @@ file)) (def ^:private sql:get-files-for-library - "SELECT f.id, f.data, f.modified_at, f.features, f.version + "SELECT f.id, + f.data, + f.modified_at, + f.features, + f.version, + f.data_backend, + f.data_ref_id FROM file AS f LEFT JOIN file_library_rel AS fl ON (fl.file_id = f.id) WHERE fl.library_file_id = ? From 0ce981a68ca703db66514dc6c92aafd6253c5a22 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 12 Sep 2024 21:32:34 +0200 Subject: [PATCH 05/19] :bug: Add missing ref-id unsassign on srepl helpers for process file --- backend/src/app/srepl/helpers.clj | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/app/srepl/helpers.clj b/backend/src/app/srepl/helpers.clj index 38ea61dd8..702790eba 100644 --- a/backend/src/app/srepl/helpers.clj +++ b/backend/src/app/srepl/helpers.clj @@ -75,6 +75,7 @@ :created-at (:created-at file) :modified-at (:modified-at file) :data-backend nil + :data-ref-id nil :has-media-trimmed false} {:id (:id file)}))) From f29112537736e6c21d73d987ecfa4cfc09c7a8f9 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 12 Sep 2024 21:33:01 +0200 Subject: [PATCH 06/19] :bug: Add migration for invalid value on layout-wrap-type on shape prop --- common/src/app/common/files/defaults.cljc | 2 +- common/src/app/common/files/migrations.cljc | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/common/src/app/common/files/defaults.cljc b/common/src/app/common/files/defaults.cljc index 6ef70b5ea..4c2dd1d96 100644 --- a/common/src/app/common/files/defaults.cljc +++ b/common/src/app/common/files/defaults.cljc @@ -6,4 +6,4 @@ (ns app.common.files.defaults) -(def version 51) +(def version 52) diff --git a/common/src/app/common/files/migrations.cljc b/common/src/app/common/files/migrations.cljc index 111d05072..218103f32 100644 --- a/common/src/app/common/files/migrations.cljc +++ b/common/src/app/common/files/migrations.cljc @@ -1010,13 +1010,25 @@ (defn migrate-up-51 "This migration fixes library invalid colors" - [data] (let [update-colors (fn [colors] (into {} (filter #(-> % val valid-color?) colors)))] (update data :colors update-colors))) +(defn migrate-up-52 + "Fixes incorrect value on `layout-wrap-type` prop" + [data] + (letfn [(update-shape [shape] + (if (= :no-wrap (:layout-wrap-type shape)) + (assoc shape :layout-wrap-type :nowrap) + shape)) + + (update-page [page] + (d/update-when page :objects update-vals update-shape))] + + (update data :pages-index update-vals update-page))) + (def migrations "A vector of all applicable migrations" [{:id 2 :migrate-up migrate-up-2} @@ -1059,4 +1071,5 @@ {:id 48 :migrate-up migrate-up-48} {:id 49 :migrate-up migrate-up-49} {:id 50 :migrate-up migrate-up-50} - {:id 51 :migrate-up migrate-up-51}]) + {:id 51 :migrate-up migrate-up-51} + {:id 52 :migrate-up migrate-up-52}]) From 65bb7951995912a8885b80cf639e00b992e42630 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 16 Sep 2024 09:07:57 +0200 Subject: [PATCH 07/19] :bug: Fix visual problem with stroke cap menu --- CHANGES.md | 1 + .../app/main/ui/workspace/sidebar/options/rows/stroke_row.scss | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 8943da36c..444c2db9c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -81,6 +81,7 @@ - Fix layer panel overflowing [Taiga #8665](https://tree.taiga.io/project/penpot/issue/8665) - Fix problem when creating a component instance from grid layout [Github #4881](https://github.com/penpot/penpot/issues/4881) - Fix problem when dismissing shared library update [Taiga #8669](https://tree.taiga.io/project/penpot/issue/8669) +- Fix visual problem with stroke cap menu [Taiga #8730](https://tree.taiga.io/project/penpot/issue/8730) ## 2.1.5 diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.scss b/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.scss index 3a8a0f23c..a9fd66fa4 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.scss @@ -32,7 +32,7 @@ } .cap-select { - width: $s-124; + width: 100%; } .stroke-cap-dropdown, .stroke-cap-dropdown-start { From 979828ffe3424503edf497543bf4afeabfa3dfa1 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 16 Sep 2024 09:08:51 +0200 Subject: [PATCH 08/19] :bug: Fix issue when exporting libraries when merging libraries --- CHANGES.md | 1 + frontend/src/app/worker/export.cljs | 143 ++++++++++------------------ 2 files changed, 51 insertions(+), 93 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 444c2db9c..49b339c20 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -82,6 +82,7 @@ - Fix problem when creating a component instance from grid layout [Github #4881](https://github.com/penpot/penpot/issues/4881) - Fix problem when dismissing shared library update [Taiga #8669](https://tree.taiga.io/project/penpot/issue/8669) - Fix visual problem with stroke cap menu [Taiga #8730](https://tree.taiga.io/project/penpot/issue/8730) +- Fix issue when exporting libraries when merging libraries [Taiga #8758](https://tree.taiga.io/project/penpot/issue/8758) ## 2.1.5 diff --git a/frontend/src/app/worker/export.cljs b/frontend/src/app/worker/export.cljs index 2c346e718..005108e68 100644 --- a/frontend/src/app/worker/export.cljs +++ b/frontend/src/app/worker/export.cljs @@ -202,108 +202,61 @@ (defn make-local-external-references [file file-id] - (let [detach-text + (let [change-fill + (fn [fill] + (cond-> fill + (not= file-id (:fill-color-ref-file fill)) + (assoc :fill-color-ref-file file-id))) + + change-stroke + (fn [stroke] + (cond-> stroke + (not= file-id (:stroke-color-ref-file stroke)) + (assoc :stroke-color-ref-file file-id))) + + change-text (fn [content] (->> content (ct/transform-nodes - #(cond-> % - (not= file-id (:fill-color-ref-file %)) - (assoc :fill-color-ref-file file-id) + (fn [node] + (-> node + (d/update-when :fills #(mapv change-fill %)) + (cond-> (not= file-id (:typography-ref-file node)) + (assoc :typography-ref-file file-id))))))) - (not= file-id (:typography-ref-file %)) - (assoc :typography-ref-file file-id))))) - - detach-shape + change-shape (fn [shape] - (cond-> shape - (not= file-id (:fill-color-ref-file shape)) - (assoc :fill-color-ref-file file-id) + (-> shape + (d/update-when :fills #(mapv change-fill %)) + (d/update-when :strokes #(mapv change-stroke %)) + (cond-> (not= file-id (:component-file shape)) + (assoc :component-file file-id)) - (not= file-id (:stroke-color-ref-file shape)) - (assoc :stroke-color-ref-file file-id) + (cond-> (= :text (:type shape)) + (update :content change-text)))) - (not= file-id (:component-file shape)) - (assoc :component-file file-id) - - (= :text (:type shape)) - (update :content detach-text))) - - detach-objects + change-objects (fn [objects] (->> objects - (d/mapm #(detach-shape %2)))) + (d/mapm #(change-shape %2)))) - detach-pages + change-pages (fn [pages-index] (->> pages-index (d/mapm (fn [_ data] (-> data - (update :objects detach-objects))))))] + (update :objects change-objects))))))] (-> file - (update-in [:data :pages-index] detach-pages)))) - -(defn collect-external-references - [file] - - (let [get-text-refs - (fn [content] - (->> content - (ct/node-seq #(or (contains? % :fill-color-ref-id) - (contains? % :typography-ref-id))) - - (mapcat (fn [node] - (cond-> [] - (contains? node :fill-color-ref-id) - (conj {:id (:fill-color-ref-id node) - :file-id (:fill-color-ref-file node)}) - - (contains? node :typography-ref-id) - (conj {:id (:typography-ref-id node) - :file-id (:typography-ref-file node)})))) - - (into []))) - - get-shape-refs - (fn [[_ shape]] - (cond-> [] - (contains? shape :fill-color-ref-id) - (conj {:id (:fill-color-ref-id shape) - :file-id (:fill-color-ref-file shape)}) - - (contains? shape :stroke-color-ref-id) - (conj {:id (:stroke-color-ref-id shape) - :file-id (:stroke-color-ref-file shape)}) - - (contains? shape :component-id) - (conj {:id (:component-id shape) - :file-id (:component-file shape)}) - - (= :text (:type shape)) - (into (get-text-refs (:content shape)))))] - - (->> (get-in file [:data :pages-index]) - (vals) - (mapcat :objects) - (mapcat get-shape-refs) - (filter (comp some? :file-id)) - (filter (comp some? :id)) - (group-by :file-id) - (d/mapm #(mapv :id %2))))) + (update-in [:data :pages-index] change-pages)))) (defn merge-assets [target-file assets-files] - (let [external-refs (collect-external-references target-file) - - merge-file-assets + (let [merge-file-assets (fn [target file] - (let [colors (-> (get-in file [:data :colors]) - (select-keys (get external-refs (:id file)))) - typographies (-> (get-in file [:data :typographies]) - (select-keys (get external-refs (:id file)))) - media (-> (get-in file [:data :media]) - (select-keys (get external-refs (:id file)))) - components (-> (ctkl/components (:data file)) - (select-keys (get external-refs (:id file))))] + (let [colors (get-in file [:data :colors]) + typographies (get-in file [:data :typographies]) + media (get-in file [:data :media]) + components (ctkl/components (:data file))] (cond-> target (d/not-empty? colors) (update-in [:data :colors] merge colors) @@ -323,16 +276,20 @@ (defn process-export [file-id export-type files] - (case export-type - :all files - :merge (let [file-list (-> files (d/without-keys [file-id]) vals)] - (-> (select-keys files [file-id]) - (update file-id merge-assets file-list) - (update file-id make-local-external-references file-id) - (update file-id dissoc :libraries))) - :detach (-> (select-keys files [file-id]) - (update file-id ctf/detach-external-references file-id) - (update file-id dissoc :libraries)))) + (let [result + (case export-type + :all files + :merge (let [file-list (-> files (d/without-keys [file-id]) vals)] + (-> (select-keys files [file-id]) + (update file-id merge-assets file-list) + (update file-id make-local-external-references file-id) + (update file-id dissoc :libraries))) + :detach (-> (select-keys files [file-id]) + (update file-id ctf/detach-external-references file-id) + (update file-id dissoc :libraries)))] + + ;;(.log js/console (clj->js result)) + result)) (defn collect-files [file-id export-type features] From 475d14edf440e553c925fc88eea61e2600624609 Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Mon, 16 Sep 2024 14:42:52 +0200 Subject: [PATCH 09/19] :bug: Fix request access to the Team don't go to Your Penpot team --- frontend/src/app/main/ui/static.cljs | 32 +++++++++++++++++++--------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/frontend/src/app/main/ui/static.cljs b/frontend/src/app/main/ui/static.cljs index 08c6ec06b..e069a9486 100644 --- a/frontend/src/app/main/ui/static.cljs +++ b/frontend/src/app/main/ui/static.cljs @@ -167,19 +167,19 @@ (mf/defc request-dialog {::mf/props :obj} - [{:keys [title content button-text on-button-click cancel-text]}] - (let [on-click (or on-button-click rt/nav-root)] + [{:keys [title content button-text on-button-click cancel-text on-close]}] + (let [on-click (or on-button-click on-close)] [:div {:class (stl/css :overlay)} [:div {:class (stl/css :dialog)} [:div {:class (stl/css :modal-close)} - [:button {:class (stl/css :modal-close-button) :on-click rt/nav-root} + [:button {:class (stl/css :modal-close-button) :on-click on-close} i/close]] [:div {:class (stl/css :dialog-title)} title] (for [txt content] [:div txt]) [:div {:class (stl/css :sign-info)} (when cancel-text - [:button {:class (stl/css :cancel-button) :on-click rt/nav-root} cancel-text]) + [:button {:class (stl/css :cancel-button) :on-click on-close} cancel-text]) [:button {:on-click on-click} button-text]]]])) @@ -190,6 +190,11 @@ requested* (mf/use-state {:sent false :already-requested false}) requested (deref requested*) show-dialog (mf/use-state true) + on-close + (mf/use-fn + (mf/deps profile) + (fn [] + (st/emit! (rt/nav :dashboard-projects {:team-id (:default-team-id profile)})))) on-success (mf/use-fn #(reset! requested* {:sent true :already-requested false})) @@ -243,22 +248,27 @@ [:& login-dialog {:show-dialog show-dialog}] is-default - [:& request-dialog {:title (tr "not-found.no-permission.project") :button-text (tr "not-found.no-permission.go-dashboard")}] + [:& request-dialog {:title (tr "not-found.no-permission.project") + :button-text (tr "not-found.no-permission.go-dashboard") + :on-close on-close}] (and (some? file-id) (:already-requested requested)) [:& request-dialog {:title (tr "not-found.no-permission.already-requested.file") :content [(tr "not-found.no-permission.already-requested.or-others.file")] - :button-text (tr "not-found.no-permission.go-dashboard")}] + :button-text (tr "not-found.no-permission.go-dashboard") + :on-close on-close}] (:already-requested requested) [:& request-dialog {:title (tr "not-found.no-permission.already-requested.project") :content [(tr "not-found.no-permission.already-requested.or-others.project")] - :button-text (tr "not-found.no-permission.go-dashboard")}] + :button-text (tr "not-found.no-permission.go-dashboard") + :on-close on-close}] (:sent requested) [:& request-dialog {:title (tr "not-found.no-permission.done.success") :content [(tr "not-found.no-permission.done.remember")] - :button-text (tr "not-found.no-permission.go-dashboard")}] + :button-text (tr "not-found.no-permission.go-dashboard") + :on-close on-close}] (some? file-id) [:& request-dialog {:title (tr "not-found.no-permission.file") @@ -266,7 +276,8 @@ (tr "not-found.no-permission.if-approves")] :button-text (tr "not-found.no-permission.ask") :on-button-click on-request-access - :cancel-text (tr "not-found.no-permission.go-dashboard")}] + :cancel-text (tr "not-found.no-permission.go-dashboard") + :on-close on-close}] (some? team-id) [:& request-dialog {:title (tr "not-found.no-permission.project") @@ -274,7 +285,8 @@ (tr "not-found.no-permission.if-approves")] :button-text (tr "not-found.no-permission.ask") :on-button-click on-request-access - :cancel-text (tr "not-found.no-permission.go-dashboard")}]))])) + :cancel-text (tr "not-found.no-permission.go-dashboard") + :on-close on-close}]))])) (mf/defc not-found* [] From 162507264c56c0809caf80fddf8bb3dc9b0222a0 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 13 Sep 2024 11:22:20 +0200 Subject: [PATCH 10/19] :bug: Reexecute file migration 26 again for shapes that has transform prop as nil --- common/src/app/common/files/defaults.cljc | 2 +- common/src/app/common/files/migrations.cljc | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/common/src/app/common/files/defaults.cljc b/common/src/app/common/files/defaults.cljc index 4c2dd1d96..12351cdf5 100644 --- a/common/src/app/common/files/defaults.cljc +++ b/common/src/app/common/files/defaults.cljc @@ -6,4 +6,4 @@ (ns app.common.files.defaults) -(def version 52) +(def version 53) diff --git a/common/src/app/common/files/migrations.cljc b/common/src/app/common/files/migrations.cljc index 218103f32..797f2fe24 100644 --- a/common/src/app/common/files/migrations.cljc +++ b/common/src/app/common/files/migrations.cljc @@ -1072,4 +1072,5 @@ {:id 49 :migrate-up migrate-up-49} {:id 50 :migrate-up migrate-up-50} {:id 51 :migrate-up migrate-up-51} - {:id 52 :migrate-up migrate-up-52}]) + {:id 52 :migrate-up migrate-up-52} + {:id 53 :migrate-up migrate-up-26}]) From 179d534237be361060c24407c21d931a956f4a99 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 13 Sep 2024 15:17:02 +0200 Subject: [PATCH 11/19] :bug: Fix several issues related to invalid colors inserted on shadows --- common/src/app/common/files/defaults.cljc | 2 +- common/src/app/common/files/migrations.cljc | 30 +++- common/src/app/common/types/color.cljc | 39 ++--- common/src/app/common/types/shape.cljc | 3 + common/src/app/common/types/shape/shadow.cljc | 3 + .../src/app/main/data/workspace/colors.cljs | 153 ++++++++++++++---- .../app/main/data/workspace/libraries.cljs | 43 ++--- frontend/src/app/main/ui/hooks.cljs | 8 + .../ui/workspace/sidebar/assets/colors.cljs | 4 +- .../options/menus/color_selection.cljs | 77 +++++---- .../sidebar/options/rows/color_row.cljs | 14 +- frontend/src/app/util/color.cljs | 3 - 12 files changed, 266 insertions(+), 113 deletions(-) diff --git a/common/src/app/common/files/defaults.cljc b/common/src/app/common/files/defaults.cljc index 12351cdf5..b15aac919 100644 --- a/common/src/app/common/files/defaults.cljc +++ b/common/src/app/common/files/defaults.cljc @@ -6,4 +6,4 @@ (ns app.common.files.defaults) -(def version 53) +(def version 54) diff --git a/common/src/app/common/files/migrations.cljc b/common/src/app/common/files/migrations.cljc index 797f2fe24..4a649c265 100644 --- a/common/src/app/common/files/migrations.cljc +++ b/common/src/app/common/files/migrations.cljc @@ -863,11 +863,9 @@ (assoc shadow :color color))) (update-object [object] - (d/update-when object :shadow - #(into [] - (comp (map fix-shadow) - (filter valid-shadow?)) - %))) + (let [xform (comp (map fix-shadow) + (filter valid-shadow?))] + (d/update-when object :shadow #(into [] xform %)))) (update-container [container] (d/update-when container :objects update-vals update-object))] @@ -1029,6 +1027,25 @@ (update data :pages-index update-vals update-page))) +(defn migrate-up-54 + "Fixes shapes with invalid colors in shadow: it first tries a non + destructive fix, and if it is not possible, then, shadow is removed" + [data] + (letfn [(fix-shadow [shadow] + (update shadow :color d/without-nils)) + + (update-shape [shape] + (let [xform (comp (map fix-shadow) + (filter valid-shadow?))] + (d/update-when shape :shadow #(into [] xform %)))) + + (update-container [container] + (d/update-when container :objects update-vals update-shape))] + + (-> data + (update :pages-index update-vals update-container) + (update :components update-vals update-container)))) + (def migrations "A vector of all applicable migrations" [{:id 2 :migrate-up migrate-up-2} @@ -1073,4 +1090,5 @@ {:id 50 :migrate-up migrate-up-50} {:id 51 :migrate-up migrate-up-51} {:id 52 :migrate-up migrate-up-52} - {:id 53 :migrate-up migrate-up-26}]) + {:id 53 :migrate-up migrate-up-26} + {:id 54 :migrate-up migrate-up-54}]) diff --git a/common/src/app/common/types/color.cljc b/common/src/app/common/types/color.cljc index 78a7f8114..ed4def689 100644 --- a/common/src/app/common/types/color.cljc +++ b/common/src/app/common/types/color.cljc @@ -80,21 +80,23 @@ [:opacity {:optional true} [:maybe ::sm/safe-number]] [:offset ::sm/safe-number]]]]]) +(def schema:color-attrs + [:map {:title "ColorAttrs"} + [:id {:optional true} ::sm/uuid] + [:name {:optional true} :string] + [:path {:optional true} [:maybe :string]] + [:value {:optional true} [:maybe :string]] + [:color {:optional true} [:maybe ::rgb-color]] + [:opacity {:optional true} [:maybe ::sm/safe-number]] + [:modified-at {:optional true} ::sm/inst] + [:ref-id {:optional true} ::sm/uuid] + [:ref-file {:optional true} ::sm/uuid] + [:gradient {:optional true} [:maybe schema:gradient]] + [:image {:optional true} [:maybe schema:image-color]] + [:plugin-data {:optional true} ::ctpg/plugin-data]]) + (def schema:color - [:and - [:map {:title "Color"} - [:id {:optional true} ::sm/uuid] - [:name {:optional true} :string] - [:path {:optional true} [:maybe :string]] - [:value {:optional true} [:maybe :string]] - [:color {:optional true} [:maybe ::rgb-color]] - [:opacity {:optional true} [:maybe ::sm/safe-number]] - [:modified-at {:optional true} ::sm/inst] - [:ref-id {:optional true} ::sm/uuid] - [:ref-file {:optional true} ::sm/uuid] - [:gradient {:optional true} [:maybe schema:gradient]] - [:image {:optional true} [:maybe schema:image-color]] - [:plugin-data {:optional true} ::ctpg/plugin-data]] + [:and schema:color-attrs [::sm/contains-any {:strict true} [:color :gradient :image]]]) (def schema:recent-color @@ -111,12 +113,13 @@ (sm/register! ::gradient schema:gradient) (sm/register! ::image-color schema:image-color) (sm/register! ::recent-color schema:recent-color) +(sm/register! ::color-attrs schema:color-attrs) -(def valid-color? - (sm/lazy-validator schema:color)) +(def check-color! + (sm/check-fn schema:color)) -(def valid-recent-color? - (sm/lazy-validator schema:recent-color)) +(def check-recent-color! + (sm/check-fn schema:recent-color)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; HELPERS diff --git a/common/src/app/common/types/shape.cljc b/common/src/app/common/types/shape.cljc index b502c4c65..cf437d2dc 100644 --- a/common/src/app/common/types/shape.cljc +++ b/common/src/app/common/types/shape.cljc @@ -125,6 +125,9 @@ (sm/register! ::stroke schema:stroke) +(def check-stroke! + (sm/check-fn schema:stroke)) + (def schema:shape-base-attrs [:map {:title "ShapeMinimalRecord"} [:id ::sm/uuid] diff --git a/common/src/app/common/types/shape/shadow.cljc b/common/src/app/common/types/shape/shadow.cljc index 0c3389893..b1ec5342f 100644 --- a/common/src/app/common/types/shape/shadow.cljc +++ b/common/src/app/common/types/shape/shadow.cljc @@ -27,3 +27,6 @@ [:color ::ctc/color]]) (sm/register! ::shadow schema:shadow) + +(def check-shadow! + (sm/check-fn schema:shadow)) diff --git a/frontend/src/app/main/data/workspace/colors.cljs b/frontend/src/app/main/data/workspace/colors.cljs index dc0a44d4a..004edc696 100644 --- a/frontend/src/app/main/data/workspace/colors.cljs +++ b/frontend/src/app/main/data/workspace/colors.cljs @@ -12,6 +12,9 @@ [app.common.files.helpers :as cfh] [app.common.schema :as sm] [app.common.text :as txt] + [app.common.types.color :as ctc] + [app.common.types.shape :refer [check-stroke!]] + [app.common.types.shape.shadow :refer [check-shadow!]] [app.main.broadcast :as mbc] [app.main.data.events :as ev] [app.main.data.modal :as md] @@ -21,7 +24,6 @@ [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.texts :as dwt] [app.main.data.workspace.undo :as dwu] - [app.util.color :as uc] [app.util.storage :refer [storage]] [beicon.v2.core :as rx] [cuerdas.core :as str] @@ -165,6 +167,15 @@ (defn add-fill [ids color] + + (dm/assert! + "expected a valid color struct" + (ctc/check-color! color)) + + (dm/assert! + "expected a valid coll of uuid's" + (every? uuid? ids)) + (ptk/reify ::add-fill ptk/WatchEvent (watch [_ state _] @@ -175,6 +186,15 @@ (defn remove-fill [ids color position] + + (dm/assert! + "expected a valid color struct" + (ctc/check-color! color)) + + (dm/assert! + "expected a valid coll of uuid's" + (every? uuid? ids)) + (ptk/reify ::remove-fill ptk/WatchEvent (watch [_ state _] @@ -187,13 +207,21 @@ (defn remove-all-fills [ids color] + + (dm/assert! + "expected a valid color struct" + (ctc/check-color! color)) + + (dm/assert! + "expected a valid coll of uuid's" + (every? uuid? ids)) + (ptk/reify ::remove-all-fills ptk/WatchEvent (watch [_ state _] (let [remove-all (fn [shape _] (assoc shape :fills []))] (transform-fill state ids color remove-all))))) - (defn change-hide-fill-on-export [ids hide-fill-on-export] (ptk/reify ::change-hide-fill-on-export @@ -272,17 +300,25 @@ ;; example using the color selection from ;; multiple shapes) let's use the first stop ;; color - attrs (cond-> attrs - (:gradient attrs) (get-in [:gradient :stops 0])) - new-attrs (-> (merge (get-in shape [:shadow index :color]) attrs) - (d/without-nils))] - (assoc-in shape [:shadow index :color] new-attrs)))))))) + attrs (cond-> attrs + (:gradient attrs) + (dm/get-in [:gradient :stops 0])) + + attrs' (-> (dm/get-in shape [:shadow index :color]) + (merge attrs) + (d/without-nils))] + (assoc-in shape [:shadow index :color] attrs')))))))) (defn add-shadow [ids shadow] + + (dm/assert! + "expected a valid shadow struct" + (check-shadow! shadow)) + (dm/assert! "expected a valid coll of uuid's" - (sm/check-coll-of-uuid! ids)) + (every? uuid? ids)) (ptk/reify ::add-shadow ptk/WatchEvent @@ -293,6 +329,15 @@ (defn add-stroke [ids stroke] + + (dm/assert! + "expected a valid stroke struct" + (check-stroke! stroke)) + + (dm/assert! + "expected a valid coll of uuid's" + (every? uuid? ids)) + (ptk/reify ::add-stroke ptk/WatchEvent (watch [_ _ _] @@ -301,6 +346,11 @@ (defn remove-stroke [ids position] + + (dm/assert! + "expected a valid coll of uuid's" + (every? uuid? ids)) + (ptk/reify ::remove-stroke ptk/WatchEvent (watch [_ _ _] @@ -314,6 +364,11 @@ (defn remove-all-strokes [ids] + + (dm/assert! + "expected a valid coll of uuid's" + (every? uuid? ids)) + (ptk/reify ::remove-all-strokes ptk/WatchEvent (watch [_ _ _] @@ -376,7 +431,7 @@ :on-change handle-change-color} :allow-click-outside true}))))))) -(defn color-att->text +(defn- color-att->text [color] {:fill-color (when (:color color) (str/lower (:color color))) :fill-opacity (:opacity color) @@ -395,26 +450,57 @@ (some? has-color?) (assoc-in [:fills index] parsed-new-color)))) +(def ^:private schema:change-color-operation + [:map + [:prop [:enum :fill :stroke :shadow :content]] + [:shape-id ::sm/uuid] + [:index :int]]) + +(def ^:private schema:change-color-operations + [:vector schema:change-color-operation]) + +(def ^:private check-change-color-operations! + (sm/check-fn schema:change-color-operations)) + (defn change-color-in-selected - [new-color shapes-by-color old-color] + [operations new-color old-color] + + (dm/verify! + "expected valid change color operations" + (check-change-color-operations! operations)) + + (dm/verify! + "expected a valid color struct for new-color param" + (ctc/check-color! new-color)) + + (dm/verify! + "expected a valid color struct for old-color param" + (ctc/check-color! old-color)) + (ptk/reify ::change-color-in-selected ptk/WatchEvent (watch [_ _ _] (let [undo-id (js/Symbol)] (rx/concat (rx/of (dwu/start-undo-transaction undo-id)) - (->> (rx/from shapes-by-color) - (rx/map (fn [shape] (case (:prop shape) - :fill (change-fill [(:shape-id shape)] new-color (:index shape)) - :stroke (change-stroke [(:shape-id shape)] new-color (:index shape)) - :shadow (change-shadow [(:shape-id shape)] new-color (:index shape)) - :content (dwt/update-text-with-function - (:shape-id shape) - (partial change-text-color old-color new-color (:index shape))))))) + (->> (rx/from operations) + (rx/map (fn [{:keys [shape-id index] :as operation}] + (case (:prop operation) + :fill (change-fill [shape-id] new-color index) + :stroke (change-stroke [shape-id] new-color index) + :shadow (change-shadow [shape-id] new-color index) + :content (dwt/update-text-with-function + shape-id + (partial change-text-color old-color new-color index)))))) (rx/of (dwu/commit-undo-transaction undo-id))))))) (defn apply-color-from-palette [color stroke?] + + (dm/assert! + "should be a valid color" + (ctc/check-color! color)) + (ptk/reify ::apply-color-from-palette ptk/WatchEvent (watch [_ state _] @@ -437,9 +523,10 @@ result (cond-> result (not group?) (conj cur))] (recur (rest pending) result))))] + (if stroke? - (rx/of (change-stroke ids (merge uc/empty-color color) 0)) - (rx/of (change-fill ids (merge uc/empty-color color) 0))))))) + (rx/of (change-stroke ids color 0)) + (rx/of (change-fill ids color 0))))))) (declare activate-colorpicker-color) (declare activate-colorpicker-gradient) @@ -448,15 +535,22 @@ (defn apply-color-from-colorpicker [color] + + (dm/assert! + "expected valid color structure" + (ctc/check-color! color)) + (ptk/reify ::apply-color-from-colorpicker ptk/WatchEvent (watch [_ _ _] - (rx/of - (cond - (:image color) (activate-colorpicker-image) - (:color color) (activate-colorpicker-color) - (= :linear (get-in color [:gradient :type])) (activate-colorpicker-gradient :linear-gradient) - (= :radial (get-in color [:gradient :type])) (activate-colorpicker-gradient :radial-gradient)))))) + ;; FIXME: revisit this + (let [gradient-type (dm/get-in color [:gradient :type])] + (rx/of + (cond + (:image color) (activate-colorpicker-image) + (:color color) (activate-colorpicker-color) + (= :linear gradient-type) (activate-colorpicker-gradient :linear-gradient) + (= :radial gradient-type) (activate-colorpicker-gradient :radial-gradient))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -596,7 +690,8 @@ (update :current-color merge changes) (update :current-color materialize-color-components) (update :current-color #(if (not= type :image) (dissoc % :image) %)) - ;; current color can be a library one I'm changing via colorpicker + ;; current color can be a library one + ;; I'm changing via colorpicker (d/dissoc-in [:current-color :id]) (d/dissoc-in [:current-color :file-id]))] (if-let [stop (:editing-stop state)] @@ -614,7 +709,8 @@ :colorpicker :type) formated-color (get-color-from-colorpicker-state (:colorpicker state)) - ;; Type is set to color on closing the colorpicker, but we can can close it while still uploading an image fill + ;; Type is set to color on closing the colorpicker, but we + ;; can can close it while still uploading an image fill ignore-color? (and (= selected-type :color) (nil? (:color formated-color)))] (when (and add-recent? (not ignore-color?)) (rx/of (dwl/add-recent-color formated-color))))))) @@ -686,6 +782,7 @@ (defn select-color [position add-color] + ;; FIXME: revisit (ptk/reify ::select-color ptk/WatchEvent (watch [_ state _] diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index a6c6cb8b3..cfa7e5f12 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -116,8 +116,13 @@ (update :id #(or % (uuid/next))) (assoc :name (or (get-in color [:image :name]) (:color color) - (uc/gradient-type->string (get-in color [:gradient :type])))))] - (dm/assert! ::ctc/color color) + (uc/gradient-type->string (get-in color [:gradient :type])))) + (d/without-nils))] + + (dm/assert! + "expect valid color structure" + (ctc/check-color! color)) + (ptk/reify ::add-color ev/Event (-data [_] color) @@ -135,8 +140,8 @@ [color] (dm/assert! - "expected valid recent color map" - (ctc/valid-recent-color? color)) + "expected valid recent color structure" + (ctc/check-recent-color! color)) (ptk/reify ::add-recent-color ptk/UpdateEvent @@ -155,7 +160,7 @@ (update [_ state] (assoc-in state [:workspace-local :color-for-rename] nil)))) -(defn- do-update-color +(defn- update-color* [it state color file-id] (let [data (get state :workspace-data) [path name] (cfh/parse-path-name (:name color)) @@ -171,19 +176,20 @@ (defn update-color [color file-id] + (let [color (d/without-nils color)] - (dm/assert! - "expected valid parameters" - (ctc/valid-color? color)) + (dm/assert! + "expected valid color data structure" + (ctc/check-color! color)) - (dm/assert! - "expected file-id" - (uuid? file-id)) + (dm/assert! + "expected file-id" + (uuid? file-id)) - (ptk/reify ::update-color - ptk/WatchEvent - (watch [it state _] - (do-update-color it state color file-id)))) + (ptk/reify ::update-color + ptk/WatchEvent + (watch [it state _] + (update-color* it state color file-id))))) (defn rename-color [file-id id new-name] @@ -198,9 +204,10 @@ (if (str/empty? new-name) (rx/empty) (let [data (get state :workspace-data) - object (get-in data [:colors id]) - object (assoc object :name new-name)] - (do-update-color it state object file-id))))))) + color (get-in data [:colors id]) + color (assoc color :name new-name) + color (d/without-nils color)] + (update-color* it state color file-id))))))) (defn delete-color [{:keys [id] :as params}] diff --git a/frontend/src/app/main/ui/hooks.cljs b/frontend/src/app/main/ui/hooks.cljs index 14bff1559..912fb8bc8 100644 --- a/frontend/src/app/main/ui/hooks.cljs +++ b/frontend/src/app/main/ui/hooks.cljs @@ -260,6 +260,14 @@ (when ^boolean obj (apply (.-f obj) args))))))) +(defn use-ref-value + "Returns a ref that will be automatically updated when the value is changed" + [v] + (let [ref (mf/use-ref v)] + (mf/with-effect [v] + (mf/set-ref-val! ref v)) + ref)) + (defn use-equal-memo [val] (let [ref (mf/use-ref nil)] diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/colors.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets/colors.cljs index 965f02bb3..7f7e67d92 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets/colors.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets/colors.cljs @@ -42,7 +42,7 @@ (cond-> color (:value color) (assoc :color (:value color) :opacity 1) (:value color) (dissoc :value) - true (assoc :file-id file-id))) + :always (assoc :file-id file-id))) color-id (:id color) @@ -70,7 +70,7 @@ (fn [event] (st/emit! (dwl/add-recent-color color) - (dc/apply-color-from-palette (merge uc/empty-color color) (kbd/alt? event))))) + (dc/apply-color-from-palette color (kbd/alt? event))))) rename-color (mf/use-fn diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs index b0c5b0cdc..e6c904beb 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs @@ -14,6 +14,7 @@ [app.main.data.workspace.selection :as dws] [app.main.store :as st] [app.main.ui.components.title-bar :refer [title-bar]] + [app.main.ui.hooks :as h] [app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]] [app.util.i18n :as i18n :refer [tr]] [rumext.v2 :as mf])) @@ -21,82 +22,96 @@ (defn- prepare-colors [shapes file-id shared-libs] (let [data (into [] (remove nil? (ctc/extract-all-colors shapes file-id shared-libs))) - grouped-colors (group-by :attrs data) + groups (d/group-by :attrs #(dissoc % :attrs) data) all-colors (distinct (mapv :attrs data)) tmp (group-by #(some? (:id %)) all-colors) library-colors (get tmp true) colors (get tmp false)] - {:grouped-colors grouped-colors + {:groups groups :all-colors all-colors :colors colors :library-colors library-colors})) +(def xf:map-shape-id + (map :shape-id)) + (mf/defc color-selection-menu {::mf/wrap [#(mf/memo' % (mf/check-props ["shapes"]))] ::mf/wrap-props false} [{:keys [shapes file-id shared-libs]}] - (let [{:keys [grouped-colors library-colors colors]} (mf/with-memo [shapes file-id shared-libs] - (prepare-colors shapes file-id shared-libs)) + (let [{:keys [groups library-colors colors]} (mf/with-memo [shapes file-id shared-libs] + (prepare-colors shapes file-id shared-libs)) - state* (mf/use-state true) - open? (deref state*) + state* (mf/use-state true) + open? (deref state*) - has-colors? (or (some? (seq colors)) (some? (seq library-colors))) + has-colors? (or (some? (seq colors)) (some? (seq library-colors))) - toggle-content (mf/use-fn #(swap! state* not)) + toggle-content (mf/use-fn #(swap! state* not)) expand-lib-color (mf/use-state false) expand-color (mf/use-state false) - grouped-colors* (mf/use-var nil) - prev-colors* (mf/use-var []) + groups-ref (h/use-ref-value groups) + prev-colors-ref (mf/use-ref nil) + + ;; grouped-colors* (mf/use-var nil) + ;; prev-colors* (mf/use-var []) on-change (mf/use-fn (fn [new-color old-color from-picker?] - (let [old-color (-> old-color (dissoc :name :path) d/without-nils) + (prn "new-color" new-color) + (prn "old-color" old-color) + (let [old-color (-> old-color + (dissoc :name :path) + (d/without-nils)) ;; When dragging on the color picker sometimes all ;; the shapes hasn't updated the color to the prev ;; value so we need this extra calculation - shapes-by-old-color (get @grouped-colors* old-color) - prev-color (d/seek #(get @grouped-colors* %) @prev-colors*) - shapes-by-prev-color (get @grouped-colors* prev-color) - shapes-by-color (or shapes-by-prev-color shapes-by-old-color)] + groups (mf/ref-val groups-ref) + prev-colors (mf/ref-val prev-colors-ref) + + prev-color (d/seek (partial get groups) prev-colors) + + cops-old (get groups old-color) + cops-prev (get groups prev-colors) + cops (or cops-prev cops-old) + old-color (or prev-color old-color)] (when from-picker? - (swap! prev-colors* conj (-> new-color (dissoc :name :path) d/without-nils))) + (let [color (-> new-color + (dissoc :name :path) + (d/without-nils))] + (mf/set-ref-val! prev-colors-ref + (conj prev-colors color)))) - (st/emit! (dc/change-color-in-selected new-color shapes-by-color (or prev-color old-color)))))) + (st/emit! (dc/change-color-in-selected cops new-color old-color))))) on-open - (mf/use-fn - (fn [] - (reset! prev-colors* []))) + (mf/use-fn #(mf/set-ref-val! prev-colors-ref [])) on-close - (mf/use-fn - (fn [] - (reset! prev-colors* []))) + (mf/use-fn #(mf/set-ref-val! prev-colors-ref [])) on-detach (mf/use-fn (fn [color] - (let [shapes-by-color (get @grouped-colors* color) - new-color (assoc color :id nil :file-id nil)] - (st/emit! (dc/change-color-in-selected new-color shapes-by-color color))))) + (let [groups (mf/ref-val groups-ref) + cops (get groups color) + color' (dissoc color :id :file-id)] + (st/emit! (dc/change-color-in-selected cops color' color))))) select-only (mf/use-fn (fn [color] - (let [shapes-by-color (get @grouped-colors* color) - ids (into (d/ordered-set) (map :shape-id) shapes-by-color)] + (let [groups (mf/ref-val groups-ref) + cops (get groups color) + ids (into (d/ordered-set) xf:map-shape-id cops)] (st/emit! (dws/select-shapes ids)))))] - (mf/with-effect [grouped-colors] - (reset! grouped-colors* grouped-colors)) - [:div {:class (stl/css :element-set)} [:div {:class (stl/css :element-title)} [:& title-bar {:collapsable has-colors? diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs index dcbabace6..d6ad61822 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs @@ -113,9 +113,9 @@ handle-value-change (mf/use-fn (mf/deps color on-change) - (fn [new-value] + (fn [value] (let [color (-> color - (assoc :color new-value) + (assoc :color value) (dissoc :gradient))] (st/emit! (dwl/add-recent-color color) (on-change color))))) @@ -146,7 +146,9 @@ :else color) - {:keys [x y]} (dom/get-client-position event) + cpos (dom/get-client-position event) + x (dm/get-prop cpos :x) + y (dm/get-prop cpos :y) props {:x x :y y @@ -154,14 +156,14 @@ :disable-opacity disable-opacity :disable-image disable-image ;; on-change second parameter means if the source is the color-picker - :on-change #(on-change (merge uc/empty-color %) true) + :on-change #(on-change % true) :on-close (fn [value opacity id file-id] (when on-close (on-close value opacity id file-id))) :data color}] - (when on-open - (on-open (merge uc/empty-color color))) + (when (fn? on-open) + (on-open color)) (modal/show! :colorpicker props)))) diff --git a/frontend/src/app/util/color.cljs b/frontend/src/app/util/color.cljs index aeb95d007..078102d90 100644 --- a/frontend/src/app/util/color.cljs +++ b/frontend/src/app/util/color.cljs @@ -80,9 +80,6 @@ (= id :multiple) (= file-id :multiple))) -(def empty-color - (into {} (map #(vector % nil)) [:color :id :file-id :gradient :opacity :image])) - (defn get-color-name [color] (or (:color-library-name color) From 86c5ca42135f04a26d17e5b9549ab85c1bbfe54c Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 16 Sep 2024 18:21:25 +0200 Subject: [PATCH 12/19] :bug: Fix incorrect redirect handling on request-access go-home button --- frontend/src/app/main.cljs | 1 - frontend/src/app/main/data/users.cljs | 1 + frontend/src/app/main/ui.cljs | 4 +- frontend/src/app/main/ui/dashboard.cljs | 25 ++- frontend/src/app/main/ui/routes.cljs | 6 +- frontend/src/app/main/ui/static.cljs | 236 +++++++++++++++--------- frontend/src/app/util/dom.cljs | 6 +- frontend/src/app/util/router.cljs | 12 +- 8 files changed, 182 insertions(+), 109 deletions(-) diff --git a/frontend/src/app/main.cljs b/frontend/src/app/main.cljs index 3cbbe0d70..517376ba2 100644 --- a/frontend/src/app/main.cljs +++ b/frontend/src/app/main.cljs @@ -143,4 +143,3 @@ (reinit)))) (set! (.-stackTraceLimit js/Error) 50) - diff --git a/frontend/src/app/main/data/users.cljs b/frontend/src/app/main/data/users.cljs index 9cf0f9966..f4ea04997 100644 --- a/frontend/src/app/main/data/users.cljs +++ b/frontend/src/app/main/data/users.cljs @@ -171,6 +171,7 @@ (let [team-id (get-current-team-id profile) welcome-file-id (dm/get-in profile [:props :welcome-file-id]) redirect-href (:login-redirect @s/session)] + (cond (some? redirect-href) (binding [s/*sync* true] diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs index 8a53010de..9de71e8e2 100644 --- a/frontend/src/app/main/ui.cljs +++ b/frontend/src/app/main/ui.cljs @@ -42,7 +42,8 @@ (mf/lazy-component app.main.ui.workspace/workspace)) (mf/defc main-page - {::mf/props :obj} + {::mf/props :obj + ::mf/private true} [{:keys [route profile]}] (let [{:keys [data params]} route props (get profile :props) @@ -68,6 +69,7 @@ (:onboarding-viewed props) (not= (:release-notes-viewed props) (:main cf/version)) (not= "0.0" (:main cf/version)))] + [:& (mf/provider ctx/current-route) {:value route} (case (:name data) (:auth-login diff --git a/frontend/src/app/main/ui/dashboard.cljs b/frontend/src/app/main/ui/dashboard.cljs index 78c9902a9..0ec58ba16 100644 --- a/frontend/src/app/main/ui/dashboard.cljs +++ b/frontend/src/app/main/ui/dashboard.cljs @@ -42,9 +42,7 @@ (let [search-term (get-in route [:params :query :search-term]) team-id (get-in route [:params :path :team-id]) project-id (get-in route [:params :path :project-id])] - (cond-> - {:search-term search-term} - + (cond-> {:search-term search-term} (uuid-str? team-id) (assoc :team-id (uuid team-id)) @@ -84,10 +82,10 @@ (mf/use-effect on-resize) - [:div {:class (stl/css :dashboard-content) :style {:pointer-events (when file-menu-open? "none")} - :on-click clear-selected-fn :ref container} + :on-click clear-selected-fn + :ref container} (case section :dashboard-projects [:* @@ -146,7 +144,8 @@ (l/derived :current-team-id st/state)) (mf/defc dashboard - [{:keys [route profile] :as props}] + {::mf/props :obj} + [{:keys [route profile]}] (let [section (get-in route [:data :name]) params (parse-params route) @@ -181,13 +180,13 @@ [:& (mf/provider ctx/current-team-id) {:value team-id} [:& (mf/provider ctx/current-project-id) {:value project-id} - ;; NOTE: dashboard events and other related functions assumes - ;; that the team is a implicit context variable that is - ;; available using react context or accessing - ;; the :current-team-id on the state. We set the key to the - ;; team-id because we want to completely refresh all the - ;; components on team change. Many components assumes that the - ;; team is already set so don't put the team into mf/deps. + ;; NOTE: dashboard events and other related functions assumes + ;; that the team is a implicit context variable that is + ;; available using react context or accessing + ;; the :current-team-id on the state. We set the key to the + ;; team-id because we want to completely refresh all the + ;; components on team change. Many components assumes that the + ;; team is already set so don't put the team into mf/deps. (when (and team initialized?) [:main {:class (stl/css :dashboard) :key (:id team)} diff --git a/frontend/src/app/main/ui/routes.cljs b/frontend/src/app/main/ui/routes.cljs index ff943a0f7..7b26b8561 100644 --- a/frontend/src/app/main/ui/routes.cljs +++ b/frontend/src/app/main/ui/routes.cljs @@ -106,9 +106,9 @@ (st/emit! (rt/navigated match)) :else - ;; We just recheck with an additional profile request; this avoids - ;; some race conditions that causes unexpected redirects on - ;; invitations workflows (and probably other cases). + ;; We just recheck with an additional profile request; this + ;; avoids some race conditions that causes unexpected redirects + ;; on invitations workflows (and probably other cases). (->> (rp/cmd! :get-profile) (rx/subs! (fn [{:keys [id] :as profile}] (cond diff --git a/frontend/src/app/main/ui/static.cljs b/frontend/src/app/main/ui/static.cljs index e069a9486..8f6ea87b2 100644 --- a/frontend/src/app/main/ui/static.cljs +++ b/frontend/src/app/main/ui/static.cljs @@ -7,19 +7,21 @@ (ns app.main.ui.static (:require-macros [app.main.style :as stl]) (:require + ["rxjs" :as rxjs] [app.common.data :as d] [app.common.pprint :as pp] [app.common.uri :as u] [app.main.data.common :as dc] [app.main.data.events :as ev] + [app.main.refs :as refs] [app.main.repo :as rp] [app.main.store :as st] [app.main.ui.auth.login :refer [login-methods]] [app.main.ui.auth.recovery-request :refer [recovery-request-page recovery-sent-page]] - [app.main.ui.auth.register :refer [register-methods register-validate-form register-success-page terms-register]] + [app.main.ui.auth.register :as register] [app.main.ui.dashboard.sidebar :refer [sidebar]] [app.main.ui.icons :as i] - [app.main.ui.viewer.header :as header] + [app.main.ui.viewer.header :as viewer.header] [app.util.dom :as dom] [app.util.i18n :refer [tr]] [app.util.router :as rt] @@ -29,10 +31,14 @@ [potok.v2.core :as ptk] [rumext.v2 :as mf])) +;; FIXME: this is a workaround until we export this class on beicon library +(def TimeoutError rxjs/TimeoutError) + (mf/defc error-container* {::mf/props :obj} [{:keys [children]}] - (let [profile-id (:profile-id @st/state)] + (let [profile-id (:profile-id @st/state) + on-nav-root (mf/use-fn #(st/emit! (rt/nav-root)))] [:section {:class (stl/css :exception-layout)} [:button {:class (stl/css :exception-header) @@ -44,7 +50,7 @@ [:div {:class (stl/css :deco-before)} i/logo-error-screen] (when-not profile-id [:button {:class (stl/css :login-header) - :on-click rt/nav-root} + :on-click on-nav-root} (tr "labels.login")]) [:div {:class (stl/css :exception-content)} @@ -65,8 +71,8 @@ {::mf/props :obj} [{:keys [show-dialog]}] (let [current-section (mf/use-state :login) - user-email (mf/use-state "") - register-token (mf/use-state "") + user-email (mf/use-state "") + register-token (mf/use-state "") set-section (mf/use-fn @@ -85,29 +91,37 @@ #(reset! current-section :login)) success-login - (fn [] - (reset! show-dialog false) - (.reload js/window.location true)) + (mf/use-fn + (fn [] + (reset! show-dialog false) + (st/emit! (rt/reload true)))) success-register - (fn [data] - (reset! register-token (:token data)) - (reset! current-section :register-validate)) + (mf/use-fn + (fn [data] + (reset! register-token (:token data)) + (reset! current-section :register-validate))) register-email-sent - (fn [email] - (reset! user-email email) - (reset! current-section :register-email-sent)) + (mf/use-fn + (fn [email] + (reset! user-email email) + (reset! current-section :register-email-sent))) recovery-email-sent - (fn [email] - (reset! user-email email) - (reset! current-section :recovery-email-sent))] + (mf/use-fn + (fn [email] + (reset! user-email email) + (reset! current-section :recovery-email-sent))) + + on-nav-root + (mf/use-fn #(st/emit! (rt/nav-root)))] [:div {:class (stl/css :overlay)} [:div {:class (stl/css :dialog-login)} [:div {:class (stl/css :modal-close)} - [:button {:class (stl/css :modal-close-button) :on-click rt/nav-root} + [:button {:class (stl/css :modal-close-button) + :on-click on-nav-root} i/close]] [:div {:class (stl/css :login)} [:div {:class (stl/css :logo)} i/logo] @@ -125,13 +139,14 @@ (tr "auth.register") " " [:a {:data-section "register" - :on-click set-section} (tr "auth.register-submit")]]] + :on-click set-section} + (tr "auth.register-submit")]]] :register [:* [:div {:class (stl/css :logo-title)} (tr "not-found.login.signup-free")] [:div {:class (stl/css :logo-subtitle)} (tr "not-found.login.start-using")] - [:& register-methods {:on-success-callback success-register :hide-separator true}] + [:& register/register-methods {:on-success-callback success-register :hide-separator true}] #_[:hr {:class (stl/css :separator)}] [:div {:class (stl/css :separator)}] [:div {:class (stl/css :change-section)} @@ -141,12 +156,13 @@ :on-click set-section} (tr "auth.login-here")]] [:div {:class (stl/css :links)} [:hr {:class (stl/css :separator)}] - [:& terms-register]]] + [:& register/terms-register]]] :register-validate [:div {:class (stl/css :form-container)} - [:& register-validate-form {:params {:token @register-token} - :on-success-callback register-email-sent}] + [:& register/register-validate-form + {:params {:token @register-token} + :on-success-callback register-email-sent}] [:div {:class (stl/css :links)} [:div {:class (stl/css :register)} [:a {:data-section "register" @@ -155,7 +171,7 @@ :register-email-sent [:div {:class (stl/css :form-container)} - [:& register-success-page {:params {:email @user-email :hide-logo true}}]] + [:& register/register-success-page {:params {:email @user-email :hide-logo true}}]] :recovery-request [:& recovery-request-page {:go-back-callback set-section-login @@ -175,40 +191,49 @@ [:button {:class (stl/css :modal-close-button) :on-click on-close} i/close]] [:div {:class (stl/css :dialog-title)} title] - (for [txt content] - [:div txt]) + (for [[index content] (d/enumerate content)] + [:div {:key index} content]) [:div {:class (stl/css :sign-info)} (when cancel-text - [:button {:class (stl/css :cancel-button) :on-click on-close} cancel-text]) + [:button {:class (stl/css :cancel-button) + :on-click on-close} + cancel-text]) [:button {:on-click on-click} button-text]]]])) - (mf/defc request-access {::mf/props :obj} [{:keys [file-id team-id is-default workspace?]}] - (let [profile (:profile @st/state) + (let [profile (mf/deref refs/profile) requested* (mf/use-state {:sent false :already-requested false}) requested (deref requested*) show-dialog (mf/use-state true) + on-close (mf/use-fn (mf/deps profile) (fn [] (st/emit! (rt/nav :dashboard-projects {:team-id (:default-team-id profile)})))) + on-success (mf/use-fn #(reset! requested* {:sent true :already-requested false})) + on-error (mf/use-fn #(reset! requested* {:sent true :already-requested true})) + on-request-access (mf/use-fn (mf/deps file-id team-id workspace?) (fn [] - (let [params (if (some? file-id) {:file-id file-id :is-viewer (not workspace?)} {:team-id team-id}) - mdata {:on-success on-success :on-error on-error}] - (st/emit! (dc/create-team-access-request (with-meta params mdata))))))] - + (let [params (if (some? file-id) + {:file-id file-id + :is-viewer (not workspace?)} + {:team-id team-id}) + mdata {:on-success on-success + :on-error on-error}] + (st/emit! (dc/create-team-access-request + (with-meta params mdata))))))] [:* (if (some? file-id) @@ -220,17 +245,24 @@ [:div {:class (stl/css :project-name)} (tr "not-found.no-permission.project-name")] [:div {:class (stl/css :file-name)} (tr "not-found.no-permission.penpot-file")]]] [:div {:class (stl/css :workspace-right)}]] + [:div {:class (stl/css :viewer)} - [:& header/header {:project {:name (tr "not-found.no-permission.project-name")} - :index 0 - :file {:name (tr "not-found.no-permission.penpot-file")} - :page nil - :frame nil - :permissions {:is-logged true} - :zoom 1 - :section :interactions - :shown-thumbnails false - :interactions-mode nil}]]) + ;; FIXME: the viewer header was never designed to be reused + ;; from other parts of the application, and this code looks + ;; like a fast workaround reusing it as-is without a proper + ;; component adaptation for be able to use it easily it on + ;; viewer context or static error page context + [:& viewer.header/header {:project + {:name (tr "not-found.no-permission.project-name")} + :index 0 + :file {:name (tr "not-found.no-permission.penpot-file")} + :page nil + :frame nil + :permissions {:is-logged true} + :zoom 1 + :section :interactions + :shown-thumbnails false + :interactions-mode nil}]]) [:div {:class (stl/css :dashboard)} [:div {:class (stl/css :dashboard-sidebar)} @@ -392,64 +424,92 @@ [:div {:class (stl/css :sign-info)} [:button {:on-click on-reset} (tr "labels.retry")]]])) +(defn- load-info + "Load exception page info" + [path-params] + (let [default {:loaded true} + stream (cond + (:file-id path-params) + (->> (rp/cmd! :get-file-info {:id (:file-id path-params)}) + (rx/map (fn [info] + {:loaded true + :file-id (:id info)}))) + + (:team-id path-params) + (->> (rp/cmd! :get-team-info {:id (:team-id path-params)}) + (rx/map (fn [info] + {:loaded true + :team-id (:id info) + :team-default (:is-default info)}))) + + :else + (rx/of default))] + + (->> stream + (rx/timeout 3000) + (rx/catch (fn [cause] + (if (instance? TimeoutError cause) + (rx/of default) + (rx/throw cause))))))) + + (mf/defc exception-page* {::mf/props :obj} [{:keys [data route] :as props}] - (let [file-info (mf/use-state {:pending true}) - team-info (mf/use-state {:pending true}) - type (:type data) - path (:path route) + (let [type (:type data) + path (:path route) - workspace? (str/includes? path "workspace") - dashboard? (str/includes? path "dashboard") - view? (str/includes? path "view") + query-params (:query-params route) + path-params (:path-params route) - request-access? (and - (or workspace? dashboard? view?) - (or (not (str/empty? (:file-id @file-info))) (not (str/empty? (:team-id @team-info))))) + workspace? (str/includes? path "workspace") + dashboard? (str/includes? path "dashboard") + view? (str/includes? path "view") - query-params (u/map->query-string (:query-params route)) - pparams (:path-params route) - on-file-info (mf/use-fn - (fn [info] - (reset! file-info {:file-id (:id info)}))) - on-team-info (mf/use-fn - (fn [info] - (reset! team-info {:team-id (:id info) :is-default (:is-default info)})))] + ;; We stora the request access info int this state + info* (mf/use-state nil) + info (deref info*) - (mf/with-effect [type path query-params pparams @file-info @team-info] - (st/emit! (ptk/event ::ev/event {::ev/name "exception-page" :type type :path path :query-params query-params})) + loaded? (get info :loaded false) - (when (and (:file-id pparams) (:pending @file-info)) - (->> (rp/cmd! :get-file-info {:id (:file-id pparams)}) - (rx/subs! on-file-info))) + request-access? + (and + (or workspace? dashboard? view?) + (or (:file-id info) + (:team-id info)))] - (when (and (:team-id pparams) (:pending @team-info)) - (->> (rp/cmd! :get-team-info {:id (:team-id pparams)}) - (rx/subs! on-team-info)))) + (mf/with-effect [type path query-params path-params] + (let [query-params (u/map->query-string query-params) + event-params {::ev/name "exception-page" + :type type + :path path + :query-params query-params}] + (st/emit! (ptk/event ::ev/event event-params)))) - (case (:type data) - :not-found + (mf/with-effect [path-params info] + (when-not (:loaded info) + (->> (load-info path-params) + (rx/subs! (partial reset! info*))))) + + (when loaded? (if request-access? - [:& request-access {:file-id (:file-id @file-info) - :team-id (:team-id @team-info) - :is-default (:is-default @team-info) + [:& request-access {:file-id (:file-id info) + :team-id (:team-id info) + :is-default (:team-default info) :workspace? workspace?}] - [:> not-found* {}]) - :authentication - (if request-access? - [:& request-access {:file-id (:file-id @file-info) - :team-id (:team-id @team-info) - :is-default (:is-default @team-info) - :workspace? workspace?}] - [:> not-found* {}]) + (case (:type data) + :not-found + [:> not-found* {}] - :bad-gateway - [:> bad-gateway* props] + :authentication + [:> not-found* {}] - :service-unavailable - [:& service-unavailable] + :bad-gateway + [:> bad-gateway* props] - [:> internal-error* props]))) + :service-unavailable + [:& service-unavailable] + + [:> internal-error* props]))))) diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index 08b89d364..488a0d728 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -760,8 +760,10 @@ (.back (.-history js/window))) (defn reload-current-window - [] - (.reload (.-location js/window))) + ([] + (.reload globals/location)) + ([force?] + (.reload globals/location force?))) (defn scroll-by! ([element x y] diff --git a/frontend/src/app/util/router.cljs b/frontend/src/app/util/router.cljs index 9180d5d77..1a92d4ec7 100644 --- a/frontend/src/app/util/router.cljs +++ b/frontend/src/app/util/router.cljs @@ -148,7 +148,17 @@ (defn nav-root "Navigate to the root page." [] - (set! (.-href globals/location) "/")) + (ptk/reify ::nav-root + ptk/EffectEvent + (effect [_ _ _] + (set! (.-href globals/location) "/")))) + +(defn reload + [force?] + (ptk/reify ::reload + ptk/EffectEvent + (effect [_ _ _] + (ts/asap (partial dom/reload-current-window force?))))) (defn nav-raw [& {:keys [href uri]}] From f5f255e2d5ad08e72e61b587591ca9987c7cdfa4 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 17 Sep 2024 14:13:17 +0200 Subject: [PATCH 13/19] :bug: Fix problem with comments max length --- CHANGES.md | 1 + backend/src/app/rpc/commands/comments.clj | 2 +- frontend/src/app/main/ui/comments.cljs | 13 +++++++++---- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 49b339c20..a909dc22a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -83,6 +83,7 @@ - Fix problem when dismissing shared library update [Taiga #8669](https://tree.taiga.io/project/penpot/issue/8669) - Fix visual problem with stroke cap menu [Taiga #8730](https://tree.taiga.io/project/penpot/issue/8730) - Fix issue when exporting libraries when merging libraries [Taiga #8758](https://tree.taiga.io/project/penpot/issue/8758) +- Fix problem with comments max length [Taiga #8778](https://tree.taiga.io/project/penpot/issue/8778) ## 2.1.5 diff --git a/backend/src/app/rpc/commands/comments.clj b/backend/src/app/rpc/commands/comments.clj index 41645a8be..cbc214262 100644 --- a/backend/src/app/rpc/commands/comments.clj +++ b/backend/src/app/rpc/commands/comments.clj @@ -292,7 +292,7 @@ [:map {:title "create-comment-thread"} [:file-id ::sm/uuid] [:position ::gpt/point] - [:content [:string {:max 250}]] + [:content [:string {:max 750}]] [:page-id ::sm/uuid] [:frame-id ::sm/uuid] [:share-id {:optional true} [:maybe ::sm/uuid]]]) diff --git a/frontend/src/app/main/ui/comments.cljs b/frontend/src/app/main/ui/comments.cljs index 5427b29f1..6fa55cc07 100644 --- a/frontend/src/app/main/ui/comments.cljs +++ b/frontend/src/app/main/ui/comments.cljs @@ -35,6 +35,7 @@ on-focus (unchecked-get props "on-focus") on-blur (unchecked-get props "on-blur") placeholder (unchecked-get props "placeholder") + max-length (unchecked-get props "max-length") on-change (unchecked-get props "on-change") on-esc (unchecked-get props "on-esc") on-ctrl-enter (unchecked-get props "on-ctrl-enter") @@ -88,7 +89,8 @@ :on-blur on-blur :value value :placeholder placeholder - :on-change on-change*}])) + :on-change on-change* + :max-length max-length}])) (mf/defc reply-form [{:keys [thread] :as props}] @@ -128,7 +130,8 @@ :on-focus on-focus :select-on-focus? false :on-ctrl-enter on-submit - :on-change on-change}] + :on-change on-change + :max-length 750}] (when (or @show-buttons? (seq @content)) [:div {:class (stl/css :buttons-wrapper)} [:input.btn-secondary @@ -196,7 +199,8 @@ :select-on-focus? false :on-esc on-esc :on-change on-change - :on-ctrl-enter on-submit}] + :on-ctrl-enter on-submit + :max-length 750}] [:div {:class (stl/css :buttons-wrapper)} [:input {:on-click on-esc @@ -233,7 +237,8 @@ :select-on-focus true :select-on-focus? false :on-ctrl-enter on-submit* - :on-change on-change}] + :on-change on-change + :max-length 750}] [:div {:class (stl/css :buttons-wrapper)} [:input {:type "button" :value "Cancel" From ebaf30727c79ea7f63a7f8ef40bd885d3f958f8e Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 17 Sep 2024 14:18:35 +0200 Subject: [PATCH 14/19] :bug: Fix copy/paste images in Safari --- CHANGES.md | 1 + frontend/src/app/main/data/workspace.cljs | 46 ++++++++++++++++++----- frontend/src/app/util/webapi.cljs | 8 ++++ 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a909dc22a..83cce1003 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -84,6 +84,7 @@ - Fix visual problem with stroke cap menu [Taiga #8730](https://tree.taiga.io/project/penpot/issue/8730) - Fix issue when exporting libraries when merging libraries [Taiga #8758](https://tree.taiga.io/project/penpot/issue/8758) - Fix problem with comments max length [Taiga #8778](https://tree.taiga.io/project/penpot/issue/8778) +- Fix copy/paste images in Safari [Taiga #8771](https://tree.taiga.io/project/penpot/issue/8771) ## 2.1.5 diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index dd62ff70d..6806ce89b 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -85,7 +85,8 @@ [beicon.v2.core :as rx] [cljs.spec.alpha :as s] [cuerdas.core :as str] - [potok.v2.core :as ptk])) + [potok.v2.core :as ptk] + [promesa.core :as p])) (def default-workspace-local {:zoom 1}) (log/set-level! :debug) @@ -1551,15 +1552,40 @@ shapes (->> (cfh/selected-with-children objects selected) (keep (d/getf objects)))] - (->> (rx/from shapes) - (rx/merge-map (partial prepare-object objects frame-id)) - (rx/reduce collect-data initial) - (rx/map (partial sort-selected state)) - (rx/map (partial advance-copies state selected)) - (rx/map #(t/encode-str % {:type :json-verbose})) - (rx/map wapi/write-to-clipboard) - (rx/catch on-copy-error) - (rx/ignore))))))))) + ;; The clipboard API doesn't handle well asynchronous calls because it expects to use + ;; the clipboard in an user interaction. If you do an async call the callback is outside + ;; the thread of the UI and so Safari blocks the copying event. + ;; We use the API `ClipboardItem` that allows promises to be passed and so the event + ;; will wait for the promise to resolve and everything should work as expected. + ;; This only works in the current versions of the browsers. + (if (some? (unchecked-get ug/global "ClipboardItem")) + (let [resolve-data-promise + (p/create + (fn [resolve reject] + (->> (rx/from shapes) + (rx/merge-map (partial prepare-object objects frame-id)) + (rx/reduce collect-data initial) + (rx/map (partial sort-selected state)) + (rx/map (partial advance-copies state selected)) + (rx/map #(t/encode-str % {:type :json-verbose})) + (rx/map #(wapi/create-blob % "text/plain")) + (rx/subs! resolve reject))))] + (->> (rx/from (wapi/write-to-clipboard-promise "text/plain" resolve-data-promise)) + (rx/catch on-copy-error) + (rx/ignore))) + + ;; FIXME: this is to support Firefox versions below 116 that don't support `ClipboardItem` + ;; after the version 116 is less common we could remove this. + ;; https://caniuse.com/?search=ClipboardItem + (->> (rx/from shapes) + (rx/merge-map (partial prepare-object objects frame-id)) + (rx/reduce collect-data initial) + (rx/map (partial sort-selected state)) + (rx/map (partial advance-copies state selected)) + (rx/map #(t/encode-str % {:type :json-verbose})) + (rx/map wapi/write-to-clipboard) + (rx/catch on-copy-error) + (rx/ignore)))))))))) (declare ^:private paste-transit) (declare ^:private paste-text) diff --git a/frontend/src/app/util/webapi.cljs b/frontend/src/app/util/webapi.cljs index 9d1ba5c2e..2225a96db 100644 --- a/frontend/src/app/util/webapi.cljs +++ b/frontend/src/app/util/webapi.cljs @@ -103,6 +103,14 @@ (let [cboard (unchecked-get js/navigator "clipboard")] (.writeText ^js cboard data))) +(defn write-to-clipboard-promise + [mimetype promise] + (let [cboard (unchecked-get js/navigator "clipboard") + data (js/ClipboardItem. + (-> (obj/create) + (obj/set! mimetype promise)))] + (.write ^js cboard #js [data]))) + (defn read-from-clipboard [] (try From 9a587c91a8c879ac29affe02b395ca51bf828a86 Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Tue, 17 Sep 2024 16:29:02 +0200 Subject: [PATCH 15/19] :bug: Fix arrow key movement on tabs --- frontend/playwright/ui/specs/workspace.spec.js | 12 ++++++++++++ frontend/src/app/main/ui/ds/tab_switcher.cljs | 4 ++-- frontend/src/app/main/ui/ds/tab_switcher.scss | 10 ++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/frontend/playwright/ui/specs/workspace.spec.js b/frontend/playwright/ui/specs/workspace.spec.js index 066486f50..8ce047fe3 100644 --- a/frontend/playwright/ui/specs/workspace.spec.js +++ b/frontend/playwright/ui/specs/workspace.spec.js @@ -177,3 +177,15 @@ test("Bug 7489 - Workspace-palette items stay hidden when opening with keyboard- ), ).toBeVisible(); }); + +test("Bug 8784 - Use keyboard arrow to move inside a text input does not change tabs", async ({ + page, +}) => { + const workspacePage = new WorkspacePage(page); + await workspacePage.setupEmptyFile(); + await workspacePage.goToWorkspace(); + await workspacePage.pageName.click(); + await page.keyboard.press("ArrowLeft"); + + await expect(workspacePage.pageName).toHaveText("Page 1"); +}); diff --git a/frontend/src/app/main/ui/ds/tab_switcher.cljs b/frontend/src/app/main/ui/ds/tab_switcher.cljs index 521ec239f..fa17d1bc3 100644 --- a/frontend/src/app/main/ui/ds/tab_switcher.cljs +++ b/frontend/src/app/main/ui/ds/tab_switcher.cljs @@ -175,8 +175,7 @@ class (dm/str class " " (stl/css :tabs)) - props (mf/spread-props props {:class class - :on-key-down on-key-down})] + props (mf/spread-props props {:class class})] (mf/with-effect [tabs] (mf/set-ref-val! tabs-ref tabs)) @@ -188,6 +187,7 @@ :tabs tabs :on-ref on-ref :selected selected + :on-key-down on-key-down :on-click on-click}]] (let [active-tab (get-tab tabs selected) diff --git a/frontend/src/app/main/ui/ds/tab_switcher.scss b/frontend/src/app/main/ui/ds/tab_switcher.scss index e8df69e6c..0b24f3343 100644 --- a/frontend/src/app/main/ui/ds/tab_switcher.scss +++ b/frontend/src/app/main/ui/ds/tab_switcher.scss @@ -101,7 +101,17 @@ } .tab-panel { + --tab-panel-outline-color: none; + &:focus { + outline: none; + } + + &:focus-visible { + --tab-panel-outline-color: var(--color-accent-primary); + } + display: grid; width: 100%; height: 100%; + outline: $b-1 solid var(--tab-panel-outline-color); } From b58edea544fe79c08216f9a2db7213302fc80bd6 Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Tue, 17 Sep 2024 17:00:40 +0200 Subject: [PATCH 16/19] :bug: Fix path side panel options --- .../ui/workspace/sidebar/options/rows/stroke_row.scss | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.scss b/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.scss index a9fd66fa4..d2a367b49 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.scss @@ -18,8 +18,9 @@ } .stroke-options { - @include flexRow; display: grid; + align-items: center; + gap: $s-4; grid-template-columns: 1fr 2fr 2fr; .stroke-width-input-element { @@ -28,7 +29,10 @@ } } .stroke-caps-options { - @include flexRow; + display: grid; + align-items: center; + gap: $s-4; + grid-template-columns: 1fr auto 1fr; } .cap-select { From b5121657eeeca75a0c012b84334eb72a85275744 Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Wed, 18 Sep 2024 16:23:12 +0200 Subject: [PATCH 17/19] :sparkles: Update templates links to binary v2 --- backend/resources/app/onboarding.edn | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/backend/resources/app/onboarding.edn b/backend/resources/app/onboarding.edn index 07a11859d..43a701f24 100644 --- a/backend/resources/app/onboarding.edn +++ b/backend/resources/app/onboarding.edn @@ -1,42 +1,42 @@ [{:id "wireframing-kit" :name "Wireframe library" - :file-uri "https://github.com/penpot/penpot-files/raw/binary-files/wireframing-kit.penpot"} + :file-uri "https://github.com/penpot/penpot-files/raw/main/wireframing-kit.penpot"} {:id "prototype-examples" :name "Prototype template" - :file-uri "https://github.com/penpot/penpot-files/raw/binary-files/prototype-examples.penpot"} + :file-uri "https://github.com/penpot/penpot-files/raw/main/prototype-examples.penpot"} {:id "plants-app" :name "UI mockup example" - :file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Plants-app.penpot"} + :file-uri "https://github.com/penpot/penpot-files/raw/main/Plants-app.penpot"} {:id "penpot-design-system" :name "Design system example" - :file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Penpot-Design-system.penpot"} + :file-uri "https://github.com/penpot/penpot-files/raw/main/Penpot-Design-system.penpot"} {:id "tutorial-for-beginners" :name "Tutorial for beginners" - :file-uri "https://github.com/penpot/penpot-files/raw/binary-files/tutorial-for-beginners.penpot"} + :file-uri "https://github.com/penpot/penpot-files/raw/main/tutorial-for-beginners.penpot"} {:id "lucide-icons" :name "Lucide Icons" - :file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Lucide-icons.penpot"} + :file-uri "https://github.com/penpot/penpot-files/raw/main/Lucide-icons.penpot"} {:id "font-awesome" :name "Font Awesome" - :file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Font-Awesome.penpot"} + :file-uri "https://github.com/penpot/penpot-files/raw/main/Font-Awesome.penpot"} {:id "black-white-mobile-templates" :name "Black & White Mobile Templates" - :file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Black-White-Mobile-Templates.penpot"} + :file-uri "https://github.com/penpot/penpot-files/raw/main/Black-White-Mobile-Templates.penpot"} {:id "avataaars" :name "Avataaars" - :file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Avataaars-by-Pablo-Stanley.penpot"} + :file-uri "https://github.com/penpot/penpot-files/raw/main/Avataaars-by-Pablo-Stanley.penpot"} {:id "ux-notes" :name "UX Notes" - :file-uri "https://github.com/penpot/penpot-files/raw/binary-files/UX-Notes.penpot"} + :file-uri "https://github.com/penpot/penpot-files/raw/main/UX-Notes.penpot"} {:id "whiteboarding-kit" :name "Whiteboarding Kit" - :file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Whiteboarding-mapping-kit.penpot"} + :file-uri "https://github.com/penpot/penpot-files/raw/main/Whiteboarding-mapping-kit.penpot"} {:id "open-color-scheme" :name "Open Color Scheme" - :file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Open-Color-Scheme.penpot"} + :file-uri "https://github.com/penpot/penpot-files/raw/main/Open-Color-Scheme.penpot"} {:id "flex-layout-playground" :name "Flex Layout Playground" - :file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Flex-Layout-Playground.penpot"} + :file-uri "https://github.com/penpot/penpot-files/raw/main/Flex-Layout-Playground.penpot"} {:id "welcome" :name "Welcome" - :file-uri "https://github.com/penpot/penpot-files/raw/binary-files/welcome.penpot"}] + :file-uri "https://github.com/penpot/penpot-files/raw/main/welcome.penpot"}] From e0034dc205a3f24653d88a455f97951d358b1726 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 23 Sep 2024 06:30:19 +0200 Subject: [PATCH 18/19] :bug: Fix onboarding edn urls --- backend/resources/app/onboarding.edn | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/resources/app/onboarding.edn b/backend/resources/app/onboarding.edn index 43a701f24..5762f09b9 100644 --- a/backend/resources/app/onboarding.edn +++ b/backend/resources/app/onboarding.edn @@ -18,10 +18,10 @@ :file-uri "https://github.com/penpot/penpot-files/raw/main/Lucide-icons.penpot"} {:id "font-awesome" :name "Font Awesome" - :file-uri "https://github.com/penpot/penpot-files/raw/main/Font-Awesome.penpot"} + :file-uri "https://github.com/penpot/penpot-files/raw/main/FontAwesome.penpot"} {:id "black-white-mobile-templates" :name "Black & White Mobile Templates" - :file-uri "https://github.com/penpot/penpot-files/raw/main/Black-White-Mobile-Templates.penpot"} + :file-uri "https://github.com/penpot/penpot-files/raw/main/Black-&-White-Mobile-Templates.penpot"} {:id "avataaars" :name "Avataaars" :file-uri "https://github.com/penpot/penpot-files/raw/main/Avataaars-by-Pablo-Stanley.penpot"} @@ -33,10 +33,10 @@ :file-uri "https://github.com/penpot/penpot-files/raw/main/Whiteboarding-mapping-kit.penpot"} {:id "open-color-scheme" :name "Open Color Scheme" - :file-uri "https://github.com/penpot/penpot-files/raw/main/Open-Color-Scheme.penpot"} + :file-uri "https://github.com/penpot/penpot-files/raw/main/Open%20Color%20Scheme%20(v1.9.1).penpot"} {:id "flex-layout-playground" :name "Flex Layout Playground" - :file-uri "https://github.com/penpot/penpot-files/raw/main/Flex-Layout-Playground.penpot"} + :file-uri "https://github.com/penpot/penpot-files/raw/main/Flex%20Layout%20Playground.penpot"} {:id "welcome" :name "Welcome" :file-uri "https://github.com/penpot/penpot-files/raw/main/welcome.penpot"}] From 7fe95f218b7a805a5c650f726e9a1f531ffc0e1e Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 23 Sep 2024 10:30:31 +0200 Subject: [PATCH 19/19] :bug: Fix logged in redirect --- frontend/src/app/main/data/users.cljs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/main/data/users.cljs b/frontend/src/app/main/data/users.cljs index 7b017f508..9a0cd7b80 100644 --- a/frontend/src/app/main/data/users.cljs +++ b/frontend/src/app/main/data/users.cljs @@ -172,12 +172,12 @@ (letfn [(get-redirect-events [] (let [team-id (get-current-team-id profile) welcome-file-id (dm/get-in profile [:props :welcome-file-id]) - redirect-href (:login-redirect @s/session)] + redirect-href (::login-redirect storage/session)] (cond (some? redirect-href) - (binding [s/*sync* true] - (swap! s/session dissoc :login-redirect) + (binding [storage/*sync* true] + (swap! storage/session dissoc ::login-redirect) (rx/of (rt/nav-raw :href redirect-href))) (some? welcome-file-id)